Files
star-wars-wild-space/server/configLoader.js
T
2026-04-03 14:25:19 +02:00

132 lines
4.3 KiB
JavaScript

import fs from "fs";
import path from "path";
import { fileURLToPath } from "url";
const __dirname = path.dirname(fileURLToPath(import.meta.url));
const CONFIG_FILE_PATH =
process.env.CONFIG_FILE_PATH ?? path.join(__dirname, "..", "config", "game.settings.json");
const EPOCH_FILE_PATH =
process.env.EPOCH_FILE_PATH ?? path.join(path.dirname(CONFIG_FILE_PATH), ".timing_epoch");
let cached = {
dailyActionQuota: 100,
teamActionQuota: 100,
actionsResetIntervalSeconds: 3600,
databaseWipeoutIntervalCycles: 6,
configReloadIntervalSeconds: 30,
elementWorth: {},
resourceWorth: { common: {}, rare: {} },
militaryPower: {},
};
/** Raw JSON content from the last successful read (for change detection). */
let lastRawContent = "";
/** Unix seconds marking the origin of all timing slot calculations. */
let timingEpochSec = 0;
/** Signature of timing-relevant values from the last successful load. */
let lastTimingSig = "";
function timingSignature() {
return `${cached.actionsResetIntervalSeconds}:${cached.databaseWipeoutIntervalCycles}`;
}
function loadTimingEpoch() {
try {
const raw = fs.readFileSync(EPOCH_FILE_PATH, "utf8").trim();
const v = Number(raw);
if (Number.isFinite(v) && v > 0) return v;
} catch { /* ignore */ }
return 0;
}
function saveTimingEpoch(sec) {
try {
fs.writeFileSync(EPOCH_FILE_PATH, String(sec), "utf8");
} catch (e) {
console.error("[config] Could not persist timing epoch:", e.message);
}
}
function parseBool(v) {
if (typeof v === "boolean") return v;
if (typeof v === "string") {
const s = v.trim().toLowerCase();
if (s === "true") return true;
if (s === "false") return false;
}
return null;
}
export function loadConfigFile() {
try {
// Always read the file — mtime is unreliable on Docker bind mounts (Windows).
// The file is tiny and polled every ~30 s, so the cost is negligible.
const raw = fs.readFileSync(CONFIG_FILE_PATH, "utf8");
if (raw === lastRawContent) {
return cached;
}
lastRawContent = raw;
const j = JSON.parse(raw);
if (typeof j.dailyActionQuota === "number" && j.dailyActionQuota >= 1) {
cached.dailyActionQuota = Math.floor(j.dailyActionQuota);
}
if (typeof j.teamActionQuota === "number" && j.teamActionQuota >= 1) {
cached.teamActionQuota = Math.floor(j.teamActionQuota);
}
if (typeof j.actionsResetIntervalSeconds === "number" && j.actionsResetIntervalSeconds >= 1) {
cached.actionsResetIntervalSeconds = Math.floor(j.actionsResetIntervalSeconds);
}
if (typeof j.databaseWipeoutIntervalCycles === "number" && j.databaseWipeoutIntervalCycles >= 1) {
cached.databaseWipeoutIntervalCycles = Math.floor(j.databaseWipeoutIntervalCycles);
}
if (typeof j.configReloadIntervalSeconds === "number" && j.configReloadIntervalSeconds >= 5) {
cached.configReloadIntervalSeconds = j.configReloadIntervalSeconds;
}
if (j.elementWorth && typeof j.elementWorth === "object") {
cached.elementWorth = j.elementWorth;
}
if (j.resourceWorth && typeof j.resourceWorth === "object") {
cached.resourceWorth = j.resourceWorth;
} if (j.militaryPower && typeof j.militaryPower === 'object') {
cached.militaryPower = j.militaryPower;
}
// Detect timing changes and reset the epoch when they happen
const newSig = timingSignature();
if (!timingEpochSec) {
// First load: try to restore from disk
timingEpochSec = loadTimingEpoch();
}
if (!timingEpochSec || (lastTimingSig && newSig !== lastTimingSig)) {
// No persisted epoch, or timing values changed → new epoch
timingEpochSec = Math.floor(Date.now() / 1000);
saveTimingEpoch(timingEpochSec);
if (lastTimingSig) {
console.log(`[config] Timing values changed (${lastTimingSig}${newSig}), epoch reset to ${timingEpochSec}`);
}
}
lastTimingSig = newSig;
} catch (e) {
if (e.code === "ENOENT") {
lastRawContent = "";
} else {
console.error("[config]", e.message);
}
}
return cached;
}
export function getConfig() {
const c = { ...cached };
c.databaseWipeoutIntervalSeconds = c.actionsResetIntervalSeconds * c.databaseWipeoutIntervalCycles;
c.timingEpochSec = timingEpochSec;
return c;
}
export function getConfigFilePath() {
return CONFIG_FILE_PATH;
}