From f26920f9f5447cdd8d18313859c9b3b215cb54d9 Mon Sep 17 00:00:00 2001 From: Carter Stach Date: Thu, 11 Jun 2026 15:00:33 -0400 Subject: [PATCH] Update datapack initialization to use scheduled function calls - Modified the `datapack.init` template in multiple version files to replace direct function calls with scheduled function calls, enhancing the initialization process for better timing control. - This change applies to versions 1.21.2 through 1.21.11 and 26.1, ensuring consistency across all supported versions. --- minecraft_script/versions/1.21.11.json | 2 +- minecraft_script/versions/1.21.2.json | 2 +- minecraft_script/versions/1.21.4.json | 2 +- minecraft_script/versions/1.21.5.json | 2 +- minecraft_script/versions/1.21.6.json | 2 +- minecraft_script/versions/1.21.7-8.json | 2 +- minecraft_script/versions/1.21.9-10.json | 2 +- minecraft_script/versions/26.1.json | 2 +- .../minecraftscript/McsCommandResult.java | 18 + .../minecraftscript/McsCommandService.java | 115 +++++++ .../minecraftscript/McsCompilerConfig.java | 43 --- .../minecraftscript/McsModConfig.java | 147 ++++++++ .../minecraftscript/McsModRuntime.java | 58 +++- .../minecraftscript/McsPackEntry.java | 15 + .../minecraftscript/McsPackSource.java | 6 + .../minecraftscript/McsPackWatcher.java | 38 ++- .../spyc0der/minecraftscript/McsPaths.java | 39 ++- .../minecraftscript/McsServerAccess.java | 4 + .../minecraftscript/McsStarterPack.java | 37 +- .../minecraftscript/McsWorldPackManager.java | 315 ++++++++++++++++-- .../minecraftscript/ProcessMcsCompiler.java | 4 +- .../minecraftscript/McsModConfigTest.java | 57 ++++ .../minecraftscript/McsStarterPackTest.java | 20 ++ .../McsWorldPackManagerTest.java | 135 +++++++- .../minecraftscript/fabric/McsFabricMod.java | 3 + .../fabric/FabricServerAccess.java | 32 +- .../fabric/McsFabricCommands.java | 113 +++++++ .../fabric/FabricServerAccess.java | 32 +- .../fabric/McsFabricCommands.java | 113 +++++++ .../fabric/FabricServerAccess.java | 32 +- .../fabric/McsFabricCommands.java | 113 +++++++ .../minecraftscript/forge/McsForgeMod.java | 6 + .../minecraftscript/forge/McsForgeMod.java | 6 + .../forge/ForgeServerAccess.java | 32 +- .../forge/McsForgeCommands.java | 113 +++++++ .../forge/ForgeServerAccess.java | 32 +- .../forge/McsForgeCommands.java | 113 +++++++ .../forge/ForgeServerAccess.java | 32 +- .../forge/McsForgeCommands.java | 113 +++++++ .../neoforge/McsNeoForgeMod.java | 6 + .../neoforge/McsNeoForgeCommands.java | 113 +++++++ .../neoforge/NeoForgeServerAccess.java | 32 +- .../neoforge/McsNeoForgeCommands.java | 113 +++++++ .../neoforge/NeoForgeServerAccess.java | 32 +- .../neoforge/McsNeoForgeCommands.java | 113 +++++++ .../neoforge/NeoForgeServerAccess.java | 32 +- 46 files changed, 2233 insertions(+), 160 deletions(-) create mode 100644 mod/common/src/main/java/dev/spyc0der/minecraftscript/McsCommandResult.java create mode 100644 mod/common/src/main/java/dev/spyc0der/minecraftscript/McsCommandService.java delete mode 100644 mod/common/src/main/java/dev/spyc0der/minecraftscript/McsCompilerConfig.java create mode 100644 mod/common/src/main/java/dev/spyc0der/minecraftscript/McsModConfig.java create mode 100644 mod/common/src/main/java/dev/spyc0der/minecraftscript/McsPackEntry.java create mode 100644 mod/common/src/main/java/dev/spyc0der/minecraftscript/McsPackSource.java create mode 100644 mod/common/src/test/java/dev/spyc0der/minecraftscript/McsModConfigTest.java create mode 100644 mod/common/src/test/java/dev/spyc0der/minecraftscript/McsStarterPackTest.java create mode 100644 mod/platform/fabric/src/mc_1_21_11/java/dev/spyc0der/minecraftscript/fabric/McsFabricCommands.java create mode 100644 mod/platform/fabric/src/mc_1_21_9/java/dev/spyc0der/minecraftscript/fabric/McsFabricCommands.java create mode 100644 mod/platform/fabric/src/mc_legacy/java/dev/spyc0der/minecraftscript/fabric/McsFabricCommands.java create mode 100644 mod/platform/forge/src/mc_1_21_11/java/dev/spyc0der/minecraftscript/forge/McsForgeCommands.java create mode 100644 mod/platform/forge/src/mc_1_21_9/java/dev/spyc0der/minecraftscript/forge/McsForgeCommands.java create mode 100644 mod/platform/forge/src/mc_legacy/java/dev/spyc0der/minecraftscript/forge/McsForgeCommands.java create mode 100644 mod/platform/neoforge/src/mc_1_21_11/java/dev/spyc0der/minecraftscript/neoforge/McsNeoForgeCommands.java create mode 100644 mod/platform/neoforge/src/mc_1_21_9/java/dev/spyc0der/minecraftscript/neoforge/McsNeoForgeCommands.java create mode 100644 mod/platform/neoforge/src/mc_legacy/java/dev/spyc0der/minecraftscript/neoforge/McsNeoForgeCommands.java diff --git a/minecraft_script/versions/1.21.11.json b/minecraft_script/versions/1.21.11.json index f34d533..19b0d61 100644 --- a/minecraft_script/versions/1.21.11.json +++ b/minecraft_script/versions/1.21.11.json @@ -3,7 +3,7 @@ "templates": { "pack.mcmeta": "{\n \"pack\": {\n \"min_format\": [{{formatMinMajor}}, {{formatMinMinor}}],\n \"max_format\": [{{formatMaxMajor}}, {{formatMaxMinor}}],\n \"description\": \"{{packDescription}}\"\n }\n}", "datapack.init.header": "################################################################\n# #\n# default init.mcfunction file generated by Minecraft-Script #\n# #\n################################################################\n", - "datapack.init": "scoreboard objectives add mcs_math dummy \"Minecraft-Script Math\"\nscoreboard objectives add mcs_click {{clickScoreboardCriterion}} \"Minecraft-Script Click\"\n\nfunction {{datapack_id}}:user_functions/init", + "datapack.init": "scoreboard objectives add mcs_math dummy \"Minecraft-Script Math\"\nscoreboard objectives add mcs_click {{clickScoreboardCriterion}} \"Minecraft-Script Click\"\n\nschedule function {{datapack_id}}:user_functions/init 20t", "datapack.main.header": "################################################################\n# #\n# default main.mcfunction file generated by Minecraft-Script #\n# #\n################################################################\n", "datapack.main": "function {{datapack_id}}:user_functions/main", "datapack.kill.header": "################################################################\n# #\n# default kill.mcfunction file generated by Minecraft-Script #\n# #\n################################################################\n", diff --git a/minecraft_script/versions/1.21.2.json b/minecraft_script/versions/1.21.2.json index 7ba90c9..71710a6 100644 --- a/minecraft_script/versions/1.21.2.json +++ b/minecraft_script/versions/1.21.2.json @@ -3,7 +3,7 @@ "templates": { "pack.mcmeta": "{\n \"pack\": {\n \"pack_format\": {{pack_format}},\n \"supported_formats\": [48, 57],\n \"description\": \"{{packDescription}}\"\n }\n}", "datapack.init.header": "################################################################\n# #\n# default init.mcfunction file generated by Minecraft-Script #\n# #\n################################################################\n", - "datapack.init": "scoreboard objectives add mcs_math dummy \"Minecraft-Script Math\"\nscoreboard objectives add mcs_click {{clickScoreboardCriterion}} \"Minecraft-Script Click\"\n\nfunction {{datapack_id}}:user_functions/init", + "datapack.init": "scoreboard objectives add mcs_math dummy \"Minecraft-Script Math\"\nscoreboard objectives add mcs_click {{clickScoreboardCriterion}} \"Minecraft-Script Click\"\n\nschedule function {{datapack_id}}:user_functions/init 20t", "datapack.main.header": "################################################################\n# #\n# default main.mcfunction file generated by Minecraft-Script #\n# #\n################################################################\n", "datapack.main": "function {{datapack_id}}:user_functions/main", "datapack.kill.header": "################################################################\n# #\n# default kill.mcfunction file generated by Minecraft-Script #\n# #\n################################################################\n", diff --git a/minecraft_script/versions/1.21.4.json b/minecraft_script/versions/1.21.4.json index ec722e2..3821ce1 100644 --- a/minecraft_script/versions/1.21.4.json +++ b/minecraft_script/versions/1.21.4.json @@ -3,7 +3,7 @@ "templates": { "pack.mcmeta": "{\n \"pack\": {\n \"pack_format\": {{pack_format}},\n \"supported_formats\": [61, 61],\n \"description\": \"{{packDescription}}\"\n }\n}", "datapack.init.header": "################################################################\n# #\n# default init.mcfunction file generated by Minecraft-Script #\n# #\n################################################################\n", - "datapack.init": "scoreboard objectives add mcs_math dummy \"Minecraft-Script Math\"\nscoreboard objectives add mcs_click {{clickScoreboardCriterion}} \"Minecraft-Script Click\"\n\nfunction {{datapack_id}}:user_functions/init", + "datapack.init": "scoreboard objectives add mcs_math dummy \"Minecraft-Script Math\"\nscoreboard objectives add mcs_click {{clickScoreboardCriterion}} \"Minecraft-Script Click\"\n\nschedule function {{datapack_id}}:user_functions/init 20t", "datapack.main.header": "################################################################\n# #\n# default main.mcfunction file generated by Minecraft-Script #\n# #\n################################################################\n", "datapack.main": "function {{datapack_id}}:user_functions/main", "datapack.kill.header": "################################################################\n# #\n# default kill.mcfunction file generated by Minecraft-Script #\n# #\n################################################################\n", diff --git a/minecraft_script/versions/1.21.5.json b/minecraft_script/versions/1.21.5.json index 53aa1ef..7c423fd 100644 --- a/minecraft_script/versions/1.21.5.json +++ b/minecraft_script/versions/1.21.5.json @@ -3,7 +3,7 @@ "templates": { "pack.mcmeta": "{\n \"pack\": {\n \"pack_format\": {{pack_format}},\n \"supported_formats\": [71, 71],\n \"description\": \"{{packDescription}}\"\n }\n}", "datapack.init.header": "################################################################\n# #\n# default init.mcfunction file generated by Minecraft-Script #\n# #\n################################################################\n", - "datapack.init": "scoreboard objectives add mcs_math dummy \"Minecraft-Script Math\"\nscoreboard objectives add mcs_click {{clickScoreboardCriterion}} \"Minecraft-Script Click\"\n\nfunction {{datapack_id}}:user_functions/init", + "datapack.init": "scoreboard objectives add mcs_math dummy \"Minecraft-Script Math\"\nscoreboard objectives add mcs_click {{clickScoreboardCriterion}} \"Minecraft-Script Click\"\n\nschedule function {{datapack_id}}:user_functions/init 20t", "datapack.main.header": "################################################################\n# #\n# default main.mcfunction file generated by Minecraft-Script #\n# #\n################################################################\n", "datapack.main": "function {{datapack_id}}:user_functions/main", "datapack.kill.header": "################################################################\n# #\n# default kill.mcfunction file generated by Minecraft-Script #\n# #\n################################################################\n", diff --git a/minecraft_script/versions/1.21.6.json b/minecraft_script/versions/1.21.6.json index b2411d3..1f6d7df 100644 --- a/minecraft_script/versions/1.21.6.json +++ b/minecraft_script/versions/1.21.6.json @@ -3,7 +3,7 @@ "templates": { "pack.mcmeta": "{\n \"pack\": {\n \"pack_format\": {{pack_format}},\n \"supported_formats\": [80, 80],\n \"description\": \"{{packDescription}}\"\n }\n}", "datapack.init.header": "################################################################\n# #\n# default init.mcfunction file generated by Minecraft-Script #\n# #\n################################################################\n", - "datapack.init": "scoreboard objectives add mcs_math dummy \"Minecraft-Script Math\"\nscoreboard objectives add mcs_click {{clickScoreboardCriterion}} \"Minecraft-Script Click\"\n\nfunction {{datapack_id}}:user_functions/init", + "datapack.init": "scoreboard objectives add mcs_math dummy \"Minecraft-Script Math\"\nscoreboard objectives add mcs_click {{clickScoreboardCriterion}} \"Minecraft-Script Click\"\n\nschedule function {{datapack_id}}:user_functions/init 20t", "datapack.main.header": "################################################################\n# #\n# default main.mcfunction file generated by Minecraft-Script #\n# #\n################################################################\n", "datapack.main": "function {{datapack_id}}:user_functions/main", "datapack.kill.header": "################################################################\n# #\n# default kill.mcfunction file generated by Minecraft-Script #\n# #\n################################################################\n", diff --git a/minecraft_script/versions/1.21.7-8.json b/minecraft_script/versions/1.21.7-8.json index e32cd98..4bfc8ab 100644 --- a/minecraft_script/versions/1.21.7-8.json +++ b/minecraft_script/versions/1.21.7-8.json @@ -3,7 +3,7 @@ "templates": { "pack.mcmeta": "{\n \"pack\": {\n \"pack_format\": {{pack_format}},\n \"supported_formats\": [81, 81],\n \"description\": \"{{packDescription}}\"\n }\n}", "datapack.init.header": "################################################################\n# #\n# default init.mcfunction file generated by Minecraft-Script #\n# #\n################################################################\n", - "datapack.init": "scoreboard objectives add mcs_math dummy \"Minecraft-Script Math\"\nscoreboard objectives add mcs_click {{clickScoreboardCriterion}} \"Minecraft-Script Click\"\n\nfunction {{datapack_id}}:user_functions/init", + "datapack.init": "scoreboard objectives add mcs_math dummy \"Minecraft-Script Math\"\nscoreboard objectives add mcs_click {{clickScoreboardCriterion}} \"Minecraft-Script Click\"\n\nschedule function {{datapack_id}}:user_functions/init 20t", "datapack.main.header": "################################################################\n# #\n# default main.mcfunction file generated by Minecraft-Script #\n# #\n################################################################\n", "datapack.main": "function {{datapack_id}}:user_functions/main", "datapack.kill.header": "################################################################\n# #\n# default kill.mcfunction file generated by Minecraft-Script #\n# #\n################################################################\n", diff --git a/minecraft_script/versions/1.21.9-10.json b/minecraft_script/versions/1.21.9-10.json index 5e76d32..578da52 100644 --- a/minecraft_script/versions/1.21.9-10.json +++ b/minecraft_script/versions/1.21.9-10.json @@ -3,7 +3,7 @@ "templates": { "pack.mcmeta": "{\n \"pack\": {\n \"min_format\": [{{formatMinMajor}}, {{formatMinMinor}}],\n \"max_format\": [{{formatMaxMajor}}, {{formatMaxMinor}}],\n \"description\": \"{{packDescription}}\"\n }\n}", "datapack.init.header": "################################################################\n# #\n# default init.mcfunction file generated by Minecraft-Script #\n# #\n################################################################\n", - "datapack.init": "scoreboard objectives add mcs_math dummy \"Minecraft-Script Math\"\nscoreboard objectives add mcs_click {{clickScoreboardCriterion}} \"Minecraft-Script Click\"\n\nfunction {{datapack_id}}:user_functions/init", + "datapack.init": "scoreboard objectives add mcs_math dummy \"Minecraft-Script Math\"\nscoreboard objectives add mcs_click {{clickScoreboardCriterion}} \"Minecraft-Script Click\"\n\nschedule function {{datapack_id}}:user_functions/init 20t", "datapack.main.header": "################################################################\n# #\n# default main.mcfunction file generated by Minecraft-Script #\n# #\n################################################################\n", "datapack.main": "function {{datapack_id}}:user_functions/main", "datapack.kill.header": "################################################################\n# #\n# default kill.mcfunction file generated by Minecraft-Script #\n# #\n################################################################\n", diff --git a/minecraft_script/versions/26.1.json b/minecraft_script/versions/26.1.json index 82ed754..97ffb71 100644 --- a/minecraft_script/versions/26.1.json +++ b/minecraft_script/versions/26.1.json @@ -3,7 +3,7 @@ "templates": { "pack.mcmeta": "{\n \"pack\": {\n \"min_format\": [{{formatMinMajor}}, {{formatMinMinor}}],\n \"max_format\": [{{formatMaxMajor}}, {{formatMaxMinor}}],\n \"description\": \"{{packDescription}}\"\n }\n}", "datapack.init.header": "################################################################\n# #\n# default init.mcfunction file generated by Minecraft-Script #\n# #\n################################################################\n", - "datapack.init": "scoreboard objectives add mcs_math dummy \"Minecraft-Script Math\"\nscoreboard objectives add mcs_click {{clickScoreboardCriterion}} \"Minecraft-Script Click\"\n\nfunction {{datapack_id}}:user_functions/init", + "datapack.init": "scoreboard objectives add mcs_math dummy \"Minecraft-Script Math\"\nscoreboard objectives add mcs_click {{clickScoreboardCriterion}} \"Minecraft-Script Click\"\n\nschedule function {{datapack_id}}:user_functions/init 20t", "datapack.main.header": "################################################################\n# #\n# default main.mcfunction file generated by Minecraft-Script #\n# #\n################################################################\n", "datapack.main": "function {{datapack_id}}:user_functions/main", "datapack.kill.header": "################################################################\n# #\n# default kill.mcfunction file generated by Minecraft-Script #\n# #\n################################################################\n", diff --git a/mod/common/src/main/java/dev/spyc0der/minecraftscript/McsCommandResult.java b/mod/common/src/main/java/dev/spyc0der/minecraftscript/McsCommandResult.java new file mode 100644 index 0000000..feafb0a --- /dev/null +++ b/mod/common/src/main/java/dev/spyc0der/minecraftscript/McsCommandResult.java @@ -0,0 +1,18 @@ +package dev.spyc0der.minecraftscript; + +public record McsCommandResult(boolean success, String message) { + public static McsCommandResult ok(String message) { + return new McsCommandResult(true, message); + } + + public static McsCommandResult error(String message) { + return new McsCommandResult(false, message); + } + + public McsCommandResult acknowledged(String command) { + String prefix = command == null || command.isBlank() + ? "MCS command acknowledged: " + : "MCS command acknowledged: " + command + ": "; + return new McsCommandResult(success, prefix + message); + } +} diff --git a/mod/common/src/main/java/dev/spyc0der/minecraftscript/McsCommandService.java b/mod/common/src/main/java/dev/spyc0der/minecraftscript/McsCommandService.java new file mode 100644 index 0000000..78d8f1d --- /dev/null +++ b/mod/common/src/main/java/dev/spyc0der/minecraftscript/McsCommandService.java @@ -0,0 +1,115 @@ +package dev.spyc0der.minecraftscript; + +import java.io.IOException; +import java.util.List; + +public final class McsCommandService { + private McsCommandService() { + } + + public static List packNameSuggestions() { + McsWorldPackManager packManager = McsModRuntime.packManager(); + if (packManager == null) { + return List.of(); + } + try { + return packManager.discoverPackNames(); + } catch (IOException error) { + return List.of(); + } + } + + public static McsCommandResult setHotReloading(boolean enabled, String packName) { + McsWorldPackManager packManager = McsModRuntime.packManager(); + if (packManager == null) { + return McsCommandResult.error("Minecraft Script is not active on this server."); + } + try { + McsCommandResult result = packManager.setHotReloading(enabled, packName); + if (result.success() && (packName == null || packName.isBlank())) { + McsModRuntime.applyHotReloadState(enabled); + } + return result; + } catch (IOException error) { + return McsCommandResult.error("Could not save MCS config: " + error.getMessage()); + } + } + + public static McsCommandResult setPackEnabled(String packName, boolean enabled) { + McsWorldPackManager packManager = McsModRuntime.packManager(); + McsServerAccess serverAccess = McsModRuntime.serverAccess(); + if (packManager == null || serverAccess == null) { + return McsCommandResult.error("Minecraft Script is not active on this server."); + } + try { + McsCommandResult result = packManager.setPackEnabled(packName, enabled); + if (result.success()) { + serverAccess.executeReload(); + } + return result; + } catch (IOException error) { + return McsCommandResult.error("Could not update pack state: " + error.getMessage()); + } + } + + public static McsCommandResult listPacks() { + McsWorldPackManager packManager = McsModRuntime.packManager(); + if (packManager == null) { + return McsCommandResult.error("Minecraft Script is not active on this server."); + } + try { + return packManager.listPacks(); + } catch (IOException error) { + return McsCommandResult.error("Could not list MCS packs: " + error.getMessage()); + } + } + + public static McsCommandResult createPack(String packName, String scope) { + McsWorldPackManager packManager = McsModRuntime.packManager(); + McsServerAccess serverAccess = McsModRuntime.serverAccess(); + if (packManager == null || serverAccess == null) { + return McsCommandResult.error("Minecraft Script is not active on this server."); + } + McsPackSource source = parsePackScope(scope); + if (source == null) { + return McsCommandResult.error("Scope must be 'global' or 'local'."); + } + try { + McsCommandResult result = packManager.createPack(packName, source); + if (result.success()) { + serverAccess.executeReload(); + } + return result; + } catch (IOException error) { + return McsCommandResult.error("Could not create MCS pack: " + error.getMessage()); + } + } + + private static McsPackSource parsePackScope(String scope) { + if (scope == null) { + return null; + } + return switch (scope.trim().toLowerCase()) { + case "global" -> McsPackSource.GLOBAL; + case "local" -> McsPackSource.WORLD; + default -> null; + }; + } + + public static McsCommandResult reload(String packName) { + McsWorldPackManager packManager = McsModRuntime.packManager(); + McsServerAccess serverAccess = McsModRuntime.serverAccess(); + if (packManager == null || serverAccess == null) { + return McsCommandResult.error("Minecraft Script is not active on this server."); + } + try { + McsCommandResult result = packManager.reload(packName); + if (result.success()) { + serverAccess.executeReload(); + } + return result; + } catch (IOException error) { + return McsCommandResult.error("Could not reload MCS packs: " + error.getMessage()); + } + } +} diff --git a/mod/common/src/main/java/dev/spyc0der/minecraftscript/McsCompilerConfig.java b/mod/common/src/main/java/dev/spyc0der/minecraftscript/McsCompilerConfig.java deleted file mode 100644 index 1c1c043..0000000 --- a/mod/common/src/main/java/dev/spyc0der/minecraftscript/McsCompilerConfig.java +++ /dev/null @@ -1,43 +0,0 @@ -package dev.spyc0der.minecraftscript; - -import java.io.IOException; -import java.io.Reader; -import java.nio.file.Files; -import java.nio.file.Path; -import java.util.Properties; - -public record McsCompilerConfig(String pythonCommand) { - public static final String SYSTEM_PROPERTY = "minecraft_script.pythonCommand"; - public static final String ENVIRONMENT_VARIABLE = "MCS_PYTHON_COMMAND"; - public static final String CONFIG_FILE = "mcs-mod.properties"; - public static final String CONFIG_KEY = "pythonCommand"; - public static final String DEFAULT_COMMAND = "python -m minecraft_script"; - - public static McsCompilerConfig load(Path mcsRoot) { - String configured = System.getProperty(SYSTEM_PROPERTY); - if (configured == null || configured.isBlank()) { - configured = System.getenv(ENVIRONMENT_VARIABLE); - } - if ((configured == null || configured.isBlank()) && mcsRoot != null) { - configured = readConfigFile(mcsRoot.resolve(CONFIG_FILE)); - } - if (configured == null || configured.isBlank()) { - configured = DEFAULT_COMMAND; - } - return new McsCompilerConfig(configured.trim()); - } - - private static String readConfigFile(Path configFile) { - if (!Files.isRegularFile(configFile)) { - return null; - } - - Properties properties = new Properties(); - try (Reader reader = Files.newBufferedReader(configFile)) { - properties.load(reader); - } catch (IOException ignored) { - return null; - } - return properties.getProperty(CONFIG_KEY); - } -} diff --git a/mod/common/src/main/java/dev/spyc0der/minecraftscript/McsModConfig.java b/mod/common/src/main/java/dev/spyc0der/minecraftscript/McsModConfig.java new file mode 100644 index 0000000..d44319a --- /dev/null +++ b/mod/common/src/main/java/dev/spyc0der/minecraftscript/McsModConfig.java @@ -0,0 +1,147 @@ +package dev.spyc0der.minecraftscript; + +import java.io.IOException; +import java.io.Reader; +import java.io.Writer; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.Arrays; +import java.util.LinkedHashSet; +import java.util.Locale; +import java.util.Properties; +import java.util.Set; +import java.util.stream.Collectors; + +public record McsModConfig( + String pythonCommand, + boolean hotReload, + Set hotReloadDisabledPacks, + Set disabledPacks +) { + public static final String SYSTEM_PROPERTY_PYTHON = "minecraft_script.pythonCommand"; + public static final String ENVIRONMENT_VARIABLE_PYTHON = "MCS_PYTHON_COMMAND"; + public static final String CONFIG_FILE = "mcs-mod.properties"; + public static final String KEY_PYTHON_COMMAND = "pythonCommand"; + public static final String KEY_HOT_RELOAD = "hotReload"; + public static final String KEY_HOT_RELOAD_DISABLED_PACKS = "hotReloadDisabledPacks"; + public static final String KEY_DISABLED_PACKS = "disabledPacks"; + public static final String DEFAULT_COMMAND = "python -m minecraft_script"; + + public McsModConfig { + pythonCommand = pythonCommand == null || pythonCommand.isBlank() ? DEFAULT_COMMAND : pythonCommand.trim(); + hotReloadDisabledPacks = normalizePackSet(hotReloadDisabledPacks); + disabledPacks = normalizePackSet(disabledPacks); + } + + public static McsModConfig load(Path configDirectory) { + Properties properties = new Properties(); + Path configFile = configDirectory.resolve(CONFIG_FILE); + if (Files.isRegularFile(configFile)) { + try (Reader reader = Files.newBufferedReader(configFile)) { + properties.load(reader); + } catch (IOException ignored) { + } + } + + String pythonCommand = System.getProperty(SYSTEM_PROPERTY_PYTHON); + if (pythonCommand == null || pythonCommand.isBlank()) { + pythonCommand = System.getenv(ENVIRONMENT_VARIABLE_PYTHON); + } + if (pythonCommand == null || pythonCommand.isBlank()) { + pythonCommand = properties.getProperty(KEY_PYTHON_COMMAND); + } + + boolean hotReload = parseBoolean(properties.getProperty(KEY_HOT_RELOAD), true); + Set hotReloadDisabledPacks = parsePackSet(properties.getProperty(KEY_HOT_RELOAD_DISABLED_PACKS)); + Set disabledPacks = parsePackSet(properties.getProperty(KEY_DISABLED_PACKS)); + + return new McsModConfig(pythonCommand, hotReload, hotReloadDisabledPacks, disabledPacks); + } + + public void save(Path configDirectory) throws IOException { + Files.createDirectories(configDirectory); + Properties properties = new Properties(); + properties.setProperty(KEY_PYTHON_COMMAND, pythonCommand); + properties.setProperty(KEY_HOT_RELOAD, Boolean.toString(hotReload)); + writePackSetProperty(properties, KEY_HOT_RELOAD_DISABLED_PACKS, hotReloadDisabledPacks); + writePackSetProperty(properties, KEY_DISABLED_PACKS, disabledPacks); + + Path configFile = configDirectory.resolve(CONFIG_FILE); + try (Writer writer = Files.newBufferedWriter(configFile)) { + properties.store(writer, "Minecraft Script mod settings"); + } + } + + public boolean isPackEnabled(String packName) { + return !disabledPacks.contains(normalizePackName(packName)); + } + + public boolean isHotReloadEnabledForPack(String packName) { + return hotReload && !hotReloadDisabledPacks.contains(normalizePackName(packName)); + } + + public McsModConfig withHotReload(boolean value) { + return new McsModConfig(pythonCommand, value, hotReloadDisabledPacks, disabledPacks); + } + + public McsModConfig withHotReloadForPack(String packName, boolean enabled) { + String normalized = normalizePackName(packName); + LinkedHashSet updated = new LinkedHashSet<>(hotReloadDisabledPacks); + if (enabled) { + updated.remove(normalized); + } else { + updated.add(normalized); + } + return new McsModConfig(pythonCommand, hotReload, updated, disabledPacks); + } + + public McsModConfig withPackEnabled(String packName, boolean enabled) { + String normalized = normalizePackName(packName); + LinkedHashSet updated = new LinkedHashSet<>(disabledPacks); + if (enabled) { + updated.remove(normalized); + } else { + updated.add(normalized); + } + return new McsModConfig(pythonCommand, hotReload, hotReloadDisabledPacks, updated); + } + + public static String normalizePackName(String packName) { + return packName.trim().toLowerCase(Locale.ROOT); + } + + private static Set normalizePackSet(Set packNames) { + if (packNames == null || packNames.isEmpty()) { + return Set.of(); + } + return packNames.stream() + .map(McsModConfig::normalizePackName) + .filter(name -> !name.isBlank()) + .collect(Collectors.toCollection(LinkedHashSet::new)); + } + + private static boolean parseBoolean(String value, boolean defaultValue) { + if (value == null || value.isBlank()) { + return defaultValue; + } + return Boolean.parseBoolean(value.trim()); + } + + private static Set parsePackSet(String value) { + if (value == null || value.isBlank()) { + return Set.of(); + } + return Arrays.stream(value.split(",")) + .map(McsModConfig::normalizePackName) + .filter(name -> !name.isBlank()) + .collect(Collectors.toCollection(LinkedHashSet::new)); + } + + private static void writePackSetProperty(Properties properties, String key, Set packNames) { + if (packNames.isEmpty()) { + properties.remove(key); + return; + } + properties.setProperty(key, packNames.stream().sorted().collect(Collectors.joining(","))); + } +} diff --git a/mod/common/src/main/java/dev/spyc0der/minecraftscript/McsModRuntime.java b/mod/common/src/main/java/dev/spyc0der/minecraftscript/McsModRuntime.java index c6f4f0e..c12c7ab 100644 --- a/mod/common/src/main/java/dev/spyc0der/minecraftscript/McsModRuntime.java +++ b/mod/common/src/main/java/dev/spyc0der/minecraftscript/McsModRuntime.java @@ -1,9 +1,12 @@ package dev.spyc0der.minecraftscript; import java.io.IOException; +import java.nio.file.Path; public final class McsModRuntime { private static McsPackWatcher activeWatcher; + private static McsWorldPackManager activePackManager; + private static McsServerAccess activeServerAccess; private McsModRuntime() { } @@ -12,16 +15,19 @@ public static synchronized void serverStarted(McsServerAccess serverAccess) { serverStopping(); try { + Path configDirectory = McsPaths.configDirectory(serverAccess.serverDirectory()); + McsModConfig config = McsModConfig.load(configDirectory); McsWorldPackManager packManager = new McsWorldPackManager( serverAccess, - new ProcessMcsCompiler(McsCompilerConfig.load(McsPaths.mcsPacksRoot(serverAccess.saveRoot()))) + new ProcessMcsCompiler(config), + config ); packManager.initializeWorldFolders(); - if (packManager.compileAll()) { - serverAccess.executeReload(); - } - activeWatcher = new McsPackWatcher(packManager, serverAccess); - activeWatcher.start(); + packManager.compileAll(); + activePackManager = packManager; + activeServerAccess = serverAccess; + applyHotReloadState(packManager.config().hotReload()); + serverAccess.executeStartupReload(); } catch (IOException error) { String message = "Could not initialize mcs_packs: " + error.getMessage(); System.out.println("[Minecraft Script] " + message); @@ -30,6 +36,46 @@ public static synchronized void serverStarted(McsServerAccess serverAccess) { } public static synchronized void serverStopping() { + stopWatcher(); + activePackManager = null; + activeServerAccess = null; + } + + public static synchronized McsWorldPackManager packManager() { + return activePackManager; + } + + public static synchronized McsServerAccess serverAccess() { + return activeServerAccess; + } + + public static synchronized void applyHotReloadState(boolean enabled) { + if (activePackManager == null || activeServerAccess == null) { + return; + } + + if (enabled) { + startWatcher(); + return; + } + stopWatcher(); + } + + private static void startWatcher() { + if (activeWatcher != null || activePackManager == null || activeServerAccess == null) { + return; + } + try { + activeWatcher = new McsPackWatcher(activePackManager, activeServerAccess); + activeWatcher.start(); + } catch (IOException error) { + String message = "Could not start MCS hot reload watcher: " + error.getMessage(); + System.out.println("[Minecraft Script] " + message); + activeServerAccess.notifyOperators(message); + } + } + + private static void stopWatcher() { if (activeWatcher == null) { return; } diff --git a/mod/common/src/main/java/dev/spyc0der/minecraftscript/McsPackEntry.java b/mod/common/src/main/java/dev/spyc0der/minecraftscript/McsPackEntry.java new file mode 100644 index 0000000..c23c52b --- /dev/null +++ b/mod/common/src/main/java/dev/spyc0der/minecraftscript/McsPackEntry.java @@ -0,0 +1,15 @@ +package dev.spyc0der.minecraftscript; + +import java.nio.file.Path; +import java.util.Objects; + +public record McsPackEntry(Path packFolder, McsPackSource source) { + public McsPackEntry { + Objects.requireNonNull(packFolder, "packFolder"); + Objects.requireNonNull(source, "source"); + } + + public String packName() { + return packFolder.getFileName().toString(); + } +} diff --git a/mod/common/src/main/java/dev/spyc0der/minecraftscript/McsPackSource.java b/mod/common/src/main/java/dev/spyc0der/minecraftscript/McsPackSource.java new file mode 100644 index 0000000..027e7ab --- /dev/null +++ b/mod/common/src/main/java/dev/spyc0der/minecraftscript/McsPackSource.java @@ -0,0 +1,6 @@ +package dev.spyc0der.minecraftscript; + +public enum McsPackSource { + WORLD, + GLOBAL +} diff --git a/mod/common/src/main/java/dev/spyc0der/minecraftscript/McsPackWatcher.java b/mod/common/src/main/java/dev/spyc0der/minecraftscript/McsPackWatcher.java index 27729ab..aa3d456 100644 --- a/mod/common/src/main/java/dev/spyc0der/minecraftscript/McsPackWatcher.java +++ b/mod/common/src/main/java/dev/spyc0der/minecraftscript/McsPackWatcher.java @@ -32,6 +32,7 @@ public final class McsPackWatcher implements Closeable { private final McsWorldPackManager packManager; private final McsServerAccess serverAccess; + private final List watchedRoots; private final WatchService watchService; private final Map keys = new ConcurrentHashMap<>(); private final Map> pendingCompiles = new ConcurrentHashMap<>(); @@ -54,8 +55,11 @@ public final class McsPackWatcher implements Closeable { public McsPackWatcher(McsWorldPackManager packManager, McsServerAccess serverAccess) throws IOException { this.packManager = packManager; this.serverAccess = serverAccess; - this.watchService = packManager.mcsRoot().getFileSystem().newWatchService(); - registerRecursively(packManager.mcsRoot()); + this.watchedRoots = packManager.watchedMcsRoots(); + this.watchService = packManager.worldMcsRoot().getFileSystem().newWatchService(); + for (Path root : watchedRoots) { + registerRecursively(root); + } seedPackMtines(); scheduler.scheduleAtFixedRate( this::pollPackMtines, @@ -116,7 +120,7 @@ private void watchLoop() { } } - Optional packFolder = McsPaths.packFolderForChangedPath(packManager.mcsRoot(), changed); + Optional packFolder = McsPaths.packFolderForChangedPath(watchedRoots, changed); packFolder.ifPresent(this::schedulePackRefresh); } @@ -136,6 +140,18 @@ private void scheduleFullScan() { private void schedulePackRefresh(Path packFolder) { Path normalized = packFolder.toAbsolutePath().normalize(); + try { + Optional entry = packManager.findPackEntry(packFolder.getFileName().toString()); + if (entry.isEmpty() + || !packManager.isPackEnabled(entry.get()) + || !packManager.isHotReloadEnabledForPack(entry.get())) { + return; + } + } catch (IOException error) { + System.out.println("[Minecraft Script] Could not resolve pack for " + packFolder + ": " + error.getMessage()); + return; + } + long now = System.currentTimeMillis(); if (firstChangeAt.putIfAbsent(normalized, now) == null) { packManager.notifyPackChangePending(packFolder); @@ -169,8 +185,10 @@ private void scheduleReload() { private void seedPackMtines() { try { - for (Path packFolder : packManager.discoverPackFolders()) { - rememberPackMtime(packFolder); + for (McsPackEntry entry : packManager.discoverPackEntries()) { + if (packManager.isPackEnabled(entry) && packManager.isHotReloadEnabledForPack(entry)) { + rememberPackMtime(entry.packFolder()); + } } } catch (IOException error) { System.out.println("[Minecraft Script] Could not seed pack mtimes: " + error.getMessage()); @@ -182,8 +200,11 @@ private void pollPackMtines() { return; } try { - List packFolders = packManager.discoverPackFolders(); - for (Path packFolder : packFolders) { + for (McsPackEntry entry : packManager.discoverPackEntries()) { + if (!packManager.isPackEnabled(entry) || !packManager.isHotReloadEnabledForPack(entry)) { + continue; + } + Path packFolder = entry.packFolder(); Path packFile = packFolder.resolve(McsPaths.PACK_FILE); if (!Files.isRegularFile(packFile)) { continue; @@ -217,7 +238,8 @@ private static boolean shouldIgnoreChange(Path changed) { return fileName.endsWith("~") || fileName.endsWith(".swp") || fileName.endsWith(".tmp") - || fileName.startsWith(".#"); + || fileName.startsWith(".#") + || fileName.equals(McsModConfig.CONFIG_FILE); } private void registerRecursively(Path root) throws IOException { diff --git a/mod/common/src/main/java/dev/spyc0der/minecraftscript/McsPaths.java b/mod/common/src/main/java/dev/spyc0der/minecraftscript/McsPaths.java index cc1b5b6..ebc1ba1 100644 --- a/mod/common/src/main/java/dev/spyc0der/minecraftscript/McsPaths.java +++ b/mod/common/src/main/java/dev/spyc0der/minecraftscript/McsPaths.java @@ -1,6 +1,7 @@ package dev.spyc0der.minecraftscript; import java.nio.file.Path; +import java.util.List; import java.util.Locale; import java.util.Optional; @@ -13,14 +14,36 @@ public final class McsPaths { private McsPaths() { } - public static Path mcsPacksRoot(Path saveRoot) { + public static Path worldMcsPacksRoot(Path saveRoot) { return saveRoot.resolve(SOURCE_ROOT); } + public static Path mcsPacksRoot(Path saveRoot) { + return worldMcsPacksRoot(saveRoot); + } + + public static Path globalMcsPacksRoot(Path serverDirectory) { + return serverDirectory.resolve(SOURCE_ROOT); + } + + public static Path configDirectory(Path serverDirectory) { + return serverDirectory; + } + public static Path datapacksRoot(Path saveRoot) { return saveRoot.resolve(DATAPACK_ROOT); } + public static Optional packFolderForChangedPath(List mcsRoots, Path changedPath) { + for (Path mcsRoot : mcsRoots) { + Optional packFolder = packFolderForChangedPath(mcsRoot, changedPath); + if (packFolder.isPresent()) { + return packFolder; + } + } + return Optional.empty(); + } + public static Optional packFolderForChangedPath(Path mcsRoot, Path changedPath) { Path normalizedRoot = mcsRoot.toAbsolutePath().normalize(); Path normalizedChanged = changedPath.toAbsolutePath().normalize(); @@ -42,6 +65,18 @@ public static String sanitizePackFolderName(String folderName) { } public static String generatedDatapackName(Path packFolder) { - return GENERATED_PREFIX + sanitizePackFolderName(packFolder.getFileName().toString()); + return generatedDatapackName(packFolder, McsPackSource.WORLD); + } + + public static String generatedDatapackName(Path packFolder, McsPackSource source) { + String sanitized = sanitizePackFolderName(packFolder.getFileName().toString()); + if (source == McsPackSource.GLOBAL) { + return GENERATED_PREFIX + "global_" + sanitized; + } + return GENERATED_PREFIX + sanitized; + } + + public static String packKey(String packName) { + return McsModConfig.normalizePackName(packName); } } diff --git a/mod/common/src/main/java/dev/spyc0der/minecraftscript/McsServerAccess.java b/mod/common/src/main/java/dev/spyc0der/minecraftscript/McsServerAccess.java index 05f1eee..5461051 100644 --- a/mod/common/src/main/java/dev/spyc0der/minecraftscript/McsServerAccess.java +++ b/mod/common/src/main/java/dev/spyc0der/minecraftscript/McsServerAccess.java @@ -5,9 +5,13 @@ public interface McsServerAccess { Path saveRoot(); + Path serverDirectory(); + String minecraftVersion(); void executeReload(); + void executeStartupReload(); + void notifyOperators(String message); } diff --git a/mod/common/src/main/java/dev/spyc0der/minecraftscript/McsStarterPack.java b/mod/common/src/main/java/dev/spyc0der/minecraftscript/McsStarterPack.java index 636a7e9..e35b4e1 100644 --- a/mod/common/src/main/java/dev/spyc0der/minecraftscript/McsStarterPack.java +++ b/mod/common/src/main/java/dev/spyc0der/minecraftscript/McsStarterPack.java @@ -1,7 +1,9 @@ package dev.spyc0der.minecraftscript; public final class McsStarterPack { - public static final String SOURCE = """ + public static final String STARTER_NAME = "starter"; + + public static final String STARTER_SOURCE = """ function init() { tellraw("@a", text().text("MCS Starter Pack Loaded")); } @@ -12,4 +14,37 @@ function main() { private McsStarterPack() { } + + public static String packSource(String packName) { + return """ + function init() { + tellraw("@a", text().text("Hello from %s")); + } + + function main() { + } + """.formatted(escapeForTextLiteral(packName)); + } + + public static String escapeForTextLiteral(String value) { + return value.replace("\\", "\\\\").replace("\"", "\\\""); + } + + public static boolean isValidPackName(String packName) { + if (packName == null || packName.isBlank()) { + return false; + } + if (packName.equals(".") || packName.equals("..")) { + return false; + } + for (int index = 0; index < packName.length(); index++) { + char character = packName.charAt(index); + if (character == '/' || character == '\\' || character == ':' || character == '*' + || character == '?' || character == '"' || character == '<' || character == '>' + || character == '|') { + return false; + } + } + return true; + } } diff --git a/mod/common/src/main/java/dev/spyc0der/minecraftscript/McsWorldPackManager.java b/mod/common/src/main/java/dev/spyc0der/minecraftscript/McsWorldPackManager.java index 2e4900a..9097847 100644 --- a/mod/common/src/main/java/dev/spyc0der/minecraftscript/McsWorldPackManager.java +++ b/mod/common/src/main/java/dev/spyc0der/minecraftscript/McsWorldPackManager.java @@ -8,61 +8,142 @@ import java.nio.file.StandardCopyOption; import java.nio.file.attribute.BasicFileAttributes; import java.util.ArrayList; +import java.util.LinkedHashMap; import java.util.List; +import java.util.Map; import java.util.Objects; +import java.util.Optional; public final class McsWorldPackManager { private final Path saveRoot; - private final Path mcsRoot; + private final Path worldMcsRoot; + private final Path globalMcsRoot; private final Path datapacksRoot; + private final Path configDirectory; private final String minecraftVersion; private final McsCompiler compiler; private final McsServerAccess serverAccess; + private McsModConfig config; - public McsWorldPackManager(McsServerAccess serverAccess, McsCompiler compiler) { + public McsWorldPackManager(McsServerAccess serverAccess, McsCompiler compiler, McsModConfig config) { this.serverAccess = Objects.requireNonNull(serverAccess, "serverAccess"); this.compiler = Objects.requireNonNull(compiler, "compiler"); + this.config = Objects.requireNonNull(config, "config"); this.saveRoot = serverAccess.saveRoot(); - this.mcsRoot = McsPaths.mcsPacksRoot(saveRoot); + this.worldMcsRoot = McsPaths.worldMcsPacksRoot(saveRoot); + this.globalMcsRoot = McsPaths.globalMcsPacksRoot(serverAccess.serverDirectory()); this.datapacksRoot = McsPaths.datapacksRoot(saveRoot); + this.configDirectory = McsPaths.configDirectory(serverAccess.serverDirectory()); this.minecraftVersion = serverAccess.minecraftVersion(); } + public Path worldMcsRoot() { + return worldMcsRoot; + } + + public Path globalMcsRoot() { + return globalMcsRoot; + } + public Path mcsRoot() { - return mcsRoot; + return worldMcsRoot; + } + + public List watchedMcsRoots() { + return List.of(worldMcsRoot, globalMcsRoot); + } + + public McsModConfig config() { + return config; + } + + public Path configDirectory() { + return configDirectory; } public void initializeWorldFolders() throws IOException { - Files.createDirectories(mcsRoot); + Files.createDirectories(worldMcsRoot); + Files.createDirectories(globalMcsRoot); Files.createDirectories(datapacksRoot); - Path starterPack = mcsRoot.resolve("starter").resolve(McsPaths.PACK_FILE); + Path starterPack = globalMcsRoot.resolve(McsStarterPack.STARTER_NAME).resolve(McsPaths.PACK_FILE); if (!Files.exists(starterPack)) { Files.createDirectories(starterPack.getParent()); - Files.writeString(starterPack, McsStarterPack.SOURCE); + Files.writeString(starterPack, McsStarterPack.STARTER_SOURCE); } } - public List discoverPackFolders() throws IOException { - List packs = new ArrayList<>(); - if (!Files.isDirectory(mcsRoot)) { - return packs; + public McsCommandResult createPack(String packName, McsPackSource source) throws IOException { + String trimmedName = packName == null ? "" : packName.trim(); + if (!McsStarterPack.isValidPackName(trimmedName)) { + return McsCommandResult.error("Invalid pack name '" + trimmedName + "'."); + } + if (findPackEntry(trimmedName).isPresent()) { + return McsCommandResult.error("Pack '" + trimmedName + "' already exists."); } - try (var stream = Files.list(mcsRoot)) { - stream.filter(Files::isDirectory) - .filter(path -> Files.isRegularFile(path.resolve(McsPaths.PACK_FILE))) - .sorted() - .forEach(packs::add); + Path root = source == McsPackSource.GLOBAL ? globalMcsRoot : worldMcsRoot; + Path packFolder = root.resolve(trimmedName); + Path packFile = packFolder.resolve(McsPaths.PACK_FILE); + Files.createDirectories(packFolder); + Files.writeString(packFile, McsStarterPack.packSource(trimmedName)); + + McsPackEntry entry = new McsPackEntry(packFolder, source); + boolean compiled = refreshPackEntry(entry); + return McsCommandResult.ok( + "Created " + sourceLabel(source) + " pack '" + trimmedName + "' at " + + packFolder + + (compiled ? " and compiled it." : ".") + ); + } + + public List discoverPackEntries() throws IOException { + Map packsByKey = new LinkedHashMap<>(); + + for (Path packFolder : listPackFolders(globalMcsRoot)) { + packsByKey.putIfAbsent( + McsPaths.packKey(packFolder.getFileName().toString()), + new McsPackEntry(packFolder, McsPackSource.GLOBAL) + ); } - return packs; + for (Path packFolder : listPackFolders(worldMcsRoot)) { + packsByKey.put( + McsPaths.packKey(packFolder.getFileName().toString()), + new McsPackEntry(packFolder, McsPackSource.WORLD) + ); + } + + return List.copyOf(packsByKey.values()); + } + + public List discoverPackFolders() throws IOException { + return discoverPackEntries().stream().map(McsPackEntry::packFolder).toList(); + } + + public List discoverPackNames() throws IOException { + return discoverPackEntries().stream().map(McsPackEntry::packName).toList(); + } + + public Optional findPackEntry(String packName) throws IOException { + String packKey = McsPaths.packKey(packName); + return discoverPackEntries().stream() + .filter(entry -> McsPaths.packKey(entry.packName()).equals(packKey)) + .findFirst(); + } + + public boolean isPackEnabled(McsPackEntry entry) { + return config.isPackEnabled(entry.packName()); + } + + public boolean isHotReloadEnabledForPack(McsPackEntry entry) { + return config.isHotReloadEnabledForPack(entry.packName()); } public boolean compileAll() { boolean changed = false; try { - for (Path packFolder : discoverPackFolders()) { - changed |= compilePack(packFolder); + for (McsPackEntry entry : discoverPackEntries()) { + changed |= refreshPackEntry(entry); } } catch (IOException error) { report("Could not scan mcs_packs: " + error.getMessage()); @@ -75,15 +156,45 @@ public void notifyPackChangePending(Path packFolder) { } public boolean refreshPack(Path packFolder) { - Path packFile = packFolder.resolve(McsPaths.PACK_FILE); + try { + Optional entry = findPackEntry(packFolder.getFileName().toString()); + if (entry.isEmpty()) { + return deleteGeneratedPack(packFolder, resolvePackSource(packFolder)); + } + return refreshPackEntry(entry.get()); + } catch (IOException error) { + report("Could not refresh " + packFolder.getFileName() + ": " + error.getMessage()); + return false; + } + } + + public boolean refreshPackEntry(McsPackEntry entry) { + Path packFile = entry.packFolder().resolve(McsPaths.PACK_FILE); + if (!config.isPackEnabled(entry.packName())) { + return deleteGeneratedPack(entry.packFolder(), entry.source()); + } if (Files.isRegularFile(packFile)) { - return compilePack(packFolder); + return compilePack(entry); + } + return deleteGeneratedPack(entry.packFolder(), entry.source()); + } + + public boolean compilePack(McsPackEntry entry) { + if (!config.isPackEnabled(entry.packName())) { + return false; } - return deleteGeneratedPack(packFolder); + return compilePack(entry.packFolder(), entry.source()); } public boolean compilePack(Path packFolder) { - String datapackName = McsPaths.generatedDatapackName(packFolder); + return compilePack(packFolder, resolvePackSource(packFolder)); + } + + public boolean compilePack(Path packFolder, McsPackSource source) { + if (!config.isPackEnabled(packFolder.getFileName().toString())) { + return false; + } + String datapackName = McsPaths.generatedDatapackName(packFolder, source); Path temporaryRoot = null; try { temporaryRoot = Files.createTempDirectory(datapacksRoot, "." + datapackName + "-compile-"); @@ -105,7 +216,8 @@ public boolean compilePack(Path packFolder) { } replaceGeneratedDatapack(compiledDatapack, datapacksRoot.resolve(datapackName)); - report("Compiled MCS pack " + packFolder.getFileName() + " for Minecraft " + minecraftVersion); + report("Compiled MCS pack " + packFolder.getFileName() + " (" + sourceLabel(source) + ") for Minecraft " + + minecraftVersion); return true; } catch (IOException error) { report("Could not compile " + packFolder.getFileName() + ": " + error.getMessage()); @@ -125,7 +237,11 @@ public boolean compilePack(Path packFolder) { } public boolean deleteGeneratedPack(Path packFolder) { - Path target = datapacksRoot.resolve(McsPaths.generatedDatapackName(packFolder)); + return deleteGeneratedPack(packFolder, resolvePackSource(packFolder)); + } + + public boolean deleteGeneratedPack(Path packFolder, McsPackSource source) { + Path target = datapacksRoot.resolve(McsPaths.generatedDatapackName(packFolder, source)); if (!Files.exists(target)) { return false; } @@ -139,6 +255,155 @@ public boolean deleteGeneratedPack(Path packFolder) { } } + public McsCommandResult setHotReloading(boolean enabled, String packName) throws IOException { + if (packName == null || packName.isBlank()) { + if (config.hotReload() == enabled) { + return McsCommandResult.ok("Hot reloading is already " + (enabled ? "enabled" : "disabled") + "."); + } + config = config.withHotReload(enabled); + config.save(configDirectory); + return McsCommandResult.ok("Hot reloading " + (enabled ? "enabled" : "disabled") + " globally."); + } + + Optional entry = findPackEntry(packName); + if (entry.isEmpty()) { + return McsCommandResult.error("Unknown MCS pack '" + packName + "'."); + } + + boolean currentlyEnabled = config.isHotReloadEnabledForPack(entry.get().packName()); + if (currentlyEnabled == enabled) { + return McsCommandResult.ok( + "Hot reloading for pack '" + entry.get().packName() + "' is already " + + (enabled ? "enabled" : "disabled") + "." + ); + } + + config = config.withHotReloadForPack(entry.get().packName(), enabled); + config.save(configDirectory); + return McsCommandResult.ok( + "Hot reloading for pack '" + entry.get().packName() + "' " + + (enabled ? "enabled" : "disabled") + "." + ); + } + + public McsCommandResult setPackEnabled(String packName, boolean enabled) throws IOException { + if (packName == null || packName.isBlank()) { + List entries = discoverPackEntries(); + if (entries.isEmpty()) { + return McsCommandResult.error("No MCS packs found."); + } + + boolean changed = false; + for (McsPackEntry entry : entries) { + if (config.isPackEnabled(entry.packName()) != enabled) { + config = config.withPackEnabled(entry.packName(), enabled); + changed = true; + } + } + if (!changed) { + return McsCommandResult.ok("All MCS packs are already " + (enabled ? "enabled" : "disabled") + "."); + } + config.save(configDirectory); + + boolean datapacksChanged = false; + for (McsPackEntry entry : entries) { + datapacksChanged |= refreshPackEntry(entry); + } + return McsCommandResult.ok( + "All MCS packs " + (enabled ? "enabled" : "disabled") + + (datapacksChanged ? " and datapacks were updated." : ".") + ); + } + + Optional entry = findPackEntry(packName); + if (entry.isEmpty()) { + return McsCommandResult.error("Unknown MCS pack '" + packName + "'."); + } + + boolean currentlyEnabled = config.isPackEnabled(entry.get().packName()); + if (currentlyEnabled == enabled) { + return McsCommandResult.ok( + "Pack '" + entry.get().packName() + "' is already " + (enabled ? "enabled" : "disabled") + "." + ); + } + + config = config.withPackEnabled(entry.get().packName(), enabled); + config.save(configDirectory); + + boolean changed = refreshPackEntry(entry.get()); + return McsCommandResult.ok( + "Pack '" + entry.get().packName() + "' (" + sourceLabel(entry.get().source()) + ") " + + (enabled ? "enabled" : "disabled") + + (changed ? " and datapacks were updated." : ".") + ); + } + + public McsCommandResult reload(String packName) throws IOException { + if (packName == null || packName.isBlank()) { + boolean changed = compileAll(); + return McsCommandResult.ok( + changed ? "Recompiled enabled MCS packs." : "No enabled MCS pack changes were needed." + ); + } + + Optional entry = findPackEntry(packName); + if (entry.isEmpty()) { + return McsCommandResult.error("Unknown MCS pack '" + packName + "'."); + } + if (!config.isPackEnabled(entry.get().packName())) { + return McsCommandResult.error("Pack '" + entry.get().packName() + "' is disabled."); + } + + boolean changed = refreshPackEntry(entry.get()); + return McsCommandResult.ok( + changed + ? "Reloaded pack '" + entry.get().packName() + "'." + : "No changes were needed for pack '" + entry.get().packName() + "'." + ); + } + + public McsCommandResult listPacks() throws IOException { + List lines = new ArrayList<>(); + for (McsPackEntry entry : discoverPackEntries()) { + String packState = config.isPackEnabled(entry.packName()) ? "enabled" : "disabled"; + String hotReloadState = config.isHotReloadEnabledForPack(entry.packName()) ? "on" : "off"; + lines.add(entry.packName() + " [" + sourceLabel(entry.source()) + ", pack " + packState + + ", hot reload " + hotReloadState + "]"); + } + if (lines.isEmpty()) { + return McsCommandResult.ok("No MCS packs found."); + } + String globalHotReload = config.hotReload() ? "enabled" : "disabled"; + return McsCommandResult.ok("Global hot reloading: " + globalHotReload + "\n" + String.join("\n", lines)); + } + + private List listPackFolders(Path root) throws IOException { + List packs = new ArrayList<>(); + if (!Files.isDirectory(root)) { + return packs; + } + + try (var stream = Files.list(root)) { + stream.filter(Files::isDirectory) + .filter(path -> Files.isRegularFile(path.resolve(McsPaths.PACK_FILE))) + .sorted() + .forEach(packs::add); + } + return packs; + } + + private McsPackSource resolvePackSource(Path packFolder) { + Path normalized = packFolder.toAbsolutePath().normalize(); + if (normalized.startsWith(globalMcsRoot.toAbsolutePath().normalize())) { + return McsPackSource.GLOBAL; + } + return McsPackSource.WORLD; + } + + private static String sourceLabel(McsPackSource source) { + return source == McsPackSource.GLOBAL ? "global" : "world"; + } + private void replaceGeneratedDatapack(Path compiledDatapack, Path target) throws IOException { Path backup = target.resolveSibling(target.getFileName() + ".mcs_backup"); deleteRecursively(backup); diff --git a/mod/common/src/main/java/dev/spyc0der/minecraftscript/ProcessMcsCompiler.java b/mod/common/src/main/java/dev/spyc0der/minecraftscript/ProcessMcsCompiler.java index d2a970c..5fe4500 100644 --- a/mod/common/src/main/java/dev/spyc0der/minecraftscript/ProcessMcsCompiler.java +++ b/mod/common/src/main/java/dev/spyc0der/minecraftscript/ProcessMcsCompiler.java @@ -11,9 +11,9 @@ public final class ProcessMcsCompiler implements McsCompiler { private static final long COMPILE_TIMEOUT_SECONDS = 60; - private final McsCompilerConfig config; + private final McsModConfig config; - public ProcessMcsCompiler(McsCompilerConfig config) { + public ProcessMcsCompiler(McsModConfig config) { this.config = config; } diff --git a/mod/common/src/test/java/dev/spyc0der/minecraftscript/McsModConfigTest.java b/mod/common/src/test/java/dev/spyc0der/minecraftscript/McsModConfigTest.java new file mode 100644 index 0000000..ea859cb --- /dev/null +++ b/mod/common/src/test/java/dev/spyc0der/minecraftscript/McsModConfigTest.java @@ -0,0 +1,57 @@ +package dev.spyc0der.minecraftscript; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.io.TempDir; + +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.Set; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; + +class McsModConfigTest { + @TempDir + Path tempDir; + + @Test + void loadsDefaultsWhenConfigMissing() { + McsModConfig config = McsModConfig.load(tempDir); + + assertEquals(McsModConfig.DEFAULT_COMMAND, config.pythonCommand()); + assertTrue(config.hotReload()); + assertTrue(config.hotReloadDisabledPacks().isEmpty()); + assertTrue(config.disabledPacks().isEmpty()); + } + + @Test + void savesAndReloadsConfigValues() throws Exception { + McsModConfig config = new McsModConfig( + "python3 -m minecraft_script", + false, + Set.of("starter"), + Set.of("custom") + ); + config.save(tempDir); + + McsModConfig loaded = McsModConfig.load(tempDir); + assertEquals("python3 -m minecraft_script", loaded.pythonCommand()); + assertFalse(loaded.hotReload()); + assertFalse(loaded.isHotReloadEnabledForPack("starter")); + assertFalse(loaded.isHotReloadEnabledForPack("other")); + assertFalse(loaded.isPackEnabled("custom")); + assertTrue(loaded.isPackEnabled("starter")); + } + + @Test + void withPackEnabledUpdatesDisabledSet() { + McsModConfig config = new McsModConfig(McsModConfig.DEFAULT_COMMAND, true, Set.of(), Set.of("starter")); + + McsModConfig enabled = config.withPackEnabled("starter", true); + assertTrue(enabled.isPackEnabled("starter")); + + McsModConfig disabled = enabled.withPackEnabled("Custom", false); + assertFalse(disabled.isPackEnabled("custom")); + } +} diff --git a/mod/common/src/test/java/dev/spyc0der/minecraftscript/McsStarterPackTest.java b/mod/common/src/test/java/dev/spyc0der/minecraftscript/McsStarterPackTest.java new file mode 100644 index 0000000..cee1e8b --- /dev/null +++ b/mod/common/src/test/java/dev/spyc0der/minecraftscript/McsStarterPackTest.java @@ -0,0 +1,20 @@ +package dev.spyc0der.minecraftscript; + +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; + +class McsStarterPackTest { + @Test + void packSourceIncludesHelloMessage() { + assertTrue(McsStarterPack.packSource("Demo").contains("Hello from Demo")); + } + + @Test + void rejectsInvalidPackNames() { + assertFalse(McsStarterPack.isValidPackName("")); + assertFalse(McsStarterPack.isValidPackName("bad/name")); + assertTrue(McsStarterPack.isValidPackName("My Pack")); + } +} diff --git a/mod/common/src/test/java/dev/spyc0der/minecraftscript/McsWorldPackManagerTest.java b/mod/common/src/test/java/dev/spyc0der/minecraftscript/McsWorldPackManagerTest.java index 474773f..2320ff9 100644 --- a/mod/common/src/test/java/dev/spyc0der/minecraftscript/McsWorldPackManagerTest.java +++ b/mod/common/src/test/java/dev/spyc0der/minecraftscript/McsWorldPackManagerTest.java @@ -8,6 +8,7 @@ import java.nio.file.Path; import java.util.ArrayList; import java.util.List; +import java.util.Set; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; @@ -18,26 +19,84 @@ class McsWorldPackManagerTest { Path tempDir; @Test - void createsStarterPackAndDiscoversOnlyPackFolders() throws IOException { + void createsStarterPackInGlobalFolder() throws IOException { McsWorldPackManager manager = newManager(new SuccessfulCompiler()); manager.initializeWorldFolders(); - Files.createDirectories(tempDir.resolve("mcs_packs/empty")); Files.createDirectories(tempDir.resolve("mcs_packs/custom")); Files.writeString(tempDir.resolve("mcs_packs/custom/pack.mcs"), "function main() {}"); - List packNames = manager.discoverPackFolders().stream() - .map(path -> path.getFileName().toString()) + List packNames = manager.discoverPackEntries().stream() + .map(McsPackEntry::packName) + .sorted() .toList(); assertEquals(List.of("custom", "starter"), packNames); - assertTrue(Files.readString(tempDir.resolve("mcs_packs/starter/pack.mcs")) + assertTrue(Files.readString(tempDir.resolve("global/mcs_packs/starter/pack.mcs")) .contains("MCS Starter Pack Loaded")); + assertEquals( + McsPackSource.GLOBAL, + manager.findPackEntry("starter").orElseThrow().source() + ); + } + + @Test + void discoversGlobalAndWorldPacksWithWorldTakingPrecedence() throws IOException { + McsWorldPackManager manager = newManager(new SuccessfulCompiler()); + manager.initializeWorldFolders(); + + Files.createDirectories(tempDir.resolve("global/mcs_packs/shared")); + Files.writeString(tempDir.resolve("global/mcs_packs/shared/pack.mcs"), "function main() {}"); + Files.createDirectories(tempDir.resolve("mcs_packs/shared")); + Files.writeString(tempDir.resolve("mcs_packs/shared/pack.mcs"), "function main() {}"); + Files.createDirectories(tempDir.resolve("mcs_packs/global-only")); + Files.writeString(tempDir.resolve("mcs_packs/global-only/pack.mcs"), "function main() {}"); + + List packNames = manager.discoverPackEntries().stream() + .map(McsPackEntry::packName) + .sorted() + .toList(); + + assertEquals(List.of("global-only", "shared", "starter"), packNames); + assertEquals( + McsPackSource.WORLD, + manager.findPackEntry("shared").orElseThrow().source() + ); + assertEquals( + McsPackSource.GLOBAL, + manager.findPackEntry("starter").orElseThrow().source() + ); + } + + @Test + void createPackWritesHelloMessageAndCompiles() throws IOException { + McsWorldPackManager manager = newManager(new SuccessfulCompiler()); + manager.initializeWorldFolders(); + + McsCommandResult result = manager.createPack("Demo", McsPackSource.WORLD); + assertTrue(result.success()); + assertTrue(Files.readString(tempDir.resolve("mcs_packs/Demo/pack.mcs")).contains("Hello from Demo")); + assertTrue(Files.isDirectory(tempDir.resolve("datapacks/mcs_demo"))); + + McsCommandResult duplicate = manager.createPack("Demo", McsPackSource.GLOBAL); + assertFalse(duplicate.success()); + } + + @Test + void createGlobalPackUsesGlobalFolder() throws IOException { + McsWorldPackManager manager = newManager(new SuccessfulCompiler()); + manager.initializeWorldFolders(); + + McsCommandResult result = manager.createPack("Shared", McsPackSource.GLOBAL); + assertTrue(result.success()); + assertTrue(Files.isRegularFile(tempDir.resolve("global/mcs_packs/Shared/pack.mcs"))); + assertTrue(Files.isDirectory(tempDir.resolve("datapacks/mcs_global_shared"))); } @Test void sanitizesGeneratedDatapackNames() { assertEquals("mcs_my_pack_1", McsPaths.generatedDatapackName(Path.of("My Pack 1"))); + assertEquals("mcs_global_my_pack_1", McsPaths.generatedDatapackName(Path.of("My Pack 1"), McsPackSource.GLOBAL)); assertEquals("pack", McsPaths.sanitizePackFolderName("!!!")); } @@ -56,37 +115,87 @@ void successfulCompileReplacesGeneratedDatapack() throws IOException { assertEquals("compiled", Files.readString(generated.resolve("pack.mcmeta"))); } + @Test + void globalPacksCompileToGlobalDatapackPrefix() throws IOException { + McsWorldPackManager manager = newManager(new SuccessfulCompiler()); + manager.initializeWorldFolders(); + Path packFolder = tempDir.resolve("global/mcs_packs/My Pack"); + Files.createDirectories(packFolder); + Files.writeString(packFolder.resolve("pack.mcs"), "function main() {}"); + + McsPackEntry entry = manager.findPackEntry("My Pack").orElseThrow(); + assertEquals(McsPackSource.GLOBAL, entry.source()); + assertTrue(manager.compilePack(entry)); + + assertTrue(Files.isDirectory(tempDir.resolve("datapacks/mcs_global_my_pack"))); + } + @Test void failedCompileKeepsExistingDatapack() throws IOException { - Path generated = tempDir.resolve("datapacks/mcs_starter"); + Path generated = tempDir.resolve("datapacks/mcs_global_starter"); Files.createDirectories(generated); Files.writeString(generated.resolve("pack.mcmeta"), "old"); McsWorldPackManager manager = newManager(new FailingCompiler()); manager.initializeWorldFolders(); - assertFalse(manager.compilePack(tempDir.resolve("mcs_packs/starter"))); + assertFalse(manager.compilePack(manager.findPackEntry("starter").orElseThrow())); assertEquals("old", Files.readString(generated.resolve("pack.mcmeta"))); } + @Test + void disabledPacksAreSkippedAndRemoved() throws IOException { + McsModConfig config = new McsModConfig(McsModConfig.DEFAULT_COMMAND, true, Set.of(), Set.of("starter")); + McsWorldPackManager manager = new McsWorldPackManager(new TestServerAccess(tempDir), new SuccessfulCompiler(), config); + manager.initializeWorldFolders(); + + Path generated = tempDir.resolve("datapacks/mcs_global_starter"); + Files.createDirectories(generated); + Files.writeString(generated.resolve("pack.mcmeta"), "old"); + + McsPackEntry starter = manager.findPackEntry("starter").orElseThrow(); + assertTrue(manager.refreshPackEntry(starter)); + assertFalse(Files.exists(generated)); + } + @Test void deletedPackRemovesGeneratedDatapack() throws IOException { McsWorldPackManager manager = newManager(new SuccessfulCompiler()); manager.initializeWorldFolders(); - Path generated = tempDir.resolve("datapacks/mcs_starter"); + Path generated = tempDir.resolve("datapacks/mcs_global_starter"); Files.createDirectories(generated); Files.writeString(generated.resolve("pack.mcmeta"), "old"); - Files.delete(tempDir.resolve("mcs_packs/starter/pack.mcs")); + Files.delete(tempDir.resolve("global/mcs_packs/starter/pack.mcs")); - assertTrue(manager.refreshPack(tempDir.resolve("mcs_packs/starter"))); + assertTrue(manager.refreshPack(tempDir.resolve("global/mcs_packs/starter"))); assertFalse(Files.exists(generated)); } + @Test + void savesConfigToProfileDirectory() throws IOException { + McsWorldPackManager manager = newManager(new SuccessfulCompiler()); + manager.initializeWorldFolders(); + + McsCommandResult result = manager.setHotReloading(false, null); + assertTrue(result.success()); + assertTrue(Files.isRegularFile(tempDir.resolve("global/mcs-mod.properties"))); + assertFalse(Files.exists(tempDir.resolve("mcs_packs/mcs-mod.properties"))); + } + private McsWorldPackManager newManager(McsCompiler compiler) { - return new McsWorldPackManager(new TestServerAccess(tempDir), compiler); + return new McsWorldPackManager( + new TestServerAccess(tempDir), + compiler, + McsModConfig.load(tempDir.resolve("global")) + ); } private record TestServerAccess(Path saveRoot) implements McsServerAccess { + @Override + public Path serverDirectory() { + return saveRoot.resolve("global"); + } + @Override public String minecraftVersion() { return "1.21.2"; @@ -96,6 +205,10 @@ public String minecraftVersion() { public void executeReload() { } + @Override + public void executeStartupReload() { + } + @Override public void notifyOperators(String message) { } diff --git a/mod/platform/fabric/src/main/java/dev/spyc0der/minecraftscript/fabric/McsFabricMod.java b/mod/platform/fabric/src/main/java/dev/spyc0der/minecraftscript/fabric/McsFabricMod.java index ad9487a..7ab8566 100644 --- a/mod/platform/fabric/src/main/java/dev/spyc0der/minecraftscript/fabric/McsFabricMod.java +++ b/mod/platform/fabric/src/main/java/dev/spyc0der/minecraftscript/fabric/McsFabricMod.java @@ -2,11 +2,14 @@ import dev.spyc0der.minecraftscript.McsModRuntime; import net.fabricmc.api.ModInitializer; +import net.fabricmc.fabric.api.command.v2.CommandRegistrationCallback; import net.fabricmc.fabric.api.event.lifecycle.v1.ServerLifecycleEvents; public final class McsFabricMod implements ModInitializer { @Override public void onInitialize() { + CommandRegistrationCallback.EVENT.register((dispatcher, registry, environment) -> + McsFabricCommands.register(dispatcher)); ServerLifecycleEvents.SERVER_STARTED.register(server -> McsModRuntime.serverStarted(new FabricServerAccess(server))); ServerLifecycleEvents.SERVER_STOPPING.register(server -> McsModRuntime.serverStopping()); diff --git a/mod/platform/fabric/src/mc_1_21_11/java/dev/spyc0der/minecraftscript/fabric/FabricServerAccess.java b/mod/platform/fabric/src/mc_1_21_11/java/dev/spyc0der/minecraftscript/fabric/FabricServerAccess.java index 92b217a..66bc4d0 100644 --- a/mod/platform/fabric/src/mc_1_21_11/java/dev/spyc0der/minecraftscript/fabric/FabricServerAccess.java +++ b/mod/platform/fabric/src/mc_1_21_11/java/dev/spyc0der/minecraftscript/fabric/FabricServerAccess.java @@ -24,6 +24,11 @@ public Path saveRoot() { return server.getWorldPath(LevelResource.ROOT); } + @Override + public Path serverDirectory() { + return server.getServerDirectory(); + } + @Override public String minecraftVersion() { return McsBuildInfo.MINECRAFT_VERSION; @@ -31,12 +36,27 @@ public String minecraftVersion() { @Override public void executeReload() { - server.execute(() -> { - CommandSourceStack source = server.createCommandSourceStack() - .withPermission(LevelBasedPermissionSet.ADMIN) - .withSuppressedOutput(); - server.getCommands().performPrefixedCommand(source, "reload"); - }); + runOnServerThread(this::performReload); + } + + @Override + public void executeStartupReload() { + runOnServerThread(this::performReload); + } + + private void performReload() { + CommandSourceStack source = server.createCommandSourceStack() + .withPermission(LevelBasedPermissionSet.ADMIN) + .withSuppressedOutput(); + server.getCommands().performPrefixedCommand(source, "reload"); + } + + private void runOnServerThread(Runnable action) { + if (server.isSameThread()) { + action.run(); + } else { + server.execute(action); + } } @Override diff --git a/mod/platform/fabric/src/mc_1_21_11/java/dev/spyc0der/minecraftscript/fabric/McsFabricCommands.java b/mod/platform/fabric/src/mc_1_21_11/java/dev/spyc0der/minecraftscript/fabric/McsFabricCommands.java new file mode 100644 index 0000000..1743512 --- /dev/null +++ b/mod/platform/fabric/src/mc_1_21_11/java/dev/spyc0der/minecraftscript/fabric/McsFabricCommands.java @@ -0,0 +1,113 @@ +package dev.spyc0der.minecraftscript.fabric; + +import com.mojang.brigadier.CommandDispatcher; +import com.mojang.brigadier.arguments.StringArgumentType; +import com.mojang.brigadier.context.CommandContext; +import com.mojang.brigadier.suggestion.SuggestionProvider; +import dev.spyc0der.minecraftscript.McsCommandResult; +import dev.spyc0der.minecraftscript.McsCommandService; +import net.minecraft.commands.CommandSourceStack; +import net.minecraft.commands.Commands; +import net.minecraft.network.chat.Component; + +import java.util.function.Supplier; + +public final class McsFabricCommands { + private static final SuggestionProvider PACK_SUGGESTIONS = (context, builder) -> { + for (String packName : McsCommandService.packNameSuggestions()) { + builder.suggest(packName); + } + return builder.buildFuture(); + }; + + private McsFabricCommands() { + } + + public static void register(CommandDispatcher dispatcher) { + dispatcher.register(Commands.literal("mcs") + .requires(Commands.hasPermission(Commands.LEVEL_ADMINS)) + .then(Commands.literal("hotreloading") + .then(Commands.literal("enable") + .executes(ctx -> run(() -> McsCommandService.setHotReloading(true, null), ctx)) + .then(Commands.argument("pack", StringArgumentType.string()) + .suggests(PACK_SUGGESTIONS) + .executes(ctx -> run( + () -> McsCommandService.setHotReloading( + true, + StringArgumentType.getString(ctx, "pack") + ), + ctx + )))) + .then(Commands.literal("disable") + .executes(ctx -> run(() -> McsCommandService.setHotReloading(false, null), ctx)) + .then(Commands.argument("pack", StringArgumentType.string()) + .suggests(PACK_SUGGESTIONS) + .executes(ctx -> run( + () -> McsCommandService.setHotReloading( + false, + StringArgumentType.getString(ctx, "pack") + ), + ctx + ))))) + .then(Commands.literal("packs") + .then(Commands.literal("enable") + .executes(ctx -> run(() -> McsCommandService.setPackEnabled(null, true), ctx)) + .then(Commands.argument("pack", StringArgumentType.string()) + .suggests(PACK_SUGGESTIONS) + .executes(ctx -> run( + () -> McsCommandService.setPackEnabled( + StringArgumentType.getString(ctx, "pack"), + true + ), + ctx + )))) + .then(Commands.literal("disable") + .executes(ctx -> run(() -> McsCommandService.setPackEnabled(null, false), ctx)) + .then(Commands.argument("pack", StringArgumentType.string()) + .suggests(PACK_SUGGESTIONS) + .executes(ctx -> run( + () -> McsCommandService.setPackEnabled( + StringArgumentType.getString(ctx, "pack"), + false + ), + ctx + ))))) + .then(Commands.literal("reload") + .executes(ctx -> run(() -> McsCommandService.reload(null), ctx)) + .then(Commands.argument("pack", StringArgumentType.string()) + .suggests(PACK_SUGGESTIONS) + .executes(ctx -> run( + () -> McsCommandService.reload(StringArgumentType.getString(ctx, "pack")), + ctx + )))) + .then(Commands.literal("create") + .then(Commands.argument("name", StringArgumentType.string()) + .then(Commands.literal("global") + .executes(ctx -> run( + () -> McsCommandService.createPack( + StringArgumentType.getString(ctx, "name"), + "global" + ), + ctx + ))) + .then(Commands.literal("local") + .executes(ctx -> run( + () -> McsCommandService.createPack( + StringArgumentType.getString(ctx, "name"), + "local" + ), + ctx + ))))) + .then(Commands.literal("list").executes(ctx -> run(McsCommandService::listPacks, ctx)))); + } + + private static int run(Supplier action, CommandContext context) { + McsCommandResult result = action.get().acknowledged(context.getInput()); + if (result.success()) { + context.getSource().sendSuccess(() -> Component.literal(result.message()), true); + return 1; + } + context.getSource().sendFailure(Component.literal(result.message())); + return 0; + } +} diff --git a/mod/platform/fabric/src/mc_1_21_9/java/dev/spyc0der/minecraftscript/fabric/FabricServerAccess.java b/mod/platform/fabric/src/mc_1_21_9/java/dev/spyc0der/minecraftscript/fabric/FabricServerAccess.java index 67953d9..3beea4f 100644 --- a/mod/platform/fabric/src/mc_1_21_9/java/dev/spyc0der/minecraftscript/fabric/FabricServerAccess.java +++ b/mod/platform/fabric/src/mc_1_21_9/java/dev/spyc0der/minecraftscript/fabric/FabricServerAccess.java @@ -23,6 +23,11 @@ public Path saveRoot() { return server.getWorldPath(LevelResource.ROOT); } + @Override + public Path serverDirectory() { + return server.getServerDirectory(); + } + @Override public String minecraftVersion() { return McsBuildInfo.MINECRAFT_VERSION; @@ -30,12 +35,27 @@ public String minecraftVersion() { @Override public void executeReload() { - server.execute(() -> { - CommandSourceStack source = server.createCommandSourceStack() - .withPermission(4) - .withSuppressedOutput(); - server.getCommands().performPrefixedCommand(source, "reload"); - }); + runOnServerThread(this::performReload); + } + + @Override + public void executeStartupReload() { + runOnServerThread(this::performReload); + } + + private void performReload() { + CommandSourceStack source = server.createCommandSourceStack() + .withPermission(4) + .withSuppressedOutput(); + server.getCommands().performPrefixedCommand(source, "reload"); + } + + private void runOnServerThread(Runnable action) { + if (server.isSameThread()) { + action.run(); + } else { + server.execute(action); + } } @Override diff --git a/mod/platform/fabric/src/mc_1_21_9/java/dev/spyc0der/minecraftscript/fabric/McsFabricCommands.java b/mod/platform/fabric/src/mc_1_21_9/java/dev/spyc0der/minecraftscript/fabric/McsFabricCommands.java new file mode 100644 index 0000000..ae1c2d3 --- /dev/null +++ b/mod/platform/fabric/src/mc_1_21_9/java/dev/spyc0der/minecraftscript/fabric/McsFabricCommands.java @@ -0,0 +1,113 @@ +package dev.spyc0der.minecraftscript.fabric; + +import com.mojang.brigadier.CommandDispatcher; +import com.mojang.brigadier.arguments.StringArgumentType; +import com.mojang.brigadier.context.CommandContext; +import com.mojang.brigadier.suggestion.SuggestionProvider; +import dev.spyc0der.minecraftscript.McsCommandResult; +import dev.spyc0der.minecraftscript.McsCommandService; +import net.minecraft.commands.CommandSourceStack; +import net.minecraft.commands.Commands; +import net.minecraft.network.chat.Component; + +import java.util.function.Supplier; + +public final class McsFabricCommands { + private static final SuggestionProvider PACK_SUGGESTIONS = (context, builder) -> { + for (String packName : McsCommandService.packNameSuggestions()) { + builder.suggest(packName); + } + return builder.buildFuture(); + }; + + private McsFabricCommands() { + } + + public static void register(CommandDispatcher dispatcher) { + dispatcher.register(Commands.literal("mcs") + .requires(source -> source.hasPermission(4)) + .then(Commands.literal("hotreloading") + .then(Commands.literal("enable") + .executes(ctx -> run(() -> McsCommandService.setHotReloading(true, null), ctx)) + .then(Commands.argument("pack", StringArgumentType.string()) + .suggests(PACK_SUGGESTIONS) + .executes(ctx -> run( + () -> McsCommandService.setHotReloading( + true, + StringArgumentType.getString(ctx, "pack") + ), + ctx + )))) + .then(Commands.literal("disable") + .executes(ctx -> run(() -> McsCommandService.setHotReloading(false, null), ctx)) + .then(Commands.argument("pack", StringArgumentType.string()) + .suggests(PACK_SUGGESTIONS) + .executes(ctx -> run( + () -> McsCommandService.setHotReloading( + false, + StringArgumentType.getString(ctx, "pack") + ), + ctx + ))))) + .then(Commands.literal("packs") + .then(Commands.literal("enable") + .executes(ctx -> run(() -> McsCommandService.setPackEnabled(null, true), ctx)) + .then(Commands.argument("pack", StringArgumentType.string()) + .suggests(PACK_SUGGESTIONS) + .executes(ctx -> run( + () -> McsCommandService.setPackEnabled( + StringArgumentType.getString(ctx, "pack"), + true + ), + ctx + )))) + .then(Commands.literal("disable") + .executes(ctx -> run(() -> McsCommandService.setPackEnabled(null, false), ctx)) + .then(Commands.argument("pack", StringArgumentType.string()) + .suggests(PACK_SUGGESTIONS) + .executes(ctx -> run( + () -> McsCommandService.setPackEnabled( + StringArgumentType.getString(ctx, "pack"), + false + ), + ctx + ))))) + .then(Commands.literal("reload") + .executes(ctx -> run(() -> McsCommandService.reload(null), ctx)) + .then(Commands.argument("pack", StringArgumentType.string()) + .suggests(PACK_SUGGESTIONS) + .executes(ctx -> run( + () -> McsCommandService.reload(StringArgumentType.getString(ctx, "pack")), + ctx + )))) + .then(Commands.literal("create") + .then(Commands.argument("name", StringArgumentType.string()) + .then(Commands.literal("global") + .executes(ctx -> run( + () -> McsCommandService.createPack( + StringArgumentType.getString(ctx, "name"), + "global" + ), + ctx + ))) + .then(Commands.literal("local") + .executes(ctx -> run( + () -> McsCommandService.createPack( + StringArgumentType.getString(ctx, "name"), + "local" + ), + ctx + ))))) + .then(Commands.literal("list").executes(ctx -> run(McsCommandService::listPacks, ctx)))); + } + + private static int run(Supplier action, CommandContext context) { + McsCommandResult result = action.get().acknowledged(context.getInput()); + if (result.success()) { + context.getSource().sendSuccess(() -> Component.literal(result.message()), true); + return 1; + } + context.getSource().sendFailure(Component.literal(result.message())); + return 0; + } +} diff --git a/mod/platform/fabric/src/mc_legacy/java/dev/spyc0der/minecraftscript/fabric/FabricServerAccess.java b/mod/platform/fabric/src/mc_legacy/java/dev/spyc0der/minecraftscript/fabric/FabricServerAccess.java index b4c8db0..9c40cc7 100644 --- a/mod/platform/fabric/src/mc_legacy/java/dev/spyc0der/minecraftscript/fabric/FabricServerAccess.java +++ b/mod/platform/fabric/src/mc_legacy/java/dev/spyc0der/minecraftscript/fabric/FabricServerAccess.java @@ -22,6 +22,11 @@ public Path saveRoot() { return server.getWorldPath(LevelResource.ROOT); } + @Override + public Path serverDirectory() { + return server.getServerDirectory(); + } + @Override public String minecraftVersion() { return McsBuildInfo.MINECRAFT_VERSION; @@ -29,12 +34,27 @@ public String minecraftVersion() { @Override public void executeReload() { - server.execute(() -> { - CommandSourceStack source = server.createCommandSourceStack() - .withPermission(4) - .withSuppressedOutput(); - server.getCommands().performPrefixedCommand(source, "reload"); - }); + runOnServerThread(this::performReload); + } + + @Override + public void executeStartupReload() { + runOnServerThread(this::performReload); + } + + private void performReload() { + CommandSourceStack source = server.createCommandSourceStack() + .withPermission(4) + .withSuppressedOutput(); + server.getCommands().performPrefixedCommand(source, "reload"); + } + + private void runOnServerThread(Runnable action) { + if (server.isSameThread()) { + action.run(); + } else { + server.execute(action); + } } @Override diff --git a/mod/platform/fabric/src/mc_legacy/java/dev/spyc0der/minecraftscript/fabric/McsFabricCommands.java b/mod/platform/fabric/src/mc_legacy/java/dev/spyc0der/minecraftscript/fabric/McsFabricCommands.java new file mode 100644 index 0000000..ae1c2d3 --- /dev/null +++ b/mod/platform/fabric/src/mc_legacy/java/dev/spyc0der/minecraftscript/fabric/McsFabricCommands.java @@ -0,0 +1,113 @@ +package dev.spyc0der.minecraftscript.fabric; + +import com.mojang.brigadier.CommandDispatcher; +import com.mojang.brigadier.arguments.StringArgumentType; +import com.mojang.brigadier.context.CommandContext; +import com.mojang.brigadier.suggestion.SuggestionProvider; +import dev.spyc0der.minecraftscript.McsCommandResult; +import dev.spyc0der.minecraftscript.McsCommandService; +import net.minecraft.commands.CommandSourceStack; +import net.minecraft.commands.Commands; +import net.minecraft.network.chat.Component; + +import java.util.function.Supplier; + +public final class McsFabricCommands { + private static final SuggestionProvider PACK_SUGGESTIONS = (context, builder) -> { + for (String packName : McsCommandService.packNameSuggestions()) { + builder.suggest(packName); + } + return builder.buildFuture(); + }; + + private McsFabricCommands() { + } + + public static void register(CommandDispatcher dispatcher) { + dispatcher.register(Commands.literal("mcs") + .requires(source -> source.hasPermission(4)) + .then(Commands.literal("hotreloading") + .then(Commands.literal("enable") + .executes(ctx -> run(() -> McsCommandService.setHotReloading(true, null), ctx)) + .then(Commands.argument("pack", StringArgumentType.string()) + .suggests(PACK_SUGGESTIONS) + .executes(ctx -> run( + () -> McsCommandService.setHotReloading( + true, + StringArgumentType.getString(ctx, "pack") + ), + ctx + )))) + .then(Commands.literal("disable") + .executes(ctx -> run(() -> McsCommandService.setHotReloading(false, null), ctx)) + .then(Commands.argument("pack", StringArgumentType.string()) + .suggests(PACK_SUGGESTIONS) + .executes(ctx -> run( + () -> McsCommandService.setHotReloading( + false, + StringArgumentType.getString(ctx, "pack") + ), + ctx + ))))) + .then(Commands.literal("packs") + .then(Commands.literal("enable") + .executes(ctx -> run(() -> McsCommandService.setPackEnabled(null, true), ctx)) + .then(Commands.argument("pack", StringArgumentType.string()) + .suggests(PACK_SUGGESTIONS) + .executes(ctx -> run( + () -> McsCommandService.setPackEnabled( + StringArgumentType.getString(ctx, "pack"), + true + ), + ctx + )))) + .then(Commands.literal("disable") + .executes(ctx -> run(() -> McsCommandService.setPackEnabled(null, false), ctx)) + .then(Commands.argument("pack", StringArgumentType.string()) + .suggests(PACK_SUGGESTIONS) + .executes(ctx -> run( + () -> McsCommandService.setPackEnabled( + StringArgumentType.getString(ctx, "pack"), + false + ), + ctx + ))))) + .then(Commands.literal("reload") + .executes(ctx -> run(() -> McsCommandService.reload(null), ctx)) + .then(Commands.argument("pack", StringArgumentType.string()) + .suggests(PACK_SUGGESTIONS) + .executes(ctx -> run( + () -> McsCommandService.reload(StringArgumentType.getString(ctx, "pack")), + ctx + )))) + .then(Commands.literal("create") + .then(Commands.argument("name", StringArgumentType.string()) + .then(Commands.literal("global") + .executes(ctx -> run( + () -> McsCommandService.createPack( + StringArgumentType.getString(ctx, "name"), + "global" + ), + ctx + ))) + .then(Commands.literal("local") + .executes(ctx -> run( + () -> McsCommandService.createPack( + StringArgumentType.getString(ctx, "name"), + "local" + ), + ctx + ))))) + .then(Commands.literal("list").executes(ctx -> run(McsCommandService::listPacks, ctx)))); + } + + private static int run(Supplier action, CommandContext context) { + McsCommandResult result = action.get().acknowledged(context.getInput()); + if (result.success()) { + context.getSource().sendSuccess(() -> Component.literal(result.message()), true); + return 1; + } + context.getSource().sendFailure(Component.literal(result.message())); + return 0; + } +} diff --git a/mod/platform/forge/src/forge_legacy/java/dev/spyc0der/minecraftscript/forge/McsForgeMod.java b/mod/platform/forge/src/forge_legacy/java/dev/spyc0der/minecraftscript/forge/McsForgeMod.java index 82f3da7..9a0640d 100644 --- a/mod/platform/forge/src/forge_legacy/java/dev/spyc0der/minecraftscript/forge/McsForgeMod.java +++ b/mod/platform/forge/src/forge_legacy/java/dev/spyc0der/minecraftscript/forge/McsForgeMod.java @@ -2,6 +2,7 @@ import dev.spyc0der.minecraftscript.McsModRuntime; import net.minecraftforge.common.MinecraftForge; +import net.minecraftforge.event.RegisterCommandsEvent; import net.minecraftforge.event.server.ServerStartedEvent; import net.minecraftforge.event.server.ServerStoppingEvent; import net.minecraftforge.eventbus.api.SubscribeEvent; @@ -13,6 +14,11 @@ public McsForgeMod() { MinecraftForge.EVENT_BUS.register(this); } + @SubscribeEvent + public void onRegisterCommands(RegisterCommandsEvent event) { + McsForgeCommands.register(event.getDispatcher()); + } + @SubscribeEvent public void onServerStarted(ServerStartedEvent event) { McsModRuntime.serverStarted(new ForgeServerAccess(event.getServer())); diff --git a/mod/platform/forge/src/forge_modern/java/dev/spyc0der/minecraftscript/forge/McsForgeMod.java b/mod/platform/forge/src/forge_modern/java/dev/spyc0der/minecraftscript/forge/McsForgeMod.java index 222b324..de320c6 100644 --- a/mod/platform/forge/src/forge_modern/java/dev/spyc0der/minecraftscript/forge/McsForgeMod.java +++ b/mod/platform/forge/src/forge_modern/java/dev/spyc0der/minecraftscript/forge/McsForgeMod.java @@ -1,6 +1,7 @@ package dev.spyc0der.minecraftscript.forge; import dev.spyc0der.minecraftscript.McsModRuntime; +import net.minecraftforge.event.RegisterCommandsEvent; import net.minecraftforge.event.server.ServerStartedEvent; import net.minecraftforge.event.server.ServerStoppingEvent; import net.minecraftforge.fml.common.Mod; @@ -8,10 +9,15 @@ @Mod("minecraft_script") public final class McsForgeMod { public McsForgeMod() { + RegisterCommandsEvent.BUS.addListener(this::onRegisterCommands); ServerStartedEvent.BUS.addListener(this::onServerStarted); ServerStoppingEvent.BUS.addListener(this::onServerStopping); } + private void onRegisterCommands(RegisterCommandsEvent event) { + McsForgeCommands.register(event.getDispatcher()); + } + private void onServerStarted(ServerStartedEvent event) { McsModRuntime.serverStarted(new ForgeServerAccess(event.getServer())); } diff --git a/mod/platform/forge/src/mc_1_21_11/java/dev/spyc0der/minecraftscript/forge/ForgeServerAccess.java b/mod/platform/forge/src/mc_1_21_11/java/dev/spyc0der/minecraftscript/forge/ForgeServerAccess.java index 8707027..ae89640 100644 --- a/mod/platform/forge/src/mc_1_21_11/java/dev/spyc0der/minecraftscript/forge/ForgeServerAccess.java +++ b/mod/platform/forge/src/mc_1_21_11/java/dev/spyc0der/minecraftscript/forge/ForgeServerAccess.java @@ -24,6 +24,11 @@ public Path saveRoot() { return server.getWorldPath(LevelResource.ROOT); } + @Override + public Path serverDirectory() { + return server.getServerDirectory(); + } + @Override public String minecraftVersion() { return McsBuildInfo.MINECRAFT_VERSION; @@ -31,12 +36,27 @@ public String minecraftVersion() { @Override public void executeReload() { - server.execute(() -> { - CommandSourceStack source = server.createCommandSourceStack() - .withPermission(LevelBasedPermissionSet.ADMIN) - .withSuppressedOutput(); - server.getCommands().performPrefixedCommand(source, "reload"); - }); + runOnServerThread(this::performReload); + } + + @Override + public void executeStartupReload() { + runOnServerThread(this::performReload); + } + + private void performReload() { + CommandSourceStack source = server.createCommandSourceStack() + .withPermission(LevelBasedPermissionSet.ADMIN) + .withSuppressedOutput(); + server.getCommands().performPrefixedCommand(source, "reload"); + } + + private void runOnServerThread(Runnable action) { + if (server.isSameThread()) { + action.run(); + } else { + server.execute(action); + } } @Override diff --git a/mod/platform/forge/src/mc_1_21_11/java/dev/spyc0der/minecraftscript/forge/McsForgeCommands.java b/mod/platform/forge/src/mc_1_21_11/java/dev/spyc0der/minecraftscript/forge/McsForgeCommands.java new file mode 100644 index 0000000..b268b9c --- /dev/null +++ b/mod/platform/forge/src/mc_1_21_11/java/dev/spyc0der/minecraftscript/forge/McsForgeCommands.java @@ -0,0 +1,113 @@ +package dev.spyc0der.minecraftscript.forge; + +import com.mojang.brigadier.CommandDispatcher; +import com.mojang.brigadier.arguments.StringArgumentType; +import com.mojang.brigadier.context.CommandContext; +import com.mojang.brigadier.suggestion.SuggestionProvider; +import dev.spyc0der.minecraftscript.McsCommandResult; +import dev.spyc0der.minecraftscript.McsCommandService; +import net.minecraft.commands.CommandSourceStack; +import net.minecraft.commands.Commands; +import net.minecraft.network.chat.Component; + +import java.util.function.Supplier; + +public final class McsForgeCommands { + private static final SuggestionProvider PACK_SUGGESTIONS = (context, builder) -> { + for (String packName : McsCommandService.packNameSuggestions()) { + builder.suggest(packName); + } + return builder.buildFuture(); + }; + + private McsForgeCommands() { + } + + public static void register(CommandDispatcher dispatcher) { + dispatcher.register(Commands.literal("mcs") + .requires(Commands.hasPermission(Commands.LEVEL_ADMINS)) + .then(Commands.literal("hotreloading") + .then(Commands.literal("enable") + .executes(ctx -> run(() -> McsCommandService.setHotReloading(true, null), ctx)) + .then(Commands.argument("pack", StringArgumentType.string()) + .suggests(PACK_SUGGESTIONS) + .executes(ctx -> run( + () -> McsCommandService.setHotReloading( + true, + StringArgumentType.getString(ctx, "pack") + ), + ctx + )))) + .then(Commands.literal("disable") + .executes(ctx -> run(() -> McsCommandService.setHotReloading(false, null), ctx)) + .then(Commands.argument("pack", StringArgumentType.string()) + .suggests(PACK_SUGGESTIONS) + .executes(ctx -> run( + () -> McsCommandService.setHotReloading( + false, + StringArgumentType.getString(ctx, "pack") + ), + ctx + ))))) + .then(Commands.literal("packs") + .then(Commands.literal("enable") + .executes(ctx -> run(() -> McsCommandService.setPackEnabled(null, true), ctx)) + .then(Commands.argument("pack", StringArgumentType.string()) + .suggests(PACK_SUGGESTIONS) + .executes(ctx -> run( + () -> McsCommandService.setPackEnabled( + StringArgumentType.getString(ctx, "pack"), + true + ), + ctx + )))) + .then(Commands.literal("disable") + .executes(ctx -> run(() -> McsCommandService.setPackEnabled(null, false), ctx)) + .then(Commands.argument("pack", StringArgumentType.string()) + .suggests(PACK_SUGGESTIONS) + .executes(ctx -> run( + () -> McsCommandService.setPackEnabled( + StringArgumentType.getString(ctx, "pack"), + false + ), + ctx + ))))) + .then(Commands.literal("reload") + .executes(ctx -> run(() -> McsCommandService.reload(null), ctx)) + .then(Commands.argument("pack", StringArgumentType.string()) + .suggests(PACK_SUGGESTIONS) + .executes(ctx -> run( + () -> McsCommandService.reload(StringArgumentType.getString(ctx, "pack")), + ctx + )))) + .then(Commands.literal("create") + .then(Commands.argument("name", StringArgumentType.string()) + .then(Commands.literal("global") + .executes(ctx -> run( + () -> McsCommandService.createPack( + StringArgumentType.getString(ctx, "name"), + "global" + ), + ctx + ))) + .then(Commands.literal("local") + .executes(ctx -> run( + () -> McsCommandService.createPack( + StringArgumentType.getString(ctx, "name"), + "local" + ), + ctx + ))))) + .then(Commands.literal("list").executes(ctx -> run(McsCommandService::listPacks, ctx)))); + } + + private static int run(Supplier action, CommandContext context) { + McsCommandResult result = action.get().acknowledged(context.getInput()); + if (result.success()) { + context.getSource().sendSuccess(() -> Component.literal(result.message()), true); + return 1; + } + context.getSource().sendFailure(Component.literal(result.message())); + return 0; + } +} diff --git a/mod/platform/forge/src/mc_1_21_9/java/dev/spyc0der/minecraftscript/forge/ForgeServerAccess.java b/mod/platform/forge/src/mc_1_21_9/java/dev/spyc0der/minecraftscript/forge/ForgeServerAccess.java index da52bf4..19f3584 100644 --- a/mod/platform/forge/src/mc_1_21_9/java/dev/spyc0der/minecraftscript/forge/ForgeServerAccess.java +++ b/mod/platform/forge/src/mc_1_21_9/java/dev/spyc0der/minecraftscript/forge/ForgeServerAccess.java @@ -23,6 +23,11 @@ public Path saveRoot() { return server.getWorldPath(LevelResource.ROOT); } + @Override + public Path serverDirectory() { + return server.getServerDirectory(); + } + @Override public String minecraftVersion() { return McsBuildInfo.MINECRAFT_VERSION; @@ -30,12 +35,27 @@ public String minecraftVersion() { @Override public void executeReload() { - server.execute(() -> { - CommandSourceStack source = server.createCommandSourceStack() - .withPermission(4) - .withSuppressedOutput(); - server.getCommands().performPrefixedCommand(source, "reload"); - }); + runOnServerThread(this::performReload); + } + + @Override + public void executeStartupReload() { + runOnServerThread(this::performReload); + } + + private void performReload() { + CommandSourceStack source = server.createCommandSourceStack() + .withPermission(4) + .withSuppressedOutput(); + server.getCommands().performPrefixedCommand(source, "reload"); + } + + private void runOnServerThread(Runnable action) { + if (server.isSameThread()) { + action.run(); + } else { + server.execute(action); + } } @Override diff --git a/mod/platform/forge/src/mc_1_21_9/java/dev/spyc0der/minecraftscript/forge/McsForgeCommands.java b/mod/platform/forge/src/mc_1_21_9/java/dev/spyc0der/minecraftscript/forge/McsForgeCommands.java new file mode 100644 index 0000000..d2db46c --- /dev/null +++ b/mod/platform/forge/src/mc_1_21_9/java/dev/spyc0der/minecraftscript/forge/McsForgeCommands.java @@ -0,0 +1,113 @@ +package dev.spyc0der.minecraftscript.forge; + +import com.mojang.brigadier.CommandDispatcher; +import com.mojang.brigadier.arguments.StringArgumentType; +import com.mojang.brigadier.context.CommandContext; +import com.mojang.brigadier.suggestion.SuggestionProvider; +import dev.spyc0der.minecraftscript.McsCommandResult; +import dev.spyc0der.minecraftscript.McsCommandService; +import net.minecraft.commands.CommandSourceStack; +import net.minecraft.commands.Commands; +import net.minecraft.network.chat.Component; + +import java.util.function.Supplier; + +public final class McsForgeCommands { + private static final SuggestionProvider PACK_SUGGESTIONS = (context, builder) -> { + for (String packName : McsCommandService.packNameSuggestions()) { + builder.suggest(packName); + } + return builder.buildFuture(); + }; + + private McsForgeCommands() { + } + + public static void register(CommandDispatcher dispatcher) { + dispatcher.register(Commands.literal("mcs") + .requires(source -> source.hasPermission(4)) + .then(Commands.literal("hotreloading") + .then(Commands.literal("enable") + .executes(ctx -> run(() -> McsCommandService.setHotReloading(true, null), ctx)) + .then(Commands.argument("pack", StringArgumentType.string()) + .suggests(PACK_SUGGESTIONS) + .executes(ctx -> run( + () -> McsCommandService.setHotReloading( + true, + StringArgumentType.getString(ctx, "pack") + ), + ctx + )))) + .then(Commands.literal("disable") + .executes(ctx -> run(() -> McsCommandService.setHotReloading(false, null), ctx)) + .then(Commands.argument("pack", StringArgumentType.string()) + .suggests(PACK_SUGGESTIONS) + .executes(ctx -> run( + () -> McsCommandService.setHotReloading( + false, + StringArgumentType.getString(ctx, "pack") + ), + ctx + ))))) + .then(Commands.literal("packs") + .then(Commands.literal("enable") + .executes(ctx -> run(() -> McsCommandService.setPackEnabled(null, true), ctx)) + .then(Commands.argument("pack", StringArgumentType.string()) + .suggests(PACK_SUGGESTIONS) + .executes(ctx -> run( + () -> McsCommandService.setPackEnabled( + StringArgumentType.getString(ctx, "pack"), + true + ), + ctx + )))) + .then(Commands.literal("disable") + .executes(ctx -> run(() -> McsCommandService.setPackEnabled(null, false), ctx)) + .then(Commands.argument("pack", StringArgumentType.string()) + .suggests(PACK_SUGGESTIONS) + .executes(ctx -> run( + () -> McsCommandService.setPackEnabled( + StringArgumentType.getString(ctx, "pack"), + false + ), + ctx + ))))) + .then(Commands.literal("reload") + .executes(ctx -> run(() -> McsCommandService.reload(null), ctx)) + .then(Commands.argument("pack", StringArgumentType.string()) + .suggests(PACK_SUGGESTIONS) + .executes(ctx -> run( + () -> McsCommandService.reload(StringArgumentType.getString(ctx, "pack")), + ctx + )))) + .then(Commands.literal("create") + .then(Commands.argument("name", StringArgumentType.string()) + .then(Commands.literal("global") + .executes(ctx -> run( + () -> McsCommandService.createPack( + StringArgumentType.getString(ctx, "name"), + "global" + ), + ctx + ))) + .then(Commands.literal("local") + .executes(ctx -> run( + () -> McsCommandService.createPack( + StringArgumentType.getString(ctx, "name"), + "local" + ), + ctx + ))))) + .then(Commands.literal("list").executes(ctx -> run(McsCommandService::listPacks, ctx)))); + } + + private static int run(Supplier action, CommandContext context) { + McsCommandResult result = action.get().acknowledged(context.getInput()); + if (result.success()) { + context.getSource().sendSuccess(() -> Component.literal(result.message()), true); + return 1; + } + context.getSource().sendFailure(Component.literal(result.message())); + return 0; + } +} diff --git a/mod/platform/forge/src/mc_legacy/java/dev/spyc0der/minecraftscript/forge/ForgeServerAccess.java b/mod/platform/forge/src/mc_legacy/java/dev/spyc0der/minecraftscript/forge/ForgeServerAccess.java index d08c0b7..ce3e081 100644 --- a/mod/platform/forge/src/mc_legacy/java/dev/spyc0der/minecraftscript/forge/ForgeServerAccess.java +++ b/mod/platform/forge/src/mc_legacy/java/dev/spyc0der/minecraftscript/forge/ForgeServerAccess.java @@ -22,6 +22,11 @@ public Path saveRoot() { return server.getWorldPath(LevelResource.ROOT); } + @Override + public Path serverDirectory() { + return server.getServerDirectory(); + } + @Override public String minecraftVersion() { return McsBuildInfo.MINECRAFT_VERSION; @@ -29,12 +34,27 @@ public String minecraftVersion() { @Override public void executeReload() { - server.execute(() -> { - CommandSourceStack source = server.createCommandSourceStack() - .withPermission(4) - .withSuppressedOutput(); - server.getCommands().performPrefixedCommand(source, "reload"); - }); + runOnServerThread(this::performReload); + } + + @Override + public void executeStartupReload() { + runOnServerThread(this::performReload); + } + + private void performReload() { + CommandSourceStack source = server.createCommandSourceStack() + .withPermission(4) + .withSuppressedOutput(); + server.getCommands().performPrefixedCommand(source, "reload"); + } + + private void runOnServerThread(Runnable action) { + if (server.isSameThread()) { + action.run(); + } else { + server.execute(action); + } } @Override diff --git a/mod/platform/forge/src/mc_legacy/java/dev/spyc0der/minecraftscript/forge/McsForgeCommands.java b/mod/platform/forge/src/mc_legacy/java/dev/spyc0der/minecraftscript/forge/McsForgeCommands.java new file mode 100644 index 0000000..d2db46c --- /dev/null +++ b/mod/platform/forge/src/mc_legacy/java/dev/spyc0der/minecraftscript/forge/McsForgeCommands.java @@ -0,0 +1,113 @@ +package dev.spyc0der.minecraftscript.forge; + +import com.mojang.brigadier.CommandDispatcher; +import com.mojang.brigadier.arguments.StringArgumentType; +import com.mojang.brigadier.context.CommandContext; +import com.mojang.brigadier.suggestion.SuggestionProvider; +import dev.spyc0der.minecraftscript.McsCommandResult; +import dev.spyc0der.minecraftscript.McsCommandService; +import net.minecraft.commands.CommandSourceStack; +import net.minecraft.commands.Commands; +import net.minecraft.network.chat.Component; + +import java.util.function.Supplier; + +public final class McsForgeCommands { + private static final SuggestionProvider PACK_SUGGESTIONS = (context, builder) -> { + for (String packName : McsCommandService.packNameSuggestions()) { + builder.suggest(packName); + } + return builder.buildFuture(); + }; + + private McsForgeCommands() { + } + + public static void register(CommandDispatcher dispatcher) { + dispatcher.register(Commands.literal("mcs") + .requires(source -> source.hasPermission(4)) + .then(Commands.literal("hotreloading") + .then(Commands.literal("enable") + .executes(ctx -> run(() -> McsCommandService.setHotReloading(true, null), ctx)) + .then(Commands.argument("pack", StringArgumentType.string()) + .suggests(PACK_SUGGESTIONS) + .executes(ctx -> run( + () -> McsCommandService.setHotReloading( + true, + StringArgumentType.getString(ctx, "pack") + ), + ctx + )))) + .then(Commands.literal("disable") + .executes(ctx -> run(() -> McsCommandService.setHotReloading(false, null), ctx)) + .then(Commands.argument("pack", StringArgumentType.string()) + .suggests(PACK_SUGGESTIONS) + .executes(ctx -> run( + () -> McsCommandService.setHotReloading( + false, + StringArgumentType.getString(ctx, "pack") + ), + ctx + ))))) + .then(Commands.literal("packs") + .then(Commands.literal("enable") + .executes(ctx -> run(() -> McsCommandService.setPackEnabled(null, true), ctx)) + .then(Commands.argument("pack", StringArgumentType.string()) + .suggests(PACK_SUGGESTIONS) + .executes(ctx -> run( + () -> McsCommandService.setPackEnabled( + StringArgumentType.getString(ctx, "pack"), + true + ), + ctx + )))) + .then(Commands.literal("disable") + .executes(ctx -> run(() -> McsCommandService.setPackEnabled(null, false), ctx)) + .then(Commands.argument("pack", StringArgumentType.string()) + .suggests(PACK_SUGGESTIONS) + .executes(ctx -> run( + () -> McsCommandService.setPackEnabled( + StringArgumentType.getString(ctx, "pack"), + false + ), + ctx + ))))) + .then(Commands.literal("reload") + .executes(ctx -> run(() -> McsCommandService.reload(null), ctx)) + .then(Commands.argument("pack", StringArgumentType.string()) + .suggests(PACK_SUGGESTIONS) + .executes(ctx -> run( + () -> McsCommandService.reload(StringArgumentType.getString(ctx, "pack")), + ctx + )))) + .then(Commands.literal("create") + .then(Commands.argument("name", StringArgumentType.string()) + .then(Commands.literal("global") + .executes(ctx -> run( + () -> McsCommandService.createPack( + StringArgumentType.getString(ctx, "name"), + "global" + ), + ctx + ))) + .then(Commands.literal("local") + .executes(ctx -> run( + () -> McsCommandService.createPack( + StringArgumentType.getString(ctx, "name"), + "local" + ), + ctx + ))))) + .then(Commands.literal("list").executes(ctx -> run(McsCommandService::listPacks, ctx)))); + } + + private static int run(Supplier action, CommandContext context) { + McsCommandResult result = action.get().acknowledged(context.getInput()); + if (result.success()) { + context.getSource().sendSuccess(() -> Component.literal(result.message()), true); + return 1; + } + context.getSource().sendFailure(Component.literal(result.message())); + return 0; + } +} diff --git a/mod/platform/neoforge/src/main/java/dev/spyc0der/minecraftscript/neoforge/McsNeoForgeMod.java b/mod/platform/neoforge/src/main/java/dev/spyc0der/minecraftscript/neoforge/McsNeoForgeMod.java index 4f4d856..99130be 100644 --- a/mod/platform/neoforge/src/main/java/dev/spyc0der/minecraftscript/neoforge/McsNeoForgeMod.java +++ b/mod/platform/neoforge/src/main/java/dev/spyc0der/minecraftscript/neoforge/McsNeoForgeMod.java @@ -4,6 +4,7 @@ import net.neoforged.bus.api.SubscribeEvent; import net.neoforged.fml.common.Mod; import net.neoforged.neoforge.common.NeoForge; +import net.neoforged.neoforge.event.RegisterCommandsEvent; import net.neoforged.neoforge.event.server.ServerStartedEvent; import net.neoforged.neoforge.event.server.ServerStoppingEvent; @@ -13,6 +14,11 @@ public McsNeoForgeMod() { NeoForge.EVENT_BUS.register(this); } + @SubscribeEvent + public void onRegisterCommands(RegisterCommandsEvent event) { + McsNeoForgeCommands.register(event.getDispatcher()); + } + @SubscribeEvent public void onServerStarted(ServerStartedEvent event) { McsModRuntime.serverStarted(new NeoForgeServerAccess(event.getServer())); diff --git a/mod/platform/neoforge/src/mc_1_21_11/java/dev/spyc0der/minecraftscript/neoforge/McsNeoForgeCommands.java b/mod/platform/neoforge/src/mc_1_21_11/java/dev/spyc0der/minecraftscript/neoforge/McsNeoForgeCommands.java new file mode 100644 index 0000000..c5f3de5 --- /dev/null +++ b/mod/platform/neoforge/src/mc_1_21_11/java/dev/spyc0der/minecraftscript/neoforge/McsNeoForgeCommands.java @@ -0,0 +1,113 @@ +package dev.spyc0der.minecraftscript.neoforge; + +import com.mojang.brigadier.CommandDispatcher; +import com.mojang.brigadier.arguments.StringArgumentType; +import com.mojang.brigadier.context.CommandContext; +import com.mojang.brigadier.suggestion.SuggestionProvider; +import dev.spyc0der.minecraftscript.McsCommandResult; +import dev.spyc0der.minecraftscript.McsCommandService; +import net.minecraft.commands.CommandSourceStack; +import net.minecraft.commands.Commands; +import net.minecraft.network.chat.Component; + +import java.util.function.Supplier; + +public final class McsNeoForgeCommands { + private static final SuggestionProvider PACK_SUGGESTIONS = (context, builder) -> { + for (String packName : McsCommandService.packNameSuggestions()) { + builder.suggest(packName); + } + return builder.buildFuture(); + }; + + private McsNeoForgeCommands() { + } + + public static void register(CommandDispatcher dispatcher) { + dispatcher.register(Commands.literal("mcs") + .requires(Commands.hasPermission(Commands.LEVEL_ADMINS)) + .then(Commands.literal("hotreloading") + .then(Commands.literal("enable") + .executes(ctx -> run(() -> McsCommandService.setHotReloading(true, null), ctx)) + .then(Commands.argument("pack", StringArgumentType.string()) + .suggests(PACK_SUGGESTIONS) + .executes(ctx -> run( + () -> McsCommandService.setHotReloading( + true, + StringArgumentType.getString(ctx, "pack") + ), + ctx + )))) + .then(Commands.literal("disable") + .executes(ctx -> run(() -> McsCommandService.setHotReloading(false, null), ctx)) + .then(Commands.argument("pack", StringArgumentType.string()) + .suggests(PACK_SUGGESTIONS) + .executes(ctx -> run( + () -> McsCommandService.setHotReloading( + false, + StringArgumentType.getString(ctx, "pack") + ), + ctx + ))))) + .then(Commands.literal("packs") + .then(Commands.literal("enable") + .executes(ctx -> run(() -> McsCommandService.setPackEnabled(null, true), ctx)) + .then(Commands.argument("pack", StringArgumentType.string()) + .suggests(PACK_SUGGESTIONS) + .executes(ctx -> run( + () -> McsCommandService.setPackEnabled( + StringArgumentType.getString(ctx, "pack"), + true + ), + ctx + )))) + .then(Commands.literal("disable") + .executes(ctx -> run(() -> McsCommandService.setPackEnabled(null, false), ctx)) + .then(Commands.argument("pack", StringArgumentType.string()) + .suggests(PACK_SUGGESTIONS) + .executes(ctx -> run( + () -> McsCommandService.setPackEnabled( + StringArgumentType.getString(ctx, "pack"), + false + ), + ctx + ))))) + .then(Commands.literal("reload") + .executes(ctx -> run(() -> McsCommandService.reload(null), ctx)) + .then(Commands.argument("pack", StringArgumentType.string()) + .suggests(PACK_SUGGESTIONS) + .executes(ctx -> run( + () -> McsCommandService.reload(StringArgumentType.getString(ctx, "pack")), + ctx + )))) + .then(Commands.literal("create") + .then(Commands.argument("name", StringArgumentType.string()) + .then(Commands.literal("global") + .executes(ctx -> run( + () -> McsCommandService.createPack( + StringArgumentType.getString(ctx, "name"), + "global" + ), + ctx + ))) + .then(Commands.literal("local") + .executes(ctx -> run( + () -> McsCommandService.createPack( + StringArgumentType.getString(ctx, "name"), + "local" + ), + ctx + ))))) + .then(Commands.literal("list").executes(ctx -> run(McsCommandService::listPacks, ctx)))); + } + + private static int run(Supplier action, CommandContext context) { + McsCommandResult result = action.get().acknowledged(context.getInput()); + if (result.success()) { + context.getSource().sendSuccess(() -> Component.literal(result.message()), true); + return 1; + } + context.getSource().sendFailure(Component.literal(result.message())); + return 0; + } +} diff --git a/mod/platform/neoforge/src/mc_1_21_11/java/dev/spyc0der/minecraftscript/neoforge/NeoForgeServerAccess.java b/mod/platform/neoforge/src/mc_1_21_11/java/dev/spyc0der/minecraftscript/neoforge/NeoForgeServerAccess.java index a5696cc..07d2160 100644 --- a/mod/platform/neoforge/src/mc_1_21_11/java/dev/spyc0der/minecraftscript/neoforge/NeoForgeServerAccess.java +++ b/mod/platform/neoforge/src/mc_1_21_11/java/dev/spyc0der/minecraftscript/neoforge/NeoForgeServerAccess.java @@ -24,6 +24,11 @@ public Path saveRoot() { return server.getWorldPath(LevelResource.ROOT); } + @Override + public Path serverDirectory() { + return server.getServerDirectory(); + } + @Override public String minecraftVersion() { return McsBuildInfo.MINECRAFT_VERSION; @@ -31,12 +36,27 @@ public String minecraftVersion() { @Override public void executeReload() { - server.execute(() -> { - CommandSourceStack source = server.createCommandSourceStack() - .withPermission(LevelBasedPermissionSet.ADMIN) - .withSuppressedOutput(); - server.getCommands().performPrefixedCommand(source, "reload"); - }); + runOnServerThread(this::performReload); + } + + @Override + public void executeStartupReload() { + runOnServerThread(this::performReload); + } + + private void performReload() { + CommandSourceStack source = server.createCommandSourceStack() + .withPermission(LevelBasedPermissionSet.ADMIN) + .withSuppressedOutput(); + server.getCommands().performPrefixedCommand(source, "reload"); + } + + private void runOnServerThread(Runnable action) { + if (server.isSameThread()) { + action.run(); + } else { + server.execute(action); + } } @Override diff --git a/mod/platform/neoforge/src/mc_1_21_9/java/dev/spyc0der/minecraftscript/neoforge/McsNeoForgeCommands.java b/mod/platform/neoforge/src/mc_1_21_9/java/dev/spyc0der/minecraftscript/neoforge/McsNeoForgeCommands.java new file mode 100644 index 0000000..7f0f5c6 --- /dev/null +++ b/mod/platform/neoforge/src/mc_1_21_9/java/dev/spyc0der/minecraftscript/neoforge/McsNeoForgeCommands.java @@ -0,0 +1,113 @@ +package dev.spyc0der.minecraftscript.neoforge; + +import com.mojang.brigadier.CommandDispatcher; +import com.mojang.brigadier.arguments.StringArgumentType; +import com.mojang.brigadier.context.CommandContext; +import com.mojang.brigadier.suggestion.SuggestionProvider; +import dev.spyc0der.minecraftscript.McsCommandResult; +import dev.spyc0der.minecraftscript.McsCommandService; +import net.minecraft.commands.CommandSourceStack; +import net.minecraft.commands.Commands; +import net.minecraft.network.chat.Component; + +import java.util.function.Supplier; + +public final class McsNeoForgeCommands { + private static final SuggestionProvider PACK_SUGGESTIONS = (context, builder) -> { + for (String packName : McsCommandService.packNameSuggestions()) { + builder.suggest(packName); + } + return builder.buildFuture(); + }; + + private McsNeoForgeCommands() { + } + + public static void register(CommandDispatcher dispatcher) { + dispatcher.register(Commands.literal("mcs") + .requires(source -> source.hasPermission(4)) + .then(Commands.literal("hotreloading") + .then(Commands.literal("enable") + .executes(ctx -> run(() -> McsCommandService.setHotReloading(true, null), ctx)) + .then(Commands.argument("pack", StringArgumentType.string()) + .suggests(PACK_SUGGESTIONS) + .executes(ctx -> run( + () -> McsCommandService.setHotReloading( + true, + StringArgumentType.getString(ctx, "pack") + ), + ctx + )))) + .then(Commands.literal("disable") + .executes(ctx -> run(() -> McsCommandService.setHotReloading(false, null), ctx)) + .then(Commands.argument("pack", StringArgumentType.string()) + .suggests(PACK_SUGGESTIONS) + .executes(ctx -> run( + () -> McsCommandService.setHotReloading( + false, + StringArgumentType.getString(ctx, "pack") + ), + ctx + ))))) + .then(Commands.literal("packs") + .then(Commands.literal("enable") + .executes(ctx -> run(() -> McsCommandService.setPackEnabled(null, true), ctx)) + .then(Commands.argument("pack", StringArgumentType.string()) + .suggests(PACK_SUGGESTIONS) + .executes(ctx -> run( + () -> McsCommandService.setPackEnabled( + StringArgumentType.getString(ctx, "pack"), + true + ), + ctx + )))) + .then(Commands.literal("disable") + .executes(ctx -> run(() -> McsCommandService.setPackEnabled(null, false), ctx)) + .then(Commands.argument("pack", StringArgumentType.string()) + .suggests(PACK_SUGGESTIONS) + .executes(ctx -> run( + () -> McsCommandService.setPackEnabled( + StringArgumentType.getString(ctx, "pack"), + false + ), + ctx + ))))) + .then(Commands.literal("reload") + .executes(ctx -> run(() -> McsCommandService.reload(null), ctx)) + .then(Commands.argument("pack", StringArgumentType.string()) + .suggests(PACK_SUGGESTIONS) + .executes(ctx -> run( + () -> McsCommandService.reload(StringArgumentType.getString(ctx, "pack")), + ctx + )))) + .then(Commands.literal("create") + .then(Commands.argument("name", StringArgumentType.string()) + .then(Commands.literal("global") + .executes(ctx -> run( + () -> McsCommandService.createPack( + StringArgumentType.getString(ctx, "name"), + "global" + ), + ctx + ))) + .then(Commands.literal("local") + .executes(ctx -> run( + () -> McsCommandService.createPack( + StringArgumentType.getString(ctx, "name"), + "local" + ), + ctx + ))))) + .then(Commands.literal("list").executes(ctx -> run(McsCommandService::listPacks, ctx)))); + } + + private static int run(Supplier action, CommandContext context) { + McsCommandResult result = action.get().acknowledged(context.getInput()); + if (result.success()) { + context.getSource().sendSuccess(() -> Component.literal(result.message()), true); + return 1; + } + context.getSource().sendFailure(Component.literal(result.message())); + return 0; + } +} diff --git a/mod/platform/neoforge/src/mc_1_21_9/java/dev/spyc0der/minecraftscript/neoforge/NeoForgeServerAccess.java b/mod/platform/neoforge/src/mc_1_21_9/java/dev/spyc0der/minecraftscript/neoforge/NeoForgeServerAccess.java index 6e61bef..6cf0a19 100644 --- a/mod/platform/neoforge/src/mc_1_21_9/java/dev/spyc0der/minecraftscript/neoforge/NeoForgeServerAccess.java +++ b/mod/platform/neoforge/src/mc_1_21_9/java/dev/spyc0der/minecraftscript/neoforge/NeoForgeServerAccess.java @@ -23,6 +23,11 @@ public Path saveRoot() { return server.getWorldPath(LevelResource.ROOT); } + @Override + public Path serverDirectory() { + return server.getServerDirectory(); + } + @Override public String minecraftVersion() { return McsBuildInfo.MINECRAFT_VERSION; @@ -30,12 +35,27 @@ public String minecraftVersion() { @Override public void executeReload() { - server.execute(() -> { - CommandSourceStack source = server.createCommandSourceStack() - .withPermission(4) - .withSuppressedOutput(); - server.getCommands().performPrefixedCommand(source, "reload"); - }); + runOnServerThread(this::performReload); + } + + @Override + public void executeStartupReload() { + runOnServerThread(this::performReload); + } + + private void performReload() { + CommandSourceStack source = server.createCommandSourceStack() + .withPermission(4) + .withSuppressedOutput(); + server.getCommands().performPrefixedCommand(source, "reload"); + } + + private void runOnServerThread(Runnable action) { + if (server.isSameThread()) { + action.run(); + } else { + server.execute(action); + } } @Override diff --git a/mod/platform/neoforge/src/mc_legacy/java/dev/spyc0der/minecraftscript/neoforge/McsNeoForgeCommands.java b/mod/platform/neoforge/src/mc_legacy/java/dev/spyc0der/minecraftscript/neoforge/McsNeoForgeCommands.java new file mode 100644 index 0000000..7f0f5c6 --- /dev/null +++ b/mod/platform/neoforge/src/mc_legacy/java/dev/spyc0der/minecraftscript/neoforge/McsNeoForgeCommands.java @@ -0,0 +1,113 @@ +package dev.spyc0der.minecraftscript.neoforge; + +import com.mojang.brigadier.CommandDispatcher; +import com.mojang.brigadier.arguments.StringArgumentType; +import com.mojang.brigadier.context.CommandContext; +import com.mojang.brigadier.suggestion.SuggestionProvider; +import dev.spyc0der.minecraftscript.McsCommandResult; +import dev.spyc0der.minecraftscript.McsCommandService; +import net.minecraft.commands.CommandSourceStack; +import net.minecraft.commands.Commands; +import net.minecraft.network.chat.Component; + +import java.util.function.Supplier; + +public final class McsNeoForgeCommands { + private static final SuggestionProvider PACK_SUGGESTIONS = (context, builder) -> { + for (String packName : McsCommandService.packNameSuggestions()) { + builder.suggest(packName); + } + return builder.buildFuture(); + }; + + private McsNeoForgeCommands() { + } + + public static void register(CommandDispatcher dispatcher) { + dispatcher.register(Commands.literal("mcs") + .requires(source -> source.hasPermission(4)) + .then(Commands.literal("hotreloading") + .then(Commands.literal("enable") + .executes(ctx -> run(() -> McsCommandService.setHotReloading(true, null), ctx)) + .then(Commands.argument("pack", StringArgumentType.string()) + .suggests(PACK_SUGGESTIONS) + .executes(ctx -> run( + () -> McsCommandService.setHotReloading( + true, + StringArgumentType.getString(ctx, "pack") + ), + ctx + )))) + .then(Commands.literal("disable") + .executes(ctx -> run(() -> McsCommandService.setHotReloading(false, null), ctx)) + .then(Commands.argument("pack", StringArgumentType.string()) + .suggests(PACK_SUGGESTIONS) + .executes(ctx -> run( + () -> McsCommandService.setHotReloading( + false, + StringArgumentType.getString(ctx, "pack") + ), + ctx + ))))) + .then(Commands.literal("packs") + .then(Commands.literal("enable") + .executes(ctx -> run(() -> McsCommandService.setPackEnabled(null, true), ctx)) + .then(Commands.argument("pack", StringArgumentType.string()) + .suggests(PACK_SUGGESTIONS) + .executes(ctx -> run( + () -> McsCommandService.setPackEnabled( + StringArgumentType.getString(ctx, "pack"), + true + ), + ctx + )))) + .then(Commands.literal("disable") + .executes(ctx -> run(() -> McsCommandService.setPackEnabled(null, false), ctx)) + .then(Commands.argument("pack", StringArgumentType.string()) + .suggests(PACK_SUGGESTIONS) + .executes(ctx -> run( + () -> McsCommandService.setPackEnabled( + StringArgumentType.getString(ctx, "pack"), + false + ), + ctx + ))))) + .then(Commands.literal("reload") + .executes(ctx -> run(() -> McsCommandService.reload(null), ctx)) + .then(Commands.argument("pack", StringArgumentType.string()) + .suggests(PACK_SUGGESTIONS) + .executes(ctx -> run( + () -> McsCommandService.reload(StringArgumentType.getString(ctx, "pack")), + ctx + )))) + .then(Commands.literal("create") + .then(Commands.argument("name", StringArgumentType.string()) + .then(Commands.literal("global") + .executes(ctx -> run( + () -> McsCommandService.createPack( + StringArgumentType.getString(ctx, "name"), + "global" + ), + ctx + ))) + .then(Commands.literal("local") + .executes(ctx -> run( + () -> McsCommandService.createPack( + StringArgumentType.getString(ctx, "name"), + "local" + ), + ctx + ))))) + .then(Commands.literal("list").executes(ctx -> run(McsCommandService::listPacks, ctx)))); + } + + private static int run(Supplier action, CommandContext context) { + McsCommandResult result = action.get().acknowledged(context.getInput()); + if (result.success()) { + context.getSource().sendSuccess(() -> Component.literal(result.message()), true); + return 1; + } + context.getSource().sendFailure(Component.literal(result.message())); + return 0; + } +} diff --git a/mod/platform/neoforge/src/mc_legacy/java/dev/spyc0der/minecraftscript/neoforge/NeoForgeServerAccess.java b/mod/platform/neoforge/src/mc_legacy/java/dev/spyc0der/minecraftscript/neoforge/NeoForgeServerAccess.java index 2690417..a96c8fc 100644 --- a/mod/platform/neoforge/src/mc_legacy/java/dev/spyc0der/minecraftscript/neoforge/NeoForgeServerAccess.java +++ b/mod/platform/neoforge/src/mc_legacy/java/dev/spyc0der/minecraftscript/neoforge/NeoForgeServerAccess.java @@ -22,6 +22,11 @@ public Path saveRoot() { return server.getWorldPath(LevelResource.ROOT); } + @Override + public Path serverDirectory() { + return server.getServerDirectory(); + } + @Override public String minecraftVersion() { return McsBuildInfo.MINECRAFT_VERSION; @@ -29,12 +34,27 @@ public String minecraftVersion() { @Override public void executeReload() { - server.execute(() -> { - CommandSourceStack source = server.createCommandSourceStack() - .withPermission(4) - .withSuppressedOutput(); - server.getCommands().performPrefixedCommand(source, "reload"); - }); + runOnServerThread(this::performReload); + } + + @Override + public void executeStartupReload() { + runOnServerThread(this::performReload); + } + + private void performReload() { + CommandSourceStack source = server.createCommandSourceStack() + .withPermission(4) + .withSuppressedOutput(); + server.getCommands().performPrefixedCommand(source, "reload"); + } + + private void runOnServerThread(Runnable action) { + if (server.isSameThread()) { + action.run(); + } else { + server.execute(action); + } } @Override