diff --git a/CHANGELOG.md b/CHANGELOG.md index 30ecefd..446b67a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,17 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and this project kind of adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +### WIP [1.4.0] +#### Changed +- The loading logic of the storage file + +#### Added +- **a config file!** + - The config file starts at **version 0**! +- versioning to the storage file for better handling of migrations. + - We start at **version 1**, all previous version are version 0. + + ### [1.3.4] - Updated Italian translations (Thanks to [Jump-333](https://github.com/Jump-333)!) - Turned DeathLocation into a Hashmap diff --git a/README.md b/README.md index c251d75..ef02038 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,7 @@ A Minecraft server-side mod that adds various teleportation related commands, li Here is the [Changelog](https://github.com/MrSn0wy/TeleportCommands/blob/main/CHANGELOG.md) -### Currently available commands: +## Currently available commands: - `/worldspawn []` - Teleports you to the world spawn (in the overworld), if given true it will not do safety checks - `/back []` - Teleports you to the location where you last died, if given true it will not do safety checks @@ -32,14 +32,14 @@ Here is the [Changelog](https://github.com/MrSn0wy/TeleportCommands/blob/main/CH
-### TODO: -#### Planned commands: +## TODO: +### Planned commands: - [ ] `/wild` - Teleports you to a random location in the Overworld - [x] `/worldspawn` - Teleports you to the worldspawn -- [ ] `/spawn ` - Teleports you to your spawnpoint in a dimension, defaults to your current dimension +- [ ] `/spawn ` - Teleports you to your spawn point in a dimension, defaults to your current dimension - [ ] `/previous` - Go to the last teleported location -#### Improvements: +### Improvements: - [ ] Look into changing the mod into the more safe and sane kotlin (I love java) - [ ] Add game tests - [ ] Find the easiest way to backport the mod to older version (help) @@ -56,31 +56,34 @@ Here is the [Changelog](https://github.com/MrSn0wy/TeleportCommands/blob/main/CH - [x] Add Quilt support and NeoForge -### Want to help? +## Want to help? 1. You can create a translation file so other people can use the mod in their native language: [translations.md](./common/src/main/resources/assets/teleport_commands/lang/translations.md) -### How to build -#### Getting the correct environment +## How to build +### Getting the correct environment +#### Nixos If you are on nixos you can simply go into the folder of where you cloned the repo, and run `nix develop .`. This will give you the environment I use (apart from the IDE) :3. +#### Generic linux On any other linux distro, just install the jetbrains jdk, or try openjdk21. -On windows probably go to the openjdk website and install the 21 version? idk goodluck. +#### Windows +On windows probably go to the openjdk website and install the 21 version? Idk goodluck. -#### Building -Then on linux just do `./gradlew build` and to make it in a single mod jar `./gradlew mergeJars`. +### Building +Then on linux just run `./gradlew build`. To make it into a single mod jar just run `./gradlew mergeJars`. Or on windows, just do `.\gradlew.bat build` and `.\gradlew.bat mergeJars`. Note that this isn't tested for windows, but I think that is how it works. -#### Getting the jars +### Getting the jars Then you can find your jars in `fabric/build/libs/` (for fabric), `neoforge/build/libs/` (for neoforge) or `merged/build/libs/` (if you made the merged jar file). If you have any issues just make an issue or contact me on Discord `@mrsnowy_` -### Notes -Colors: +## Random notes lol +Text Colors: - Green = When something succeeds and an action will happen - Aqua = When something needs attention - White = When something is done diff --git a/common/build.gradle b/common/build.gradle index e47427c..a3d45d8 100644 --- a/common/build.gradle +++ b/common/build.gradle @@ -37,4 +37,4 @@ configurations { artifacts { commonJava sourceSets.main.java.sourceDirectories.singleFile commonResources sourceSets.main.resources.sourceDirectories.singleFile -} \ No newline at end of file +} diff --git a/common/src/main/java/dev/mrsnowy/teleport_commands/TeleportCommands.java b/common/src/main/java/dev/mrsnowy/teleport_commands/TeleportCommands.java index 8dfd2e4..c2d215e 100644 --- a/common/src/main/java/dev/mrsnowy/teleport_commands/TeleportCommands.java +++ b/common/src/main/java/dev/mrsnowy/teleport_commands/TeleportCommands.java @@ -5,21 +5,15 @@ import com.mojang.brigadier.CommandDispatcher; import dev.mrsnowy.teleport_commands.storage.StorageManager; import dev.mrsnowy.teleport_commands.commands.*; import dev.mrsnowy.teleport_commands.storage.DeathLocationStorage; +import dev.mrsnowy.teleport_commands.storage.ConfigManager; import net.minecraft.commands.CommandSourceStack; -import net.minecraft.commands.Commands; import net.minecraft.server.MinecraftServer; import net.minecraft.server.level.ServerPlayer; import net.minecraft.world.level.storage.LevelResource; - +import net.minecraft.core.BlockPos; import java.io.*; -import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; -import java.nio.file.StandardOpenOption; - -import net.minecraft.core.BlockPos; - -import static dev.mrsnowy.teleport_commands.storage.StorageManager.*; public class TeleportCommands { public static String MOD_LOADER; @@ -36,9 +30,9 @@ public class TeleportCommands { CONFIG_DIR = Paths.get(System.getProperty("user.dir")).resolve("config"); // Construct the game directory path SERVER = server; - StorageManager.STORAGE = storageValidator(); // Initialize the storage file + StorageManager.StorageInit(); // Initialize the storage file + ConfigManager.ConfigInit(); DeathLocationStorage.clearDeathLocations(); // Clear data of death locations. - } // initialize commands, also allows me to easily disable any when there is a config @@ -48,6 +42,7 @@ public class TeleportCommands { tpa.register(dispatcher); warp.register(dispatcher); worldspawn.register(dispatcher); + main.register(dispatcher); } // Runs when the playerDeath mixin calls it, updates the /back command position @@ -58,175 +53,4 @@ public class TeleportCommands { DeathLocationStorage.setDeathLocation(uuid, pos, world); } - - // cleans and updates Storage to the newest "version". This is painful - private static StorageClass storageValidator() { - Constants.LOGGER.info("Cleaning and updating Storage!"); - - try { - StorageInit(); - - long startFileSize = Files.size(StorageManager.STORAGE_FILE); - - FileReader reader = new FileReader(StorageManager.STORAGE_FILE.toString()); - JsonElement jsonElement = JsonParser.parseReader(reader); - - if (jsonElement.isJsonObject()) { - - JsonObject mainJsonObject = jsonElement.getAsJsonObject(); - JsonArray newWarpsArray = new JsonArray(); - JsonArray newPlayersArray = new JsonArray(); - - // get the Warps list - if (mainJsonObject.has("Warps") && mainJsonObject.get("Warps").isJsonArray()) { - - // Warps - for (JsonElement warpElement : mainJsonObject.get("Warps").getAsJsonArray()) { - - // Warp - if (warpElement.isJsonObject()) { - JsonObject warp = warpElement.getAsJsonObject(); - - String warpName = warp.has("name") ? warp.get("name").getAsString() : ""; - Integer warpX = warp.has("x") ? warp.get("x").getAsInt() : null; - Integer warpY = warp.has("y") ? warp.get("y").getAsInt() : null; - Integer warpZ = warp.has("z") ? warp.get("z").getAsInt() : null; - String warpWorld = warp.has("world") ? warp.get("world").getAsString() : ""; - - // check if it is valid - if (!warpName.isBlank() && !warpWorld.isBlank() && warpX != null && warpY != null && warpZ != null) { - JsonObject newWarp = new JsonObject(); - - newWarp.addProperty("name", warpName); - newWarp.addProperty("x", warpX); - newWarp.addProperty("y", warpY); - newWarp.addProperty("z", warpZ); - newWarp.addProperty("world", warpWorld); - - newWarpsArray.add(newWarp); - } - } - } - } - - - // get the Players list - if (mainJsonObject.has("Players") && mainJsonObject.get("Players").isJsonArray()) { - - // players - for (JsonElement playerElement : mainJsonObject.get("Players").getAsJsonArray()) { - - // player - if (playerElement.isJsonObject()) { - - JsonObject player = playerElement.getAsJsonObject(); - boolean hasInformation = false; - - String UUID = player.has("Player_UUID") - ? player.get("Player_UUID").getAsString() : (player.has("UUID") - ? player.get("UUID").getAsString() : null); - - String DefaultHome = player.has("DefaultHome") - ? player.get("DefaultHome").getAsString() : ""; - - JsonArray homes = new JsonArray(); - - if (player.has("Homes") && player.get("Homes").isJsonArray() ) { - JsonArray tempHomes = player.get("Homes").getAsJsonArray(); - boolean defaultHomeFound = false; - - - for (JsonElement homeElement : tempHomes) { - if (homeElement.isJsonObject()) { - JsonObject home = homeElement.getAsJsonObject(); - - String homeName = home.has("name") - ? home.get("name").getAsString() : ""; - - // upgrade doubles to int - Integer homeX = home.has("x") && home.get("x").isJsonPrimitive() && home.get("x").getAsJsonPrimitive().isNumber() - ? (int) Math.floor(home.get("x").getAsDouble()) : null; - - Integer homeY = home.has("y") && home.get("y").isJsonPrimitive() && home.get("y").getAsJsonPrimitive().isNumber() - ? (int) Math.floor(home.get("y").getAsDouble()) : null; - - Integer homeZ = home.has("z") && home.get("z").isJsonPrimitive() && home.get("z").getAsJsonPrimitive().isNumber() - ? (int) Math.floor(home.get("z").getAsDouble()) : null; - - String homeWorld = home.has("world") - ? home.get("world").getAsString() : ""; - - // check if it is valid - if (!homeName.isBlank() && !homeWorld.isBlank() && homeX != null && homeY != null && homeZ != null) { - - // check if it is the default home - if (!DefaultHome.isBlank() && homeName.equals(DefaultHome)) { - defaultHomeFound = true; - } - - JsonObject newHome = new JsonObject(); - - newHome.addProperty("name", homeName); - newHome.addProperty("x", homeX); - newHome.addProperty("y", homeY); - newHome.addProperty("z", homeZ); - newHome.addProperty("world", homeWorld); - - homes.add(newHome); - hasInformation = true; - } - } - } - - // clean DefaultHome if there is no home with the name - if (!defaultHomeFound) { - DefaultHome = ""; - } - } - - // if it isn't empty it gets added to the newPlayersArray - if ((UUID != null && !UUID.isBlank()) && hasInformation) { - - JsonObject newPlayer = new JsonObject(); - - newPlayer.addProperty("UUID", UUID); - newPlayer.addProperty("DefaultHome", DefaultHome); - newPlayer.add("Homes", homes); - - newPlayersArray.add(newPlayer); - } - } - } - } - - // save the cleaned and updated file - mainJsonObject.remove("Warps"); - mainJsonObject.add("Warps", newWarpsArray); - - mainJsonObject.remove("Players"); - mainJsonObject.add("Players", newPlayersArray); - - // save the cleaned database - Gson gson = new GsonBuilder().create(); - byte[] json = gson.toJson(mainJsonObject).getBytes(); - Files.write(StorageManager.STORAGE_FILE, json, StandardOpenOption.WRITE, StandardOpenOption.TRUNCATE_EXISTING); - - // Only show amount cleaned when it isn't 0B lool - int diff = Math.round(( startFileSize - Files.size(StorageManager.STORAGE_FILE) )); - - if (diff > 0) { - Constants.LOGGER.info("Success! Cleaned: {}B", diff); - } else { - Constants.LOGGER.info("Success!"); - } - - return gson.fromJson(mainJsonObject, StorageManager.StorageClass.class); - } - - } catch (IOException e) { - Constants.LOGGER.error("Error while cleaning the database!", e); - } - - return null; - } } \ No newline at end of file diff --git a/common/src/main/java/dev/mrsnowy/teleport_commands/commands/main.java b/common/src/main/java/dev/mrsnowy/teleport_commands/commands/main.java index 952618f..1abad90 100644 --- a/common/src/main/java/dev/mrsnowy/teleport_commands/commands/main.java +++ b/common/src/main/java/dev/mrsnowy/teleport_commands/commands/main.java @@ -1,76 +1,138 @@ -//package dev.mrsnowy.teleport_commands.commands; -// -//import com.mojang.brigadier.arguments.BoolArgumentType; -//import dev.mrsnowy.teleport_commands.TeleportCommands; -//import dev.mrsnowy.teleport_commands.common.DeathLocation; -//import dev.mrsnowy.teleport_commands.storage.DeathLocationStorage; -//import net.minecraft.ChatFormatting; -//import net.minecraft.commands.Commands; -//import net.minecraft.core.BlockPos; -//import net.minecraft.network.chat.ClickEvent; -//import net.minecraft.network.chat.Component; -//import net.minecraft.server.level.ServerLevel; -//import net.minecraft.server.level.ServerPlayer; -//import net.minecraft.world.phys.Vec3; -// -//import java.util.Optional; -// -//import static dev.mrsnowy.teleport_commands.utils.tools.*; -//import static net.minecraft.commands.Commands.argument; -// -//// TODO! add option to reload registered commands! -// -//public class main { -// -// public static void register(Commands commandManager) { -// -// commandManager.getDispatcher().register(Commands.literal("teleportcommands") -// .then(Commands.literal("help") -// .requires(source -> source.getPlayer() != null) -// .executes(context -> { -// final ServerPlayer player = context.getSource().getPlayerOrException(); -// -// try { -// printCommands(player); -// -// } catch (Exception e) { -// TeleportCommands.LOGGER.error("Error while going back! => ", e); -// player.displayClientMessage(getTranslatedText("commands.teleport_commands.common.error", player).withStyle(ChatFormatting.RED, ChatFormatting.BOLD), true); -// return 1; -// } -// return 0; -// })) -// .then(argument("Disable Safety", BoolArgumentType.bool()) -// .requires(source -> source.getPlayer() != null) -// .executes(context -> { -// final boolean safety = BoolArgumentType.getBool(context, "Disable Safety"); -// final ServerPlayer player = context.getSource().getPlayerOrException(); -// -// try { -//// ToDeathLocation(player, safety); -// -// } catch (Exception e) { -// TeleportCommands.LOGGER.error("Error while going back! => ", e); -// player.displayClientMessage(getTranslatedText("commands.teleport_commands.common.error", player).withStyle(ChatFormatting.RED, ChatFormatting.BOLD), true); -// return 1; -// } -// return 0; -// })) -// ); -// } -// -// -// // ----- -// -// -// // Gets the DeathLocation of the player and teleports the player to it -// private static void printCommands(ServerPlayer player) throws Exception { -// -// player.displayClientMessage(Component.literal("Thank you for using Teleport Commands (V)!").withStyle(ChatFormatting.AQUA), false); -// player.displayClientMessage(Component.literal("Teleport Commands is a server-side mod that adds various teleportation related commands").withStyle(ChatFormatting.AQUA), false); -// -// player.displayClientMessage(Component.literal("----").withStyle(ChatFormatting.AQUA), false); -// -// player.displayClientMessage(Component.literal("Usage:").withStyle(ChatFormatting.AQUA), false); -// } -//} +package dev.mrsnowy.teleport_commands.commands; + +import com.mojang.brigadier.CommandDispatcher; +import com.mojang.brigadier.arguments.StringArgumentType; +import com.mojang.brigadier.exceptions.SimpleCommandExceptionType; +import com.mojang.brigadier.suggestion.SuggestionProvider; +import dev.mrsnowy.teleport_commands.Constants; +import dev.mrsnowy.teleport_commands.TeleportCommands; +import dev.mrsnowy.teleport_commands.storage.ConfigManager; +import net.minecraft.ChatFormatting; +import net.minecraft.commands.CommandSourceStack; +import net.minecraft.commands.Commands; +import net.minecraft.commands.SharedSuggestionProvider; +import net.minecraft.network.chat.Component; +import net.minecraft.network.chat.MutableComponent; +import net.minecraft.server.level.ServerPlayer; + +import java.util.Arrays; + +import static dev.mrsnowy.teleport_commands.utils.tools.*; + +public class main { + + // TODO! Make this automatically generate based on the commands we have + private static final String[] available_commands = { + "back", + "home", + "tpa", + "warp", + "worldspawn" + }; + + // sum lists + private static final String[] enabled_commands = available_commands; // TODO! get enabled commands + private static final String[] disabled_commands = available_commands; // TODO! get disabled commands + + // Create sum suggestion providers + private static final SuggestionProvider disabled_commands_suggester = (context, builder) -> SharedSuggestionProvider.suggest(disabled_commands, builder); + private static final SuggestionProvider enabled_commands_suggester = (context, builder) -> SharedSuggestionProvider.suggest(enabled_commands, builder); + + + public static void register(CommandDispatcher dispatcher) { + + dispatcher.register(Commands.literal("teleportcommands") + .then(Commands.literal("reloadConfig") + .executes(context -> { + try { + ConfigManager.ConfigLoader(); + } catch (Exception e) { + Constants.LOGGER.error("Failed to reload config!", e); + throw new SimpleCommandExceptionType(Component.literal(e.toString())).create(); + } + return 0; + })) + .then(Commands.literal("disable") + .then(Commands.argument("command", StringArgumentType.word()) + .suggests(enabled_commands_suggester) + .requires(source -> source.hasPermission(4)) // Require OP + .requires(source -> source.getPlayer() != null) + .executes(context -> { + final ServerPlayer player = context.getSource().getPlayerOrException(); + final String string = StringArgumentType.getString(context, "command"); + + // todo! maybe move this check outside for reusability? + if (!Arrays.asList(enabled_commands).contains(string)) { + // TODO! make this translatable + throw new SimpleCommandExceptionType(Component.literal("\"" + string + "\" is not available as an command!").withStyle(ChatFormatting.RED, ChatFormatting.BOLD)).create(); + } + + try { + player.displayClientMessage(Component.literal("meow " + string), true); + + } catch (Exception e) { + Constants.LOGGER.error("Error while disabling a command! => ", e); + // TODO replace the error below with something normal? + throw new SimpleCommandExceptionType(getTranslatedText("commands.teleport_commands.common.error", player).withStyle(ChatFormatting.RED, ChatFormatting.BOLD)).create(); + } + return 0; + }) + )) + .then(Commands.literal("enable") + .then(Commands.argument("command", StringArgumentType.word()) + .suggests(disabled_commands_suggester) + .requires(source -> source.hasPermission(4)) // Require OP + .requires(source -> source.getPlayer() != null) + .executes(context -> { + final ServerPlayer player = context.getSource().getPlayerOrException(); + final String string = StringArgumentType.getString(context, "command"); + + // todo! maybe move this check outside for reusability? + if (!Arrays.asList(disabled_commands).contains(string)) { + // TODO! make this translatable + throw new SimpleCommandExceptionType(Component.literal("\"" + string + "\" is not available as an command!")).create(); + } + + try { + player.displayClientMessage(Component.literal("meow " + string), true); + + } catch (Exception e) { + Constants.LOGGER.error("Error while disabling a command! => ", e); + // TODO replace the error below with something normal? + throw new SimpleCommandExceptionType(getTranslatedText("commands.teleport_commands.common.error", player).withStyle(ChatFormatting.RED, ChatFormatting.BOLD)).create(); + } + return 0; + }) + )) + // Todo! Is this still needed? + .then(Commands.literal("reload") + .requires(source -> source.hasPermission(4)) // Require OP + .executes(context -> { + TeleportCommands.registerCommands(context.getSource().dispatcher()); + return 0; + })) + + .then(Commands.literal("help") + .executes(context -> { + context.getSource().sendSuccess(main::printCommands, false); + return 0; + })) + ); + } + + + // ----- + + + private static MutableComponent printCommands() { + MutableComponent message = Component.empty(); + + message.append(Component.literal("Thank you for using Teleport Commands (V" + Constants.VERSION + ")!").withStyle(ChatFormatting.AQUA)); + message.append(Component.literal("Teleport Commands is a server-side mod that adds various teleportation related commands").withStyle(ChatFormatting.AQUA)); + + message.append(Component.literal("----").withStyle(ChatFormatting.AQUA)); + message.append(Component.literal("Usage:").withStyle(ChatFormatting.AQUA)); + + return message; + } +} diff --git a/common/src/main/java/dev/mrsnowy/teleport_commands/common/ModCommand.java b/common/src/main/java/dev/mrsnowy/teleport_commands/common/ModCommand.java new file mode 100644 index 0000000..cdcc41a --- /dev/null +++ b/common/src/main/java/dev/mrsnowy/teleport_commands/common/ModCommand.java @@ -0,0 +1,9 @@ +package dev.mrsnowy.teleport_commands.common; + +public enum ModCommand { + back, + home, + tpa, + warp, + worldspawn, +} diff --git a/common/src/main/java/dev/mrsnowy/teleport_commands/common/Player.java b/common/src/main/java/dev/mrsnowy/teleport_commands/common/Player.java index a27cd4a..20f7c99 100644 --- a/common/src/main/java/dev/mrsnowy/teleport_commands/common/Player.java +++ b/common/src/main/java/dev/mrsnowy/teleport_commands/common/Player.java @@ -1,7 +1,6 @@ package dev.mrsnowy.teleport_commands.common; import dev.mrsnowy.teleport_commands.storage.StorageManager; -import net.minecraft.core.BlockPos; import java.util.ArrayList; import java.util.List; diff --git a/common/src/main/java/dev/mrsnowy/teleport_commands/storage/ConfigManager.java b/common/src/main/java/dev/mrsnowy/teleport_commands/storage/ConfigManager.java new file mode 100644 index 0000000..5de172f --- /dev/null +++ b/common/src/main/java/dev/mrsnowy/teleport_commands/storage/ConfigManager.java @@ -0,0 +1,252 @@ +package dev.mrsnowy.teleport_commands.storage; + +import com.google.gson.*; +import dev.mrsnowy.teleport_commands.Constants; +import dev.mrsnowy.teleport_commands.TeleportCommands; + +import java.io.FileReader; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.StandardOpenOption; + +public class ConfigManager { + public static Path CONFIG_FILE; + public static ConfigClass CONFIG; + private static final Gson GSON = new GsonBuilder().setPrettyPrinting().create(); + private static final int defaultVersion = new ConfigClass().getVersion(); + + public static void ConfigInit() { + CONFIG_FILE = TeleportCommands.CONFIG_DIR.resolve("teleport_commands.json"); + + try { + ConfigLoader(); + + } catch (Exception e) { + // crashing is probably better here, otherwise the whole mod will be broken + Constants.LOGGER.error("Error while initializing the config file! Exiting! => ", e); + throw new RuntimeException("Error while initializing the config file! Exiting! => ", e); + } + } + + public static void ConfigLoader() throws Exception { + if (!CONFIG_FILE.toFile().exists() || CONFIG_FILE.toFile().length() == 0) { + Files.createDirectories(TeleportCommands.CONFIG_DIR); + + Constants.LOGGER.warn("Config file was not found or was empty! Initializing config"); + CONFIG = new ConfigClass(); + ConfigSaver(); + Constants.LOGGER.info("Config created successfully!"); + } + + ConfigMigrator(); + + FileReader reader = new FileReader(CONFIG_FILE.toFile()); + CONFIG = GSON.fromJson(reader, ConfigClass.class); + if (CONFIG == null) { + Constants.LOGGER.warn("Config file was empty! Loading defaults..."); + CONFIG = new ConfigClass(); + ConfigSaver(); + } + + ConfigSaver(); // Save it so any missing values get added to the file. + Constants.LOGGER.info("Config loaded successfully!"); + } + + /// This function checks what version the config file is and migrates it to the current version of the mod. + public static void ConfigMigrator() throws Exception { + FileReader reader = new FileReader(CONFIG_FILE.toFile()); + JsonObject jsonObject = GSON.fromJson(reader, JsonObject.class); + + int version = jsonObject.has("version") ? jsonObject.get("version").getAsInt() : 0; + + if (version < defaultVersion) { +// Constants.LOGGER.warn("Config file is v{}, migrating to v{}!", version, defaultVersion); +// +// // Save the storage :3 +// byte[] json = GSON.toJson(jsonObject, JsonArray.class).getBytes(); +// Files.write(CONFIG_FILE, json, StandardOpenOption.WRITE, StandardOpenOption.TRUNCATE_EXISTING, StandardOpenOption.CREATE); +// +// Constants.LOGGER.info("Config file migrated to v{} successfully!", defaultVersion); + } else if (version > defaultVersion) { + String message = String.format("Teleport Commands: The config file's version is newer than the supported version, found v%s, expected <= v%s.\n" + + "If you intentionally backported then you can attempt to downgrade the config file located at this location: \"%s\".\n", + version, defaultVersion, CONFIG_FILE.toAbsolutePath()); + + throw new IllegalStateException(message); + } + } + + public static void ConfigSaver() throws Exception { + // todo! maybe throttle saves? + byte[] json = GSON.toJson( ConfigManager.CONFIG ).getBytes(); + + Files.write(CONFIG_FILE, json, StandardOpenOption.WRITE, StandardOpenOption.TRUNCATE_EXISTING, StandardOpenOption.CREATE); + } + + public static class ConfigClass { + private final int version = 0; + public Teleporting teleporting = new Teleporting(); + public Back back = new Back(); + public Home home = new Home(); + public Tpa tpa = new Tpa(); + public Warp warp = new Warp(); + public WorldSpawn worldSpawn = new WorldSpawn(); + + public int getVersion() { + return version; + } + + public static final class Teleporting { + private int delay = 5; + private boolean whileMoving = true; + private boolean whileFighting = false; + private int fightCooldown = 10; + + public int getDelay() { + return delay; + } + + public void setDelay(int delay) { + this.delay = delay; + } + + public boolean isWhileMoving() { + return whileMoving; + } + + public void setWhileMoving(boolean whileMoving) { + this.whileMoving = whileMoving; + } + + public boolean isWhileFighting() { + return whileFighting; + } + + public void setWhileFighting(boolean whileFighting) { + this.whileFighting = whileFighting; + } + + public int getFightCooldown() { + return fightCooldown; + } + + public void setFightCooldown(int fightCooldown) { + this.fightCooldown = fightCooldown; + } + } + + public final class Back { + private boolean enabled = true; + private boolean deleteAfterTeleport = false; + + public boolean isEnabled() { + return enabled; + } + + public void setEnabled(boolean enabled) { + this.enabled = enabled; + } + + public boolean isDeleteAfterTeleport() { + return deleteAfterTeleport; + } + + public void setDeleteAfterTeleport(boolean deleteAfterTeleport) { + this.deleteAfterTeleport = deleteAfterTeleport; + } + } + + public final class Home { + private boolean enabled = true; + private int playerMaximum = 20; + private boolean deleteInvalid = false; + + public boolean isEnabled() { + return enabled; + } + + public void setEnabled(boolean enabled) { + this.enabled = enabled; + } + + public int getPlayerMaximum() { + return playerMaximum; + } + + public void setPlayerMaximum(int playerMaximum) { + this.playerMaximum = playerMaximum; + } + + public boolean isDeleteInvalid() { + return deleteInvalid; + } + + public void setDeleteInvalid(boolean deleteInvalid) { + this.deleteInvalid = deleteInvalid; + } + } + + public final class Tpa { + private boolean enabled = true; + + public boolean isEnabled() { + return enabled; + } + + public void setEnabled(boolean enabled) { + this.enabled = enabled; + } + } + + public final class Warp { + private boolean enabled = true; + private boolean deleteInvalid = false; + + public boolean isEnabled() { + return enabled; + } + + public void setEnabled(boolean enabled) { + this.enabled = enabled; + } + + public boolean isDeleteInvalid() { + return deleteInvalid; + } + + public void setDeleteInvalid(boolean deleteInvalid) { + this.deleteInvalid = deleteInvalid; + } + } + + public final class WorldSpawn { + private boolean enabled = true; + private String world_id = "minecraft:overworld"; + + public boolean isEnabled() { + return enabled; + } + + public void setEnabled(boolean enabled) { + this.enabled = enabled; + } + + public String getWorld_id() { + return world_id; + } + + public void setWorld_id(String world_id) { + this.world_id = world_id; + } + } + } + + // --- Ideas! --- + // Make config options for disabling certain commands + // Make config options for renaming certain commands + // Make config option for changing required permission level for certain commands + // Make config for setting max homes + // Make config that adds a delay between teleports and when fighting. (in teleport function?) + // Make config that automatically deletes namedLocations (warps/homes) with invalid world id's + // Make config for setting the world_id for /worldspawn ? +} diff --git a/common/src/main/java/dev/mrsnowy/teleport_commands/storage/StorageManager.java b/common/src/main/java/dev/mrsnowy/teleport_commands/storage/StorageManager.java index f66aa0d..5689348 100644 --- a/common/src/main/java/dev/mrsnowy/teleport_commands/storage/StorageManager.java +++ b/common/src/main/java/dev/mrsnowy/teleport_commands/storage/StorageManager.java @@ -1,13 +1,12 @@ package dev.mrsnowy.teleport_commands.storage; -import com.google.gson.Gson; -import com.google.gson.GsonBuilder; +import com.google.gson.*; import dev.mrsnowy.teleport_commands.Constants; import dev.mrsnowy.teleport_commands.TeleportCommands; import dev.mrsnowy.teleport_commands.common.NamedLocation; import dev.mrsnowy.teleport_commands.common.Player; -import java.io.File; +import java.io.FileReader; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.StandardOpenOption; @@ -22,49 +21,150 @@ public class StorageManager { public static Path STORAGE_FOLDER; public static Path STORAGE_FILE; public static StorageClass STORAGE; + private static final Gson GSON = new GsonBuilder().create(); + private static final int defaultVersion = new StorageClass().getVersion(); + /// Initializes the StorageManager class and loads the storage from the filesystem. public static void StorageInit() { STORAGE_FOLDER = TeleportCommands.SAVE_DIR.resolve("TeleportCommands/"); STORAGE_FILE = STORAGE_FOLDER.resolve("storage.json"); try { - // check if the folder exists and create it - if (!Files.exists(STORAGE_FOLDER)) { - Files.createDirectories(STORAGE_FOLDER); - } + StorageLoader(); - // check if the file exists and create it - if (!Files.exists(STORAGE_FILE)) { - Files.createFile(STORAGE_FILE); - } + } catch (Exception e) { + // crashing is probably better here, otherwise the whole mod will be broken + Constants.LOGGER.error("Error while initializing the storage file! Exiting! => ", e); + throw new RuntimeException("Error while initializing the storage file! Exiting! => ", e); + } + } + + /// Loads the storage from the filesystem + public static void StorageLoader() throws Exception { + + if (!STORAGE_FILE.toFile().exists() || STORAGE_FILE.toFile().length() == 0) { + Constants.LOGGER.warn("Storage file was not found or was empty! Initializing storage"); + + Files.createDirectories(STORAGE_FOLDER); + STORAGE = new StorageClass(); + StorageSaver(); + Constants.LOGGER.info("Storage created successfully!"); + } + + StorageMigrator(); + + FileReader reader = new FileReader(STORAGE_FILE.toFile()); + STORAGE = GSON.fromJson(reader, StorageClass.class); + if (STORAGE == null) { + Constants.LOGGER.warn("Storage file was empty! Initializing storage"); + STORAGE = new StorageClass(); + StorageSaver(); + } + + STORAGE.cleanup(); + + StorageSaver(); // Save it so any missing values get added to the file. + Constants.LOGGER.info("Storage loaded successfully!"); + } + + /// This function checks what version the storage file is and migrates it to the current version of the mod. + public static void StorageMigrator() throws Exception { + FileReader reader = new FileReader(STORAGE_FILE.toFile()); + JsonObject jsonObject = GSON.fromJson(reader, JsonObject.class); + + int version = jsonObject.has("version") ? jsonObject.get("version").getAsInt() : 0; + + if (version < defaultVersion) { + Constants.LOGGER.warn("Storage file is v{}, migrating to v{}!", version, defaultVersion); - // create the basic storage if it is empty - if (new File(String.valueOf(STORAGE_FILE)).length() == 0) { - StorageManager.STORAGE = new StorageClass(); - StorageSaver(); // todo! verify that it creates em correctly + // In v1.1.0 "Player_UUID" got renamed to "UUID". Since the storage file didn't have a version yet, it is set to version 0. + if (version == 0) { + + if (jsonObject.has("Players") && jsonObject.get("Players").isJsonArray()) { + + JsonArray players = jsonObject.get("Players").getAsJsonArray(); + + for (JsonElement playerElement : players) { + JsonObject player = playerElement.getAsJsonObject(); + + String UUID = player.has("Player_UUID") + ? player.get("Player_UUID").getAsString() : (player.has("UUID") + ? player.get("UUID").getAsString() : null); + + if (UUID == null || UUID.isBlank()) { + // remove it then, it's an invalid entry 0.0 + players.remove(player); // may return true or false for success, but idc + + } else { + player.remove("Player_UUID"); + player.addProperty("UUID", UUID); + } + } + } + + jsonObject.addProperty("version", 1); } - } catch (Exception e) { - Constants.LOGGER.error("Error while creating the storage file! Exiting! => ", e); - // crashing is probably better here, otherwise the whole mod will be broken - System.exit(1); + // Save the storage :3 + byte[] json = GSON.toJson(jsonObject, JsonArray.class).getBytes(); + Files.write(STORAGE_FILE, json, StandardOpenOption.WRITE, StandardOpenOption.TRUNCATE_EXISTING, StandardOpenOption.CREATE); + + Constants.LOGGER.info("Storage file migrated to v{} successfully!", defaultVersion); + } else if (version > defaultVersion) { + String message = String.format("Teleport Commands: The storage file's version is newer than the supported version, found v%s, expected <= v%s.\n" + + "If you intentionally backported then you can attempt to downgrade the storage file located at this location: \"%s\".\n", + version, defaultVersion, STORAGE_FILE.toAbsolutePath()); + + throw new IllegalStateException(message); } } + /// Saves the storage to the filesystem public static void StorageSaver() throws Exception { // todo! maybe throttle saves? - Gson gson = new GsonBuilder().create(); - byte[] json = gson.toJson( StorageManager.STORAGE ).getBytes(); + byte[] json = GSON.toJson( StorageManager.STORAGE ).getBytes(); - Files.write(STORAGE_FILE, json, StandardOpenOption.WRITE, StandardOpenOption.TRUNCATE_EXISTING); + Files.write(STORAGE_FILE, json, StandardOpenOption.WRITE, StandardOpenOption.TRUNCATE_EXISTING, StandardOpenOption.CREATE); } public static class StorageClass { + private final int version = 1; private final ArrayList Warps = new ArrayList<>(); private final ArrayList Players = new ArrayList<>(); - // ----- + /// Cleans up any values in the storage class + public void cleanup() throws Exception { + for (Player player : Players) { + // Remove players with invalid UUID's + if (player.getUUID().isBlank()) { + Players.remove(player); + } + + List homes = player.getHomes(); + + // Delete any homes with an invalid world_id (if enabled in config) + if (ConfigManager.CONFIG.home.isDeleteInvalid()) { + homes.removeIf(home -> home.getWorld().isEmpty()); + } + + // Remove players with no homes + if (homes.isEmpty()) { + Players.remove(player); + } + } + + // Delete any warps with an invalid world_id (if enabled in config) + if (ConfigManager.CONFIG.warp.isDeleteInvalid()) { + Warps.removeIf(warp -> warp.getWorld().isEmpty()); + } + + StorageSaver(); + } + + public int getVersion() { + return version; + } // returns all warps public List getWarps() { diff --git a/common/src/main/java/dev/mrsnowy/teleport_commands/storage/configManager.java b/common/src/main/java/dev/mrsnowy/teleport_commands/storage/configManager.java deleted file mode 100644 index 8b307eb..0000000 --- a/common/src/main/java/dev/mrsnowy/teleport_commands/storage/configManager.java +++ /dev/null @@ -1,14 +0,0 @@ -package dev.mrsnowy.teleport_commands.storage; - -public class configManager { -// Currently nothing to see here... yet - - // --- Ideas! --- - // Make config options for disabling certain commands - // Make config options for renaming certain commands - // Make config option for changing required permission level for certain commands - // Make config for setting max homes - // Make config that adds a delay between teleports and when fighting. (in teleport function?) - // Make config that automatically deletes namedLocations (warps/homes) with invalid world id's - // Make config for setting the world_id for /worldspawn ? -} diff --git a/flake.nix b/flake.nix index ea84758..4a9edad 100644 --- a/flake.nix +++ b/flake.nix @@ -22,6 +22,7 @@ default = pkgs.mkShell { packages = with pkgs; [ jetbrains.jdk-no-jcef # Jetbrains jdk + just # for the justfile flite # Make mc not complain # Took these from https://github.com/NixOS/nixpkgs/blob/nixos-25.05/pkgs/by-name/pr/prismlauncher/package.nix#L123 diff --git a/justfile b/justfile index fc07239..48992e6 100644 --- a/justfile +++ b/justfile @@ -1,3 +1,13 @@ +# A justfile :3 + +build: + ./gradlew build + +clean: + ./gradlew clean + +mergeJars: + ./gradlew mergeJars ide: - nohup idea-ultimate ./ >/dev/null 2>&1 & + nohup idea ./ >/dev/null 2>&1 &