/** * 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 }; })();