132 lines
4.3 KiB
JavaScript
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;
|
|
}
|