'use strict'; /* ============================================================ NEON SURVIVORS โ€” data.js : all game content definitions Weapons / Evolutions / Passives / Characters / Enemies / Bosses / Spawn timeline / Meta shop. ============================================================ */ /* ---------------- WEAPONS ---------------- Stat model: effective stat = base + sum(per[0..level-2]) dmg damage per hit cd seconds between activations n projectile/count spd projectile speed (px/s) pierce extra enemies pierced dur lifetime (s) area size multiplier kb knockback impulse */ const WEAPONS = { wand: { sym: '๐Ÿช„', col: '#b39dff', max: 8, base: { dmg: 12, cd: 1.1, n: 1, spd: 430, pierce: 0, dur: 1.6, area: 1, kb: 40 }, per: [ { dmg: 5 }, { n: 1 }, { cd: -0.15 }, { dmg: 6 }, { n: 1, spd: 60 }, { pierce: 1, dmg: 4 }, { n: 1 }, { cd: -0.15, dmg: 8 } ], evo: { into: 'e_wand', need: 'p_cd' } }, whip: { sym: '๐Ÿฉน', col: '#ff7a9c', max: 8, base: { dmg: 14, cd: 1.35, n: 1, area: 1.1, kb: 140 }, per: [ { dmg: 6 }, { area: 0.15 }, { dmg: 8 }, { n: 1 }, { dmg: 10 }, { area: 0.2 }, { cd: -0.15 }, { dmg: 12 } ], evo: { into: 'e_whip', need: 'p_hp' } }, knife: { sym: '๐Ÿ”ช', col: '#cfe8ff', max: 8, base: { dmg: 8, cd: 0.9, n: 2, spd: 520, pierce: 0, dur: 1.1, kb: 20 }, per: [ { n: 1 }, { dmg: 4 }, { n: 2 }, { dmg: 4 }, { pierce: 1 }, { n: 2 }, { dmg: 6 }, { n: 2, spd: 80 } ], evo: { into: 'e_knife', need: 'p_spd' } }, orb: { sym: '๐Ÿ”ฎ', col: '#7dfaff', max: 8, base: { dmg: 10, cd: 1.0, n: 2, spd: 2.4, area: 1, hitCd: 0.5 }, per: [ { n: 1 }, { spd: 0.4 }, { dmg: 5 }, { n: 1 }, { area: 0.15 }, { dmg: 6 }, { n: 1, spd: 0.4 }, { dmg: 8, area: 0.15 } ], evo: { into: 'e_orb', need: 'p_area' } }, axe: { sym: '๐Ÿช“', col: '#ffb347', max: 8, base: { dmg: 26, cd: 1.7, n: 1, spd: 240, pierce: 99, area: 1, kb: 200, grav: 900 }, per: [ { dmg: 12 }, { n: 1 }, { dmg: 12 }, { n: 1 }, { area: 0.25 }, { dmg: 16 }, { n: 1, cd: -0.2 }, { dmg: 20 } ], evo: { into: 'e_axe', need: 'p_might' } }, garlic: { sym: '๐Ÿ’ก', col: '#fff3b0', max: 8, base: { dmg: 5, cd: 0.45, area: 1, kb: 90, radius: 95 }, per: [ { area: 0.18 }, { dmg: 3 }, { area: 0.18 }, { kb: 40 }, { dmg: 4 }, { area: 0.22 }, { dmg: 5, cd: -0.05 }, { area: 0.25, dmg: 6 } ], evo: { into: 'e_garlic', need: 'p_regen' } }, light: { sym: 'โšก', col: '#9bf6ff', max: 8, base: { dmg: 30, cd: 2.2, n: 2, area: 1, blastR: 70 }, per: [ { n: 1 }, { dmg: 12 }, { n: 1 }, { area: 0.2 }, { dmg: 16 }, { n: 1 }, { cd: -0.3 }, { dmg: 20, n: 1 } ], evo: { into: 'e_light', need: 'p_dur' } }, fire: { sym: '๐Ÿ”ฅ', col: '#ff8c42', max: 8, base: { dmg: 20, cd: 2.0, n: 1, spd: 300, area: 1, blastR: 85, burnDps: 8, burnDur: 2, kb: 60 }, per: [ { dmg: 8 }, { n: 1 }, { dmg: 10 }, { area: 0.25 }, { n: 1 }, { dmg: 12 }, { cd: -0.3 }, { area: 0.3, dmg: 14 } ], evo: { into: 'e_fire', need: 'p_luck' } }, frost: { sym: 'โ„๏ธ', col: '#a0e7ff', max: 8, base: { dmg: 10, cd: 1.2, n: 1, spd: 480, pierce: 2, dur: 1.4, slow: 0.35, slowDur: 2, kb: 30 }, per: [ { n: 1 }, { dmg: 5 }, { slow: 0.1 }, { pierce: 2 }, { dmg: 6 }, { n: 1 }, { slow: 0.1, dmg: 6 }, { n: 2, dmg: 8 } ], evo: { into: 'e_frost', need: 'p_mag' } }, cross: { sym: 'โœ๏ธ', col: '#ffffff', max: 8, base: { dmg: 16, cd: 1.9, n: 1, spd: 420, pierce: 99, dur: 2.2, area: 1, rehit: 0.45, kb: 80 }, per: [ { dmg: 8 }, { n: 1 }, { dmg: 8 }, { spd: 60 }, { n: 1 }, { dmg: 10 }, { dur: 0.4, area: 0.15 }, { n: 1, dmg: 12 } ], evo: { into: 'e_cross', need: 'p_move' } }, mine: { sym: '๐Ÿงจ', col: '#ff6b9d', max: 8, base: { dmg: 26, cd: 2.6, n: 1, dur: 10, blastR: 85, kb: 140, pierce: 0 }, per: [ { dmg: 10 }, { dur: 2 }, { blastR: 15 }, { n: 1 }, { dmg: 14 }, { cd: -0.4 }, { blastR: 20, dmg: 12 }, { n: 1, dmg: 18 } ], evo: { into: 'e_mine', need: 'p_area' } } }; /* ---------------- EVOLVED WEAPONS (max level 1, from chests) ---------------- */ const EVOLVED = { e_mine: { sym: '๐Ÿ’ฃ', col: '#ff4d88', base: { dmg: 55, cd: 1.5, n: 3, dur: 14, blastR: 150, kb: 220 } }, e_wand: { sym: '๐ŸŒŸ', col: '#ffe066', base: { dmg: 30, cd: 0.55, n: 6, spd: 560, pierce: 2, dur: 1.8, kb: 60 } }, e_whip: { sym: '๐Ÿฉธ', col: '#ff4d6d', base: { dmg: 44, cd: 1.0, n: 2, area: 1.5, kb: 180, lifesteal: 0.08 } }, e_knife: { sym: '๐Ÿ—ก๏ธ', col: '#e8fbff', base: { dmg: 18, cd: 0.45, n: 8, spd: 640, pierce: 3, dur: 1.2, kb: 20 } }, e_orb: { sym: '๐Ÿ’ซ', col: '#7dfaff', base: { dmg: 28, cd: 0.35, n: 6, spd: 3.4, area: 1.5, hitCd: 0.4 } }, e_axe: { sym: '๐ŸŒ€', col: '#ffd166', base: { dmg: 70, cd: 1.2, n: 4, spd: 320, pierce: 999, area: 1.6, kb: 260, grav: 700 } }, e_garlic:{ sym: '๐Ÿ˜‡', col: '#fffbe0', base: { dmg: 16, cd: 0.3, area: 1.9, kb: 160, radius: 175, healOnKill: 1 } }, e_light: { sym: '๐ŸŒฉ๏ธ', col: '#d0fbff', base: { dmg: 65, cd: 1.3, n: 5, area: 1.4, blastR: 100, chain: 2, stun: 1.0 } }, e_fire: { sym: 'โ˜„๏ธ', col: '#ff5c33', base: { dmg: 50, cd: 1.4, n: 2, spd: 340, area: 1.7, blastR: 130, burnDps: 30, burnDur: 4, groundDur: 3.5, groundDps: 35, kb: 90 } }, e_frost: { sym: '๐Ÿ’ ', col: '#cdf5ff', base: { dmg: 34, cd: 0.9, n: 3, spd: 560, pierce: 99, dur: 1.6, slow: 0.6, slowDur: 2.5, freeze: 1.4, kb: 40 } }, e_cross: { sym: 'โšœ๏ธ', col: '#ffffff', base: { dmg: 60, cd: 1.5, n: 2, spd: 500, pierce: 999, dur: 3.2, area: 1.8, rehit: 0.4, kb: 120 } } }; /** Unified weapon definition accessor. */ function wdef(id) { return EVOLVED[id] || WEAPONS[id]; } /** Compute effective stats for a weapon at a given level. */ function wstats(id, lvl) { const ev = EVOLVED[id]; if (ev) return Object.assign({}, ev.base); const d = WEAPONS[id]; const s = Object.assign({}, d.base); for (let i = 0; i < Math.min(lvl - 1, d.per.length); i++) { const add = d.per[i]; for (const k in add) s[k] = (s[k] || 0) + add[k]; } s.cd = Math.max(0.12, s.cd); return s; } /* ---------------- PASSIVES ---------------- */ const PASSIVES = { p_might: { sym: '๐Ÿ’ช', col: '#ff7a7a', max: 5, val: 0.10 }, p_armor: { sym: '๐Ÿ›ก๏ธ', col: '#9db4ff', max: 5, val: 1 }, p_hp: { sym: 'โค๏ธ', col: '#ff5f7a', max: 5, val: 0.20 }, p_regen: { sym: '๐ŸŒฟ', col: '#7dff9e', max: 5, val: 0.6 }, p_cd: { sym: 'โฑ๏ธ', col: '#7dd0ff', max: 5, val: 0.08 }, p_area: { sym: '๐ŸŒ', col: '#c07dff', max: 5, val: 0.12 }, p_spd: { sym: '๐Ÿน', col: '#ffd57d', max: 5, val: 0.15 }, p_dur: { sym: 'โŒ›', col: '#ffc7ec', max: 5, val: 0.15 }, p_move: { sym: '๐Ÿ‘Ÿ', col: '#9effd0', max: 5, val: 0.10 }, p_mag: { sym: '๐Ÿงฒ', col: '#ffa8f6', max: 5, val: 0.30 }, p_luck: { sym: '๐Ÿ€', col: '#8aff6b', max: 5, val: 0.10 }, p_greed: { sym: '๐Ÿ’ฐ', col: '#ffd24a', max: 5, val: 0.20 }, p_growth: { sym: '๐Ÿ“˜', col: '#7db8ff', max: 5, val: 0.10 } }; /** Passive name/desc keys derive from id: p_might -> 'p_might'/'p_might_d'. */ /* ---------------- CHARACTERS ---------------- */ const CHARS = { kaito: { sym: '๐Ÿง™', col: '#9b8cff', weapon: 'wand', mods: {}, cond: { key: 'cond_free', fn: () => true } }, mira: { sym: '๐Ÿ›ก๏ธ', col: '#ff9db0', weapon: 'whip', mods: { armorFlat: 1, hpMul: 0.10 }, cond: { key: 'cond_kills500', fn: () => Store.data.totals.kills >= 500 } }, bolt: { sym: 'โšก', col: '#ffe066', weapon: 'knife', mods: { moveMul: 0.12, pspdMul: 0.15 }, cond: { key: 'cond_time10', fn: () => Store.data.totals.best >= 600 } }, luna: { sym: '๐ŸŒ™', col: '#b8a9ff', weapon: 'orb', mods: { areaMul: 0.18, cdrMul: 0.10 }, cond: { key: 'cond_gold2000', fn: () => Store.data.totals.gold >= 2000 } }, rook: { sym: '๐Ÿช“', col: '#ffb347', weapon: 'axe', mods: { dmgMul: 0.20, moveMul: -0.05 }, cond: { key: 'cond_boss1', fn: () => !!Store.data.flags.boss1 } }, nyx: { sym: '๐Ÿง›', col: '#ff6b81', weapon: 'garlic', mods: { regenFlat: 0.4, magMul: 0.40 }, cond: { key: 'cond_time15', fn: () => Store.data.totals.best >= 900 } }, sera: { sym: '๐ŸŒฉ๏ธ', col: '#7dfaff', weapon: 'light', mods: { cdrMul: 0.12, luckFlat: 0.05 }, cond: { key: 'cond_win', fn: () => Store.data.totals.wins >= 1 } }, vane: { sym: 'โœ๏ธ', col: '#e8e8ff', weapon: 'cross', mods: { luckFlat: 0.15, greedMul: 0.10 }, cond: { key: 'cond_kills5000', fn: () => Store.data.totals.kills >= 5000 } }, hana: { sym: '๐Ÿน', col: '#7dff9e', weapon: 'mine', mods: { critC: 0.15, critM: 0.3 }, cond: { key: 'cond_time15', fn: () => Store.data.totals.best >= 900 } }, bram: { sym: '๐Ÿงฑ', col: '#c96f4a', weapon: 'fire', mods: { areaMul: 0.20, moveMul: -0.05, hpMul: 0.10 }, cond: { key: 'cond_gold2000', fn: () => Store.data.totals.gold >= 2000 } } }; /* ---------------- COSMETIC SKINS ---------------- * Deterministic per-character palettes; purchasable with gold. */ const SKIN_POOL = [ { key: 'ember', col: '#ff8c42', vi: 'Lแปญa', en: 'Ember', price: 300 }, { key: 'frost', col: '#7fdcff', vi: 'Bฤƒng', en: 'Frost', price: 300 }, { key: 'toxic', col: '#a4f94d', vi: 'ฤแป™c', en: 'Toxic', price: 450 }, { key: 'rose', col: '#ff9db0', vi: 'Hแป“ng', en: 'Rose', price: 450 }, { key: 'gold', col: '#ffd24a', vi: 'Vร ng', en: 'Gold', price: 800 } ]; function _strHash(s) { let h = 2166136261; for (let i = 0; i < s.length; i++) { h ^= s.charCodeAt(i); h = (h * 16777619) >>> 0; } return h; } /** Two extra skins per character, chosen deterministically from the pool. */ function skinsFor(charId) { const h = _strHash(charId); const a = SKIN_POOL[h % SKIN_POOL.length]; let b = SKIN_POOL[(h + 2) % SKIN_POOL.length]; if (b.key === a.key) b = SKIN_POOL[(h + 4) % SKIN_POOL.length]; return [a, b]; } /* ---------------- ENEMIES ---------------- */ const ENEMIES = { bat: { nk: 'en_bat', hp: 8, spd: 100, dmg: 6, xp: 1, r: 10, col: '#b06bff', shape: 'bat', ai: 'chase', wiggle: 1.6 }, zombie: { nk: 'en_zombie', hp: 18, spd: 68, dmg: 10, xp: 1, r: 13, col: '#6bd66b', shape: 'zombie', ai: 'chase' }, imp: { nk: 'en_imp', hp: 11, spd: 118, dmg: 8, xp: 1, r: 10, col: '#ff8c42', shape: 'imp', ai: 'chase', wiggle: 2.6 }, arch: { nk: 'en_arch', hp: 24, spd: 76, dmg: 10, xp: 2, r: 13, col: '#e8e8e8', shape: 'archer', ai: 'shooter', range: 270, bspd: 235, bcd: 2.3 }, brute: { nk: 'en_brute', hp: 135, spd: 46, dmg: 22, xp: 5, r: 24, col: '#c96f4a', shape: 'brute', ai: 'chase', kbRes: 0.75 }, ghost: { nk: 'en_ghost', hp: 32, spd: 82, dmg: 12, xp: 2, r: 13, col: '#9ad5ff', shape: 'ghost', ai: 'ghost' }, spit: { nk: 'en_spit', hp: 38, spd: 62, dmg: 12, xp: 3, r: 15, col: '#a4f94d', shape: 'spit', ai: 'shooter', range: 250, bspd: 210, bcd: 2.8, spread: 3 }, garg: { nk: 'en_garg', hp: 95, spd: 60, dmg: 18, xp: 4, r: 17, col: '#8fa3b8', shape: 'garg', ai: 'charger', chargeSpd: 340 }, /* breakable prop โ€” no contact damage, drops coins when smashed */ lamp: { nk: 'en_lamp', hp: 26, spd: 0, dmg: 0, xp: 0, r: 15, col: '#8fd0ff', shape: 'prop', ai: 'prop', prop: true } }; /* Difficulty multipliers by elapsed minute. */ function diffScale(minute) { return { hp: 1 + minute * 0.5 + minute * minute * 0.035, dmg: 1 + minute * 0.08, spd: Math.min(1 + minute * 0.012, 1.3) }; } /* Which enemy types can appear at a given minute (weighted evenly). */ const SPAWN_TABLE = [ /*00*/ ['bat'], /*01*/ ['bat', 'zombie'], /*02*/ ['zombie', 'bat', 'imp'], /*03*/ ['zombie', 'imp', 'bat'], /*04*/ ['zombie', 'imp', 'arch'], /*05*/ ['imp', 'arch', 'zombie', 'bat'], /*06*/ ['arch', 'imp', 'ghost'], /*07*/ ['ghost', 'arch', 'brute'], /*08*/ ['brute', 'ghost', 'imp', 'arch'], /*09*/ ['brute', 'spit', 'ghost'], /*10*/ ['spit', 'brute', 'garg', 'imp'], /*11*/ ['garg', 'spit', 'ghost', 'brute'], /*12*/ ['garg', 'brute', 'spit', 'bat'], /*13*/ ['garg', 'spit', 'brute', 'ghost'], /*14*/ ['garg', 'brute', 'spit', 'garg'], /*15*/ ['garg', 'brute', 'spit', 'ghost'], /*16*/ ['garg', 'brute', 'spit', 'imp'], /*17*/ ['garg', 'brute', 'garg', 'spit'], /*18*/ ['garg', 'brute', 'spit', 'garg'], /*19*/ ['garg', 'brute', 'spit', 'garg'] ]; /* Elite spawn schedule (seconds). Elites drop treasure chests. */ const ELITE_TIMES = [70, 145, 220, 295, 370, 445, 520, 595, 670, 745, 820, 895, 970, 1045, 1120]; /* Random elite modifiers โ€” one may roll on spawn (65%). Bilingual inline. */ const ELITE_AFFIXES = { haste: { icon: 'โšก', vi: 'Tแป‘c ฤแป™', en: 'Swift', col: '#ffe066', apply(e) { e.spd *= 1.45; } }, tank: { icon: '๐Ÿ›ก๏ธ', vi: 'Cแปฉng Cรกp', en: 'Sturdy', col: '#9db4ff', apply(e) { e.hp *= 1.6; e.maxhp = e.hp; e.dmg *= 1.15; e.kbRes = Math.min(0.92, (e.def.kbRes || 0) + 0.55); } }, blast: { icon: '๐Ÿ’ฅ', vi: 'Nแป• Tung', en: 'Volatile', col: '#ff8c42', apply(e) { e.blastOnDeath = true; } } }; /* Boss plan โ€” killing the final boss wins the run. */ const RUN_LENGTH = 1200; const BOSS_PLAN = [ { t: 300, id: 'b_wolf' }, { t: 600, id: 'b_lich' }, { t: 900, id: 'b_behemoth' }, { t: 1140, id: 'b_death' } ]; const BOSSES = { b_wolf: { nk: 'bn_wolf', hp: 850, spd: 165, dmg: 25, r: 30, col: '#8a5cff', shape: 'wolf', ai: 'boss_wolf', xp: 60 }, b_lich: { nk: 'bn_lich', hp: 2400, spd: 58, dmg: 28, r: 30, col: '#c9f0ff', shape: 'lich', ai: 'boss_lich', xp: 100 }, b_behemoth: { nk: 'bn_behemoth', hp: 6500, spd: 42, dmg: 34, r: 44, col: '#ff5c5c', shape: 'behemoth', ai: 'boss_beho', xp: 160 }, b_death: { nk: 'bn_death', hp: 15000, spd: 195, dmg: 45, r: 34, col: '#ddddff', shape: 'death', ai: 'boss_death', xp: 400 } }; /* ---------------- STAGES ---------------- * Each biome swaps the spawn table / boss plan / palette and applies * hp/gold multipliers. Killing the plan's final boss wins (unless Endless). */ const STAGES = { graveyard: { id: 'graveyard', icon: '๐Ÿชฆ', unlock: () => true, music: { shift: 0, tempo: 1 }, bg: { grid: 'rgba(90,90,160,0.10)', mote: ['#5a4d9c', '#3f7fb0'] }, table: SPAWN_TABLE, plan: [ { t: 300, id: 'b_wolf' }, { t: 600, id: 'b_lich' }, { t: 900, id: 'b_behemoth' }, { t: 1140, id: 'b_death', final: true } ], length: RUN_LENGTH, hpMul: 1, goldMul: 1 }, frostcave: { id: 'frostcave', icon: 'โ„๏ธ', unlock: s => s.flags.time10, music: { shift: -3, tempo: 0.92 }, // darker, slower lockKey: 'st_lock_frost', bg: { grid: 'rgba(120,190,255,0.10)', mote: ['#7fc8ff', '#b0e8ff'] }, table: (() => { // colder mix: ghosts early, brutes mid, gargs sooner const t = SPAWN_TABLE.map(r => r.slice()); t[2] = ['ghost', 'zombie', 'bat']; t[6] = ['ghost', 'arch', 'imp']; t[9] = ['brute', 'garg', 'spit']; t[12] = ['garg', 'garg', 'spit', 'brute']; return t; })(), plan: [ { t: 240, id: 'b_lich' }, { t: 540, id: 'b_wolf' }, { t: 870, id: 'b_behemoth' }, { t: 1140, id: 'b_death', final: true } ], length: RUN_LENGTH, hpMul: 1.12, goldMul: 1.25 }, magmalands: { id: 'magmalands', icon: '๐ŸŒ‹', unlock: s => s.totals.wins >= 1, music: { shift: 2, tempo: 1.08 }, // hotter, faster lockKey: 'st_lock_magma', bg: { grid: 'rgba(255,120,60,0.09)', mote: ['#ff8c42', '#ffd166'] }, table: (() => { // violent mix: imps early, spitters sooner, heavy late const t = SPAWN_TABLE.map(r => r.slice()); t[1] = ['bat', 'imp']; t[4] = ['imp', 'spit', 'zombie']; t[7] = ['spit', 'brute', 'garg']; t[10] = ['garg', 'brute', 'brute', 'spit']; t[16] = ['garg', 'garg', 'brute', 'brute']; return t; })(), plan: [ { t: 210, id: 'b_behemoth' }, { t: 480, id: 'b_lich' }, { t: 780, id: 'b_wolf' }, { t: 1080, id: 'b_death', final: true } ], length: RUN_LENGTH, hpMul: 1.28, goldMul: 1.5 } }; /* ---------------- META SHOP ---------------- */ const META_SHOP = [ { id: 'might', sym: '๐Ÿ’ช', base: 100, max: 5 }, { id: 'hp', sym: 'โค๏ธ', base: 100, max: 5 }, { id: 'armor', sym: '๐Ÿ›ก๏ธ', base: 120, max: 5 }, { id: 'regen', sym: '๐ŸŒฟ', base: 150, max: 5 }, { id: 'spd', sym: '๐Ÿ‘Ÿ', base: 120, max: 5 }, { id: 'cd', sym: 'โฑ๏ธ', base: 180, max: 5 }, { id: 'growth', sym: '๐Ÿ“˜', base: 130, max: 5 }, { id: 'greed', sym: '๐Ÿ’ฐ', base: 130, max: 5 }, { id: 'luck', sym: '๐Ÿ€', base: 140, max: 5 }, { id: 'magnet', sym: '๐Ÿงฒ', base: 110, max: 5 }, { id: 'reroll', sym: '๐ŸŽฒ', base: 150, max: 3 }, { id: 'revive', sym: '๐Ÿ•Š๏ธ', base: 350, max: 2 } ]; function metaCost(def, rank) { return Math.round(def.base * Math.pow(1.7, rank)); }