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;
|
||||
|
||||
import cc.reconnected.server.api.events.RccEvents;
|
||||
import cc.reconnected.server.commands.admin.*;
|
||||
import cc.reconnected.server.commands.home.*;
|
||||
import cc.reconnected.server.commands.misc.*;
|
||||
import cc.reconnected.server.commands.spawn.*;
|
||||
|
@ -101,6 +102,7 @@ public class RccServer implements ModInitializer {
|
|||
DeleteWarpCommand.register(dispatcher, registryAccess, environment);
|
||||
|
||||
TimeBarCommand.register(dispatcher, registryAccess, environment);
|
||||
RestartCommand.register(dispatcher, registryAccess, environment);
|
||||
|
||||
NearCommand.register(dispatcher, registryAccess, environment);
|
||||
});
|
||||
|
@ -111,6 +113,7 @@ public class RccServer implements ModInitializer {
|
|||
TabList.register();
|
||||
HttpApiServer.register();
|
||||
BossBarManager.register();
|
||||
AutoRestart.register();
|
||||
|
||||
ServerLifecycleEvents.SERVER_STARTED.register(server -> {
|
||||
luckPerms = LuckPermsProvider.get();
|
||||
|
|
|
@ -17,7 +17,7 @@ public class RccServerConfigModel {
|
|||
public String afkTag = "<gray>[AFK]</gray> ";
|
||||
|
||||
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;
|
||||
|
||||
|
@ -38,8 +38,30 @@ public class RccServerConfigModel {
|
|||
public int nearCommandDefaultRange = 32;
|
||||
|
||||
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(
|
||||
"06: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.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.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.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.core.BossBarManager;
|
|
@ -1,17 +1,166 @@
|
|||
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 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 {
|
||||
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() {
|
||||
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 net.fabricmc.fabric.api.event.lifecycle.v1.ServerLifecycleEvents;
|
||||
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.tag.resolver.Placeholder;
|
||||
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) {
|
||||
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();
|
||||
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) {
|
||||
|
@ -128,18 +130,22 @@ public class BossBarManager {
|
|||
}
|
||||
|
||||
public void updateName() {
|
||||
var text = parseLabel(label);
|
||||
bossBar.setName(Components.toText(text));
|
||||
}
|
||||
|
||||
public Component parseLabel(String labelString) {
|
||||
var totalTime = formatTime(this.time);
|
||||
var elapsedTime = formatTime(this.elapsedSeconds);
|
||||
|
||||
var remaining = time - elapsedSeconds;
|
||||
var remaining = getRemainingSeconds();
|
||||
var remainingTime = formatTime(remaining);
|
||||
var text = miniMessage.deserialize(label, TagResolver.resolver(
|
||||
|
||||
return miniMessage.deserialize(labelString, TagResolver.resolver(
|
||||
Placeholder.parsed("total_time", totalTime),
|
||||
Placeholder.parsed("elapsed_time", elapsedTime),
|
||||
Placeholder.parsed("remaining_time", remainingTime)
|
||||
));
|
||||
|
||||
bossBar.setName(Components.toText(text));
|
||||
}
|
||||
|
||||
public UUID getUuid() {
|
||||
|
@ -162,6 +168,10 @@ public class BossBarManager {
|
|||
return elapsedSeconds;
|
||||
}
|
||||
|
||||
public int getRemainingSeconds() {
|
||||
return time - elapsedSeconds;
|
||||
}
|
||||
|
||||
public boolean isCountdown() {
|
||||
return countdown;
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue