feat(admin-configurator): add web configurator, visual level editor, audit logs and session management

This commit is contained in:
2026-09-22 09:25:01 +00:00
parent e820d1354d
commit d8be8f4366
13 changed files with 11204 additions and 292 deletions
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+209
View File
@@ -0,0 +1,209 @@
/**
* PvZ Dynamic Game Configuration & Balance System (PvZConfig)
* Fetches active balance multipliers from /api/config with fallback defaults and offline localStorage cache.
*/
window.PvZConfig = (function () {
const DEFAULT_CONFIG = {
preset: 'Classic 2009',
economy: {
sunMultiplier: 1.0,
startingSun: 50,
coinDropRate: 1.0,
sunDropRate: 1.0,
plantCostMultiplier: 1.0,
upgradeCostMultiplier: 1.0,
plantPrices: {
sunflower: 0,
peashooter: 0,
wallnut: 0,
potatomine: 150,
snowpea: 350,
repeater: 500,
chomper: 450,
cherrybomb: 400,
jalapeno: 450,
sunshroom: 250,
fumeshroom: 300,
lilypad: 200,
gravebuster: 250,
flowerpot: 200,
cabbagepult: 400,
melonpult: 750
},
plantSunCosts: {
sunflower: 50,
peashooter: 100,
wallnut: 50,
potatomine: 25,
snowpea: 175,
repeater: 200,
chomper: 150,
cherrybomb: 150,
jalapeno: 125,
sunshroom: 25,
fumeshroom: 75,
lilypad: 25,
gravebuster: 75,
flowerpot: 25,
cabbagepult: 100,
melonpult: 300
}
},
zombies: {
hpMultiplier: 1.0,
speedMultiplier: 1.0,
damageMultiplier: 1.0,
waveDensityMultiplier: 1.0,
types: {
normal: { hp: 200, speed: 22, damage: 30 },
conehead: { hp: 300, speed: 22, damage: 30 },
buckethead: { hp: 600, speed: 22, damage: 30 },
newspaper: { hp: 300, speed: 18, damage: 30 },
screendoor: { hp: 650, speed: 20, damage: 30 },
football: { hp: 600, speed: 42, damage: 35 },
dancing: { hp: 350, speed: 25, damage: 30 },
gargantuar: { hp: 3000, speed: 14, damage: 300 },
zomboss: { hp: 10000, speed: 0, damage: 500 },
polevaulter: { hp: 180, speed: 44, damage: 30 },
duckytube: { hp: 120, speed: 24, damage: 30 },
flag: { hp: 200, speed: 28, damage: 30 }
}
},
plants: {
damageMultiplier: 1.0,
hpMultiplier: 1.0,
cooldownMultiplier: 1.0,
sunProduceIntervalMultiplier: 1.0
},
zenGarden: {
dropRateMultiplier: 1.0,
growthRateMultiplier: 1.0,
fertilizerMultiplier: 1.0,
waterIntervalMinutes: 30
},
events: {
doubleGold: false,
fastCooldowns: false,
infiniteSun: false,
turboZombies: false,
activeEventName: 'none'
}
};
let activeConfig = JSON.parse(JSON.stringify(DEFAULT_CONFIG));
// Try loading cached config from localStorage
try {
const cached = localStorage.getItem('pvz_game_config');
if (cached) {
const parsed = JSON.parse(cached);
if (parsed && typeof parsed === 'object') {
activeConfig = Object.assign({}, DEFAULT_CONFIG, parsed);
}
}
} catch (e) {
// ignore
}
async function fetchConfig() {
try {
const controller = new AbortController();
const timeoutId = setTimeout(() => controller.abort(), 2500);
const res = await fetch('/api/config', { signal: controller.signal });
clearTimeout(timeoutId);
if (res.ok) {
const data = await res.json();
if (data && typeof data === 'object') {
activeConfig = Object.assign({}, DEFAULT_CONFIG, data);
try {
localStorage.setItem('pvz_game_config', JSON.stringify(activeConfig));
} catch (e) {}
}
}
} catch (err) {
console.warn('[PvZConfig] Using cached/default balance config:', err.message);
}
return activeConfig;
}
// Auto-fetch on script load
if (typeof window !== 'undefined') {
fetchConfig();
}
return {
fetchConfig,
getConfig: () => activeConfig,
getPreset: () => activeConfig.preset || 'Classic 2009',
// Economy
getEconomy: () => activeConfig.economy || DEFAULT_CONFIG.economy,
getStartingSun: () => {
const eco = activeConfig.economy || {};
const base = typeof eco.startingSun === 'number' ? eco.startingSun : 50;
return activeConfig.events?.infiniteSun ? Math.max(base, 250) : base;
},
getSunMultiplier: () => {
const eco = activeConfig.economy || {};
const mult = typeof eco.sunMultiplier === 'number' ? eco.sunMultiplier : 1.0;
return activeConfig.events?.infiniteSun ? mult * 1.5 : mult;
},
getCoinDropRate: () => {
const eco = activeConfig.economy || {};
const mult = typeof eco.coinDropRate === 'number' ? eco.coinDropRate : 1.0;
return activeConfig.events?.doubleGold ? mult * 2.0 : mult;
},
getSunDropRate: () => activeConfig.economy?.sunDropRate ?? 1.0,
getPlantShopPrice: (plantId) => {
const eco = activeConfig.economy || {};
const prices = eco.plantPrices || {};
const base = prices[plantId] ?? (plantId === 'sunflower' || plantId === 'peashooter' || plantId === 'wallnut' ? 0 : 250);
const mult = eco.plantCostMultiplier ?? 1.0;
return Math.round(base * mult);
},
getPlantSunCost: (plantId, defaultCost = 50) => {
const eco = activeConfig.economy || {};
const sunCosts = eco.plantSunCosts || {};
return sunCosts[plantId] !== undefined ? sunCosts[plantId] : defaultCost;
},
// Zombies & Combat
getZombieGlobal: () => activeConfig.zombies || DEFAULT_CONFIG.zombies,
getZombieHpMult: () => activeConfig.zombies?.hpMultiplier ?? 1.0,
getZombieSpeedMult: () => {
const mult = activeConfig.zombies?.speedMultiplier ?? 1.0;
return activeConfig.events?.turboZombies ? mult * 1.3 : mult;
},
getZombieDamageMult: () => activeConfig.zombies?.damageMultiplier ?? 1.0,
getWaveDensityMult: () => activeConfig.zombies?.waveDensityMultiplier ?? 1.0,
getZombieTypeConfig: (type) => {
const types = activeConfig.zombies?.types || {};
return types[type] || DEFAULT_CONFIG.zombies.types[type] || { hp: 200, speed: 22, damage: 30 };
},
// Plants
getPlantGlobal: () => activeConfig.plants || DEFAULT_CONFIG.plants,
getPlantDamageMult: () => activeConfig.plants?.damageMultiplier ?? 1.0,
getPlantHpMult: () => activeConfig.plants?.hpMultiplier ?? 1.0,
getPlantCooldownMult: () => {
const mult = activeConfig.plants?.cooldownMultiplier ?? 1.0;
return activeConfig.events?.fastCooldowns ? mult * 0.4 : mult;
},
getSunIntervalMult: () => activeConfig.plants?.sunProduceIntervalMultiplier ?? 1.0,
// Zen Garden
getZenGarden: () => activeConfig.zenGarden || DEFAULT_CONFIG.zenGarden,
getZenDropRateMult: () => {
const mult = activeConfig.zenGarden?.dropRateMultiplier ?? 1.0;
return activeConfig.events?.doubleGold ? mult * 2.0 : mult;
},
getZenGrowthRateMult: () => activeConfig.zenGarden?.growthRateMultiplier ?? 1.0,
getZenFertilizerMult: () => activeConfig.zenGarden?.fertilizerMultiplier ?? 1.0,
getZenWaterIntervalMinutes: () => activeConfig.zenGarden?.waterIntervalMinutes ?? 30,
// Events
getEvents: () => activeConfig.events || DEFAULT_CONFIG.events
};
})();