Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 28 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,34 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

### Added

**Admin GUI: Runtime Config Editor ([#40](https://github.com/HyperSystems-Development/HyperFactions/issues/40))**
- 11-tab config editor covering all HyperFactions settings: Server, Chat, Announcements, Economy, Factions, Faction Perms, Worldmap, Worlds, Backup, Debug, Gravestones
- Size-adaptive layouts: narrow (520px, 1-col), standard (780px, 2-col), wide (1020px, 4-col) — template switches automatically per tab
- Inline editing with boolean toggles, integer/double steppers, text fields, color pickers, enum dropdowns, and locale selectors
- Faction permissions editor with parent/child toggling (disabling parent auto-disables children), Default/Lock checkboxes per flag
- World overrides editor with add/remove worlds and tri-state per-world settings (Default/Allow/Deny)
- Upkeep scaling tiers modal with add/remove/reorder tiers, promote/demote disable on first/last, and live cost example
- Edit session caching — pending changes survive page close/reopen and modal round-trips
- Input validation with per-field error highlighting, debounced text updates, and save-blocked-on-invalid state
- Per-tab label fixes: "Requires Faction", "Show to Factionless", shortened section names
- ConfigSnapshot for applying changes, ConfigValidator for input bounds, ConfigV7→V8 migration

**Admin GUI: Backup Manager ([#41](https://github.com/HyperSystems-Development/HyperFactions/issues/41))**
- Paginated backup list with expand/collapse detail view per entry
- Create manual backups with optional custom name
- Restore backups with two-click confirmation and automatic safety backup
- Delete backups with two-click confirmation
- Backup type filter dropdown (All / Hourly / Daily / Weekly / Manual / Migration)

**Admin GUI: Updates Page ([#42](https://github.com/HyperSystems-Development/HyperFactions/issues/42))**
- Two-column layout: HyperFactions (left) and HyperProtect Mixin (right) with mirrored version info
- Shows current version, latest version, channel, build date, and update status for both
- Single "Check for Updates" button checks both simultaneously
- Download buttons appear when updates are available
- Changelog display for HyperFactions updates
- Rollback support with two-click confirmation
- HyperProtect detection via ProtectionMixinBridge (works even without update checker)

**Split MessageKeys into Domain-Specific Files**
- Split the monolithic `MessageKeys.java` (2,789 lines, 60 inner classes, ~1,298 constants) into 6 focused domain files:
- `CommonKeys.java` — shared messages, protection denial, territory notifications, announcements, teleport, chat display
Expand Down
22 changes: 22 additions & 0 deletions src/main/java/com/hyperfactions/HyperFactions.java
Original file line number Diff line number Diff line change
Expand Up @@ -1205,6 +1205,28 @@ public WorldMapService getWorldMapService() {
return worldMapService;
}

/**
* Restarts all interval-based runtime systems to pick up config changes.
* Called after the admin config editor saves changes.
*/
public void reloadRuntimeSystems() {
// Restart periodic tasks (auto-save, mob clear, upkeep, etc.)
if (periodicTaskManager != null) {
periodicTaskManager.cancelAll();
periodicTaskManager.startAll();
Logger.info("[Config] Periodic tasks restarted");
}

// Restart worldmap refresh scheduler with new mode/intervals
if (worldMapService != null) {
worldMapService.initializeScheduler(ConfigManager.get().worldMap());
Logger.info("[Config] World map scheduler restarted");
}

// Rebuild world settings resolver
ConfigManager.get().getWorldSettingsResolver().rebuild(ConfigManager.get().worlds());
}

/**
* Gets the map player filter service.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -264,7 +264,12 @@ private void dispatchCommand(@NotNull CommandContext ctx, @Nullable Store<Entity
}
Player playerEntity = store.getComponent(ref, Player.getComponentType());
if (playerEntity != null) {
hyperFactions.getGuiManager().openAdminConfig(playerEntity, ref, store, player);
String tab = subArgs.length > 0 ? subArgs[0] : null;
if (tab != null) {
hyperFactions.getGuiManager().openAdminConfig(playerEntity, ref, store, player, tab);
} else {
hyperFactions.getGuiManager().openAdminConfig(playerEntity, ref, store, player);
}
}
}
case "backups" -> {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -140,9 +140,6 @@ private void handleList(CommandContext ctx, @Nullable PlayerRef player) {
ctx.sendMessage(line);
}

if (!config.getClaimBlacklist().isEmpty()) {
ctx.sendMessage(msg(" Claim blacklist: " + String.join(", ", config.getClaimBlacklist()), COLOR_GRAY));
}
}

/**
Expand Down
8 changes: 8 additions & 0 deletions src/main/java/com/hyperfactions/config/ConfigFile.java
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,14 @@ public void reload() {
load();
}

/**
* Resets to factory defaults and saves.
*/
public void resetDefaults() {
createDefaults();
save();
}

/**
* Loads configuration values from the parsed JSON object.
*
Expand Down
24 changes: 24 additions & 0 deletions src/main/java/com/hyperfactions/config/ConfigManager.java
Original file line number Diff line number Diff line change
Expand Up @@ -264,6 +264,30 @@ public void reloadAll() {
Logger.info("[Config] Configuration reloaded");
}

/**
* Resets all configuration files to factory defaults and saves.
*/
public void resetAllDefaults() {
Logger.info("[Config] Resetting all configuration to defaults...");

factionsConfig.resetDefaults();
serverConfig.resetDefaults();
backupConfig.resetDefaults();
chatConfig.resetDefaults();
debugConfig.resetDefaults();
economyConfig.resetDefaults();
factionPermissionsConfig.resetDefaults();
worldMapConfig.resetDefaults();
announcementConfig.resetDefaults();
gravestoneConfig.resetDefaults();
worldsConfig.resetDefaults();

worldSettingsResolver.rebuild(worldsConfig);
validateAll();

Logger.info("[Config] Configuration reset to defaults");
}

/**
* Saves all configuration files.
*/
Expand Down
22 changes: 3 additions & 19 deletions src/main/java/com/hyperfactions/config/WorldSettingsResolver.java
Original file line number Diff line number Diff line change
Expand Up @@ -34,8 +34,7 @@ public class WorldSettingsResolver {
/** The default policy when no match is found. */
private boolean defaultAllow = true;

/** Claim blacklist (always blocked, regardless of per-world settings). */
private Set<String> claimBlacklist = new HashSet<>();
// claimBlacklist removed in v8 — migrated to per-world claiming=false entries

/** Record for a wildcard pattern with its priority. */
private record WildcardEntry(String key, Pattern pattern, int wildcardCount, WorldSettings settings) {}
Expand All @@ -50,7 +49,6 @@ public void rebuild(@NotNull WorldsConfig config) {
exactMatches.clear();
wildcardPatterns.clear();
defaultAllow = "allow".equals(config.getDefaultPolicy());
claimBlacklist = new HashSet<>(config.getClaimBlacklist());

for (Map.Entry<String, WorldSettings> entry : config.getWorlds().entrySet()) {
String key = entry.getKey();
Expand All @@ -71,8 +69,8 @@ public void rebuild(@NotNull WorldsConfig config) {
// Sort wildcards: fewer wildcards = higher priority (more specific)
wildcardPatterns.sort(Comparator.comparingInt(WildcardEntry::wildcardCount));

Logger.debug("[Worlds] Resolver rebuilt: %d exact, %d wildcard, defaultAllow=%s, blacklist=%d",
exactMatches.size(), wildcardPatterns.size(), defaultAllow, claimBlacklist.size());
Logger.debug("[Worlds] Resolver rebuilt: %d exact, %d wildcard, defaultAllow=%s",
exactMatches.size(), wildcardPatterns.size(), defaultAllow);
}

/**
Expand Down Expand Up @@ -109,11 +107,6 @@ public WorldSettings resolve(@NotNull String worldName) {
* @return true if claiming is allowed
*/
public boolean isClaimingAllowed(@NotNull String worldName) {
// Claim blacklist always takes precedence
if (claimBlacklist.contains(worldName)) {
return false;
}

WorldSettings settings = resolve(worldName);
if (settings != null && settings.claiming() != null) {
return settings.claiming();
Expand Down Expand Up @@ -180,13 +173,4 @@ public boolean isDefaultAllow() {
return defaultAllow;
}

/**
* Gets the claim blacklist.
*
* @return unmodifiable set of blacklisted world names
*/
@NotNull
public Set<String> getClaimBlacklist() {
return Collections.unmodifiableSet(claimBlacklist);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -196,6 +196,38 @@ public boolean isTerritoryNotificationsEnabled() {
return territoryNotificationsEnabled;
}

// === Setters (for admin config editor) ===

/** Sets territory notifications enabled. */
public void setTerritoryNotificationsEnabled(boolean value) { this.territoryNotificationsEnabled = value; }

/** Sets wilderness on leave zone enabled. */
public void setWildernessOnLeaveZoneEnabled(boolean value) { this.wildernessOnLeaveZoneEnabled = value; }

/** Sets wilderness on leave claim enabled. */
public void setWildernessOnLeaveClaimEnabled(boolean value) { this.wildernessOnLeaveClaimEnabled = value; }

/** Sets faction created. */
public void setFactionCreated(boolean value) { this.factionCreated = value; }

/** Sets faction disbanded. */
public void setFactionDisbanded(boolean value) { this.factionDisbanded = value; }

/** Sets leadership transfer. */
public void setLeadershipTransfer(boolean value) { this.leadershipTransfer = value; }

/** Sets overclaim. */
public void setOverclaim(boolean value) { this.overclaim = value; }

/** Sets war declared. */
public void setWarDeclared(boolean value) { this.warDeclared = value; }

/** Sets alliance formed. */
public void setAllianceFormed(boolean value) { this.allianceFormed = value; }

/** Sets alliance broken. */
public void setAllianceBroken(boolean value) { this.allianceBroken = value; }

// === Wilderness notification getters ===

public boolean isWildernessOnLeaveZoneEnabled() {
Expand Down Expand Up @@ -225,4 +257,16 @@ public String getWildernessOnLeaveClaimUpper() {
public String getWildernessOnLeaveClaimLower() {
return wildernessOnLeaveClaimLower;
}

/** Sets wilderness on leave zone upper text. */
public void setWildernessOnLeaveZoneUpper(@NotNull String value) { this.wildernessOnLeaveZoneUpper = value; }

/** Sets wilderness on leave zone lower text. */
public void setWildernessOnLeaveZoneLower(@NotNull String value) { this.wildernessOnLeaveZoneLower = value; }

/** Sets wilderness on leave claim upper text. */
public void setWildernessOnLeaveClaimUpper(@NotNull String value) { this.wildernessOnLeaveClaimUpper = value; }

/** Sets wilderness on leave claim lower text. */
public void setWildernessOnLeaveClaimLower(@NotNull String value) { this.wildernessOnLeaveClaimLower = value; }
}
20 changes: 20 additions & 0 deletions src/main/java/com/hyperfactions/config/modules/BackupConfig.java
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,26 @@ public int getShutdownRetention() {
return shutdownRetention;
}

// === Setters (for admin config editor) ===

/** Sets hourly retention. */
public void setHourlyRetention(int value) { this.hourlyRetention = value; }

/** Sets daily retention. */
public void setDailyRetention(int value) { this.dailyRetention = value; }

/** Sets weekly retention. */
public void setWeeklyRetention(int value) { this.weeklyRetention = value; }

/** Sets manual retention. */
public void setManualRetention(int value) { this.manualRetention = value; }

/** Sets on shutdown. */
public void setOnShutdown(boolean value) { this.onShutdown = value; }

/** Sets shutdown retention. */
public void setShutdownRetention(int value) { this.shutdownRetention = value; }

// === Validation ===

/** Validates . */
Expand Down
65 changes: 65 additions & 0 deletions src/main/java/com/hyperfactions/config/modules/ChatConfig.java
Original file line number Diff line number Diff line change
Expand Up @@ -326,6 +326,71 @@ public int getHistoryCleanupIntervalMinutes() {
return historyCleanupIntervalMinutes;
}

// === Setters (for admin config editor) ===

/** Sets format. */
public void setFormat(@NotNull String value) { this.format = value; }

/** Sets tag display. */
public void setTagDisplay(@NotNull String value) { this.tagDisplay = value; }

/** Sets tag format. */
public void setTagFormat(@NotNull String value) { this.tagFormat = value; }

/** Sets no faction tag. */
public void setNoFactionTag(@NotNull String value) { this.noFactionTag = value; }

/** Sets no faction tag color. */
public void setNoFactionTagColor(@NotNull String value) { this.noFactionTagColor = value; }

/** Sets player name color. */
public void setPlayerNameColor(@NotNull String value) { this.playerNameColor = value; }

/** Sets priority. */
public void setPriority(@NotNull String value) { this.priority = value; }

/** Sets relation color own. */
public void setRelationColorOwn(@NotNull String value) { this.relationColorOwn = value; }

/** Sets relation color ally. */
public void setRelationColorAlly(@NotNull String value) { this.relationColorAlly = value; }

/** Sets relation color neutral. */
public void setRelationColorNeutral(@NotNull String value) { this.relationColorNeutral = value; }

/** Sets relation color enemy. */
public void setRelationColorEnemy(@NotNull String value) { this.relationColorEnemy = value; }

/** Sets faction chat color. */
public void setFactionChatColor(@NotNull String value) { this.factionChatColor = value; }

/** Sets faction chat prefix. */
public void setFactionChatPrefix(@NotNull String value) { this.factionChatPrefix = value; }

/** Sets ally chat color. */
public void setAllyChatColor(@NotNull String value) { this.allyChatColor = value; }

/** Sets ally chat prefix. */
public void setAllyChatPrefix(@NotNull String value) { this.allyChatPrefix = value; }

/** Sets sender name color. */
public void setSenderNameColor(@NotNull String value) { this.senderNameColor = value; }

/** Sets message color. */
public void setMessageColor(@NotNull String value) { this.messageColor = value; }

/** Sets history enabled. */
public void setHistoryEnabled(boolean value) { this.historyEnabled = value; }

/** Sets history max messages. */
public void setHistoryMaxMessages(int value) { this.historyMaxMessages = value; }

/** Sets history retention days. */
public void setHistoryRetentionDays(int value) { this.historyRetentionDays = value; }

/** Sets history cleanup interval minutes. */
public void setHistoryCleanupIntervalMinutes(int value) { this.historyCleanupIntervalMinutes = value; }

// === Validation ===

/** Validates . */
Expand Down
12 changes: 12 additions & 0 deletions src/main/java/com/hyperfactions/config/modules/DebugConfig.java
Original file line number Diff line number Diff line change
Expand Up @@ -383,6 +383,18 @@ public void setSentryEnabled(boolean enabled) {
this.sentryEnabled = enabled;
}

/** Sets enabled by default. */
public void setEnabledByDefault(boolean value) { this.enabledByDefault = value; }

/** Sets log to console. */
public void setLogToConsole(boolean value) { this.logToConsole = value; applyToLogger(); }

/** Sets sentry debug mode. */
public void setSentryDebug(boolean value) { this.sentryDebug = value; }

/** Sets sentry traces sample rate. */
public void setSentryTracesSampleRate(double value) { this.sentryTracesSampleRate = value; }

// === Setters (for runtime toggle) ===

/**
Expand Down
Loading
Loading