import level01 from './level01.js'; import level02 from './level02.js'; import level03 from './level03.js'; import level04 from './level04.js'; import level05 from './level05.js'; import level06 from './level06.js'; import level07 from './level07.js'; import level08 from './level08.js'; import level09 from './level09.js'; import level10 from './level10.js'; import level11 from './level11.js'; import level12 from './level12.js'; import level13 from './level13.js'; import level14 from './level14.js'; import level15 from './level15.js'; import level16 from './level16.js'; import level17 from './level17.js'; import level18 from './level18.js'; import level19 from './level19.js'; import level20 from './level20.js'; // Campaigns group levels into themed "maps" for the level select screen. // Each campaign gets its own background art (bgKey), menu theme music // (musicKey), and in-level look (data/themes.js), so adding a new campaign // is: create its levels, add a bg image + manifest entry, add an entry to // THEMES, add a row here. Node positions are in 960x540 design units (NOT multiplied by // WORLD_SCALE - the scene scales them), and the path is rendered as a line // through them in order. `positions` may be omitted to auto-place the // campaign's levels: a 2-lane circuit (top lane right->left, bottom lane // left->right, finish flag bottom-right) that comfortably fits up to ~12 // levels (see positioning.js). Give explicit `positions` only when you want // a custom layout - the auto-layout keeps every node in the open band of // the background art (y roughly 268-412) and clear of the title plate and // bottom chrome. export const CAMPAIGNS = [ { id: 'campaign01', name: 'Evergreen Lake', bgKey: 'campaign01_bg', levels: [level01, level02, level03, level04, level05, level06, level07, level08, level09, level10], }, { id: 'campaign02', name: 'Underprivileged Community', bgKey: 'campaign02_bg', // Menu music for this campaign's map in LevelSelect (util/music.js // playMenuTrack); undefined = the main title theme. musicKey: 'urban_trap_theme', // Its old levels (level04-06) moved into campaign01 while it grew toward // 10; now full at 10 (level11-level20). level12 is a real designed level // (built via editor/levelBuilder.js's section pipeline, not hand-drawn in // the browser editor - review/playtest before shipping); level13-level20 // are placeholder stubs (flat filler terrain, "Coming Soon" name/desc) to // reserve their slots on the map - replace each in editor.html as it gets // designed for real. levels: [level11, level12, level13, level14, level15, level16, level17, level18, level19, level20], // Custom layout (see positioning.js / the module comment above) so this // campaign's map doesn't read as a reskin of campaign01's 2-lane loop: a // 3-row serpentine (row1 left->right, row2 right->left, row3 left->right) // instead of campaign01's top/bottom lane pair. Same 960x540 design-unit // space; rows sit at y=220/320/420 (vs campaign01's 268/412 two-lane // band) and x stays within 100-750 on every row so the flag's nudge past // the last node (see LevelSelectScene._buildCampaign) lands in-bounds // without needing to flip direction. positions: [ { x: 100, y: 220 }, // level11 { x: 317, y: 220 }, // level12 { x: 533, y: 220 }, // level13 { x: 750, y: 220 }, // level14 { x: 750, y: 320 }, // level15 { x: 425, y: 320 }, // level16 { x: 100, y: 320 }, // level17 { x: 100, y: 420 }, // level18 { x: 425, y: 420 }, // level19 { x: 750, y: 420 }, // level20 ], }, ]; export const LEVELS = CAMPAIGNS.flatMap((c) => c.levels); export function getLevelById(id) { return LEVELS.find((level) => level.id === id); } export function getCampaignById(id) { return CAMPAIGNS.find((c) => c.id === id); } export function getCampaignOfLevel(levelId) { return CAMPAIGNS.find((c) => c.levels.some((l) => l.id === levelId)) || null; } export function getNextLevelId(id) { const index = LEVELS.findIndex((level) => level.id === id); if (index === -1 || index === LEVELS.length - 1) return null; return LEVELS[index + 1].id; }