feat(docs): complete PlayHours mod implementation with comprehensive documentation
- Add complete PlayHours mod source code with all features: * Schedule enforcement with per-day schedules and midnight-spanning support * Login control with configurable thresholds and exemptions * Warnings and auto-kick system with countdown functionality * Force modes (NORMAL/FORCE_OPEN/FORCE_CLOSED) for maintenance * Whitelist/blacklist system for player access control * Date exceptions for holidays and special events * Multi-language support (English/French) with smart time formatting * LuckPerms integration with vanilla ops fallback * Dynamic MOTD system with real-time schedule display * Comprehensive command system with permission integration * TOML configuration with hot-reload support - Add comprehensive documentation suite: * Installation guide with step-by-step setup instructions * Complete configuration reference with all options * Commands reference with usage examples * Features overview with detailed explanations * MOTD system configuration and customization guide * Permissions system documentation with LuckPerms integration * Technical details covering architecture and limitations * Usage examples with real-world scenarios * Changelog with version history - Add resource files: * Language files (en_us.json, fr_fr.json) with localized messages * Mod metadata (mods.toml) with proper Forge configuration * Resource pack metadata (pack.mcmeta) - Update build configuration: * Gradle build system with proper dependencies * Project properties and version management * Development environment setup - Restructure documentation: * Replace old README.txt with new comprehensive README.md * Create modular documentation structure in docs/ directory * Add cross-references and navigation between documents * Include quick start guide and common use cases This commit represents the complete v1.0.0 release of PlayHours, a production-ready server operation hours enforcement mod for Minecraft Forge 1.20.1.
This commit is contained in:
@@ -0,0 +1,93 @@
|
||||
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<Integer> 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<ZonedDateTime> 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<ServerPlayer> 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user