From f35d5b8904feb2929bb68fdab32d7658c0470402 Mon Sep 17 00:00:00 2001 From: Caedis Date: Mon, 13 Jan 2025 15:31:32 -0600 Subject: [PATCH] Migrate Server Pausing From Hodgepodge --- .../serverutils/ServerUtilitiesConfig.java | 4 + .../command/ServerUtilitiesCommands.java | 5 ++ .../pausewhenempty/CmdPauseWhenEmpty.java | 12 +++ .../CmdPauseWhenEmptyOneshot.java | 26 ++++++ .../pausewhenempty/CmdPauseWhenEmptySet.java | 26 ++++++ .../serverutils/data/IPauseWhenEmpty.java | 8 ++ src/main/java/serverutils/mixin/Mixins.java | 11 ++- .../assets/serverutilities/lang/en_US.lang | 6 ++ .../MixinDedicatedServer_PauseWhenEmpty.java | 54 +++++++++++++ .../MixinMinecraftServer_PauseWhenEmpty.java | 80 +++++++++++++++++++ 10 files changed, 229 insertions(+), 3 deletions(-) create mode 100644 src/main/java/serverutils/command/pausewhenempty/CmdPauseWhenEmpty.java create mode 100644 src/main/java/serverutils/command/pausewhenempty/CmdPauseWhenEmptyOneshot.java create mode 100644 src/main/java/serverutils/command/pausewhenempty/CmdPauseWhenEmptySet.java create mode 100644 src/main/java/serverutils/data/IPauseWhenEmpty.java create mode 100644 src/mixins/java/serverutils/mixins/early/minecraft/MixinDedicatedServer_PauseWhenEmpty.java create mode 100644 src/mixins/java/serverutils/mixins/early/minecraft/MixinMinecraftServer_PauseWhenEmpty.java diff --git a/src/main/java/serverutils/ServerUtilitiesConfig.java b/src/main/java/serverutils/ServerUtilitiesConfig.java index 53cd74dc..a6daa5d3 100644 --- a/src/main/java/serverutils/ServerUtilitiesConfig.java +++ b/src/main/java/serverutils/ServerUtilitiesConfig.java @@ -44,6 +44,10 @@ public static class General { + "If set to DEFAULT, it will only merge on singleplayer worlds.") @Config.DefaultEnum("TRUE") public EnumTristate merge_offline_mode_players; + + @Config.Comment({ "Backports 1.20's 'pause-when-empty-seconds' server property", "Default value: 0 (off)" }) + @Config.DefaultBoolean(true) + public boolean enable_pause_when_empty_property; } public static class Teams { diff --git a/src/main/java/serverutils/command/ServerUtilitiesCommands.java b/src/main/java/serverutils/command/ServerUtilitiesCommands.java index 31f7e9d0..e56607ab 100644 --- a/src/main/java/serverutils/command/ServerUtilitiesCommands.java +++ b/src/main/java/serverutils/command/ServerUtilitiesCommands.java @@ -3,6 +3,7 @@ import cpw.mods.fml.common.event.FMLServerStartingEvent; import serverutils.ServerUtilitiesConfig; import serverutils.command.chunks.CmdChunks; +import serverutils.command.pausewhenempty.CmdPauseWhenEmpty; import serverutils.command.pregen.CmdPregen; import serverutils.command.ranks.CmdRanks; import serverutils.command.team.CmdTeam; @@ -35,6 +36,10 @@ public static void registerCommands(FMLServerStartingEvent event) { if (ServerUtilitiesConfig.auto_shutdown.enabled) { event.registerServerCommand(new CmdShutdownTime()); } + + if (ServerUtilitiesConfig.general.enable_pause_when_empty_property) { + event.registerServerCommand(new CmdPauseWhenEmpty()); + } } if (ServerUtilitiesConfig.commands.inv) { diff --git a/src/main/java/serverutils/command/pausewhenempty/CmdPauseWhenEmpty.java b/src/main/java/serverutils/command/pausewhenempty/CmdPauseWhenEmpty.java new file mode 100644 index 00000000..d4da5850 --- /dev/null +++ b/src/main/java/serverutils/command/pausewhenempty/CmdPauseWhenEmpty.java @@ -0,0 +1,12 @@ +package serverutils.command.pausewhenempty; + +import serverutils.lib.command.CmdTreeBase; + +public class CmdPauseWhenEmpty extends CmdTreeBase { + + public CmdPauseWhenEmpty() { + super("pause_when_empty"); + addSubcommand(new CmdPauseWhenEmptySet()); + addSubcommand(new CmdPauseWhenEmptyOneshot()); + } +} diff --git a/src/main/java/serverutils/command/pausewhenempty/CmdPauseWhenEmptyOneshot.java b/src/main/java/serverutils/command/pausewhenempty/CmdPauseWhenEmptyOneshot.java new file mode 100644 index 00000000..09d4fb69 --- /dev/null +++ b/src/main/java/serverutils/command/pausewhenempty/CmdPauseWhenEmptyOneshot.java @@ -0,0 +1,26 @@ +package serverutils.command.pausewhenempty; + +import net.minecraft.command.ICommandSender; +import net.minecraft.server.MinecraftServer; + +import serverutils.ServerUtilities; +import serverutils.data.IPauseWhenEmpty; +import serverutils.lib.command.CmdBase; + +public class CmdPauseWhenEmptyOneshot extends CmdBase { + + public CmdPauseWhenEmptyOneshot() { + super("oneshot", Level.OP); + } + + @Override + public void processCommand(ICommandSender sender, String[] args) { + checkArgs(sender, args, 1); + int newValue = parseIntWithMin(sender, args[0], -1); + + if (MinecraftServer.getServer() instanceof IPauseWhenEmpty pauseWhenEmpty) { + pauseWhenEmpty.serverUtilities$setPauseWhenEmptySeconds(newValue, true); + sender.addChatMessage(ServerUtilities.lang(sender, "cmd.pause_when_empty_oneshot", newValue)); + } + } +} diff --git a/src/main/java/serverutils/command/pausewhenempty/CmdPauseWhenEmptySet.java b/src/main/java/serverutils/command/pausewhenempty/CmdPauseWhenEmptySet.java new file mode 100644 index 00000000..2962ddc9 --- /dev/null +++ b/src/main/java/serverutils/command/pausewhenempty/CmdPauseWhenEmptySet.java @@ -0,0 +1,26 @@ +package serverutils.command.pausewhenempty; + +import net.minecraft.command.ICommandSender; +import net.minecraft.server.MinecraftServer; + +import serverutils.ServerUtilities; +import serverutils.data.IPauseWhenEmpty; +import serverutils.lib.command.CmdBase; + +public class CmdPauseWhenEmptySet extends CmdBase { + + CmdPauseWhenEmptySet() { + super("set", Level.OP); + } + + @Override + public void processCommand(ICommandSender sender, String[] args) { + checkArgs(sender, args, 1); + int newValue = parseIntWithMin(sender, args[0], 0); + + if (MinecraftServer.getServer() instanceof IPauseWhenEmpty pauseWhenEmpty) { + pauseWhenEmpty.serverUtilities$setPauseWhenEmptySeconds(newValue, false); + sender.addChatMessage(ServerUtilities.lang(sender, "cmd.pause_when_empty_updated", newValue)); + } + } +} diff --git a/src/main/java/serverutils/data/IPauseWhenEmpty.java b/src/main/java/serverutils/data/IPauseWhenEmpty.java new file mode 100644 index 00000000..5c8b9a3c --- /dev/null +++ b/src/main/java/serverutils/data/IPauseWhenEmpty.java @@ -0,0 +1,8 @@ +package serverutils.data; + +public interface IPauseWhenEmpty { + + int serverUtilities$getPauseWhenEmptySeconds(); + + void serverUtilities$setPauseWhenEmptySeconds(int value, boolean oneshot); +} diff --git a/src/main/java/serverutils/mixin/Mixins.java b/src/main/java/serverutils/mixin/Mixins.java index dcc36937..bda00b17 100644 --- a/src/main/java/serverutils/mixin/Mixins.java +++ b/src/main/java/serverutils/mixin/Mixins.java @@ -1,7 +1,6 @@ package serverutils.mixin; -import static serverutils.ServerUtilitiesConfig.commands; -import static serverutils.ServerUtilitiesConfig.ranks; +import static serverutils.ServerUtilitiesConfig.*; import static serverutils.mixin.TargetedMod.VANILLA; import java.util.ArrayList; @@ -23,7 +22,13 @@ public enum Mixins { REPLACE_TAB_NAMES(new Builder("Replace tab menu names").addTargetedMod(VANILLA).setSide(Side.CLIENT) .setPhase(Phase.EARLY).addMixinClasses("forge.MixinGuiIngameForge")), VANILLA_TP_BACK_COMPAT(new Builder("/back compat for the vanilla /tp").addTargetedMod(VANILLA).setSide(Side.BOTH) - .setPhase(Phase.EARLY).setApplyIf(() -> commands.back).addMixinClasses("minecraft.MixinCommandTeleport")),; + .setPhase(Phase.EARLY).setApplyIf(() -> commands.back).addMixinClasses("minecraft.MixinCommandTeleport")), + PAUSE_WHEN_EMPTY(new Builder("Pauses the server when empty after X seconds; Servers Only").setPhase(Phase.EARLY) + .setSide(Side.SERVER).addTargetedMod(TargetedMod.VANILLA) + .addMixinClasses( + "minecraft.MixinMinecraftServer_PauseWhenEmpty", + "minecraft.MixinDedicatedServer_PauseWhenEmpty") + .setApplyIf(() -> general.enable_pause_when_empty_property)),; private final List mixinClasses; private final Supplier applyIf; diff --git a/src/main/resources/assets/serverutilities/lang/en_US.lang b/src/main/resources/assets/serverutilities/lang/en_US.lang index f289a372..0bd217d7 100644 --- a/src/main/resources/assets/serverutilities/lang/en_US.lang +++ b/src/main/resources/assets/serverutilities/lang/en_US.lang @@ -556,6 +556,12 @@ serverutilities.gui.backup.error=An error occured while restoring backup! serverutilities.gui.backup.delete_confirm=Are you sure you want to §c§ldelete§r this backup? serverutilities.gui.backup.restore_confirm=Are you sure you want to §a§lrestore§r this backup? +# Pause When Empty +cmd.pause_when_empty_updated=Server property 'pause-when-empty-seconds' set to %d +cmd.pause_when_empty_oneshot='pause-when-empty-seconds' temporarily set to %d until next resume. Set to -1 to disable one shot +commands.pause_when_empty.set.usage=/pause_when_empty set (0 to disable) +commands.pause_when_empty.oneshot.usage=/pause_when_empty oneshot (old value is applied on the next resume; 0 to disable pausing; -1 to disable the oneshot) + # Gui ## Buttons gui.accept=Accept diff --git a/src/mixins/java/serverutils/mixins/early/minecraft/MixinDedicatedServer_PauseWhenEmpty.java b/src/mixins/java/serverutils/mixins/early/minecraft/MixinDedicatedServer_PauseWhenEmpty.java new file mode 100644 index 00000000..20886553 --- /dev/null +++ b/src/mixins/java/serverutils/mixins/early/minecraft/MixinDedicatedServer_PauseWhenEmpty.java @@ -0,0 +1,54 @@ +package serverutils.mixins.early.minecraft; + +import net.minecraft.server.dedicated.DedicatedServer; +import net.minecraft.server.dedicated.PropertyManager; + +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Shadow; +import org.spongepowered.asm.mixin.Unique; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; + +import serverutils.data.IPauseWhenEmpty; + +@Mixin(DedicatedServer.class) +public class MixinDedicatedServer_PauseWhenEmpty implements IPauseWhenEmpty { + + @Shadow + private PropertyManager settings; + + @Unique + private int serverUtilities$pauseWhenEmptySeconds = 0; + + @Unique + private int serverUtilities$pauseWhenEmptySecondsOneShot = -1; + + @Override + public int serverUtilities$getPauseWhenEmptySeconds() { + return serverUtilities$pauseWhenEmptySecondsOneShot > -1 ? serverUtilities$pauseWhenEmptySecondsOneShot + : serverUtilities$pauseWhenEmptySeconds; + } + + @Override + public void serverUtilities$setPauseWhenEmptySeconds(int value, boolean oneshot) { + if (oneshot) { + serverUtilities$pauseWhenEmptySecondsOneShot = Math.max(value, -1); + } else { + serverUtilities$pauseWhenEmptySeconds = Math.max(value, 0); + settings.setProperty("pause-when-empty-seconds", serverUtilities$pauseWhenEmptySeconds); + settings.saveProperties(); + } + } + + @Inject( + method = "startServer", + at = @At( + value = "INVOKE", + target = "Lcpw/mods/fml/common/FMLCommonHandler;onServerStarted()V", + remap = false, + shift = At.Shift.AFTER)) + public void su$setupServer(CallbackInfoReturnable cir) { + serverUtilities$pauseWhenEmptySeconds = settings.getIntProperty("pause-when-empty-seconds", 0); + } +} diff --git a/src/mixins/java/serverutils/mixins/early/minecraft/MixinMinecraftServer_PauseWhenEmpty.java b/src/mixins/java/serverutils/mixins/early/minecraft/MixinMinecraftServer_PauseWhenEmpty.java new file mode 100644 index 00000000..e3695e09 --- /dev/null +++ b/src/mixins/java/serverutils/mixins/early/minecraft/MixinMinecraftServer_PauseWhenEmpty.java @@ -0,0 +1,80 @@ +package serverutils.mixins.early.minecraft; + +import net.minecraft.network.NetworkSystem; +import net.minecraft.server.MinecraftServer; +import net.minecraft.server.dedicated.DedicatedServer; +import net.minecraft.server.management.ServerConfigurationManager; + +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Shadow; +import org.spongepowered.asm.mixin.Unique; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; + +import serverutils.ServerUtilities; +import serverutils.data.IPauseWhenEmpty; + +@Mixin(MinecraftServer.class) +public abstract class MixinMinecraftServer_PauseWhenEmpty { + + @Shadow + public abstract int getCurrentPlayerCount(); + + @Shadow + private ServerConfigurationManager serverConfigManager; + + @Shadow + protected abstract void saveAllWorlds(boolean dontLog); + + @Shadow + public abstract NetworkSystem func_147137_ag(); + + @Shadow + public abstract int getTickCounter(); + + @Unique + private int serverUtilities$emptyTicks = 0; + @Unique + private boolean serverUtilities$wasPaused = false; + + @Inject(method = "tick", at = @At("HEAD"), cancellable = true, order = 9000) + public void serverUtilities$tick(CallbackInfo ci) { + if ((Object) this instanceof DedicatedServer ds && ds instanceof IPauseWhenEmpty p) { + int pauseTicks = p.serverUtilities$getPauseWhenEmptySeconds() * 20; + if (pauseTicks > 0) { + if (this.getCurrentPlayerCount() == 0) { + this.serverUtilities$emptyTicks++; + } else { + this.serverUtilities$emptyTicks = 0; + } + + if (serverUtilities$emptyTicks >= pauseTicks) { + if (!serverUtilities$wasPaused) { + ServerUtilities.LOGGER.info( + "Server empty for {} seconds, saving and pausing", + p.serverUtilities$getPauseWhenEmptySeconds()); + this.serverConfigManager.saveAllPlayerData(); + this.saveAllWorlds(true); + serverUtilities$wasPaused = true; + } + // to finish saving chunks + net.minecraftforge.common.chunkio.ChunkIOExecutor.tick(); + // to process new connections + this.func_147137_ag().networkTick(); + // to process console commands + ds.executePendingCommands(); + ci.cancel(); + return; + } + } + + if (serverUtilities$wasPaused) { + ServerUtilities.LOGGER.info("Resuming server"); + serverUtilities$wasPaused = false; + // reset the oneshot value to -1 + p.serverUtilities$setPauseWhenEmptySeconds(-1, true); + } + } + } +}