NEON SURVIVORS v1.1 — full-featured bullet-heaven survivor game
Vanilla JS + Canvas, zero dependencies, offline-first PWA. Gameplay: - 11 weapons x8 levels + 11 evolutions (chest-based), incl. timed mines - 13 passives, crit system with directional hit-sparks & hit-stop - 10 characters w/ unique mods + unlock conditions, gold cosmetic skins - 3 biomes (Neon Graveyard / Frozen Hollow / Magma Rift) each with own spawn tables, boss plans and music flavor; Endless mode + surges; 4 difficulty grades; breakable crystal-lamp props - Elite random affixes (Swift/Sturdy/Volatile), 4 bosses, win flow - Achievements (23) w/ gold rewards, run history, daily seeded challenge Tech: - Cinematic canvas main-menu scene, game-feel FX suite (trails, muzzle, status tints, low-HP pulse), viewport culling + particle pooling - WebAudio synth SFX + generative per-biome soundtrack - Gamepad support, remappable keys, touch joystick, fullscreen - i18n VI/EN, localStorage saves w/ export-import codes - Cloudflare Workers leaderboard scaffold (KV) w/ signed submits - Headless integrity test-suite (node test/integrity.js)
This commit is contained in:
+388
@@ -0,0 +1,388 @@
|
||||
'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)); }
|
||||
Reference in New Issue
Block a user