Rename to gateway, add crafting, some other changes
Some checks failed
/ build (push) Has been cancelled

This commit is contained in:
Minecon724 2024-12-31 21:35:44 +01:00
parent 225adf0354
commit c0b85870e0
Signed by: Minecon724
GPG key ID: 3CCC4D267742C8E8
12 changed files with 152 additions and 95 deletions

View file

@ -82,7 +82,11 @@ Issue messages that the player needs to read to keep playing, and that make an a
`/emergencyalerts` (`tweaks724.emergencyalerts`)
### Remote redstone
Control redstone remotely
Adds a "gateway" item that are controlled over internet. \
[RETSTONE.md for more info](/Minecon724/tweaks724/src/branch/master/RETSTONE.md)
### Knockback
Control knockback dealt by entities
### Utility commands

View file

@ -3,8 +3,21 @@
See [retstone.py](retstone.py) for usage example
### Glossary
repeaters - the blocks which allow to interact with redstone over internet \
packet - a bunch of bytes sent over the internet, that do something with a single repeater
gateways - the blocks (daylight detector) that read or emit redstone with a specified power, controlled with UDP packets \
packet - a bunch of bytes sent over the internet, that do something with a specified gateway
### Crafting
To craft a gateway, combine:
- nether star
- ender chest
- chorus flower
- daylight detector
### Usage
Each gateway has an ID assigned. To view it, shift right-click a gateway.
To destroy a gateway, use silk touch.
### How it works
A packet is int / 4 bytes / 32 bits

View file

@ -0,0 +1,50 @@
/*
* Copyright (C) 2024 Minecon724
* Tweaks724 is licensed under the GNU General Public License. See the LICENSE.md file
* in the project root for the full license text.
*/
package eu.m724.tweaks.redstone;
import eu.m724.tweaks.Language;
import org.bukkit.Material;
import org.bukkit.NamespacedKey;
import org.bukkit.inventory.ItemStack;
import org.bukkit.inventory.ShapelessRecipe;
import org.bukkit.persistence.PersistentDataType;
import org.bukkit.plugin.Plugin;
public class GatewayItem {
private final NamespacedKey gatewayKey;
public GatewayItem(Plugin plugin) {
this.gatewayKey = new NamespacedKey(plugin, "gateway");
var recipe = new ShapelessRecipe(gatewayKey, itemStack());
recipe.addIngredient(Material.NETHER_STAR);
recipe.addIngredient(Material.ENDER_CHEST);
recipe.addIngredient(Material.CHORUS_FLOWER);
recipe.addIngredient(Material.DAYLIGHT_DETECTOR);
plugin.getServer().addRecipe(recipe);
}
public ItemStack itemStack() {
var itemStack = new ItemStack(Material.DAYLIGHT_DETECTOR);
var meta = itemStack.getItemMeta();
meta.setItemName(Language.getString("redstoneGatewayItem"));
meta.getPersistentDataContainer().set(gatewayKey, PersistentDataType.BOOLEAN, true);
meta.setEnchantmentGlintOverride(true);
itemStack.setItemMeta(meta);
return itemStack;
}
public boolean isGateway(ItemStack itemStack) {
var meta = itemStack.getItemMeta();
if (meta == null) return false;
var value = meta.getPersistentDataContainer().get(gatewayKey, PersistentDataType.BOOLEAN);
return value != null;
}
}

View file

@ -14,10 +14,10 @@ import org.bukkit.entity.Player;
import org.jetbrains.annotations.NotNull;
public class RedstoneCommands implements CommandExecutor {
private final RedstoneRepeaters redstoneRepeaters;
private final GatewayItem gatewayItem;
public RedstoneCommands(RedstoneRepeaters redstoneRepeaters) {
this.redstoneRepeaters = redstoneRepeaters;
public RedstoneCommands(GatewayItem gatewayItem) {
this.gatewayItem = gatewayItem;
}
@ -41,8 +41,9 @@ public class RedstoneCommands implements CommandExecutor {
}
}
var itemStack = redstoneRepeaters.give();
var itemStack = gatewayItem.itemStack();
player.getInventory().addItem(itemStack);
sender.sendMessage("Given to " + player.getName());
}
} else {
sender.sendMessage("Argument needed");

View file

@ -9,53 +9,26 @@ package eu.m724.tweaks.redstone;
import com.google.common.collect.BiMap;
import com.google.common.collect.HashBiMap;
import eu.m724.tweaks.DebugLogger;
import eu.m724.tweaks.Language;
import org.bukkit.Location;
import org.bukkit.Material;
import org.bukkit.NamespacedKey;
import org.bukkit.Particle;
import org.bukkit.block.Block;
import org.bukkit.block.data.AnaloguePowerable;
import org.bukkit.inventory.ItemStack;
import org.bukkit.metadata.FixedMetadataValue;
import org.bukkit.persistence.PersistentDataType;
import org.bukkit.plugin.Plugin;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.ThreadLocalRandom;
public class RedstoneRepeaters {
public class RedstoneGateways {
private final Plugin plugin;
private final NamespacedKey repeaterKey;
private final BiMap<Integer, Location> repeatersById = HashBiMap.create();
private final Map<Integer, Byte> repeatersByPower = new HashMap<>();
private final BiMap<Integer, Location> gatewaysById = HashBiMap.create();
private final Map<Integer, Byte> gatewaysByPower = new HashMap<>();
public RedstoneRepeaters(Plugin plugin) {
public RedstoneGateways(Plugin plugin) {
this.plugin = plugin;
this.repeaterKey = new NamespacedKey(plugin, "repeater");
}
/* Item functions */
public ItemStack give() {
var itemStack = new ItemStack(Material.DAYLIGHT_DETECTOR);
var meta = itemStack.getItemMeta();
meta.setItemName(Language.getString("retstoneBlockItem"));
meta.getPersistentDataContainer().set(repeaterKey, PersistentDataType.BOOLEAN, true);
meta.setEnchantmentGlintOverride(true);
itemStack.setItemMeta(meta);
return itemStack;
}
public boolean isRepeater(ItemStack itemStack) {
var meta = itemStack.getItemMeta();
if (meta == null) return false;
var value = meta.getPersistentDataContainer().get(repeaterKey, PersistentDataType.BOOLEAN);
return value != null;
}
/* Event */
@ -63,7 +36,7 @@ public class RedstoneRepeaters {
int onPlace(Block block) {
var repeaterId = ThreadLocalRandom.current().nextInt() & 0x7FFFFFF0;
repeatersById.put(repeaterId, block.getLocation());
gatewaysById.put(repeaterId, block.getLocation());
block.setMetadata("rid", new FixedMetadataValue(plugin, repeaterId));
RedstoneStore.getInstance().saveRepeaterData(block.getLocation(), repeaterId, (byte) 0);
@ -76,9 +49,9 @@ public class RedstoneRepeaters {
}
void delete(int repeaterId) {
var location = repeatersById.remove(repeaterId);
var location = gatewaysById.remove(repeaterId);
if (location == null) return;
repeatersByPower.remove(repeaterId);
gatewaysByPower.remove(repeaterId);
RedstoneStore.getInstance().deleteSavedRepeaterData(location);
}
@ -88,7 +61,7 @@ public class RedstoneRepeaters {
private boolean isValid(int repeaterId) {
// check if there's a repeater with such ID stored
// if not, we're not loading because it's loaded as the block is
var loc = repeatersById.get(repeaterId);
var loc = gatewaysById.get(repeaterId);
if (loc == null) {
DebugLogger.fine("isValid: Delete because no loc");
delete(repeaterId);
@ -124,7 +97,7 @@ public class RedstoneRepeaters {
return id;
}
var id = repeatersById.inverse().get(block.getLocation());
var id = gatewaysById.inverse().get(block.getLocation());
if (id == null) {
// not in memory, check if repeater
@ -138,8 +111,8 @@ public class RedstoneRepeaters {
id = d.getKey();
block.setMetadata("rid", new FixedMetadataValue(plugin, id));
repeatersById.put(id, block.getLocation());
repeatersByPower.put(id, d.getValue());
gatewaysById.put(id, block.getLocation());
gatewaysByPower.put(id, d.getValue());
}
if (!isValid(id)) return Integer.MIN_VALUE;
@ -148,7 +121,7 @@ public class RedstoneRepeaters {
}
Block getBlock(int repeaterId) {
var location = repeatersById.get(repeaterId);
var location = gatewaysById.get(repeaterId);
if (location == null) return null;
if (!isValid(repeaterId)) return null;
@ -182,7 +155,7 @@ public class RedstoneRepeaters {
var block = getBlock(repeaterId);
if (block == null) return -1;
var power = repeatersByPower.getOrDefault(repeaterId, (byte) 0);
var power = gatewaysByPower.getOrDefault(repeaterId, (byte) 0);
DebugLogger.fine("Got " + repeaterId + " outputs " + power);
return power;
}
@ -196,7 +169,7 @@ public class RedstoneRepeaters {
var data = (AnaloguePowerable) block.getBlockData();
repeatersByPower.put(repeaterId, power);
gatewaysByPower.put(repeaterId, power);
data.setPower(power);
block.getWorld().spawnParticle(Particle.LAVA, block.getLocation().add(0.5, 0.5, 0.5), 3);

View file

@ -13,6 +13,7 @@ import net.md_5.bungee.api.chat.ClickEvent;
import net.md_5.bungee.api.chat.ComponentBuilder;
import net.md_5.bungee.api.chat.HoverEvent;
import net.md_5.bungee.api.chat.hover.content.Text;
import org.bukkit.enchantments.Enchantment;
import org.bukkit.event.EventHandler;
import org.bukkit.event.Listener;
import org.bukkit.event.block.Action;
@ -22,30 +23,37 @@ import org.bukkit.event.block.BlockRedstoneEvent;
import org.bukkit.event.player.PlayerInteractEvent;
public class RedstoneListener implements Listener {
private final RedstoneRepeaters redstoneRepeaters;
private final RedstoneGateways redstoneGateways;
private final GatewayItem gatewayItem;
public RedstoneListener(RedstoneRepeaters redstoneRepeaters) {
this.redstoneRepeaters = redstoneRepeaters;
public RedstoneListener(RedstoneGateways redstoneGateways, GatewayItem gatewayItem) {
this.redstoneGateways = redstoneGateways;
this.gatewayItem = gatewayItem;
}
@EventHandler
public void onBlockPlace(BlockPlaceEvent event) {
if (!redstoneRepeaters.isRepeater(event.getItemInHand())) return;
if (!gatewayItem.isGateway(event.getItemInHand())) return;
var block = event.getBlockPlaced();
var id = redstoneRepeaters.onPlace(block);
var id = redstoneGateways.onPlace(block);
DebugLogger.fine("Repeater placed: " + id);
DebugLogger.fine("Gateway placed: " + id);
}
@EventHandler
public void onBlockBreak(BlockBreakEvent event) {
var id = redstoneRepeaters.getId(event.getBlock());
var id = redstoneGateways.getId(event.getBlock());
if (id == Integer.MIN_VALUE) return;
redstoneRepeaters.onBreak(id);
redstoneGateways.onBreak(id);
DebugLogger.fine("Repeater broken: " + id);
if (event.getPlayer().getInventory().getItemInMainHand().containsEnchantment(Enchantment.SILK_TOUCH)) {
event.setDropItems(false);
event.getBlock().getWorld().dropItemNaturally(event.getBlock().getLocation(), gatewayItem.itemStack());
}
DebugLogger.fine("Gateway broken: " + id);
}
@EventHandler
@ -53,28 +61,31 @@ public class RedstoneListener implements Listener {
if (event.getAction() != Action.RIGHT_CLICK_BLOCK) return;
if (!event.getPlayer().isSneaking()) return;
var id = redstoneRepeaters.getId(event.getClickedBlock());
var id = redstoneGateways.getId(event.getClickedBlock());
if (id == Integer.MIN_VALUE) return;
// TODO find a less lame way of showing ID
var component = new ComponentBuilder("Repeater ID: ").color(ChatColor.GOLD)
var component = new ComponentBuilder("Gateway ID: ").color(ChatColor.GOLD)
.append(String.valueOf(id)).color(ChatColor.AQUA)
.event(new HoverEvent(HoverEvent.Action.SHOW_TEXT, new Text(Language.getString("clickToCopy"))))
.event(new ClickEvent(ClickEvent.Action.COPY_TO_CLIPBOARD, String.valueOf(id)))
.build();
event.getPlayer().spigot().sendMessage(component);
// cancel block place
event.setCancelled(true);
}
@EventHandler
public void onBlockRedstone(BlockRedstoneEvent event) {
var block = event.getBlock();
var id = redstoneRepeaters.getId(block);
var id = redstoneGateways.getId(block);
if (id == Integer.MIN_VALUE) return;
event.setNewCurrent(redstoneRepeaters.getOutboundPower(id));
event.setNewCurrent(redstoneGateways.getOutboundPower(id));
DebugLogger.fine("Repeater redstone event: " + id);
DebugLogger.fine("Gateway redstone event: " + id);
}
}

View file

@ -18,23 +18,25 @@ import java.util.function.Consumer;
public class RedstoneManager {
private final Plugin plugin;
private final RedstoneRepeaters redstoneRepeaters;
private final RedstoneGateways redstoneGateways;
private DatagramSocket socket;
private RedstoneStateUpdateRunnable runnable;
public RedstoneManager(Plugin plugin) {
this.plugin = plugin;
this.redstoneRepeaters = new RedstoneRepeaters(plugin);
this.redstoneGateways = new RedstoneGateways(plugin);
}
public void init(PluginCommand command) {
RedstoneStore.init(plugin);
var gatewayItem = new GatewayItem(plugin);
plugin.getServer().getPluginManager().registerEvents(new RedstoneListener(redstoneRepeaters), plugin);
command.setExecutor(new RedstoneCommands(redstoneRepeaters));
plugin.getServer().getPluginManager().registerEvents(new RedstoneListener(redstoneGateways, gatewayItem), plugin);
command.setExecutor(new RedstoneCommands(gatewayItem));
this.runnable = new RedstoneStateUpdateRunnable(redstoneRepeaters);
this.runnable = new RedstoneStateUpdateRunnable(redstoneGateways);
this.runnable.runTaskTimer(plugin, 0, 20); // TODO configurable
var listenAddress = TweaksConfig.getConfig().redstoneListen().split(":");
@ -50,6 +52,7 @@ public class RedstoneManager {
} catch (SocketException e) {
throw new RuntimeException("Starting socket", e);
}
}
private void initSocket(SocketAddress bindAddress) throws SocketException {
@ -70,15 +73,15 @@ public class RedstoneManager {
boolean write = (buf[0] >> 7 & 1) == 1;
byte data = (byte) (buf[3] & 0xF);
int repeaterId = ((buf[0] & 0x7F) << 24) | ((buf[1] & 0xFF) << 16) | ((buf[2] & 0xFF) << 8) | (buf[3] & 0xF0);
int gatewayId = ((buf[0] & 0x7F) << 24) | ((buf[1] & 0xFF) << 16) | ((buf[2] & 0xFF) << 8) | (buf[3] & 0xF0);
if (write) {
enqueueUpdate(repeaterId, data);
enqueueUpdate(gatewayId, data);
} else {
var newPacket = new DatagramPacket(new byte[1], 1, packet.getSocketAddress());
enqueueRetrieve(repeaterId, value -> {
DebugLogger.fine("Retrieved for " + repeaterId + " power " + value);
enqueueRetrieve(gatewayId, value -> {
DebugLogger.fine("Retrieved for " + gatewayId + " power " + value);
newPacket.setData(new byte[] { (byte) Math.max(value, 0) });
try {
@ -92,13 +95,13 @@ public class RedstoneManager {
});
}
private void enqueueUpdate(int repeaterId, byte power) {
DebugLogger.fine("Update enqueued " + repeaterId + " " + power);
runnable.enqueueUpdate(repeaterId, power);
private void enqueueUpdate(int gatewayId, byte power) {
DebugLogger.fine("Update enqueued " + gatewayId + " " + power);
runnable.enqueueUpdate(gatewayId, power);
}
private void enqueueRetrieve(int repeaterId, Consumer<Byte> consumer) {
DebugLogger.fine("Retrieve enqueued " + repeaterId);
runnable.enqueueRetrieve(repeaterId, consumer);
private void enqueueRetrieve(int gatewayId, Consumer<Byte> consumer) {
DebugLogger.fine("Retrieve enqueued " + gatewayId);
runnable.enqueueRetrieve(gatewayId, consumer);
}
}

View file

@ -16,14 +16,14 @@ public class RedstoneStateUpdateRunnable extends BukkitRunnable {
private Map<Integer, Byte> updateQueue = new HashMap<>();
private Map<Integer, Consumer<Byte>> retrieveQueue = new HashMap<>();
private final RedstoneRepeaters redstoneRepeaters;
private final RedstoneGateways redstoneGateways;
RedstoneStateUpdateRunnable(RedstoneRepeaters redstoneRepeaters) {
this.redstoneRepeaters = redstoneRepeaters;
RedstoneStateUpdateRunnable(RedstoneGateways redstoneGateways) {
this.redstoneGateways = redstoneGateways;
}
void enqueueUpdate(int repeaterId, byte power) {
updateQueue.put(repeaterId, power);
void enqueueUpdate(int gatewayId, byte power) {
updateQueue.put(gatewayId, power);
}
void enqueueRetrieve(int repeaterId, Consumer<Byte> consumer) {
@ -38,7 +38,7 @@ public class RedstoneStateUpdateRunnable extends BukkitRunnable {
var retrieveQueue = this.retrieveQueue;
this.retrieveQueue = new HashMap<>();
updateQueue.forEach((key, value) -> redstoneRepeaters.setPower(key, value));
retrieveQueue.forEach((key, value) -> value.accept(redstoneRepeaters.getInboundPower(key)));
updateQueue.forEach((key, value) -> redstoneGateways.setPower(key, value));
retrieveQueue.forEach((key, value) -> value.accept(redstoneGateways.getInboundPower(key)));
}
}

View file

@ -19,11 +19,9 @@ import java.nio.file.Files;
public class RedstoneStore {
private static RedstoneStore INSTANCE;
private final Plugin plugin;
private final File directory;
private RedstoneStore(Plugin plugin) {
this.plugin = plugin;
this.directory = new File(plugin.getDataFolder(), "storage/redstone");
directory.mkdirs();
}
@ -46,20 +44,20 @@ public class RedstoneStore {
// TODO read just 4 bytes
bytes = Files.readAllBytes(file.toPath());
} catch (IOException e) {
throw new RuntimeException("Loading saved repeater data", e);
throw new RuntimeException("Loading saved gateway data", e);
}
var repeaterId = Ints.fromByteArray(bytes) & ~0xF;
var gatewayId = Ints.fromByteArray(bytes) & ~0xF;
var powerLevel = (byte) (bytes[3] & 0xF);
DebugLogger.fine("load " + location + " " + repeaterId + " " + powerLevel);
DebugLogger.fine("load " + location + " " + gatewayId + " " + powerLevel);
return Pair.of(repeaterId, powerLevel);
return Pair.of(gatewayId, powerLevel);
}
void saveRepeaterData(Location location, int repeaterId, byte powerLevel) {
void saveRepeaterData(Location location, int gatewayId, byte powerLevel) {
var file = getFile(location);
byte[] bytes = Ints.toByteArray((repeaterId & ~0xF) | (powerLevel & 0xF));
byte[] bytes = Ints.toByteArray((gatewayId & ~0xF) | (powerLevel & 0xF));
try {
Files.write(file.toPath(), bytes);
@ -67,7 +65,7 @@ public class RedstoneStore {
throw new RuntimeException("Saving repeater data", e);
}
DebugLogger.fine("save " + location + " " + repeaterId + " " + powerLevel);
DebugLogger.fine("save " + location + " " + gatewayId + " " + powerLevel);
}
void deleteSavedRepeaterData(Location location) {

View file

@ -103,7 +103,7 @@ auth:
# The domain of the server. Doesn't do anything other than showing in /tauth new
domain: "replace.me"
# Adds blocks which allow to interact with redstone over internet
# Adds gateways emitting redstone, controlled over internet
retstone:
enabled: true
# This takes host:port, listens on UDP

View file

@ -35,6 +35,8 @@ commands:
description: Send emergency alert
permission: tweaks724.emergencyalert
retstone:
description: Retstone commands
permission: tweaks724.retstone
permissions:
tweaks724:
@ -50,4 +52,6 @@ permissions:
default: op
emergencyalert:
default: op
retstone:
default: op

View file

@ -30,6 +30,6 @@ authKickWrongKey = You're connecting to the wrong server address. You must conne
# If force is enabled and player is not registered. Changing this reveals you're using this plugin
authKickUnregistered = You are not whitelisted on this server!
retstoneBlockItem = Online redstone block
redstoneGatewayItem = Redstone gateway
clickToCopy = Click to copy to clipboard