Files
PlayHours/src/main/java/com/mrkayjaydee/playhours/events/MOTDBuilder.java
Mr¤KayJayDee c0fd2a2787 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.
2025-10-23 23:28:20 +02:00

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;
}
}