Add automatic restart feature
This commit is contained in:
parent
8e7244b0b8
commit
6b99f27f89
9 changed files with 280 additions and 20 deletions
|
@ -1,6 +1,7 @@
|
||||||
package cc.reconnected.server;
|
package cc.reconnected.server;
|
||||||
|
|
||||||
import cc.reconnected.server.api.events.RccEvents;
|
import cc.reconnected.server.api.events.RccEvents;
|
||||||
|
import cc.reconnected.server.commands.admin.*;
|
||||||
import cc.reconnected.server.commands.home.*;
|
import cc.reconnected.server.commands.home.*;
|
||||||
import cc.reconnected.server.commands.misc.*;
|
import cc.reconnected.server.commands.misc.*;
|
||||||
import cc.reconnected.server.commands.spawn.*;
|
import cc.reconnected.server.commands.spawn.*;
|
||||||
|
@ -101,6 +102,7 @@ public class RccServer implements ModInitializer {
|
||||||
DeleteWarpCommand.register(dispatcher, registryAccess, environment);
|
DeleteWarpCommand.register(dispatcher, registryAccess, environment);
|
||||||
|
|
||||||
TimeBarCommand.register(dispatcher, registryAccess, environment);
|
TimeBarCommand.register(dispatcher, registryAccess, environment);
|
||||||
|
RestartCommand.register(dispatcher, registryAccess, environment);
|
||||||
|
|
||||||
NearCommand.register(dispatcher, registryAccess, environment);
|
NearCommand.register(dispatcher, registryAccess, environment);
|
||||||
});
|
});
|
||||||
|
@ -111,6 +113,7 @@ public class RccServer implements ModInitializer {
|
||||||
TabList.register();
|
TabList.register();
|
||||||
HttpApiServer.register();
|
HttpApiServer.register();
|
||||||
BossBarManager.register();
|
BossBarManager.register();
|
||||||
|
AutoRestart.register();
|
||||||
|
|
||||||
ServerLifecycleEvents.SERVER_STARTED.register(server -> {
|
ServerLifecycleEvents.SERVER_STARTED.register(server -> {
|
||||||
luckPerms = LuckPermsProvider.get();
|
luckPerms = LuckPermsProvider.get();
|
||||||
|
|
|
@ -17,7 +17,7 @@ public class RccServerConfigModel {
|
||||||
public String afkTag = "<gray>[AFK]</gray> ";
|
public String afkTag = "<gray>[AFK]</gray> ";
|
||||||
|
|
||||||
public String tellMessage = "<gold>[</gold><source> <gray>→</gray> <target><gold>]</gold> <message>";
|
public String tellMessage = "<gold>[</gold><source> <gray>→</gray> <target><gold>]</gold> <message>";
|
||||||
public String tellMessageSpy = "\uD83D\uDC41 <gray>[<source> → <target>]</gray> <message>";
|
public String tellMessageSpy = "\uD83D\uDC41 <gray>[<source> → <target>] <message></gray>";
|
||||||
|
|
||||||
public int teleportRequestTimeout = 120;
|
public int teleportRequestTimeout = 120;
|
||||||
|
|
||||||
|
@ -38,8 +38,30 @@ public class RccServerConfigModel {
|
||||||
public int nearCommandDefaultRange = 32;
|
public int nearCommandDefaultRange = 32;
|
||||||
|
|
||||||
public boolean enableAutoRestart = true;
|
public boolean enableAutoRestart = true;
|
||||||
|
public String restartBarLabel = "Server restarting in <remaining_time>";
|
||||||
|
public String restartKickMessage = "The server is restarting!";
|
||||||
|
public String restartChatMessage = "<red>The server is restarting in </red><gold><remaining_time></gold>";
|
||||||
|
|
||||||
public ArrayList<String> restartAt = new ArrayList<>(List.of(
|
public ArrayList<String> restartAt = new ArrayList<>(List.of(
|
||||||
"06:00",
|
"06:00",
|
||||||
"18:00"
|
"18:00"
|
||||||
));
|
));
|
||||||
|
|
||||||
|
public String restartSound = "minecraft:block.note_block.bell";
|
||||||
|
public float restartSoundPitch = 0.9f;
|
||||||
|
|
||||||
|
public ArrayList<Integer> restartNotifications = new ArrayList<>(List.of(
|
||||||
|
600,
|
||||||
|
300,
|
||||||
|
120,
|
||||||
|
60,
|
||||||
|
30,
|
||||||
|
15,
|
||||||
|
10,
|
||||||
|
5,
|
||||||
|
4,
|
||||||
|
3,
|
||||||
|
2,
|
||||||
|
1
|
||||||
|
));
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
package cc.reconnected.server.commands.misc;
|
package cc.reconnected.server.commands.admin;
|
||||||
|
|
||||||
import com.mojang.brigadier.CommandDispatcher;
|
import com.mojang.brigadier.CommandDispatcher;
|
||||||
import com.mojang.brigadier.arguments.StringArgumentType;
|
import com.mojang.brigadier.arguments.StringArgumentType;
|
|
@ -1,4 +1,4 @@
|
||||||
package cc.reconnected.server.commands.misc;
|
package cc.reconnected.server.commands.admin;
|
||||||
|
|
||||||
import com.mojang.brigadier.CommandDispatcher;
|
import com.mojang.brigadier.CommandDispatcher;
|
||||||
import com.mojang.brigadier.arguments.StringArgumentType;
|
import com.mojang.brigadier.arguments.StringArgumentType;
|
|
@ -1,4 +1,4 @@
|
||||||
package cc.reconnected.server.commands.misc;
|
package cc.reconnected.server.commands.admin;
|
||||||
|
|
||||||
import cc.reconnected.server.RccServer;
|
import cc.reconnected.server.RccServer;
|
||||||
import cc.reconnected.server.api.events.RccEvents;
|
import cc.reconnected.server.api.events.RccEvents;
|
|
@ -0,0 +1,76 @@
|
||||||
|
package cc.reconnected.server.commands.admin;
|
||||||
|
|
||||||
|
import cc.reconnected.server.RccServer;
|
||||||
|
import cc.reconnected.server.api.events.RccEvents;
|
||||||
|
import cc.reconnected.server.core.AutoRestart;
|
||||||
|
import com.mojang.brigadier.CommandDispatcher;
|
||||||
|
import com.mojang.brigadier.arguments.IntegerArgumentType;
|
||||||
|
import com.mojang.brigadier.arguments.StringArgumentType;
|
||||||
|
import com.mojang.brigadier.context.CommandContext;
|
||||||
|
import me.lucko.fabric.api.permissions.v0.Permissions;
|
||||||
|
import net.minecraft.command.CommandRegistryAccess;
|
||||||
|
import net.minecraft.server.command.CommandManager;
|
||||||
|
import net.minecraft.server.command.ServerCommandSource;
|
||||||
|
import net.minecraft.text.Text;
|
||||||
|
import net.minecraft.util.Formatting;
|
||||||
|
import org.jetbrains.annotations.Nullable;
|
||||||
|
|
||||||
|
import static net.minecraft.server.command.CommandManager.*;
|
||||||
|
|
||||||
|
public class RestartCommand {
|
||||||
|
public static void register(CommandDispatcher<ServerCommandSource> dispatcher, CommandRegistryAccess registryAccess, CommandManager.RegistrationEnvironment environment) {
|
||||||
|
var rootCommand = literal("restart")
|
||||||
|
.requires(Permissions.require("rcc.command.restart", 4))
|
||||||
|
.then(literal("schedule")
|
||||||
|
.then(argument("seconds", IntegerArgumentType.integer(0))
|
||||||
|
.executes(context -> schedule(context, IntegerArgumentType.getInteger(context, "seconds"), null))
|
||||||
|
.then(argument("message", StringArgumentType.greedyString())
|
||||||
|
.executes(context -> schedule(context, IntegerArgumentType.getInteger(context, "seconds"), StringArgumentType.getString(context, "message")))))
|
||||||
|
.then(literal("next")
|
||||||
|
.executes(RestartCommand::scheduleNext))
|
||||||
|
)
|
||||||
|
.then(literal("cancel")
|
||||||
|
.executes(RestartCommand::cancel));
|
||||||
|
|
||||||
|
dispatcher.register(rootCommand);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static int schedule(CommandContext<ServerCommandSource> context, int seconds, @Nullable String message) {
|
||||||
|
if (message == null) {
|
||||||
|
message = RccServer.CONFIG.restartBarLabel();
|
||||||
|
}
|
||||||
|
AutoRestart.schedule(seconds, message);
|
||||||
|
|
||||||
|
context.getSource().sendFeedback(() -> Text.of("Manual restart scheduled in " + seconds + " seconds."), true);
|
||||||
|
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static int scheduleNext(CommandContext<ServerCommandSource> context) {
|
||||||
|
if (AutoRestart.isScheduled()) {
|
||||||
|
context.getSource().sendFeedback(() -> Text.literal("There is already a scheduled restart.").formatted(Formatting.RED), false);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
var delay = AutoRestart.scheduleNextRestart();
|
||||||
|
|
||||||
|
if (delay == null) {
|
||||||
|
context.getSource().sendFeedback(() -> Text.literal("Could not schedule next automatic restart.").formatted(Formatting.RED), false);
|
||||||
|
} else {
|
||||||
|
context.getSource().sendFeedback(() -> Text.literal("Next automatic restart scheduled in " + delay + " seconds."), true);
|
||||||
|
}
|
||||||
|
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static int cancel(CommandContext<ServerCommandSource> context) {
|
||||||
|
if (!AutoRestart.isScheduled()) {
|
||||||
|
context.getSource().sendFeedback(() -> Text.literal("There is no scheduled restart.").formatted(Formatting.RED), false);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
AutoRestart.cancel();
|
||||||
|
context.getSource().sendFeedback(() -> Text.literal("Restart schedule canceled."), true);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,4 +1,4 @@
|
||||||
package cc.reconnected.server.commands.misc;
|
package cc.reconnected.server.commands.admin;
|
||||||
|
|
||||||
import cc.reconnected.server.api.events.BossBarEvents;
|
import cc.reconnected.server.api.events.BossBarEvents;
|
||||||
import cc.reconnected.server.core.BossBarManager;
|
import cc.reconnected.server.core.BossBarManager;
|
|
@ -1,17 +1,166 @@
|
||||||
package cc.reconnected.server.core;
|
package cc.reconnected.server.core;
|
||||||
|
|
||||||
import net.fabricmc.fabric.api.event.lifecycle.v1.ServerLifecycleEvents;
|
import cc.reconnected.server.RccServer;
|
||||||
|
import cc.reconnected.server.api.events.BossBarEvents;
|
||||||
|
import cc.reconnected.server.api.events.RccEvents;
|
||||||
|
import cc.reconnected.server.util.Components;
|
||||||
|
import net.kyori.adventure.key.InvalidKeyException;
|
||||||
|
import net.kyori.adventure.key.Key;
|
||||||
|
import net.kyori.adventure.sound.Sound;
|
||||||
|
import net.kyori.adventure.text.minimessage.MiniMessage;
|
||||||
|
import net.minecraft.entity.boss.BossBar;
|
||||||
import net.minecraft.server.MinecraftServer;
|
import net.minecraft.server.MinecraftServer;
|
||||||
|
import org.jetbrains.annotations.Nullable;
|
||||||
|
|
||||||
|
import java.time.Duration;
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
import java.time.LocalTime;
|
||||||
|
import java.util.concurrent.Executors;
|
||||||
|
import java.util.concurrent.ScheduledExecutorService;
|
||||||
|
import java.util.concurrent.ScheduledFuture;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
public class AutoRestart {
|
public class AutoRestart {
|
||||||
private static MinecraftServer server;
|
private static final ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(1);
|
||||||
|
private static BossBarManager.TimeBar restartBar = null;
|
||||||
|
private static Key notificationKey;
|
||||||
|
private static ScheduledFuture<?> currentSchedule = null;
|
||||||
|
|
||||||
|
|
||||||
public static void register() {
|
public static void register() {
|
||||||
ServerLifecycleEvents.SERVER_STARTING.register(s -> server = s);
|
|
||||||
|
|
||||||
|
var miniMessage = MiniMessage.miniMessage();
|
||||||
|
|
||||||
|
RccEvents.READY.register((server, luckPerms) -> {
|
||||||
|
if (RccServer.CONFIG.enableAutoRestart()) {
|
||||||
|
scheduleNextRestart();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
BossBarEvents.PROGRESS.register((timeBar, server) -> {
|
||||||
|
if (restartBar == null || !timeBar.getUuid().equals(restartBar.getUuid()))
|
||||||
|
return;
|
||||||
|
|
||||||
|
var notificationTimes = RccServer.CONFIG.restartNotifications();
|
||||||
|
|
||||||
|
var remainingSeconds = restartBar.getRemainingSeconds();
|
||||||
|
if (notificationTimes.contains(remainingSeconds)) {
|
||||||
|
notifyRestart(server, restartBar);
|
||||||
|
}
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
// Shutdown
|
||||||
|
BossBarEvents.END.register((timeBar, server) -> {
|
||||||
|
if (restartBar == null || !timeBar.getUuid().equals(restartBar.getUuid()))
|
||||||
|
return;
|
||||||
|
|
||||||
|
final var text = Components.toText(
|
||||||
|
miniMessage.deserialize(RccServer.CONFIG.restartKickMessage())
|
||||||
|
);
|
||||||
|
server.getPlayerManager().getPlayerList().forEach(player -> {
|
||||||
|
player.networkHandler.disconnect(text);
|
||||||
|
});
|
||||||
|
scheduler.shutdownNow();
|
||||||
|
server.stop(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
setup();
|
||||||
|
|
||||||
|
RccEvents.RELOAD.register(instance -> {
|
||||||
|
setup();
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void schedule() {
|
private static void setup() {
|
||||||
|
var soundName = RccServer.CONFIG.restartSound();
|
||||||
|
try {
|
||||||
|
notificationKey = Key.key(soundName);
|
||||||
|
} catch (InvalidKeyException e) {
|
||||||
|
RccServer.LOGGER.error("Invalid restart notification sound name", e);
|
||||||
|
notificationKey = Key.key("minecraft", "block.note_block.bell");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void schedule(int seconds, String message) {
|
||||||
|
restartBar = BossBarManager.getInstance().startTimeBar(
|
||||||
|
message,
|
||||||
|
seconds,
|
||||||
|
BossBar.Color.RED,
|
||||||
|
BossBar.Style.NOTCHED_20,
|
||||||
|
true
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean isScheduled() {
|
||||||
|
return restartBar != null || currentSchedule != null && !currentSchedule.isCancelled();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void cancel() {
|
||||||
|
if (restartBar != null) {
|
||||||
|
BossBarManager.getInstance().cancelTimeBar(restartBar);
|
||||||
|
restartBar = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(currentSchedule != null) {
|
||||||
|
currentSchedule.cancel(false);
|
||||||
|
currentSchedule = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void notifyRestart(MinecraftServer server, BossBarManager.TimeBar bar) {
|
||||||
|
var rcc = RccServer.getInstance();
|
||||||
|
var audience = rcc.adventure().players();
|
||||||
|
var sound = Sound.sound(notificationKey, Sound.Source.MASTER, 10f, RccServer.CONFIG.restartSoundPitch());
|
||||||
|
audience.playSound(sound, Sound.Emitter.self());
|
||||||
|
|
||||||
|
var comp = bar.parseLabel(RccServer.CONFIG.restartChatMessage());
|
||||||
|
rcc.broadcastMessage(server, comp);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
public static Long scheduleNextRestart() {
|
||||||
|
var delay = getNextDelay();
|
||||||
|
if (delay == null)
|
||||||
|
return null;
|
||||||
|
|
||||||
|
var barTime = 10 * 60;
|
||||||
|
// start bar 10 mins earlier
|
||||||
|
var barStartTime = delay - barTime;
|
||||||
|
|
||||||
|
currentSchedule = scheduler.schedule(() -> {
|
||||||
|
schedule(barTime, RccServer.CONFIG.restartBarLabel());
|
||||||
|
}, barStartTime, TimeUnit.SECONDS);
|
||||||
|
|
||||||
|
RccServer.LOGGER.info("Restart scheduled for in {} seconds", delay);
|
||||||
|
return delay;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
private static Long getNextDelay() {
|
||||||
|
var restartTimeStrings = RccServer.CONFIG.restartAt();
|
||||||
|
LocalDateTime now = LocalDateTime.now();
|
||||||
|
LocalDateTime nextRunTime = null;
|
||||||
|
long shortestDelay = Long.MAX_VALUE;
|
||||||
|
|
||||||
|
for (var timeString : restartTimeStrings) {
|
||||||
|
LocalTime targetTime = LocalTime.parse(timeString);
|
||||||
|
LocalDateTime targetDateTime = now.with(targetTime);
|
||||||
|
|
||||||
|
if (targetDateTime.isBefore(now)) {
|
||||||
|
targetDateTime = targetDateTime.plusDays(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
long delay = Duration.between(now, targetDateTime).toSeconds();
|
||||||
|
if (delay < shortestDelay) {
|
||||||
|
shortestDelay = delay;
|
||||||
|
nextRunTime = targetDateTime;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (nextRunTime != null) {
|
||||||
|
return shortestDelay;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,6 +5,7 @@ import cc.reconnected.server.api.events.BossBarEvents;
|
||||||
import cc.reconnected.server.util.Components;
|
import cc.reconnected.server.util.Components;
|
||||||
import net.fabricmc.fabric.api.event.lifecycle.v1.ServerLifecycleEvents;
|
import net.fabricmc.fabric.api.event.lifecycle.v1.ServerLifecycleEvents;
|
||||||
import net.fabricmc.fabric.api.event.lifecycle.v1.ServerTickEvents;
|
import net.fabricmc.fabric.api.event.lifecycle.v1.ServerTickEvents;
|
||||||
|
import net.kyori.adventure.text.Component;
|
||||||
import net.kyori.adventure.text.minimessage.MiniMessage;
|
import net.kyori.adventure.text.minimessage.MiniMessage;
|
||||||
import net.kyori.adventure.text.minimessage.tag.resolver.Placeholder;
|
import net.kyori.adventure.text.minimessage.tag.resolver.Placeholder;
|
||||||
import net.kyori.adventure.text.minimessage.tag.resolver.TagResolver;
|
import net.kyori.adventure.text.minimessage.tag.resolver.TagResolver;
|
||||||
|
@ -67,16 +68,17 @@ public class BossBarManager {
|
||||||
}
|
}
|
||||||
|
|
||||||
public TimeBar startTimeBar(String label, int seconds, BossBar.Color color, BossBar.Style style, boolean countdown) {
|
public TimeBar startTimeBar(String label, int seconds, BossBar.Color color, BossBar.Style style, boolean countdown) {
|
||||||
var countdownBar = new TimeBar(label, seconds, countdown, color, style);
|
var timeBar = new TimeBar(label, seconds, countdown, color, style);
|
||||||
|
|
||||||
timeBars.add(countdownBar);
|
timeBars.add(timeBar);
|
||||||
|
|
||||||
var players = server.getPlayerManager().getPlayerList();
|
var players = server.getPlayerManager().getPlayerList();
|
||||||
showBar(players, countdownBar);
|
showBar(players, timeBar);
|
||||||
|
|
||||||
BossBarEvents.START.invoker().onStart(countdownBar, server);
|
BossBarEvents.START.invoker().onStart(timeBar, server);
|
||||||
|
BossBarEvents.PROGRESS.invoker().onProgress(timeBar, server);
|
||||||
|
|
||||||
return countdownBar;
|
return timeBar;
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean cancelTimeBar(TimeBar timeBar) {
|
public boolean cancelTimeBar(TimeBar timeBar) {
|
||||||
|
@ -128,18 +130,22 @@ public class BossBarManager {
|
||||||
}
|
}
|
||||||
|
|
||||||
public void updateName() {
|
public void updateName() {
|
||||||
|
var text = parseLabel(label);
|
||||||
|
bossBar.setName(Components.toText(text));
|
||||||
|
}
|
||||||
|
|
||||||
|
public Component parseLabel(String labelString) {
|
||||||
var totalTime = formatTime(this.time);
|
var totalTime = formatTime(this.time);
|
||||||
var elapsedTime = formatTime(this.elapsedSeconds);
|
var elapsedTime = formatTime(this.elapsedSeconds);
|
||||||
|
|
||||||
var remaining = time - elapsedSeconds;
|
var remaining = getRemainingSeconds();
|
||||||
var remainingTime = formatTime(remaining);
|
var remainingTime = formatTime(remaining);
|
||||||
var text = miniMessage.deserialize(label, TagResolver.resolver(
|
|
||||||
|
return miniMessage.deserialize(labelString, TagResolver.resolver(
|
||||||
Placeholder.parsed("total_time", totalTime),
|
Placeholder.parsed("total_time", totalTime),
|
||||||
Placeholder.parsed("elapsed_time", elapsedTime),
|
Placeholder.parsed("elapsed_time", elapsedTime),
|
||||||
Placeholder.parsed("remaining_time", remainingTime)
|
Placeholder.parsed("remaining_time", remainingTime)
|
||||||
));
|
));
|
||||||
|
|
||||||
bossBar.setName(Components.toText(text));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public UUID getUuid() {
|
public UUID getUuid() {
|
||||||
|
@ -162,6 +168,10 @@ public class BossBarManager {
|
||||||
return elapsedSeconds;
|
return elapsedSeconds;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public int getRemainingSeconds() {
|
||||||
|
return time - elapsedSeconds;
|
||||||
|
}
|
||||||
|
|
||||||
public boolean isCountdown() {
|
public boolean isCountdown() {
|
||||||
return countdown;
|
return countdown;
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue