From 916f44da47c242c1ccc62f75909972e3e7001b38 Mon Sep 17 00:00:00 2001 From: Minecon724 Date: Mon, 30 Dec 2024 21:07:47 +0100 Subject: [PATCH] redstone WIP 2 --- RETSTONE.md | 4 +- retstone.py | 24 ++- .../tweaks/redstone/RedstoneListener.java | 13 ++ .../m724/tweaks/redstone/RedstoneManager.java | 14 +- .../tweaks/redstone/RedstoneRepeaters.java | 150 +++++++++++++----- .../redstone/RedstoneStateUpdateRunnable.java | 2 +- .../java/eu/m724/tweaks/redstone/Store.java | 76 +++++++++ 7 files changed, 231 insertions(+), 52 deletions(-) create mode 100644 src/main/java/eu/m724/tweaks/redstone/Store.java diff --git a/RETSTONE.md b/RETSTONE.md index e406b94..8f299d8 100644 --- a/RETSTONE.md +++ b/RETSTONE.md @@ -19,10 +19,10 @@ action bits 2-28 of repeater id - payload: power level if writing If writing, no response \ -If reading, response is the power level, or -1 if no repeater with that id (subject to change) +If reading, response is the power level, or 0 if not powered or no repeater with that ID Reading powers the block down of course \ -If the block was powered, you should power it down (or read), wait some, and then read again +BUT you should power it down (or read), wait some, and then read again ### Retstone diff --git a/retstone.py b/retstone.py index f1bb8dd..8bfeb7d 100644 --- a/retstone.py +++ b/retstone.py @@ -22,11 +22,23 @@ def set_power(repeater_id: int, power: int): sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) sock.sendto(message.to_bytes(4), ENDPOINT) -repeater_id = 235459920 -print("Reading from repeater") -power = get_power(repeater_id) -print("Read power:", power) +if __name__ == "__main__": + from argparse import ArgumentParser -print("Powering repeater with power 10") -set_power(repeater_id, 10) \ No newline at end of file + parser = ArgumentParser() + parser.add_argument("repeater_id", help="The repeater ID", type=int) + parser.add_argument("-p", "--power", help="Power the repeater with specified power. Leave to read.", type=int) + parser.add_argument("-c", "--copy", help="Copy input of one repeater to another. If combined with power, power is added.", type=int) + args = parser.parse_args() + + if args.copy is None: + if args.power is None: + power = get_power(args.repeater_id) + print(power) + else: + set_power(args.repeater_id, args.power) + else: + while True: + power = get_power(args.repeater_id) + set_power(args.copy, power) \ No newline at end of file diff --git a/src/main/java/eu/m724/tweaks/redstone/RedstoneListener.java b/src/main/java/eu/m724/tweaks/redstone/RedstoneListener.java index 6a3ef7d..c0f270c 100644 --- a/src/main/java/eu/m724/tweaks/redstone/RedstoneListener.java +++ b/src/main/java/eu/m724/tweaks/redstone/RedstoneListener.java @@ -51,4 +51,17 @@ public class RedstoneListener implements Listener { event.getPlayer().sendMessage("Repeater ID: " + id); } + + @EventHandler + public void b(BlockRedstoneEvent event) { + var block = event.getBlock(); + System.out.println(block); + + var id = redstoneRepeaters.getId(block); + if (id == Integer.MIN_VALUE) return; + + System.out.println("yes it isi"); + + event.setNewCurrent(redstoneRepeaters.getOutboundPower(id)); + } } diff --git a/src/main/java/eu/m724/tweaks/redstone/RedstoneManager.java b/src/main/java/eu/m724/tweaks/redstone/RedstoneManager.java index b641c3c..431bfc3 100644 --- a/src/main/java/eu/m724/tweaks/redstone/RedstoneManager.java +++ b/src/main/java/eu/m724/tweaks/redstone/RedstoneManager.java @@ -28,6 +28,8 @@ public class RedstoneManager { } public void init(PluginCommand command) { + Store.init(plugin); + plugin.getServer().getPluginManager().registerEvents(new RedstoneListener(redstoneRepeaters), plugin); command.setExecutor(new RedstoneCommands(redstoneRepeaters)); @@ -66,17 +68,17 @@ public class RedstoneManager { } boolean write = (buf[0] >> 7 & 1) == 1; - byte state = (byte) (buf[3] & 0xF); + byte data = (byte) (buf[3] & 0xF); int repeaterId = ((buf[0] & 0x7F) << 24) | ((buf[1] & 0xFF) << 16) | ((buf[2] & 0xFF) << 8) | (buf[3] & 0xF0); if (write) { - enqueueUpdate(repeaterId, state); + enqueueUpdate(repeaterId, data); } else { var newPacket = new DatagramPacket(new byte[1], 1, packet.getSocketAddress()); enqueueRetrieve(repeaterId, value -> { System.out.println("retieved state " + value); - newPacket.setData(new byte[] { value }); + newPacket.setData(new byte[] { (byte) Math.max(value, 0) }); try { socket.send(newPacket); @@ -89,9 +91,9 @@ public class RedstoneManager { }); } - private void enqueueUpdate(int repeaterId, byte state) { - System.out.println("Update enqueud " + repeaterId + " " + state); - runnable.enqueueUpdate(repeaterId, state); + private void enqueueUpdate(int repeaterId, byte power) { + System.out.println("Update enqueud " + repeaterId + " " + power); + runnable.enqueueUpdate(repeaterId, power); } private void enqueueRetrieve(int repeaterId, Consumer consumer) { diff --git a/src/main/java/eu/m724/tweaks/redstone/RedstoneRepeaters.java b/src/main/java/eu/m724/tweaks/redstone/RedstoneRepeaters.java index 5b7bc49..77b1a77 100644 --- a/src/main/java/eu/m724/tweaks/redstone/RedstoneRepeaters.java +++ b/src/main/java/eu/m724/tweaks/redstone/RedstoneRepeaters.java @@ -6,49 +6,40 @@ package eu.m724.tweaks.redstone; +import com.google.common.collect.BiMap; +import com.google.common.collect.HashBiMap; 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.*; +import java.util.HashMap; +import java.util.Map; import java.util.concurrent.ThreadLocalRandom; public class RedstoneRepeaters { private final Plugin plugin; private final NamespacedKey repeaterKey; - private final Map repeatersById = new HashMap<>(); - private final Map repeatersByLocation = new HashMap<>(); + private final BiMap repeatersById = HashBiMap.create(); + private final Map repeatersByPower = new HashMap<>(); public RedstoneRepeaters(Plugin plugin) { this.plugin = plugin; this.repeaterKey = new NamespacedKey(plugin, "repeater"); } - // - - 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; - } - - public int getId(Block block) { - return repeatersByLocation.getOrDefault(block.getLocation(), Integer.MIN_VALUE); - } - - // + /* Item functions */ public ItemStack give() { - var itemStack = new ItemStack(Material.RED_STAINED_GLASS); + var itemStack = new ItemStack(Material.DAYLIGHT_DETECTOR); var meta = itemStack.getItemMeta(); meta.setItemName(Language.getString("retstoneBlockItem")); @@ -59,15 +50,23 @@ public class RedstoneRepeaters { return itemStack; } - // TODO save in those + 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 */ int onPlace(Block block) { var repeaterId = ThreadLocalRandom.current().nextInt() & 0x7FFFFFF0; repeatersById.put(repeaterId, block.getLocation()); - repeatersByLocation.put(block.getLocation(), repeaterId); block.setMetadata("rid", new FixedMetadataValue(plugin, repeaterId)); + Store.getInstance().saveRepeaterData(block.getLocation(), repeaterId, (byte) 0); + return repeaterId; } @@ -75,12 +74,83 @@ public class RedstoneRepeaters { delete(repeaterId); } - // + void delete(int repeaterId) { + var location = repeatersById.remove(repeaterId); + if (location == null) return; + repeatersByPower.remove(repeaterId); + + Store.getInstance().deleteSavedRepeaterData(location); + } + + /* Get functions */ + + 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); + if (loc == null) { + System.err.println("Delete because no loc"); + delete(repeaterId); + return false; + } + + // check if chunk the block is in is loaded + // you may think it could be simplified, but it can't without loading the chunk (to check if it's loaded) + var isLoaded = loc.getWorld().isChunkLoaded(loc.getBlockX() / 16, loc.getBlockZ() / 16); + if (!isLoaded) return false; + + // check if the block is correct type + if (loc.getBlock().getType() != Material.DAYLIGHT_DETECTOR) { + delete(repeaterId); + return false; + } + + // check if the block has the same ID bound + var meta = loc.getBlock().getMetadata("rid"); + if (meta.isEmpty() || meta.getFirst().asInt() != repeaterId) { + System.err.println("Delete because no meta"); + delete(repeaterId); + return false; + } + + return true; + } + + public int getId(Block block) { + if (block.hasMetadata("rid")) { + var id = block.getMetadata("rid").getFirst().asInt(); + return id; + } + + var id = repeatersById.inverse().get(block.getLocation()); + + if (id == null) { + // not in memory, check if repeater + var d = Store.getInstance().getSavedRepeaterData(block.getLocation()); + + if (d == null) { + block.setMetadata("rid", new FixedMetadataValue(plugin, Integer.MIN_VALUE)); + return Integer.MIN_VALUE; + } + + id = d.getKey(); + block.setMetadata("rid", new FixedMetadataValue(plugin, id)); + + repeatersById.put(id, block.getLocation()); + repeatersByPower.put(id, d.getValue()); + } + + if (!isValid(id)) return Integer.MIN_VALUE; + + return id; + } Block getBlock(int repeaterId) { var location = repeatersById.get(repeaterId); if (location == null) return null; + if (!isValid(repeaterId)) return null; + var storedId = location.getBlock().getMetadata("rid").getFirst().asInt(); if (storedId != repeaterId) { System.out.println("retrieved but not exitt"); @@ -92,35 +162,41 @@ public class RedstoneRepeaters { return location.getBlock(); } - void delete(int repeaterId) { - var l = repeatersById.remove(repeaterId); - if (l == null) return; - repeatersByLocation.remove(l); - } + /* Control functions */ - // - - byte getPower(int repeaterId) { + byte getInboundPower(int repeaterId) { var block = getBlock(repeaterId); if (block == null) return -1; - block.setType(Material.RED_STAINED_GLASS); - block.getWorld().spawnParticle(Particle.LAVA, block.getLocation().add(0.5, 0.5, 0.5), 3); + System.out.println("Okay I got in power" + block.getBlockPower()); return (byte) block.getBlockPower(); } + byte getOutboundPower(int repeaterId) { + var block = getBlock(repeaterId); + if (block == null) return -1; + + System.out.println("Okay I got out power" + repeatersByPower.get(repeaterId)); + return repeatersByPower.getOrDefault(repeaterId, (byte) 0); + } + void setPower(int repeaterId, byte power) { + System.out.println(power); + if (power < 0 || power > 15) + throw new IllegalArgumentException("Power should be 0-15, but is " + power); + var block = getBlock(repeaterId); if (block == null) return; - if (power == 0) - block.setType(Material.RED_STAINED_GLASS); - else - block.setType(Material.REDSTONE_BLOCK); + var data = (AnaloguePowerable) block.getBlockData(); + + repeatersByPower.put(repeaterId, power); + data.setPower(power); block.getWorld().spawnParticle(Particle.LAVA, block.getLocation().add(0.5, 0.5, 0.5), 3); - } -} + System.out.println("Okay I set power"); + } +} \ No newline at end of file diff --git a/src/main/java/eu/m724/tweaks/redstone/RedstoneStateUpdateRunnable.java b/src/main/java/eu/m724/tweaks/redstone/RedstoneStateUpdateRunnable.java index 03d3d38..4368f68 100644 --- a/src/main/java/eu/m724/tweaks/redstone/RedstoneStateUpdateRunnable.java +++ b/src/main/java/eu/m724/tweaks/redstone/RedstoneStateUpdateRunnable.java @@ -39,6 +39,6 @@ public class RedstoneStateUpdateRunnable extends BukkitRunnable { this.retrieveQueue = new HashMap<>(); updateQueue.forEach((key, value) -> redstoneRepeaters.setPower(key, value)); - retrieveQueue.forEach((key, value) -> value.accept(redstoneRepeaters.getPower(key))); + retrieveQueue.forEach((key, value) -> value.accept(redstoneRepeaters.getInboundPower(key))); } } diff --git a/src/main/java/eu/m724/tweaks/redstone/Store.java b/src/main/java/eu/m724/tweaks/redstone/Store.java new file mode 100644 index 0000000..d9d8ff8 --- /dev/null +++ b/src/main/java/eu/m724/tweaks/redstone/Store.java @@ -0,0 +1,76 @@ +/* + * 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 com.google.common.primitives.Ints; +import org.apache.commons.lang3.tuple.Pair; +import org.bukkit.Location; +import org.bukkit.plugin.Plugin; + +import java.io.File; +import java.io.IOException; +import java.nio.file.Files; + +public class Store { + private static Store INSTANCE; + + private final Plugin plugin; + private final File directory; + + private Store(Plugin plugin) { + this.plugin = plugin; + this.directory = new File(plugin.getDataFolder(), "storage/redstone"); + directory.mkdirs(); + } + + static void init(Plugin plugin) { + INSTANCE = new Store(plugin); + } + + static Store getInstance() { + return INSTANCE; + } + + Pair getSavedRepeaterData(Location location) { + var file = getFile(location); + if (!file.exists()) return null; + + byte[] bytes; + + try { + // TODO read just 4 bytes + bytes = Files.readAllBytes(file.toPath()); + } catch (IOException e) { + throw new RuntimeException("Loading saved repeater data", e); + } + + var repeaterId = Ints.fromByteArray(bytes) & ~0xF; + var powerLevel = (byte) (bytes[3] & 0xF); + + return Pair.of(repeaterId, powerLevel); + } + + void saveRepeaterData(Location location, int repeaterId, byte powerLevel) { + var file = getFile(location); + System.out.println(file); + byte[] bytes = Ints.toByteArray((repeaterId & ~0xF) | (powerLevel & 0xF)); + + try { + Files.write(file.toPath(), bytes); + } catch (IOException e) { + throw new RuntimeException("Saving repeater data", e); + } + } + + void deleteSavedRepeaterData(Location location) { + getFile(location).delete(); + } + + private File getFile(Location location) { + return new File(directory, location.getWorld().getName() + " " + location.getBlockX() + " " + location.getBlockY() + " " + location.getBlockZ()); + } +}