Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Migrate Server Pausing From Hodgepodge #175

Merged
merged 1 commit into from
Jan 13, 2025
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions src/main/java/serverutils/ServerUtilitiesConfig.java
Original file line number Diff line number Diff line change
@@ -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 {
Original file line number Diff line number Diff line change
@@ -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) {
Original file line number Diff line number Diff line change
@@ -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());
}
}
Original file line number Diff line number Diff line change
@@ -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));
}
}
}
Original file line number Diff line number Diff line change
@@ -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));
}
}
}
8 changes: 8 additions & 0 deletions src/main/java/serverutils/data/IPauseWhenEmpty.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package serverutils.data;

public interface IPauseWhenEmpty {

int serverUtilities$getPauseWhenEmptySeconds();

void serverUtilities$setPauseWhenEmptySeconds(int value, boolean oneshot);
}
11 changes: 8 additions & 3 deletions src/main/java/serverutils/mixin/Mixins.java
Original file line number Diff line number Diff line change
@@ -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<String> mixinClasses;
private final Supplier<Boolean> applyIf;
6 changes: 6 additions & 0 deletions src/main/resources/assets/serverutilities/lang/en_US.lang
Original file line number Diff line number Diff line change
@@ -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 <seconds> (0 to disable)
commands.pause_when_empty.oneshot.usage=/pause_when_empty oneshot <seconds> (old value is applied on the next resume; 0 to disable pausing; -1 to disable the oneshot)

# Gui
## Buttons
gui.accept=Accept
Original file line number Diff line number Diff line change
@@ -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<Boolean> cir) {
serverUtilities$pauseWhenEmptySeconds = settings.getIntProperty("pause-when-empty-seconds", 0);
}
}
Original file line number Diff line number Diff line change
@@ -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);
}
}
}
}