Files
ai-qpid-pvz/game/js/systems/campaign.js
T

184 lines
11 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// Campaign Worlds & 25 Levels Definition System
window.CampaignSystem = (function() {
const WORLDS = [
{ id: 1, name: 'Солнечный День', icon: '☀️', biome: 'day', range: '1-1 .. 1-5' },
{ id: 2, name: 'Ночной Двор', icon: '🌙', biome: 'night', range: '2-1 .. 2-5' },
{ id: 3, name: 'Бассейн', icon: '🏊', biome: 'pool', range: '3-1 .. 3-5' },
{ id: 4, name: 'Ночной Туман', icon: '🌫️', biome: 'fog', range: '4-1 .. 4-5' },
{ id: 5, name: 'Крыша Дома', icon: '🏠', biome: 'roof', range: '5-1 .. 5-5' }
];
const DAVE_DIALOGUES = {
'2-1': {
title: '🌙 Безумный Дэйв предупреждает:',
text: 'Эй, сосед! Ночью солнце с неба не падает! Обязательно возьми Солнцегриб!',
btnText: 'ДАЛЕЕ / В БОЙ ⚔️'
},
'3-1': {
title: '🏊 Безумный Дэйв спешит на помощь:',
text: 'Там бассейн! На воде растения тонут — сначала сажай Кувшинки (Lily Pad)!',
btnText: 'ДАЛЕЕ / В БОЙ ⚔️'
},
'4-1': {
title: '🌫️ Безумный Дэйв щурится во мгле:',
text: 'Ого, какой густой туман! Зомби крадутся в темноте, используй Плантерн и Вздуватель!',
btnText: 'ДАЛЕЕ / В БОЙ ⚔️'
},
'5-1': {
title: '🏠 Безумный Дэйв лезет на скат:',
text: 'Мы на крыше! Скаты мешают прямой стрельбе — используй Горшки и Катапульты!',
btnText: 'ДАЛЕЕ / В БОЙ ⚔️'
},
'5-5': {
title: '🤖 БЕЗУМИЕ ПОЛНОГО МАСШТАБА!',
text: 'БЕЗУМИЕ! Доктор Зомбосс выкатил своего гигантского робота! Держи оборону!',
btnText: 'ДАЛЕЕ / В БОЙ ⚔️'
}
};
const LEVELS = [
// World 1: Day (1-1 .. 1-5)
{ level: 1, world: 1, stage: 1, code: '1-1', title: 'Первые шаги', desc: 'Знакомство с садом и простыми зомби', waves: 3, biome: 'day', reward: 100 },
{ level: 2, world: 1, stage: 2, code: '1-2', title: 'Опасные конусы', desc: 'Появление стойких зомби с дорожными конусами', waves: 3, biome: 'day', reward: 120 },
{ level: 3, world: 1, stage: 3, code: '1-3', title: 'Картофельный рубеж', desc: 'Тактическая оборона с картофельными минами', waves: 4, biome: 'day', reward: 140 },
{ level: 4, world: 1, stage: 4, code: '1-4', title: 'Прыгуны с шестом', desc: 'Быстрые зомби легко перепрыгивают первые растения', waves: 4, biome: 'day', reward: 160 },
{ level: 5, world: 1, stage: 5, code: '1-5', title: 'Штурм Дня', desc: 'Финальная массивная волна зомби на солнечном газоне', waves: 4, biome: 'day', reward: 200 },
// World 2: Night (2-1 .. 2-5)
{ level: 6, world: 2, stage: 1, code: '2-1', title: 'Сгущаются сумерки', desc: 'Ночь без падающего солнца: солнцегрибы и надгробия', waves: 3, biome: 'night', reward: 150 },
{ level: 7, world: 2, stage: 2, code: '2-2', title: 'Ярость газетчика', desc: 'Разъярённые зомби с газетами ускоряются в бешенстве', waves: 4, biome: 'night', reward: 170 },
{ level: 8, world: 2, stage: 3, code: '2-3', title: 'Споровый шквал', desc: 'Дымогрибы пробивают плотные ряды врагов', waves: 4, biome: 'night', reward: 190 },
{ level: 9, world: 2, stage: 4, code: '2-4', title: 'Бронекаски', desc: 'Зомби с вёдрами штурмуют тёмный двор', waves: 4, biome: 'night', reward: 210 },
{ level: 10, world: 2, stage: 5, code: '2-5', title: 'Полночный кошмар', desc: 'Зомби восстают прямо из могил в финальной волне', waves: 5, biome: 'night', reward: 250 },
// World 3: Pool (3-1 .. 3-5)
{ level: 11, world: 3, stage: 1, code: '3-1', title: 'Водная гладь', desc: '6 дорожек газона, кувшинки и зомби на утках', waves: 3, biome: 'pool', reward: 200 },
{ level: 12, world: 3, stage: 2, code: '3-2', title: 'Заплыв с шестом', desc: 'Прыгуны на суше и быстрые пловцы на воде', waves: 4, biome: 'pool', reward: 220 },
{ level: 13, world: 3, stage: 3, code: '3-3', title: 'Зубастики у воды', desc: 'Хищные зубастики против бронированных уток', waves: 4, biome: 'pool', reward: 240 },
{ level: 14, world: 3, stage: 4, code: '3-4', title: 'Футбольный матч', desc: 'Мощные футболисты прорывают береговую линию', waves: 4, biome: 'pool', reward: 260 },
{ level: 15, world: 3, stage: 5, code: '3-5', title: 'Водный шквал', desc: 'Штурм всех 6 дорожек с очистителями бассейна', waves: 5, biome: 'pool', reward: 300 },
// World 4: Fog (4-1 .. 4-5)
{ level: 16, world: 4, stage: 1, code: '4-1', title: 'Плотная мгла', desc: 'Густой туман скрывает правую половину поля', waves: 4, biome: 'fog', reward: 250 },
{ level: 17, world: 4, stage: 2, code: '4-2', title: 'Невидимая угроза', desc: 'Внезапные появления врагов прямо из белой пелены', waves: 4, biome: 'fog', reward: 270 },
{ level: 18, world: 4, stage: 3, code: '4-3', title: 'Туманный бассейн', desc: 'Водные линии скрыты во мраке ночного тумана', waves: 4, biome: 'fog', reward: 290 },
{ level: 19, world: 4, stage: 4, code: '4-4', title: 'Спринт сквозь мглу', desc: 'Футболисты и прыгуны выскакивают из тумана', waves: 5, biome: 'fog', reward: 310 },
{ level: 20, world: 4, stage: 5, code: '4-5', title: 'Штормовой туман', desc: 'Финальный прорыв орды через непроглядную пелену', waves: 5, biome: 'fog', reward: 350 },
// World 5: Roof (5-1 .. 5-5)
{ level: 21, world: 5, stage: 1, code: '5-1', title: 'Черепичный скат', desc: 'Крыша дома, цветочные горшки и капустопульты', waves: 4, biome: 'roof', reward: 300 },
{ level: 22, world: 5, stage: 2, code: '5-2', title: 'Арбузный гром', desc: 'Тяжёлые арбузопульты со сплэш-уроном по площади', waves: 4, biome: 'roof', reward: 320 },
{ level: 23, world: 5, stage: 3, code: '5-3', title: 'Опасные верхолазы', desc: 'Бронезомби карабкаются по скользкому скату крыши', waves: 4, biome: 'roof', reward: 350 },
{ level: 24, world: 5, stage: 4, code: '5-4', title: 'Осада дымохода', desc: 'Генеральный штурм перед чердаком дома', waves: 5, biome: 'roof', reward: 400 },
{ level: 25, world: 5, stage: 5, code: '5-5', title: 'Доктор Зомбосс!', desc: 'Финальная битва против шагающего робота Zombot!', waves: 1, biome: 'roof', isBoss: true, reward: 1000 }
];
let _activeLevels = [...LEVELS];
let _serverLoaded = false;
async function loadLevelsFromServer() {
try {
const resp = await fetch('/api/campaign/levels');
if (!resp.ok) return _activeLevels;
const data = await resp.json();
if (data && data.success && Array.isArray(data.levels) && data.levels.length > 0) {
_activeLevels = data.levels.map(lvl => {
const num = parseInt(lvl.level, 10) || 1;
const defaultLvl = LEVELS.find(d => d.level === num) || {};
let wavesCount = 4;
if (lvl.waves && typeof lvl.waves === 'object' && Array.isArray(lvl.waves.list)) {
wavesCount = lvl.waves.list.length;
} else if (Array.isArray(lvl.waves)) {
wavesCount = lvl.waves.length;
} else if (typeof lvl.waves === 'number') {
wavesCount = lvl.waves;
} else if (defaultLvl.waves) {
wavesCount = defaultLvl.waves;
}
const modifiers = (lvl.modifiers && typeof lvl.modifiers === 'object') ? lvl.modifiers : {};
const wavesConfig = lvl.wavesConfig || lvl.waves || defaultLvl.wavesConfig || null;
return {
...defaultLvl,
...lvl,
waves: wavesCount,
modifiers: {
speedMultiplier: modifiers.speedMultiplier || lvl.speedMultiplier || 1.0,
hpMultiplier: modifiers.hpMultiplier || lvl.hpMultiplier || 1.0,
startingSun: modifiers.startingSun !== undefined ? modifiers.startingSun : (lvl.startingSun !== undefined ? lvl.startingSun : null)
},
wavesConfig,
isBoss: Boolean(lvl.isBoss || lvl.is_boss || num === 25 || defaultLvl.isBoss)
};
});
_serverLoaded = true;
return _activeLevels;
}
} catch (e) {
console.warn('Campaign server load error, using offline standard levels:', e);
}
return _activeLevels;
}
// Load levels on initialization
if (typeof window !== 'undefined') {
setTimeout(() => {
loadLevelsFromServer();
}, 50);
}
return {
WORLDS,
LEVELS,
DAVE_DIALOGUES,
loadLevelsFromServer,
isServerLoaded() {
return _serverLoaded;
},
getAllLevels() {
return _activeLevels;
},
getDaveDialogue(levelNum, mode = 'adventure') {
if (mode === 'boss' || mode === 'bossrush') {
return DAVE_DIALOGUES['5-5'];
}
const num = parseInt(levelNum, 10) || 1;
const levelObj = this.getLevel(num);
if (levelObj) {
if (levelObj.dialogue && typeof levelObj.dialogue === 'object' && levelObj.dialogue.text) {
return levelObj.dialogue;
}
if (levelObj.daveDialogue && typeof levelObj.daveDialogue === 'object' && levelObj.daveDialogue.text) {
return levelObj.daveDialogue;
}
if (DAVE_DIALOGUES[levelObj.code]) {
return DAVE_DIALOGUES[levelObj.code];
}
}
return null;
},
getLevel(levelNum) {
const num = parseInt(levelNum, 10) || 1;
return _activeLevels.find(l => l.level === num) || LEVELS.find(l => l.level === num) || LEVELS[0];
},
getWorld(worldId) {
return WORLDS.find(w => w.id === worldId) || WORLDS[0];
},
getLevelsByWorld(worldId) {
return _activeLevels.filter(l => l.world === worldId);
},
formatLevel(levelNum) {
const item = this.getLevel(levelNum);
const world = this.getWorld(item.world);
return {
...item,
worldName: world.name,
worldIcon: world.icon,
fullLabel: `${item.code} (${world.icon} ${world.name})`
};
}
};
})();