- 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.
219 lines
8.7 KiB
Java
219 lines
8.7 KiB
Java
package com.mrkayjaydee.playhours.events;
|
|
|
|
import com.mrkayjaydee.playhours.config.MOTDConfig;
|
|
import com.mrkayjaydee.playhours.core.ForceMode;
|
|
import com.mrkayjaydee.playhours.core.ScheduleService;
|
|
import com.mrkayjaydee.playhours.events.ScheduleFormatter.FormattedSchedule;
|
|
import com.mrkayjaydee.playhours.text.Messages;
|
|
import net.minecraft.ChatFormatting;
|
|
import net.minecraft.network.chat.Component;
|
|
import net.minecraft.network.chat.MutableComponent;
|
|
|
|
import java.time.ZonedDateTime;
|
|
import java.util.ArrayList;
|
|
import java.util.List;
|
|
import java.util.Optional;
|
|
|
|
/**
|
|
* Builds MOTD components based on configuration.
|
|
* Supports multiple MOTD strategies: rotating, custom lines, custom format, and automatic.
|
|
*/
|
|
public final class MOTDBuilder {
|
|
private MOTDBuilder() {}
|
|
|
|
private static int currentRotationIndex = 0;
|
|
private static long lastRotationTime = 0;
|
|
private static List<? extends String> lastRotationTemplates = null;
|
|
|
|
/**
|
|
* Builds the MOTD component based on configuration and current schedule state.
|
|
* Priority: rotation → custom lines → custom format → automatic
|
|
*
|
|
* @param scheduleService the schedule service
|
|
* @param now the current time
|
|
* @return the MOTD component
|
|
*/
|
|
public static Component build(ScheduleService scheduleService, ZonedDateTime now) {
|
|
// Check for rotation first
|
|
if (MOTDConfig.ROTATION_ENABLED.get()) {
|
|
List<? extends String> templates = MOTDConfig.ROTATION_TEMPLATES.get();
|
|
if (templates != null && !templates.isEmpty()) {
|
|
return buildRotating(scheduleService, now, templates);
|
|
}
|
|
}
|
|
|
|
// Check for custom lines
|
|
List<? extends String> customLines = MOTDConfig.CUSTOM_LINES.get();
|
|
if (customLines != null && !customLines.isEmpty()) {
|
|
return buildCustomLines(scheduleService, now, customLines);
|
|
}
|
|
|
|
// Check for custom format
|
|
String customFormat = MOTDConfig.CUSTOM_FORMAT.get();
|
|
if (customFormat != null && !customFormat.trim().isEmpty()) {
|
|
return buildCustomFormat(scheduleService, now, customFormat);
|
|
}
|
|
|
|
// Build automatic MOTD
|
|
return buildAutomatic(scheduleService, now);
|
|
}
|
|
|
|
/**
|
|
* Builds a rotating MOTD that cycles through configured templates.
|
|
*/
|
|
private static Component buildRotating(ScheduleService scheduleService, ZonedDateTime now, List<? extends String> templates) {
|
|
if (templates.isEmpty()) {
|
|
return buildCustomLines(scheduleService, now, MOTDConfig.CUSTOM_LINES.get());
|
|
}
|
|
|
|
// Check if template list changed
|
|
if (lastRotationTemplates != templates) {
|
|
lastRotationTemplates = templates;
|
|
currentRotationIndex = 0;
|
|
lastRotationTime = System.currentTimeMillis();
|
|
}
|
|
|
|
// Calculate time elapsed since last rotation
|
|
long currentTime = System.currentTimeMillis();
|
|
long rotationIntervalMillis = MOTDConfig.ROTATION_INTERVAL_SECONDS.get() * 1000L;
|
|
long timeSinceLastRotation = currentTime - lastRotationTime;
|
|
|
|
// Check if we should advance to next template
|
|
if (timeSinceLastRotation >= rotationIntervalMillis) {
|
|
// Move to next template
|
|
currentRotationIndex = (currentRotationIndex + 1) % templates.size();
|
|
lastRotationTime = currentTime;
|
|
}
|
|
|
|
String template = templates.get(currentRotationIndex);
|
|
return MOTDFormatter.formatLine(scheduleService, now, template);
|
|
}
|
|
|
|
/**
|
|
* Builds MOTD from custom line configuration.
|
|
*/
|
|
private static Component buildCustomLines(ScheduleService scheduleService, ZonedDateTime now, List<? extends String> customLines) {
|
|
MutableComponent result = Component.empty();
|
|
|
|
for (int i = 0; i < customLines.size(); i++) {
|
|
String line = customLines.get(i);
|
|
Component formatted = MOTDFormatter.formatLine(scheduleService, now, line);
|
|
result.append(formatted);
|
|
|
|
if (i < customLines.size() - 1) {
|
|
result.append(Component.literal("\n"));
|
|
}
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
/**
|
|
* Builds MOTD from custom format string.
|
|
*/
|
|
private static Component buildCustomFormat(ScheduleService scheduleService, ZonedDateTime now, String format) {
|
|
return MOTDFormatter.formatLine(scheduleService, now, format);
|
|
}
|
|
|
|
/**
|
|
* Builds automatic MOTD based on configuration flags.
|
|
*/
|
|
private static Component buildAutomatic(ScheduleService scheduleService, ZonedDateTime now) {
|
|
List<Component> parts = new ArrayList<>();
|
|
|
|
// Get schedule information
|
|
boolean isOpen = scheduleService.isOpen(now);
|
|
ForceMode forceMode = scheduleService.getForceMode();
|
|
Optional<ZonedDateTime> nextClose = scheduleService.nextClose(now);
|
|
FormattedSchedule nextOpen = ScheduleFormatter.formatNextOpen(scheduleService.nextOpen(now));
|
|
|
|
// Show force mode if enabled
|
|
if (MOTDConfig.SHOW_FORCE_MODE.get() && forceMode != ForceMode.NORMAL) {
|
|
String forceModeText = forceMode == ForceMode.FORCE_OPEN
|
|
? Messages.get("msg.motd_force_open")
|
|
: Messages.get("msg.motd_force_closed");
|
|
parts.add(MOTDFormatter.colorize(forceModeText, ChatFormatting.GOLD));
|
|
}
|
|
|
|
// Show status if enabled
|
|
if (MOTDConfig.SHOW_STATUS.get()) {
|
|
String statusKey = isOpen ? "msg.motd_status_open" : "msg.motd_status_closed";
|
|
String statusText = Messages.get(statusKey);
|
|
ChatFormatting statusColor = isOpen
|
|
? MOTDColorParser.parseColor(MOTDConfig.OPEN_COLOR.get())
|
|
: MOTDColorParser.parseColor(MOTDConfig.CLOSED_COLOR.get());
|
|
parts.add(MOTDFormatter.colorize(statusText, statusColor));
|
|
}
|
|
|
|
// Show countdown if enabled and applicable
|
|
if (MOTDConfig.SHOW_COUNTDOWN.get() && isOpen && nextClose.isPresent()) {
|
|
addCountdownIfApplicable(parts, nextClose.get(), now);
|
|
}
|
|
|
|
// Show next close if enabled and open
|
|
if (MOTDConfig.SHOW_NEXT_CLOSE.get() && isOpen && nextClose.isPresent()) {
|
|
addNextClose(parts, nextClose.get());
|
|
}
|
|
|
|
// Show next open if enabled and closed
|
|
if (MOTDConfig.SHOW_NEXT_OPEN.get() && !isOpen) {
|
|
addNextOpen(parts, nextOpen);
|
|
}
|
|
|
|
// Combine parts
|
|
if (parts.isEmpty()) {
|
|
return Component.literal("");
|
|
}
|
|
|
|
return combineParts(parts);
|
|
}
|
|
|
|
private static void addCountdownIfApplicable(List<Component> parts, ZonedDateTime nextClose, ZonedDateTime now) {
|
|
long minutesUntilClose = java.time.temporal.ChronoUnit.MINUTES.between(now, nextClose);
|
|
int countdownThreshold = MOTDConfig.COUNTDOWN_THRESHOLD_MINUTES.get();
|
|
|
|
if (countdownThreshold > 0 && minutesUntilClose <= countdownThreshold && minutesUntilClose > 0) {
|
|
String countdownText = Messages.get("msg.motd_countdown")
|
|
.replace("%minutes%", String.valueOf(minutesUntilClose));
|
|
parts.add(MOTDFormatter.colorize(countdownText, ChatFormatting.YELLOW));
|
|
}
|
|
}
|
|
|
|
private static void addNextClose(List<Component> parts, ZonedDateTime nextClose) {
|
|
String closeTime = com.mrkayjaydee.playhours.core.TimeRange.formatTime(nextClose.toLocalTime(), Messages.getJavaLocale());
|
|
String closeText = Messages.get("msg.motd_next_close")
|
|
.replace("%closetime%", closeTime);
|
|
ChatFormatting infoColor = MOTDColorParser.parseColor(MOTDConfig.INFO_COLOR.get());
|
|
parts.add(MOTDFormatter.colorize(closeText, infoColor));
|
|
}
|
|
|
|
private static void addNextOpen(List<Component> parts, FormattedSchedule nextOpen) {
|
|
String openText = Messages.get("msg.motd_next_open")
|
|
.replace("%openday%", nextOpen.day)
|
|
.replace("%opentime%", nextOpen.time);
|
|
ChatFormatting infoColor = MOTDColorParser.parseColor(MOTDConfig.INFO_COLOR.get());
|
|
parts.add(MOTDFormatter.colorize(openText, infoColor));
|
|
}
|
|
|
|
private static Component combineParts(List<Component> parts) {
|
|
String separator = MOTDConfig.SEPARATOR.get();
|
|
boolean useSecondLine = MOTDConfig.SHOW_ON_SECOND_LINE.get();
|
|
|
|
MutableComponent result = Component.empty();
|
|
|
|
if (useSecondLine) {
|
|
// Put on second line
|
|
result.append(Component.literal("\n"));
|
|
}
|
|
|
|
for (int i = 0; i < parts.size(); i++) {
|
|
result.append(parts.get(i));
|
|
if (i < parts.size() - 1) {
|
|
result.append(Component.literal(separator));
|
|
}
|
|
}
|
|
|
|
return result;
|
|
}
|
|
}
|