import { readFileSync, existsSync, writeFileSync } from 'node:fs'; import { join } from 'node:path'; import { z } from 'zod'; const configPath = join(import.meta.dir, '..', '..', 'config', 'config.json'); export interface GameConfigType { leveling: { base: number; exponent: number; chat: { cooldownMs: number; minXp: number; maxXp: number; } }, economy: { daily: { amount: bigint; streakBonus: bigint; cooldownMs: number; }, transfers: { allowSelfTransfer: boolean; minAmount: bigint; }, exam: { multMin: number; multMax: number; } }, inventory: { maxStackSize: bigint; maxSlots: number; }, commands: Record; lootdrop: { activityWindowMs: number; minMessages: number; spawnChance: number; cooldownMs: number; reward: { min: number; max: number; currency: string; } }; } // Initial default config state export const config: GameConfigType = {} as GameConfigType; const bigIntSchema = z.union([z.string(), z.number(), z.bigint()]) .refine((val) => { try { BigInt(val); return true; } catch { return false; } }, { message: "Must be a valid integer" }) .transform((val) => BigInt(val)); const configSchema = z.object({ leveling: z.object({ base: z.number(), exponent: z.number(), chat: z.object({ cooldownMs: z.number(), minXp: z.number(), maxXp: z.number(), }) }), economy: z.object({ daily: z.object({ amount: bigIntSchema, streakBonus: bigIntSchema, cooldownMs: z.number(), }), transfers: z.object({ allowSelfTransfer: z.boolean(), minAmount: bigIntSchema, }), exam: z.object({ multMin: z.number(), multMax: z.number(), }) }), inventory: z.object({ maxStackSize: bigIntSchema, maxSlots: z.number(), }), commands: z.record(z.string(), z.boolean()), lootdrop: z.object({ activityWindowMs: z.number(), minMessages: z.number(), spawnChance: z.number(), cooldownMs: z.number(), reward: z.object({ min: z.number(), max: z.number(), currency: z.string(), }) }) }); export function reloadConfig() { if (!existsSync(configPath)) { throw new Error(`Config file not found at ${configPath}`); } const raw = readFileSync(configPath, 'utf-8'); const rawConfig = JSON.parse(raw); // Update config object in place config.leveling = rawConfig.leveling; config.economy = { daily: { ...rawConfig.economy.daily, amount: BigInt(rawConfig.economy.daily.amount), streakBonus: BigInt(rawConfig.economy.daily.streakBonus), }, transfers: { ...rawConfig.economy.transfers, minAmount: BigInt(rawConfig.economy.transfers.minAmount), }, exam: rawConfig.economy.exam, }; config.inventory = { ...rawConfig.inventory, maxStackSize: BigInt(rawConfig.inventory.maxStackSize), }; config.commands = rawConfig.commands || {}; config.lootdrop = rawConfig.lootdrop; console.log("🔄 Config reloaded from disk."); } // Initial load reloadConfig(); // Backwards compatibility alias export const GameConfig = config; export function saveConfig(newConfig: unknown) { // Validate and transform input const validatedConfig = configSchema.parse(newConfig); const replacer = (key: string, value: any) => { if (typeof value === 'bigint') { return value.toString(); } return value; }; const jsonString = JSON.stringify(validatedConfig, replacer, 4); writeFileSync(configPath, jsonString, 'utf-8'); reloadConfig(); }