forked from syntaxbullet/AuroraBot-discord
160 lines
4.1 KiB
TypeScript
160 lines
4.1 KiB
TypeScript
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<string, boolean>;
|
|
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();
|
|
}
|