Signed-off-by: Minecon724 <minecon724@noreply.git.m724.eu>
This commit is contained in:
parent
352807483c
commit
2c835a4eab
8 changed files with 2222 additions and 163 deletions
|
@ -0,0 +1,45 @@
|
|||
/*
|
||||
* 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.codec.DecoderWordsToCoords;
|
||||
import eu.m724.tweaks.module.wordcoords.codec.EncoderCoordsToWords;
|
||||
|
||||
import java.util.NoSuchElementException;
|
||||
|
||||
public class WordCoordsCodec {
|
||||
private final EncoderCoordsToWords encoder;
|
||||
private final DecoderWordsToCoords decoder;
|
||||
|
||||
public WordCoordsCodec(WordList wordList) {
|
||||
this.encoder = new EncoderCoordsToWords(wordList);
|
||||
this.decoder = new DecoderWordsToCoords(wordList);
|
||||
|
||||
DebugLogger.fine("Words: %d (%d bits/w)", wordList.getWordCount(), wordList.getBitsPerWord());
|
||||
}
|
||||
|
||||
/**
|
||||
* Encodes coords to words
|
||||
* @param x The X coordinate
|
||||
* @param z The Z coordinate
|
||||
* @return The words
|
||||
*/
|
||||
public String[] encodeWords(int x, int z) {
|
||||
return encoder.encodeWords(x, z);
|
||||
}
|
||||
|
||||
/**
|
||||
* Decodes words to coords
|
||||
* @param words The words
|
||||
* @return The X,Z coordinates
|
||||
* @throws NoSuchElementException if one or more words are invalid
|
||||
*/
|
||||
public int[] decodeCoords(String[] words) throws NoSuchElementException {
|
||||
return decoder.decodeCoords(words);
|
||||
}
|
||||
}
|
|
@ -1,36 +0,0 @@
|
|||
/*
|
||||
* 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);
|
||||
}
|
||||
|
||||
}
|
|
@ -6,6 +6,7 @@
|
|||
|
||||
package eu.m724.tweaks.module.wordcoords;
|
||||
|
||||
import eu.m724.tweaks.DebugLogger;
|
||||
import eu.m724.tweaks.Language;
|
||||
import eu.m724.tweaks.module.TweaksModule;
|
||||
import net.md_5.bungee.api.ChatColor;
|
||||
|
@ -25,35 +26,64 @@ import org.bukkit.event.player.PlayerCommandPreprocessEvent;
|
|||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.io.InputStream;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.NoSuchElementException;
|
||||
|
||||
public class WordCoordsModule extends TweaksModule implements CommandExecutor, Listener {
|
||||
private WordList wordList;
|
||||
private WordCoordsConverter converter;
|
||||
private static final int MAX_RADIUS = 30_000_000;
|
||||
|
||||
private WordCoordsCodec converter;
|
||||
|
||||
@Override
|
||||
protected void onInit() {
|
||||
try {
|
||||
this.wordList = WordList.fromFile(getPlugin().getDataFolder().toPath().resolve("storage/wordlist.txt"));
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
Path wordListFile = getPlugin().getDataFolder().toPath().resolve("storage/wordlist.txt");
|
||||
|
||||
if (Files.notExists(wordListFile)) {
|
||||
try {
|
||||
saveDefaultWordList(wordListFile);
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException("Failed to save default word list", e);
|
||||
}
|
||||
}
|
||||
|
||||
this.converter = new WordCoordsConverter(wordList);
|
||||
WordList wordList;
|
||||
try {
|
||||
wordList = WordList.fromFile(wordListFile);
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException("Failed to load word list", e);
|
||||
}
|
||||
|
||||
this.converter = new WordCoordsCodec(wordList);
|
||||
|
||||
registerCommand("wordcoords", this);
|
||||
registerEvents(this);
|
||||
}
|
||||
|
||||
private void saveDefaultWordList(Path wordListFile) throws IOException {
|
||||
try (InputStream is = getPlugin().getResource("wordlist.txt")) {
|
||||
assert is != null;
|
||||
Files.copy(is, wordListFile);
|
||||
}
|
||||
}
|
||||
|
||||
@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
|
||||
|
||||
args = String.join(" ", args)
|
||||
.replaceAll("[^\\p{L}\\p{N}\\s]", " ")
|
||||
.trim()
|
||||
.split(" +");
|
||||
|
||||
if (args.length == 1 && args[0].isEmpty())
|
||||
args = new String[0]; // empty split fix
|
||||
|
||||
DebugLogger.fine("Args: %s %d", String.join(", ", args), args.length);
|
||||
|
||||
if (args.length == 0) {
|
||||
if (!(sender instanceof Player player)) {
|
||||
sender.sendMessage(Language.getString("wordCoordsPlayerOnly"));
|
||||
|
@ -64,95 +94,80 @@ public class WordCoordsModule extends TweaksModule implements CommandExecutor, L
|
|||
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]);
|
||||
} else {
|
||||
if (Character.isDigit(args[0].codePointAt(0))) {
|
||||
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));
|
||||
if (dx > MAX_RADIUS || dx < -MAX_RADIUS || dz > MAX_RADIUS || dz < -MAX_RADIUS) {
|
||||
sender.spigot().sendMessage(Language.getComponent("wordCoordsOutOfRange", ChatColor.RED));
|
||||
return true;
|
||||
}
|
||||
|
||||
x = (int) dx;
|
||||
z = (int) dz;
|
||||
|
||||
encode = true;
|
||||
} catch (NumberFormatException ignored) { }
|
||||
} else {
|
||||
sender.spigot().sendMessage(Language.getComponent("wordCoordsProvideZ", ChatColor.RED));
|
||||
return true;
|
||||
}
|
||||
|
||||
x = (int) dx;
|
||||
z = (int) dz;
|
||||
|
||||
encode = true;
|
||||
} catch (NumberFormatException ignored) { }
|
||||
}
|
||||
}
|
||||
|
||||
if (encode) {
|
||||
words = converter.encode(x, z);
|
||||
String encoded = "///" + String.join(".", words);
|
||||
|
||||
BaseComponent[] components = new ComponentBuilder()
|
||||
.append(String.format("%d, %d encodes to ", x, z))
|
||||
.color(ChatColor.GRAY)
|
||||
.append(encoded)
|
||||
.color(ChatColor.AQUA) // TODO improve color
|
||||
.event(new ClickEvent(ClickEvent.Action.COPY_TO_CLIPBOARD, encoded))
|
||||
.event(new HoverEvent(HoverEvent.Action.SHOW_TEXT, new Text("Click to copy")))
|
||||
.create();
|
||||
|
||||
sender.spigot().sendMessage(components);
|
||||
encodeAndSend(x, z, sender);
|
||||
} else {
|
||||
String strArgs = String.join(" ", args);
|
||||
words = smartDetectWords(strArgs);
|
||||
|
||||
if (words.length == 0) {
|
||||
sender.spigot().sendMessage(Language.getComponent("wordCoordsNoWords", ChatColor.GRAY));
|
||||
return true;
|
||||
}
|
||||
|
||||
try {
|
||||
int[] xz = converter.decode(words);
|
||||
x = xz[0];
|
||||
z = xz[1];
|
||||
} catch (NoSuchElementException e) {
|
||||
sender.spigot().sendMessage(Language.getComponent("wordCoordsInvalidWord", ChatColor.RED, e.getMessage()));
|
||||
return true;
|
||||
}
|
||||
|
||||
String encoded = "///" + String.join(".", words);
|
||||
|
||||
BaseComponent[] components = new ComponentBuilder()
|
||||
.append(encoded + " decodes to ")
|
||||
.color(ChatColor.GRAY)
|
||||
.append("%d, %d".formatted(x, z))
|
||||
.color(ChatColor.AQUA) // TODO improve color
|
||||
.event(new ClickEvent(ClickEvent.Action.COPY_TO_CLIPBOARD, "%d, %d".formatted(x, z)))
|
||||
.event(new HoverEvent(HoverEvent.Action.SHOW_TEXT, new Text("Click to copy")))
|
||||
.append(" ±8")
|
||||
.color(ChatColor.GRAY)
|
||||
.create();
|
||||
sender.spigot().sendMessage(components);
|
||||
decodeAndSend(args, sender);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private String[] smartDetectWords(String str) {
|
||||
List<String> words = new ArrayList<>();
|
||||
StringBuilder currentWord = new StringBuilder();
|
||||
private void encodeAndSend(int x, int z, CommandSender sender) {
|
||||
String[] words = converter.encodeWords(x, z);
|
||||
String encoded = "///" + String.join(".", words);
|
||||
|
||||
for (int i=0; i<str.length(); i++) {
|
||||
char c = str.charAt(i);
|
||||
BaseComponent[] components = new ComponentBuilder()
|
||||
.append("%d, %d -> ".formatted(x, z))
|
||||
.color(ChatColor.GRAY)
|
||||
.append(encoded)
|
||||
.color(ChatColor.AQUA) // TODO improve color
|
||||
.event(new ClickEvent(ClickEvent.Action.COPY_TO_CLIPBOARD, encoded))
|
||||
.event(new HoverEvent(HoverEvent.Action.SHOW_TEXT, new Text(Language.getString("clickToCopy"))))
|
||||
.create();
|
||||
|
||||
if (Character.isLetter(c)) {
|
||||
currentWord.append(c);
|
||||
} else {
|
||||
if (!currentWord.isEmpty()) {
|
||||
words.add(currentWord.toString());
|
||||
currentWord.setLength(0);
|
||||
}
|
||||
}
|
||||
sender.spigot().sendMessage(components);
|
||||
}
|
||||
|
||||
private void decodeAndSend(String[] words, CommandSender sender) {
|
||||
int x, z;
|
||||
|
||||
try {
|
||||
int[] xz = converter.decodeCoords(words);
|
||||
x = xz[0];
|
||||
z = xz[1];
|
||||
} catch (NoSuchElementException e) {
|
||||
sender.spigot().sendMessage(Language.getComponent("wordCoordsInvalidWord", ChatColor.RED, e.getMessage()));
|
||||
return;
|
||||
}
|
||||
|
||||
if (!currentWord.isEmpty()) {
|
||||
words.add(currentWord.toString());
|
||||
}
|
||||
String encoded = "///" + String.join(".", words);
|
||||
|
||||
return words.toArray(String[]::new);
|
||||
BaseComponent[] components = new ComponentBuilder()
|
||||
.append(encoded + " -> ")
|
||||
.color(ChatColor.GRAY)
|
||||
.append("%d, %d".formatted(x, z))
|
||||
.color(ChatColor.AQUA) // TODO improve color
|
||||
.event(new ClickEvent(ClickEvent.Action.COPY_TO_CLIPBOARD, "%d, %d".formatted(x, z)))
|
||||
.event(new HoverEvent(HoverEvent.Action.SHOW_TEXT, new Text(Language.getString("clickToCopy"))))
|
||||
.append(" ±8")
|
||||
.color(ChatColor.GRAY)
|
||||
.create();
|
||||
sender.spigot().sendMessage(components);
|
||||
}
|
||||
|
||||
@EventHandler
|
||||
|
|
|
@ -51,7 +51,7 @@ public class WordList {
|
|||
|
||||
public static WordList fromFile(Path path) throws IOException {
|
||||
try (var lines = Files.lines(path)) {
|
||||
var list = lines.filter(s -> !s.isBlank())
|
||||
var list = lines.filter(s -> !s.isBlank() && !s.startsWith("#"))
|
||||
.map(String::toLowerCase)
|
||||
.distinct()
|
||||
.toList();
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
* in the project root for the full license text.
|
||||
*/
|
||||
|
||||
package eu.m724.tweaks.module.wordcoords.converter;
|
||||
package eu.m724.tweaks.module.wordcoords.codec;
|
||||
|
||||
import eu.m724.tweaks.DebugLogger;
|
||||
import eu.m724.tweaks.module.wordcoords.WordList;
|
||||
|
@ -12,16 +12,16 @@ import eu.m724.tweaks.module.wordcoords.WordList;
|
|||
import java.util.NoSuchElementException;
|
||||
import java.util.Arrays;
|
||||
|
||||
public class Decoder {
|
||||
public class DecoderWordsToCoords {
|
||||
private final WordList wordList;
|
||||
private final int bitsPerWord;
|
||||
|
||||
public Decoder(WordList wordList) {
|
||||
public DecoderWordsToCoords(WordList wordList) {
|
||||
this.wordList = wordList;
|
||||
this.bitsPerWord = wordList.getBitsPerWord();
|
||||
}
|
||||
|
||||
public int[] decode(String[] words) throws NoSuchElementException {
|
||||
public int[] decodeCoords(String[] words) throws NoSuchElementException {
|
||||
int[] wordIndexes = new int[words.length];
|
||||
|
||||
for (int i=0; i<words.length; i++) {
|
||||
|
@ -30,10 +30,10 @@ public class Decoder {
|
|||
throw new NoSuchElementException(words[i]);
|
||||
}
|
||||
|
||||
return decode(wordIndexes);
|
||||
return decodeCoords(wordIndexes);
|
||||
}
|
||||
|
||||
public int[] decode(int[] wordIndexes) {
|
||||
public int[] decodeCoords(int[] wordIndexes) {
|
||||
DebugLogger.finer("Decoding word indexes: %s", Arrays.toString(wordIndexes));
|
||||
|
||||
int bitsRequired = wordIndexes.length * wordList.getBitsPerWord();
|
||||
|
@ -45,7 +45,7 @@ public class Decoder {
|
|||
DebugLogger.finer("Combined value: %d", combinedValue);
|
||||
|
||||
|
||||
int[] decodedCoords = decodeCoords(combinedValue, bitsRequiredPerCoordinate);
|
||||
int[] decodedCoords = decodeCombined(combinedValue, bitsRequiredPerCoordinate);
|
||||
int chunkX = decodedCoords[0];
|
||||
int chunkZ = decodedCoords[1];
|
||||
DebugLogger.finer("Chunk: %d, %d", chunkX, chunkZ);
|
||||
|
@ -61,15 +61,15 @@ public class Decoder {
|
|||
private long wordIndexesToCombinedValue(int[] wordIndexes) {
|
||||
long combinedValue = 0;
|
||||
|
||||
for (int i=0; i<wordIndexes.length; i++) {
|
||||
for (int wordIndex : wordIndexes) {
|
||||
combinedValue <<= bitsPerWord;
|
||||
combinedValue |= wordIndexes[i];
|
||||
combinedValue |= wordIndex;
|
||||
}
|
||||
|
||||
return combinedValue;
|
||||
}
|
||||
|
||||
private int[] decodeCoords(long combinedValue, int bitsRequiredPerCoordinate) {
|
||||
private int[] decodeCombined(long combinedValue, int bitsRequiredPerCoordinate) {
|
||||
int coordinateMask = (1 << bitsRequiredPerCoordinate) - 1;
|
||||
int coordinateOffset = 1 << (bitsRequiredPerCoordinate - 1);
|
||||
|
|
@ -4,21 +4,23 @@
|
|||
* in the project root for the full license text.
|
||||
*/
|
||||
|
||||
package eu.m724.tweaks.module.wordcoords.converter;
|
||||
package eu.m724.tweaks.module.wordcoords.codec;
|
||||
|
||||
import eu.m724.tweaks.DebugLogger;
|
||||
import eu.m724.tweaks.module.wordcoords.WordList;
|
||||
|
||||
import java.util.Arrays;
|
||||
public class Encoder {
|
||||
|
||||
public class EncoderCoordsToWords {
|
||||
private final WordList wordList;
|
||||
private final int bitsPerWord;
|
||||
|
||||
public Encoder(WordList wordList) {
|
||||
public EncoderCoordsToWords(WordList wordList) {
|
||||
this.wordList = wordList;
|
||||
this.bitsPerWord = wordList.getBitsPerWord();
|
||||
}
|
||||
|
||||
public String[] encode(int xCoord, int zCoord) {
|
||||
public String[] encodeWords(int xCoord, int zCoord) {
|
||||
int chunkX = Math.floorDiv(xCoord, 16);
|
||||
int chunkZ = Math.floorDiv(zCoord, 16);
|
||||
DebugLogger.finer("Chunk: %d, %d", chunkX, chunkZ);
|
||||
|
@ -39,8 +41,7 @@ public class Encoder {
|
|||
wordsRequired++;
|
||||
actualTotalBits = wordsRequired * bitsPerWord;
|
||||
}
|
||||
} // else: coords are 0 or -1, minTotalBits=0, wordsRequired=0, actualTotalBits=0. Need special handling?
|
||||
// If x/z are 0/-1, findBitsRequired returns 1, minTotalBits=2. The loop handles it.
|
||||
}
|
||||
|
||||
// Final bits per coordinate based on words
|
||||
bitsRequiredPerCoordinate = actualTotalBits / 2;
|
||||
|
@ -60,9 +61,9 @@ public class Encoder {
|
|||
return wordList.getWords(wordIndexes);
|
||||
}
|
||||
|
||||
// Calculates the minimum number of bits required to represent the coordinate
|
||||
// using the encoding scheme (offset + coord) & mask, such that the coordinate
|
||||
// fits within the range [-(1 << (bits - 1)), (1 << (bits - 1)) - 1].
|
||||
/** Calculates the minimum number of bits required to represent the coordinate
|
||||
* using the encoding scheme (offset + coord) & mask, such that the coordinate
|
||||
* fits within the range [-(1 << (bits - 1)), (1 << (bits - 1)) - 1]. */
|
||||
private int findBitsRequiredPerCoordinate(int x, int z) {
|
||||
int maxVal = Math.max(x, z);
|
||||
int minVal = Math.min(x, z);
|
||||
|
@ -72,28 +73,9 @@ public class Encoder {
|
|||
int requiredPositiveMagnitude = Math.max(maxVal + 1, -minVal);
|
||||
|
||||
if (requiredPositiveMagnitude <= 0) {
|
||||
// Occurs only if maxVal <= -1 and minVal >= 0, which is impossible,
|
||||
// OR maxVal <= 0 and -minVal <= 0 => maxVal <= 0 and minVal >= 0.
|
||||
// This means x and z are both 0.
|
||||
// The range for 1 bit is [-1, 0]. If coords are 0, 1 bit is not enough for offset+coord.
|
||||
// Example: bits=1. offset=1<<0=1. mask=(1<<1)-1=1.
|
||||
// encodeCoord(0, 1) = (1+0)&1 = 1.
|
||||
// decodeCoord(1, 1): val=1. mask=1. offset=1. (1&1)-1 = 0. Correct.
|
||||
// What if we need to represent -1? encodeCoord(-1, 1) = (1-1)&1 = 0.
|
||||
// decodeCoord(0, 1): val=0. (0&1)-1 = -1. Correct.
|
||||
// So 1 bit works for range [-1, 0]. Let's check the condition:
|
||||
// x=0, z=0 -> maxVal=0, minVal=0. reqPosMag = max(1, 0) = 1.
|
||||
// x=-1, z=-1 -> maxVal=-1, minVal=-1. reqPosMag = max(0, 1) = 1.
|
||||
// x=0, z=-1 -> maxVal=0, minVal=-1. reqPosMag = max(1, 1) = 1.
|
||||
// So requiredPositiveMagnitude is 1 for the range [-1, 0].
|
||||
requiredPositiveMagnitude = 1; // Ensure it's at least 1 if coords are 0 or -1.
|
||||
requiredPositiveMagnitude = 1; // Ensure it's at least 1 if coords are 0 or -1.
|
||||
}
|
||||
|
||||
// Calculate p = bits - 1
|
||||
// We need the smallest integer p such that (1 << p) >= requiredPositiveMagnitude.
|
||||
// If requiredPositiveMagnitude = 1, we need 1 << p >= 1, smallest p is 0.
|
||||
// If requiredPositiveMagnitude > 1, this is equivalent to finding the number of bits
|
||||
// needed to represent (requiredPositiveMagnitude - 1) in binary.
|
||||
int p;
|
||||
if (requiredPositiveMagnitude == 1) {
|
||||
p = 0;
|
||||
|
@ -101,7 +83,6 @@ public class Encoder {
|
|||
p = 32 - Integer.numberOfLeadingZeros(requiredPositiveMagnitude - 1);
|
||||
}
|
||||
|
||||
// bits = p + 1
|
||||
return p + 1;
|
||||
}
|
||||
|
||||
|
@ -119,7 +100,7 @@ public class Encoder {
|
|||
|
||||
// Break into word indexes
|
||||
int[] wordIndexes = new int[wordsRequired];
|
||||
int currentIndex = wordsRequired; // Start filling from end of array
|
||||
int currentIndex = wordsRequired; // Start filling from the end of the array
|
||||
|
||||
for (int remainingBits = bitsRequired; remainingBits > 0; remainingBits -= bitsPerWord) {
|
||||
int wordMask = (1 << bitsPerWord) - 1;
|
|
@ -34,7 +34,7 @@ authKickError = An error occurred. Please try again. If this persists, contact a
|
|||
|
||||
redstoneGatewayItem = Redstone gateway
|
||||
|
||||
clickToCopy = Click to copy to clipboard
|
||||
clickToCopy = Click to copy
|
||||
clickToExecuteCommand = Click to execute command
|
||||
|
||||
durabilityEnabled = Enabled durability alert
|
||||
|
@ -43,8 +43,8 @@ durabilityDisabled = Disabled durability alert
|
|||
# When console executes /wordcoords without arguments
|
||||
wordCoordsPlayerOnly = Only players can execute this command without arguments.
|
||||
wordCoordsOutOfRange = Those coordinates are invalid.
|
||||
wordCoordsInvalidWord = Invalid word: "%s"
|
||||
wordCoordsNoWords = Please provide the Z coordinate.
|
||||
wordCoordsInvalidWord = Invalid word or coordinate: "%s"
|
||||
wordCoordsProvideZ = Please provide the Z coordinate.
|
||||
|
||||
# /pomodoro
|
||||
pomodoroStopped = Pomodoro stopped. Restart it with /%s start
|
||||
|
|
2054
src/main/resources/wordlist.txt
Normal file
2054
src/main/resources/wordlist.txt
Normal file
File diff suppressed because it is too large
Load diff
Loading…
Add table
Add a link
Reference in a new issue