package com.mrkayjaydee.playhours.events; import com.mrkayjaydee.playhours.core.ScheduleService; import com.mrkayjaydee.playhours.config.*; import com.mrkayjaydee.playhours.text.Messages; import net.minecraft.server.level.ServerPlayer; import net.minecraftforge.event.TickEvent; import net.minecraftforge.eventbus.api.SubscribeEvent; import net.minecraftforge.fml.common.Mod; import java.time.ZonedDateTime; import java.util.*; /** * Handles periodic server tick processing for warnings and auto-kick at closing time. * Runs checks every second (20 ticks) to broadcast warnings and enforce closing times. */ @Mod.EventBusSubscriber public class TickScheduler { private static final int TICKS_PER_SECOND = 20; private static int tickCounter = 0; private static final Set sentCountdowns = new HashSet<>(); /** * Server tick event handler that checks for closing warnings and kicks players at closing time. */ @SubscribeEvent public static void onServerTick(TickEvent.ServerTickEvent event) { if (event.phase != TickEvent.Phase.END) return; tickCounter++; if (tickCounter % TICKS_PER_SECOND != 0) return; // once per second var server = net.minecraftforge.server.ServerLifecycleHooks.getCurrentServer(); if (server == null) return; ScheduleService sched = ScheduleService.get(); if (!ConfigEventHandler.isReady()) { // Config not ready yet; avoid accessing values com.mrkayjaydee.playhours.PlayHoursMod.LOGGER.debug("Tick skipped: config not ready"); return; } ZonedDateTime now = ZonedDateTime.now(sched.getZoneId()); boolean open = sched.isOpen(now); if (open) { Optional nextClose = sched.nextClose(now); if (nextClose.isPresent()) { // Broadcast warnings WarningBroadcaster.broadcastWarnings(server, sched, now, nextClose.get()); // Handle countdown messages handleCountdown(server, now, nextClose.get()); // Kick at close - use reliable time comparison if (!now.isBefore(nextClose.get())) { List players = new ArrayList<>(server.getPlayerList().getPlayers()); PlayerKickHandler.kickPlayersAtClose(players, sched, nextClose.get()); WarningBroadcaster.clearWarnings(); sentCountdowns.clear(); } } else { WarningBroadcaster.clearWarnings(); sentCountdowns.clear(); } } else { WarningBroadcaster.clearWarnings(); sentCountdowns.clear(); } } /** * Handles countdown messages before closing. * Sends messages every second for the configured number of seconds before closing. */ private static void handleCountdown(net.minecraft.server.MinecraftServer server, ZonedDateTime now, ZonedDateTime nextClose) { int countdownSeconds = GeneralConfig.COUNTDOWN_SECONDS.get(); if (countdownSeconds <= 0) return; long secondsUntilClose = java.time.Duration.between(now, nextClose).getSeconds(); if (secondsUntilClose <= countdownSeconds && secondsUntilClose > 0) { int seconds = (int) secondsUntilClose; // Only send if we haven't sent this countdown yet if (!sentCountdowns.contains(seconds)) { sentCountdowns.add(seconds); server.getPlayerList().broadcastSystemMessage(Messages.countdown(seconds), false); } } } }