From 7cd334f4a20390d2d58b3da62c6f0c867fdad7c6 Mon Sep 17 00:00:00 2001 From: Minecon724 Date: Tue, 18 Feb 2025 14:09:19 +0100 Subject: [PATCH] feat: Initial word coords module Signed-off-by: Minecon724 --- .../java/eu/m724/tweaks/TweaksPlugin.java | 3 + .../wordcoords/WordCoordsConverter.java | 36 ++++++ .../module/wordcoords/WordCoordsModule.java | 118 ++++++++++++++++++ .../tweaks/module/wordcoords/WordList.java | 63 ++++++++++ .../module/wordcoords/converter/Decoder.java | 76 +++++++++++ .../module/wordcoords/converter/Encoder.java | 82 ++++++++++++ src/main/resources/plugin.yml | 6 + src/main/resources/strings.properties | 6 +- 8 files changed, 389 insertions(+), 1 deletion(-) create mode 100644 src/main/java/eu/m724/tweaks/module/wordcoords/WordCoordsConverter.java create mode 100644 src/main/java/eu/m724/tweaks/module/wordcoords/WordCoordsModule.java create mode 100644 src/main/java/eu/m724/tweaks/module/wordcoords/WordList.java create mode 100644 src/main/java/eu/m724/tweaks/module/wordcoords/converter/Decoder.java create mode 100644 src/main/java/eu/m724/tweaks/module/wordcoords/converter/Encoder.java diff --git a/src/main/java/eu/m724/tweaks/TweaksPlugin.java b/src/main/java/eu/m724/tweaks/TweaksPlugin.java index 2b0715c..82adee7 100644 --- a/src/main/java/eu/m724/tweaks/TweaksPlugin.java +++ b/src/main/java/eu/m724/tweaks/TweaksPlugin.java @@ -27,6 +27,7 @@ import eu.m724.tweaks.module.redstone.RedstoneModule; import eu.m724.tweaks.module.sleep.SleepModule; import eu.m724.tweaks.module.swing.SwingModule; import eu.m724.tweaks.module.updater.UpdaterModule; +import eu.m724.tweaks.module.wordcoords.WordCoordsModule; import eu.m724.tweaks.module.worldborder.WorldBorderExpandModule; import eu.m724.tweaks.module.worldborder.WorldBorderHideModule; @@ -157,6 +158,8 @@ public class TweaksPlugin extends MStatsPlugin { TweaksModule.init(DurabilityModule.class); + TweaksModule.init(WordCoordsModule.class); + /* end modules */ if (config.metrics()) { diff --git a/src/main/java/eu/m724/tweaks/module/wordcoords/WordCoordsConverter.java b/src/main/java/eu/m724/tweaks/module/wordcoords/WordCoordsConverter.java new file mode 100644 index 0000000..054d1ee --- /dev/null +++ b/src/main/java/eu/m724/tweaks/module/wordcoords/WordCoordsConverter.java @@ -0,0 +1,36 @@ +/* + * Copyright (C) 2025 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.module.wordcoords; + +import eu.m724.tweaks.DebugLogger; +import eu.m724.tweaks.module.wordcoords.converter.Decoder; +import eu.m724.tweaks.module.wordcoords.converter.Encoder; + +import java.util.NoSuchElementException; + +public class WordCoordsConverter { + private final Encoder encoder; + private final Decoder decoder; + + + public WordCoordsConverter(WordList wordList) { + this.encoder = new Encoder(wordList); + this.decoder = new Decoder(wordList); + + DebugLogger.fine("Words: %d (%d bits)", wordList.getWordCount(), wordList.getBitsPerWord()); + DebugLogger.fine("Bits per word: %d", wordList.getBitsPerWord()); + } + + public String[] encode(int x, int z) { + return encoder.encode(x, z); + } + + public int[] decode(String[] words) throws NoSuchElementException { + return decoder.decode(words); + } + +} diff --git a/src/main/java/eu/m724/tweaks/module/wordcoords/WordCoordsModule.java b/src/main/java/eu/m724/tweaks/module/wordcoords/WordCoordsModule.java new file mode 100644 index 0000000..de442aa --- /dev/null +++ b/src/main/java/eu/m724/tweaks/module/wordcoords/WordCoordsModule.java @@ -0,0 +1,118 @@ +/* + * Copyright (C) 2025 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.module.wordcoords; + +import eu.m724.tweaks.Language; +import eu.m724.tweaks.module.TweaksModule; +import net.md_5.bungee.api.ChatColor; +import org.bukkit.command.Command; +import org.bukkit.command.CommandExecutor; +import org.bukkit.command.CommandSender; +import org.bukkit.entity.Player; +import org.jetbrains.annotations.NotNull; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; + +public class WordCoordsModule extends TweaksModule implements CommandExecutor { + private WordList wordList; + private WordCoordsConverter converter; + + @Override + protected void onInit() { + try { + this.wordList = WordList.fromFile(getPlugin().getDataFolder().toPath().resolve("storage/wordlist.txt")); + } catch (IOException e) { + throw new RuntimeException(e); + } + + this.converter = new WordCoordsConverter(wordList); + + registerCommand("wordcoords", this); + } + + @Override + public boolean onCommand(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label, @NotNull String[] args) { + int x = 0, z = 0; + String[] words = new String[0]; + + boolean encode = false; // means encode pos to words + + if (args.length == 0) { + if (!(sender instanceof Player player)) { + sender.sendMessage(Language.getString("wordCoordsPlayerOnly")); + return true; + } + + x = player.getLocation().getBlockX(); + z = player.getLocation().getBlockZ(); + + encode = true; + } else { + if (args.length > 1) { + try { + double dx = Double.parseDouble(args[0]); + double dz = Double.parseDouble(args[args.length > 2 ? 2 : 1]); + + if (dx > Integer.MAX_VALUE || dx < Integer.MIN_VALUE || dz > Integer.MAX_VALUE || dz < Integer.MIN_VALUE) { + sender.spigot().sendMessage(Language.getComponent("wordCoordsOutOfRange", ChatColor.RED)); + return true; + } + + x = (int) dx; + z = (int) dz; + + encode = true; + } catch (NumberFormatException ignored) { } + } + + if (!encode) { + String strArgs = String.join(" ", args); + words = smartDetectWords(strArgs); + } + } + + if (encode) { + words = converter.encode(x, z); + + sender.sendMessage("%d, %d encodes to ///%s".formatted(x, z, String.join(".", words))); + } else { + int[] xz = converter.decode(words); + x = xz[0]; + z = xz[1]; + + sender.sendMessage("///%s decodes to around %d, %d".formatted(String.join(".", words), x, z)); + } + + return true; + } + + private String[] smartDetectWords(String str) { + List words = new ArrayList<>(); + StringBuilder currentWord = new StringBuilder(); + + for (int i=0; i wordList; + private final int bitsPerWord; + + public WordList(List words) { + this.wordList = words; + this.bitsPerWord = 32 - Integer.numberOfLeadingZeros(words.size()) - 1; + } + + public String getWord(int index) { + return wordList.get(index); + } + + public int getWordIndex(String word) { + return wordList.indexOf(word); + } + + public String[] getWords(int... indexes) { + return Arrays.stream(indexes) + .mapToObj(this::getWord) + .toArray(String[]::new); + } + + public int[] getIndexes(String... words) { + return Arrays.stream(words) + .mapToInt(wordList::indexOf) + .toArray(); + } + + public int getWordCount() { + return wordList.size(); + } + + public int getBitsPerWord() { + return bitsPerWord; + } + + public static WordList fromFile(Path path) throws IOException { + try (var lines = Files.lines(path)) { + var list = lines.filter(s -> !s.isBlank()) + .map(String::toLowerCase) + .distinct() + .toList(); + + return new WordList(list); + } + + } +} diff --git a/src/main/java/eu/m724/tweaks/module/wordcoords/converter/Decoder.java b/src/main/java/eu/m724/tweaks/module/wordcoords/converter/Decoder.java new file mode 100644 index 0000000..1b74720 --- /dev/null +++ b/src/main/java/eu/m724/tweaks/module/wordcoords/converter/Decoder.java @@ -0,0 +1,76 @@ +/* + * Copyright (C) 2025 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.module.wordcoords.converter; + +import eu.m724.tweaks.DebugLogger; +import eu.m724.tweaks.module.wordcoords.WordList; + +import java.util.NoSuchElementException; + +public class Decoder { + private final WordList wordList; + private final int bitsPerWord; + + public Decoder(WordList wordList) { + this.wordList = wordList; + this.bitsPerWord = wordList.getBitsPerWord(); + } + + public int[] decode(String[] words) throws NoSuchElementException { + int[] wordIndexes = new int[words.length]; + + for (int i=0; i>= bitsPerWord; + + return combinedValue; + } + + private int[] decodeCoords(long combinedValue, int bitsRequiredPerCoordinate) { + int coordinateMask = (1 << bitsRequiredPerCoordinate) - 1; + int coordinateOffset = 1 << (bitsRequiredPerCoordinate - 1); + + int z = (int) (combinedValue & coordinateMask) - coordinateOffset; + int x = (int) (combinedValue >> bitsRequiredPerCoordinate) - coordinateOffset; + + return new int[] { x, z }; + } + +} diff --git a/src/main/java/eu/m724/tweaks/module/wordcoords/converter/Encoder.java b/src/main/java/eu/m724/tweaks/module/wordcoords/converter/Encoder.java new file mode 100644 index 0000000..caff8ac --- /dev/null +++ b/src/main/java/eu/m724/tweaks/module/wordcoords/converter/Encoder.java @@ -0,0 +1,82 @@ +/* + * Copyright (C) 2025 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.module.wordcoords.converter; + +import eu.m724.tweaks.DebugLogger; +import eu.m724.tweaks.module.wordcoords.WordList; + +public class Encoder { + private final WordList wordList; + private final int bitsPerWord; + + public Encoder(WordList wordList) { + this.wordList = wordList; + this.bitsPerWord = wordList.getBitsPerWord(); + } + + public String[] encode(int xCoord, int zCoord) { + xCoord = Math.floorDiv(xCoord, 16); + zCoord = Math.floorDiv(zCoord, 16); + DebugLogger.finer("Chunk: %d, %d", xCoord, zCoord); + + + int wordsRequired = findWordsRequired(xCoord, zCoord); + DebugLogger.finer("Words required: %d", wordsRequired); + + int bitsRequired = wordsRequired * bitsPerWord; + int bitsRequiredPerCoordinate = bitsRequired / 2; + DebugLogger.finer("Bits required: %d (per coord: %d)", bitsRequired, bitsRequiredPerCoordinate); + + + int encodedX = encodeCoord(xCoord, bitsRequiredPerCoordinate); + int encodedZ = encodeCoord(zCoord, bitsRequiredPerCoordinate); + DebugLogger.finer("Encoded coordinates: %d, %d", encodedX, encodedZ); + + + long combinedValue = ((long) encodedX << bitsRequiredPerCoordinate) | encodedZ; + DebugLogger.finer("Combined value: %d", combinedValue); + + + int[] wordIndexes = combinedValueToWordIndexes(combinedValue, wordsRequired); + return wordList.getWords(wordIndexes); + } + + private int findBitsRequiredPerCoordinate(int x, int z) { + int n = Math.max(Math.abs(x), Math.abs(z)); + return n == 0 ? 1 : 32 - Integer.numberOfLeadingZeros(n); + } + + private int findWordsRequired(int x, int z) { + int brpc = findBitsRequiredPerCoordinate(x, z); + return Math.ceilDiv(brpc * 2, wordList.getBitsPerWord()); + } + + private int encodeCoord(int coord, int bitsRequiredPerCoordinate) { + // Bitmask and offset for positive integer conversion + int coordinateMask = (1 << bitsRequiredPerCoordinate) - 1; + int coordinateOffset = 1 << (bitsRequiredPerCoordinate - 1); + + // Encode coordinates with offset into positive range + return (coordinateOffset + coord) & coordinateMask; + } + + private int[] combinedValueToWordIndexes(long combinedValue, int wordsRequired) { + int bitsRequired = wordsRequired * bitsPerWord; + + // Break into word indexes + int[] wordIndexes = new int[wordsRequired]; + int currentIndex = wordsRequired; // Start filling from end of array + + for (int remainingBits = bitsRequired; remainingBits > 0; remainingBits -= bitsPerWord) { + int wordMask = (1 << bitsPerWord) - 1; + wordIndexes[--currentIndex] = (int) (combinedValue & wordMask); + combinedValue >>= bitsPerWord; + } + + return wordIndexes; + } +} diff --git a/src/main/resources/plugin.yml b/src/main/resources/plugin.yml index 18bb896..40473fb 100644 --- a/src/main/resources/plugin.yml +++ b/src/main/resources/plugin.yml @@ -43,6 +43,10 @@ commands: durabilityalert: description: Durability alert toggle permission: tweaks724.durabilityalert + wordcoords: + description: Word to coords conversion + permission: tweaks724.wordcoords + aliases: [woco, wc, w3w] permissions: tweaks724.chatmanage: @@ -63,6 +67,8 @@ permissions: default: false tweaks724.durabilityalert: default: true + tweaks724.wordcoords: + default: true 7weaks724.ignore.this: description: "Internal, not for use. ${project.spigot.version}" diff --git a/src/main/resources/strings.properties b/src/main/resources/strings.properties index be9800a..e9dda4d 100644 --- a/src/main/resources/strings.properties +++ b/src/main/resources/strings.properties @@ -38,4 +38,8 @@ clickToCopy = Click to copy to clipboard clickToExecuteCommand = Click to execute command durabilityEnabled = Enabled durability alert -durabilityDisabled = Disabled durability alert \ No newline at end of file +durabilityDisabled = Disabled durability alert + +# When console executes /wordcoords without arguments +wordCoordsPlayerOnly = Only players can execute this command without arguments. +wordCoordsOutOfRange = Those coordinates are invalid. \ No newline at end of file