config restructure

This commit is contained in:
Darto KLoning
2026-10-03 23:18:33 +07:00
parent 0442e07dde
commit 3144eab65e
10 changed files with 246 additions and 245 deletions
@@ -62,17 +62,11 @@ public final class AuthManager {
}
saveRules(); // creates rules.json on first run and writes newly-added defaults
if (Files.exists(ConfigPaths.AUTH_ACCOUNTS)) {
try (Reader r = Files.newBufferedReader(ConfigPaths.AUTH_ACCOUNTS)) {
Map<String, Account> loaded = GSON.fromJson(r, new TypeToken<Map<String, Account>>(){}.getType());
if (loaded != null) {
accounts.clear();
accounts.putAll(loaded);
}
}
} else {
saveAccounts();
}
Map<String, Account> loadedAccounts = PlayerDataStore.loadAuth(
new TypeToken<Map<String, Account>>(){}.getType(), new HashMap<>());
accounts.clear();
if (loadedAccounts != null) accounts.putAll(loadedAccounts);
if (!Files.exists(ConfigPaths.PLAYER)) saveAccounts();
migrateAccounts();
return true;
} catch (Exception e) {
@@ -563,11 +557,8 @@ public final class AuthManager {
}
private void saveAccounts() {
try {
Files.createDirectories(ConfigPaths.AUTH_DIRECTORY);
try (Writer w = Files.newBufferedWriter(ConfigPaths.AUTH_ACCOUNTS)) { GSON.toJson(accounts, w); }
} catch (Exception e) {
LobbySystemsMod.LOGGER.error("Failed saving auth accounts", e);
synchronized (accounts) {
PlayerDataStore.saveAuth(accounts, new TypeToken<Map<String, Account>>(){}.getType());
}
}
@@ -7,16 +7,20 @@ import java.nio.file.Path;
public final class ConfigPaths {
public static final Path DIRECTORY = FabricLoader.getInstance().getConfigDir().resolve("lobby");
public static final Path CONFIG = DIRECTORY.resolve("config.json");
public static final Path PLAYER_POSITIONS = DIRECTORY.resolve("player-positions.json");
public static final Path PLAYER = DIRECTORY.resolve("player.json");
public static final Path PORTALS = DIRECTORY.resolve("portals.json");
public static final Path DIMENSION_GAMEMODES = DIRECTORY.resolve("dimension-gamemodes.json");
public static final Path DIMENSION_GAMERULES = DIRECTORY.resolve("dimension-gamerules.json");
public static final Path STARTER_KITS = DIRECTORY.resolve("starterkits.json");
public static final Path STARTER_KIT_RECIPIENTS = DIRECTORY.resolve("starterkit-recipients.json");
public static final Path AUTH_DIRECTORY = DIRECTORY.resolve("auth");
public static final Path AUTH_CONFIG = AUTH_DIRECTORY.resolve("config.json");
public static final Path AUTH_ACCOUNTS = AUTH_DIRECTORY.resolve("accounts.json");
public static final Path AUTH_RULES = AUTH_DIRECTORY.resolve("rules.json");
public static final Path DIMENSION_DIRECTORY = DIRECTORY.resolve("dimension");
public static final Path DIMENSION_GAMEMODES = DIMENSION_DIRECTORY.resolve("gamemodes.json");
public static final Path DIMENSION_GAMERULES = DIMENSION_DIRECTORY.resolve("gamerules.json");
public static final Path STARTER_KIT_DIRECTORY = DIRECTORY.resolve("starterkit");
public static final Path STARTER_KITS = STARTER_KIT_DIRECTORY.resolve("list.json");
public static final Path STARTER_KIT_RECIPIENTS = STARTER_KIT_DIRECTORY.resolve("recipients.json");
private ConfigPaths() {}
}
@@ -20,7 +20,7 @@ import java.util.Map;
/**
* Persistent per-dimension gamerule overrides owned by Lobby Systems.
*
* Values are stored in config/lobby/dimension-gamerules.json and are applied
* Values are stored in config/lobby/dimension/gamerules.json and are applied
* directly to each ServerLevel's independent GameRules instance.
*/
public final class DimensionGameRuleManager {
@@ -31,7 +31,7 @@ public final class DimensionGameRuleManager {
public boolean load() {
configured.clear();
try {
Files.createDirectories(ConfigPaths.DIRECTORY);
Files.createDirectories(ConfigPaths.DIMENSION_GAMERULES.getParent());
if (!Files.exists(ConfigPaths.DIMENSION_GAMERULES)) {
save();
return true;
@@ -45,7 +45,7 @@ public final class DimensionGameRuleManager {
JsonElement rootElement = JsonParser.parseString(json);
if (!rootElement.isJsonObject()) {
LobbySystemsMod.LOGGER.error("dimension-gamerules.json root must be a JSON object");
LobbySystemsMod.LOGGER.error("dimension/gamerules.json root must be a JSON object");
return false;
}
@@ -76,7 +76,7 @@ public final class DimensionGameRuleManager {
public boolean save() {
try {
Files.createDirectories(ConfigPaths.DIRECTORY);
Files.createDirectories(ConfigPaths.DIMENSION_GAMERULES.getParent());
JsonObject root = new JsonObject();
configured.entrySet().stream().sorted(Map.Entry.comparingByKey()).forEach(dimensionEntry -> {
JsonObject rules = new JsonObject();
@@ -1,42 +0,0 @@
package id.dartokloning.lobbysystems;
import net.fabricmc.loader.api.FabricLoader;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.StandardCopyOption;
/**
* Satu-satunya tempat yang mengetahui nama file konfigurasi versi lama.
* Runtime saat ini hanya membaca/menulis file di config/lobby/.
*/
public final class LegacyConfigMigrator {
private LegacyConfigMigrator() {}
public static void migrate() {
Path oldConfig = FabricLoader.getInstance().getConfigDir().resolve("lobby-systems.json");
Path oldPositions = FabricLoader.getInstance().getConfigDir().resolve("lobby-systems-positions.json");
migrateOne(oldConfig, ConfigPaths.CONFIG, "configuration");
migrateOne(oldPositions, ConfigPaths.PLAYER_POSITIONS, "player positions");
}
private static void migrateOne(Path source, Path target, String label) {
if (!Files.exists(source) || Files.exists(target)) return;
try {
Files.createDirectories(target.getParent());
Files.move(source, target, StandardCopyOption.REPLACE_EXISTING);
LobbySystemsMod.LOGGER.info("Migrated legacy {}: {} -> {}", label, source, target);
LobbyLog.state("LEGACY CONFIG MIGRATED", "Type: " + label, "From: " + source, "To: " + target);
} catch (Exception moveError) {
try {
Files.copy(source, target, StandardCopyOption.REPLACE_EXISTING);
LobbySystemsMod.LOGGER.info("Copied legacy {}: {} -> {}", label, source, target);
LobbyLog.state("LEGACY CONFIG COPIED", "Type: " + label, "From: " + source, "To: " + target);
} catch (Exception copyError) {
LobbySystemsMod.LOGGER.error("Failed to migrate legacy {} from {} to {}", label, source, target, copyError);
LobbyLog.state("LEGACY MIGRATION FAILED", "Type: " + label, "From: " + source, "To: " + target);
}
}
}
}
@@ -82,8 +82,6 @@ public final class LobbySystemsMod implements ModInitializer {
public void onInitialize() {
LobbyLog.startup();
LegacyConfigMigrator.migrate();
boolean configLoaded = CONFIG.load();
LobbyLog.state(
configLoaded ? "CONFIG LOADED" : "CONFIG LOAD FAILED",
@@ -95,7 +93,7 @@ public final class LobbySystemsMod implements ModInitializer {
LobbyLog.state(
authLoaded ? "AUTH SYSTEM READY" : "AUTH SYSTEM LOAD FAILED",
"Config: " + ConfigPaths.AUTH_CONFIG,
"Accounts: " + ConfigPaths.AUTH_ACCOUNTS,
"Player database: " + ConfigPaths.PLAYER,
"Rules: " + ConfigPaths.AUTH_RULES,
"Enabled: " + AUTH.config().enabled,
"Hybrid online-mode support: enabled"
@@ -105,7 +103,7 @@ public final class LobbySystemsMod implements ModInitializer {
POSITIONS.setExcludedDimension(lobbyKey());
LobbyLog.state(
"PLAYER POSITIONS READY",
"File: " + ConfigPaths.PLAYER_POSITIONS,
"File: " + ConfigPaths.PLAYER,
"Lobby persistence: excluded"
);
@@ -572,7 +570,7 @@ public final class LobbySystemsMod implements ModInitializer {
"Portals: " + ConfigPaths.PORTALS,
"Auth config: " + ConfigPaths.AUTH_CONFIG,
"Auth rules: " + ConfigPaths.AUTH_RULES,
"Auth accounts: " + ConfigPaths.AUTH_ACCOUNTS
"Player database: " + ConfigPaths.PLAYER
);
if (!CONFIG.load()) {
@@ -596,12 +594,14 @@ public final class LobbySystemsMod implements ModInitializer {
}
if (!AUTH.load()) {
source.sendFailure(Component.literal("§eConfig utama direload, tetapi auth config/rules/accounts bermasalah. Cek console."));
LobbyLog.state("RELOAD FAILED", "Reason: auth config/rules/accounts gagal dibaca");
source.sendFailure(Component.literal("§eConfig utama direload, tetapi auth config/rules/player database bermasalah. Cek console."));
LobbyLog.state("RELOAD FAILED", "Reason: auth config/rules/player database gagal dibaca");
return 0;
}
AUTH.applyReload(server);
// player.json is shared by Auth and last-position; reload both sections.
POSITIONS.load();
POSITIONS.setExcludedDimension(key);
boolean portalOk = PORTALS.load();
if (!portalOk) {
@@ -613,8 +613,8 @@ public final class LobbySystemsMod implements ModInitializer {
boolean gameModesOk = DIMENSION_GAMEMODES.load();
if (!gameModesOk) {
source.sendFailure(Component.literal("§eConfig utama direload, tetapi dimension-gamemodes.json bermasalah. Cek console."));
LobbyLog.state("RELOAD FAILED", "Reason: dimension-gamemodes.json gagal dibaca");
source.sendFailure(Component.literal("§eConfig utama direload, tetapi dimension/gamemodes.json bermasalah. Cek console."));
LobbyLog.state("RELOAD FAILED", "Reason: dimension/gamemodes.json gagal dibaca");
return 0;
}
for (String dimension : DIMENSION_GAMEMODES.all().keySet()) {
@@ -625,16 +625,16 @@ public final class LobbySystemsMod implements ModInitializer {
boolean gameRulesOk = DIMENSION_GAMERULES.load();
if (!gameRulesOk) {
source.sendFailure(Component.literal("§eConfig utama direload, tetapi dimension-gamerules.json bermasalah. Cek console."));
LobbyLog.state("RELOAD FAILED", "Reason: dimension-gamerules.json gagal dibaca");
source.sendFailure(Component.literal("§eConfig utama direload, tetapi dimension/gamerules.json bermasalah. Cek console."));
LobbyLog.state("RELOAD FAILED", "Reason: dimension/gamerules.json gagal dibaca");
return 0;
}
DimensionGameRuleManager.ApplySummary gameRuleSummary = DIMENSION_GAMERULES.applyAll(server);
boolean starterKitsOk = STARTER_KITS.load();
if (!starterKitsOk) {
source.sendFailure(Component.literal("§estarterkits.json atau starterkit-recipients.json bermasalah. Cek console."));
LobbyLog.state("RELOAD FAILED", "Reason: starter kit data gagal dibaca");
source.sendFailure(Component.literal("§estarterkit/list.json atau starterkit/recipients.json bermasalah. Cek console."));
LobbyLog.state("RELOAD FAILED", "Reason: starterkit/list.json atau starterkit/recipients.json gagal dibaca");
return 0;
}
@@ -653,6 +653,7 @@ public final class LobbySystemsMod implements ModInitializer {
"Dimension gamemodes: " + DIMENSION_GAMEMODES.all().size() + " (authoritative)",
"Dimension gamerules: " + DIMENSION_GAMERULES.all().size() + " dimensions / " + gameRuleSummary.applied() + " rules applied",
"Starter kits: " + STARTER_KITS.size() + " / active dimensions: " + STARTER_KITS.activeCount(),
"Player database: " + ConfigPaths.PLAYER,
"Player positions lobby exclusion: active"
);
return 1;
@@ -780,7 +781,7 @@ public final class LobbySystemsMod implements ModInitializer {
String normalizedRule = rule.getIdentifierWithFallback().toString();
String actualValue = gameRuleValueUnchecked(level, rule);
if (!DIMENSION_GAMERULES.set(normalizedDimension, normalizedRule, actualValue)) {
source.sendFailure(Component.literal("§cGamerule sudah diterapkan di memory, tetapi gagal menyimpan dimension-gamerules.json."));
source.sendFailure(Component.literal("§cGamerule sudah diterapkan di memory, tetapi gagal menyimpan dimension/gamerules.json."));
return 0;
}
@@ -869,7 +870,7 @@ public final class LobbySystemsMod implements ModInitializer {
String normalizedDimension = level.dimension().identifier().toString();
String normalizedMode = rawMode.trim().toLowerCase();
if (!DIMENSION_GAMEMODES.set(normalizedDimension, normalizedMode)) {
source.sendFailure(Component.literal("§cGagal menyimpan dimension-gamemodes.json. Cek console."));
source.sendFailure(Component.literal("§cGagal menyimpan dimension/gamemodes.json. Cek console."));
return 0;
}
int applied = DIMENSION_GAMEMODES.applyToDimension(server, normalizedDimension);
@@ -906,7 +907,7 @@ public final class LobbySystemsMod implements ModInitializer {
"COMPATIBILITY WARNING",
"Dimensional Inventories: detected",
"Lobby Systems dimension gamemode management: authoritative / always enabled",
"Lobby Systems will enforce dimension-gamemodes.json on join and dimension changes.",
"Lobby Systems will enforce dimension/gamemodes.json on join and dimension changes.",
"Keep Dimensional Inventories pool gamemodes aligned (or disable its gamemode handling) to avoid conflicting transitions."
);
}
@@ -0,0 +1,82 @@
package id.dartokloning.lobbysystems;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.JsonParser;
import java.io.Reader;
import java.io.Writer;
import java.lang.reflect.Type;
import java.nio.file.Files;
/**
* Shared persistent player database. Auth and last-position data intentionally
* live in one file while remaining separate logical sections, so either
* subsystem can update its own data without overwriting the other.
*/
public final class PlayerDataStore {
private static final Gson GSON = new GsonBuilder().setPrettyPrinting().create();
private static final Object LOCK = new Object();
private static final String AUTH = "auth";
private static final String POSITIONS = "positions";
private PlayerDataStore() {}
public static <T> T loadAuth(Type type, T fallback) {
return loadSection(AUTH, type, fallback);
}
public static <T> T loadPositions(Type type, T fallback) {
return loadSection(POSITIONS, type, fallback);
}
public static void saveAuth(Object value, Type type) {
saveSection(AUTH, value, type);
}
public static void savePositions(Object value, Type type) {
saveSection(POSITIONS, value, type);
}
private static <T> T loadSection(String name, Type type, T fallback) {
synchronized (LOCK) {
try {
JsonObject root = readRoot();
JsonElement section = root.get(name);
if (section == null || section.isJsonNull()) return fallback;
T loaded = GSON.fromJson(section, type);
return loaded == null ? fallback : loaded;
} catch (Exception e) {
LobbySystemsMod.LOGGER.error("Failed to load '{}' from {}", name, ConfigPaths.PLAYER, e);
return fallback;
}
}
}
private static void saveSection(String name, Object value, Type type) {
synchronized (LOCK) {
try {
JsonObject root = readRoot();
root.add(name, GSON.toJsonTree(value, type));
Files.createDirectories(ConfigPaths.PLAYER.getParent());
try (Writer writer = Files.newBufferedWriter(ConfigPaths.PLAYER)) {
GSON.toJson(root, writer);
}
} catch (Exception e) {
LobbySystemsMod.LOGGER.error("Failed to save '{}' to {}", name, ConfigPaths.PLAYER, e);
}
}
}
private static JsonObject readRoot() throws Exception {
if (!Files.exists(ConfigPaths.PLAYER)) return new JsonObject();
try (Reader reader = Files.newBufferedReader(ConfigPaths.PLAYER)) {
JsonElement parsed = JsonParser.parseReader(reader);
if (parsed == null || parsed.isJsonNull()) return new JsonObject();
if (!parsed.isJsonObject()) throw new IllegalArgumentException("player.json root must be a JSON object");
return parsed.getAsJsonObject();
}
}
}
@@ -1,24 +1,16 @@
package id.dartokloning.lobbysystems;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.reflect.TypeToken;
import net.minecraft.resources.ResourceKey;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.world.level.Level;
import java.io.Reader;
import java.io.Writer;
import java.lang.reflect.Type;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
public final class PositionManager {
private static final Gson GSON = new GsonBuilder().setPrettyPrinting().create();
private static final Path PATH = ConfigPaths.PLAYER_POSITIONS;
private static final Type DATA_TYPE = new TypeToken<Map<String, Map<String, PlayerPosition>>>() {}.getType();
private final Map<String, Map<String, PlayerPosition>> positions = new HashMap<>();
@@ -28,17 +20,8 @@ public final class PositionManager {
public void load() {
positions.clear();
if (!Files.exists(PATH)) {
save();
return;
}
try (Reader reader = Files.newBufferedReader(PATH)) {
Map<String, Map<String, PlayerPosition>> loaded = GSON.fromJson(reader, DATA_TYPE);
if (loaded != null) positions.putAll(loaded);
} catch (Exception e) {
LobbySystemsMod.LOGGER.error("Failed to load player positions from {}", PATH, e);
}
Map<String, Map<String, PlayerPosition>> loaded = PlayerDataStore.loadPositions(DATA_TYPE, new HashMap<>());
if (loaded != null) positions.putAll(loaded);
}
/**
@@ -90,15 +73,8 @@ public final class PositionManager {
// Guard terakhir: walaupun ada bug di caller, data lobby tidak boleh masuk file.
purgeExcludedDimension();
try {
Files.createDirectories(PATH.getParent());
try (Writer writer = Files.newBufferedWriter(PATH)) {
GSON.toJson(positions, DATA_TYPE, writer);
}
dirty = false;
} catch (Exception e) {
LobbySystemsMod.LOGGER.error("Failed to save player positions to {}", PATH, e);
}
PlayerDataStore.savePositions(positions, DATA_TYPE);
dirty = false;
}
private void store(UUID uuid, ResourceKey<Level> dimension, PlayerPosition position) {
@@ -225,7 +225,7 @@ public final class StarterKitManager {
if (wasActive && !previousDimension.equals(dimension)) {
config.activeByDimension.remove(previousDimension);
}
if (!saveKits()) return OperationResult.fail("Gagal menyimpan starterkits.json.");
if (!saveKits()) return OperationResult.fail("Gagal menyimpan starterkit/list.json.");
String status = name.equals(config.activeByDimension.get(dimension)) ? "ACTIVE" : "INACTIVE";
if (wasActive && !previousDimension.equals(dimension)) {
@@ -268,7 +268,7 @@ public final class StarterKitManager {
if (kit == null) return OperationResult.fail("Starter kit '" + name + "' tidak ditemukan.");
String dimension = normalizeDimension(kit.dimension);
String previous = config.activeByDimension.put(dimension, name);
if (!saveKits()) return OperationResult.fail("Gagal menyimpan starterkits.json.");
if (!saveKits()) return OperationResult.fail("Gagal menyimpan starterkit/list.json.");
if (name.equals(previous)) return OperationResult.ok("Starter kit '" + name + "' sudah ACTIVE untuk " + dimension + ".");
return OperationResult.ok("Starter kit '" + name + "' sekarang ACTIVE untuk " + dimension
+ (previous == null ? "." : ". Kit sebelumnya: '" + previous + "' -> INACTIVE."));
@@ -283,7 +283,7 @@ public final class StarterKitManager {
return OperationResult.ok("Starter kit '" + name + "' sudah INACTIVE.");
}
config.activeByDimension.remove(dimension);
if (!saveKits()) return OperationResult.fail("Gagal menyimpan starterkits.json.");
if (!saveKits()) return OperationResult.fail("Gagal menyimpan starterkit/list.json.");
return OperationResult.ok("Starter kit '" + name + "' sekarang INACTIVE. Dimension " + dimension + " tidak memiliki active starter kit.");
}
@@ -294,7 +294,7 @@ public final class StarterKitManager {
String dimension = normalizeDimension(removed.dimension);
boolean wasActive = name.equals(config.activeByDimension.get(dimension));
if (wasActive) config.activeByDimension.remove(dimension);
if (!saveKits()) return OperationResult.fail("Gagal menyimpan starterkits.json.");
if (!saveKits()) return OperationResult.fail("Gagal menyimpan starterkit/list.json.");
return OperationResult.ok("Starter kit '" + name + "' dihapus" + (wasActive ? " dan active assignment untuk " + dimension + " dilepas." : "."));
}
@@ -370,7 +370,7 @@ public final class StarterKitManager {
return OperationResult.ok("Player belum memiliki recipient history untuk " + dimension + ".");
}
if (byDimension.isEmpty()) recipients.remove(uuid.toString());
if (!saveRecipients()) return OperationResult.fail("Gagal menyimpan starterkit-recipients.json.");
if (!saveRecipients()) return OperationResult.fail("Gagal menyimpan starterkit/recipients.json.");
return OperationResult.ok("Recipient history untuk " + dimension + " sudah di-reset.");
}