feat: Initial word coords module
Some checks failed
/ build (push) Failing after 7s

Signed-off-by: Minecon724 <git@m724.eu>
This commit is contained in:
Minecon724 2025-02-18 14:09:19 +01:00
parent b421b48e51
commit 7cd334f4a2
Signed by: Minecon724
GPG key ID: 3CCC4D267742C8E8
8 changed files with 389 additions and 1 deletions

View file

@ -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()) {

View file

@ -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);
}
}

View file

@ -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<String> words = new ArrayList<>();
StringBuilder currentWord = new StringBuilder();
for (int i=0; i<str.length(); i++) {
char c = str.charAt(i);
if (Character.isLetter(c)) {
currentWord.append(c);
} else {
if (!currentWord.isEmpty()) {
words.add(currentWord.toString());
currentWord.setLength(0);
}
}
}
if (!currentWord.isEmpty()) {
words.add(currentWord.toString());
}
return words.toArray(String[]::new);
}
}

View file

@ -0,0 +1,63 @@
/*
* 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 java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Arrays;
import java.util.List;
public class WordList {
private final List<String> wordList;
private final int bitsPerWord;
public WordList(List<String> 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);
}
}
}

View file

@ -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<words.length; i++) {
wordIndexes[i] = wordList.getWordIndex(words[i]);
if (wordIndexes[i] == -1)
throw new NoSuchElementException(words[i]);
}
return decode(wordIndexes);
}
public int[] decode(int[] wordIndexes) {
int bitsRequired = wordIndexes.length * wordList.getBitsPerWord();
int bitsRequiredPerCoordinate = bitsRequired / 2;
DebugLogger.finer("Bits required: %d (per coord: %d)", bitsRequired, bitsRequiredPerCoordinate);
long combinedValue = wordIndexesToCombinedValue(wordIndexes);
DebugLogger.finer("Combined value: %d", combinedValue);
int[] decodedCoords = decodeCoords(combinedValue, bitsRequiredPerCoordinate);
int xCoord = decodedCoords[0];
int zCoord = decodedCoords[1];
DebugLogger.info("Chunk: %d, %d", xCoord, zCoord);
return new int[] { xCoord * 16 + 8, zCoord * 16 + 8 }; // +8 to make it center of chunk
}
private long wordIndexesToCombinedValue(int[] wordIndexes) {
long combinedValue = 0;
for (int i=0; i<wordIndexes.length; i++) {
combinedValue |= wordIndexes[i];
combinedValue <<= bitsPerWord;
}
combinedValue >>= 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 };
}
}

View file

@ -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;
}
}

View file

@ -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}"

View file

@ -38,4 +38,8 @@ clickToCopy = Click to copy to clipboard
clickToExecuteCommand = Click to execute command
durabilityEnabled = Enabled durability alert
durabilityDisabled = Disabled durability alert
durabilityDisabled = Disabled durability alert
# When console executes /wordcoords without arguments
wordCoordsPlayerOnly = Only players can execute this command without arguments.
wordCoordsOutOfRange = Those coordinates are invalid.