diff --git a/README.md b/README.md index 4aefca5..1e49879 100644 --- a/README.md +++ b/README.md @@ -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 diff --git a/RETSTONE.md b/RETSTONE.md index 8f299d8..d33195b 100644 --- a/RETSTONE.md +++ b/RETSTONE.md @@ -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 diff --git a/src/main/java/eu/m724/tweaks/redstone/GatewayItem.java b/src/main/java/eu/m724/tweaks/redstone/GatewayItem.java new file mode 100644 index 0000000..5d24f05 --- /dev/null +++ b/src/main/java/eu/m724/tweaks/redstone/GatewayItem.java @@ -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; + } +} diff --git a/src/main/java/eu/m724/tweaks/redstone/RedstoneCommands.java b/src/main/java/eu/m724/tweaks/redstone/RedstoneCommands.java index 45522f0..f1c6b28 100644 --- a/src/main/java/eu/m724/tweaks/redstone/RedstoneCommands.java +++ b/src/main/java/eu/m724/tweaks/redstone/RedstoneCommands.java @@ -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"); diff --git a/src/main/java/eu/m724/tweaks/redstone/RedstoneRepeaters.java b/src/main/java/eu/m724/tweaks/redstone/RedstoneGateways.java similarity index 73% rename from src/main/java/eu/m724/tweaks/redstone/RedstoneRepeaters.java rename to src/main/java/eu/m724/tweaks/redstone/RedstoneGateways.java index c554379..d7ae10f 100644 --- a/src/main/java/eu/m724/tweaks/redstone/RedstoneRepeaters.java +++ b/src/main/java/eu/m724/tweaks/redstone/RedstoneGateways.java @@ -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 repeatersById = HashBiMap.create(); - private final Map repeatersByPower = new HashMap<>(); + private final BiMap gatewaysById = HashBiMap.create(); + private final Map 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); diff --git a/src/main/java/eu/m724/tweaks/redstone/RedstoneListener.java b/src/main/java/eu/m724/tweaks/redstone/RedstoneListener.java index 7cd1ffd..bff3c1e 100644 --- a/src/main/java/eu/m724/tweaks/redstone/RedstoneListener.java +++ b/src/main/java/eu/m724/tweaks/redstone/RedstoneListener.java @@ -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); } } diff --git a/src/main/java/eu/m724/tweaks/redstone/RedstoneManager.java b/src/main/java/eu/m724/tweaks/redstone/RedstoneManager.java index 437dfc1..2dde5bc 100644 --- a/src/main/java/eu/m724/tweaks/redstone/RedstoneManager.java +++ b/src/main/java/eu/m724/tweaks/redstone/RedstoneManager.java @@ -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 consumer) { - DebugLogger.fine("Retrieve enqueued " + repeaterId); - runnable.enqueueRetrieve(repeaterId, consumer); + private void enqueueRetrieve(int gatewayId, Consumer consumer) { + DebugLogger.fine("Retrieve enqueued " + gatewayId); + runnable.enqueueRetrieve(gatewayId, consumer); } } diff --git a/src/main/java/eu/m724/tweaks/redstone/RedstoneStateUpdateRunnable.java b/src/main/java/eu/m724/tweaks/redstone/RedstoneStateUpdateRunnable.java index 4368f68..f036f49 100644 --- a/src/main/java/eu/m724/tweaks/redstone/RedstoneStateUpdateRunnable.java +++ b/src/main/java/eu/m724/tweaks/redstone/RedstoneStateUpdateRunnable.java @@ -16,14 +16,14 @@ public class RedstoneStateUpdateRunnable extends BukkitRunnable { private Map updateQueue = new HashMap<>(); private Map> 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 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))); } } diff --git a/src/main/java/eu/m724/tweaks/redstone/RedstoneStore.java b/src/main/java/eu/m724/tweaks/redstone/RedstoneStore.java index cd24665..8c812ea 100644 --- a/src/main/java/eu/m724/tweaks/redstone/RedstoneStore.java +++ b/src/main/java/eu/m724/tweaks/redstone/RedstoneStore.java @@ -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) { diff --git a/src/main/resources/config.yml b/src/main/resources/config.yml index cd0a604..bcf91fa 100644 --- a/src/main/resources/config.yml +++ b/src/main/resources/config.yml @@ -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 diff --git a/src/main/resources/plugin.yml b/src/main/resources/plugin.yml index f018798..e96250e 100644 --- a/src/main/resources/plugin.yml +++ b/src/main/resources/plugin.yml @@ -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 diff --git a/src/main/resources/strings.properties b/src/main/resources/strings.properties index 41529fa..550c4f8 100644 --- a/src/main/resources/strings.properties +++ b/src/main/resources/strings.properties @@ -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 \ No newline at end of file