- 5 Biomes: Day, Night, Pool, Fog, Roof (25 Campaign levels) - Minigames: Wall-nut Bowling, I Zombie, Roguelike, Boss Rush & Dr. Zomboss - Systems: Zen Garden, Workshop Crafting, Almanac, Tree of Wisdom, Achievements - Backend: REST API + SQLite Accounts & Save Synchronization - Infrastructure: Nginx HTTPS & Docker deployment
205 lines
6.2 KiB
JavaScript
205 lines
6.2 KiB
JavaScript
// Garden Roguelike System: Seed Drafting, Artifact Relics, and Progressive Stage Escalation
|
|
window.RoguelikeSystem = class RoguelikeSystem {
|
|
constructor(gameEngine) {
|
|
this.engine = gameEngine;
|
|
this.activeRelics = [];
|
|
this.currentFloor = 1;
|
|
this.draftChoices = [];
|
|
|
|
// All available relics in Roguelike mode
|
|
this.relicPool = [
|
|
{
|
|
id: 'relic_lightning',
|
|
name: 'Громоотвод',
|
|
icon: '⚡',
|
|
desc: 'Каждые 7 секунд молния бьет в передового зомби, нанося 160 урона.',
|
|
timer: 7
|
|
},
|
|
{
|
|
id: 'relic_toxic_soil',
|
|
name: 'Токсичный перегной',
|
|
icon: '🧪',
|
|
desc: 'Вся почва выделяет споры: все зомби замедлены на 15% и получают 6 ед. яда в секунду.'
|
|
},
|
|
{
|
|
id: 'relic_sun_resonance',
|
|
name: 'Солнечный резонанс',
|
|
icon: '☀️',
|
|
desc: 'Все подсолнухи производят на +15 больше солнца за каждую вспышку.'
|
|
},
|
|
{
|
|
id: 'relic_iron_bark',
|
|
name: 'Титановая кора',
|
|
icon: '🛡️',
|
|
desc: 'Все высаживаемые Стеноорехи моментально получают щит прочности +350 HP.'
|
|
},
|
|
{
|
|
id: 'relic_vampire_sap',
|
|
name: 'Вампирский сок',
|
|
icon: '💖',
|
|
desc: 'Каждое уничтожение зомби восстанавливает +40 HP самому раненому растению.'
|
|
},
|
|
{
|
|
id: 'relic_fertilizer_boost',
|
|
name: 'Гормон роста',
|
|
icon: '🌿',
|
|
desc: 'Все стрелковые растения атакуют на 20% быстрее.'
|
|
}
|
|
];
|
|
|
|
// Seed draft candidates
|
|
this.plantPool = [
|
|
'snowpea',
|
|
'repeater',
|
|
'wallnut',
|
|
'potatomine',
|
|
'chomper',
|
|
'cherrybomb',
|
|
'jalapeno',
|
|
'fumeshroom'
|
|
];
|
|
}
|
|
|
|
reset() {
|
|
this.activeRelics = [];
|
|
this.currentFloor = 1;
|
|
this.draftChoices = [];
|
|
}
|
|
|
|
hasRelic(id) {
|
|
return this.activeRelics.some(r => r.id === id);
|
|
}
|
|
|
|
addRelic(relic) {
|
|
if (!this.hasRelic(relic.id)) {
|
|
this.activeRelics.push({ ...relic, timer: relic.timer || 0 });
|
|
if (this.engine && this.engine.achievements) {
|
|
this.engine.achievements.trigger('roguelike_relic');
|
|
}
|
|
}
|
|
}
|
|
|
|
// Generate 3 choices: seeds or relics
|
|
generateDraftChoices() {
|
|
const choices = [];
|
|
const availableRelics = this.relicPool.filter(r => !this.hasRelic(r.id));
|
|
|
|
// 1-2 Relic choices
|
|
if (availableRelics.length > 0) {
|
|
const shuffledRelics = [...availableRelics].sort(() => 0.5 - Math.random());
|
|
const r = shuffledRelics[0];
|
|
choices.push({
|
|
type: 'relic',
|
|
id: r.id,
|
|
name: r.name,
|
|
icon: r.icon,
|
|
desc: r.desc,
|
|
raw: r
|
|
});
|
|
}
|
|
|
|
// Seed choices: plants not currently in economy deck
|
|
const currentCards = this.engine && this.engine.economy ? this.engine.economy.cards.map(c => c.id) : [];
|
|
const availablePlants = this.plantPool.filter(p => !currentCards.includes(p));
|
|
const shuffledPlants = [...availablePlants].sort(() => 0.5 - Math.random());
|
|
|
|
for (const pId of shuffledPlants.slice(0, 3 - choices.length)) {
|
|
const def = this.engine.workshop ? this.engine.workshop.plantDefs[pId] : null;
|
|
choices.push({
|
|
type: 'seed',
|
|
id: pId,
|
|
name: def ? def.name : pId,
|
|
icon: def ? def.icon : '🌱',
|
|
desc: `Добавляет семя ${def ? def.name : pId} в колоду для текущего забега!`,
|
|
raw: pId
|
|
});
|
|
}
|
|
|
|
// Fill remaining if needed
|
|
while (choices.length < 3 && this.plantPool.length > 0) {
|
|
const pId = this.plantPool[choices.length % this.plantPool.length];
|
|
choices.push({
|
|
type: 'seed',
|
|
id: pId,
|
|
name: pId,
|
|
icon: '🌱',
|
|
desc: `Дополнительное семя для текущего забега.`,
|
|
raw: pId
|
|
});
|
|
}
|
|
|
|
this.draftChoices = choices;
|
|
return choices;
|
|
}
|
|
|
|
selectChoice(index) {
|
|
const choice = this.draftChoices[index];
|
|
if (!choice) return false;
|
|
|
|
if (choice.type === 'relic') {
|
|
this.addRelic(choice.raw);
|
|
} else if (choice.type === 'seed' && this.engine && this.engine.economy) {
|
|
// Add seed to deck
|
|
const newCard = this.engine.economy.allCards.find(c => c.id === choice.raw);
|
|
if (newCard && !this.engine.economy.cards.some(c => c.id === newCard.id)) {
|
|
this.engine.economy.cards.push({ ...newCard, timer: 0 });
|
|
}
|
|
}
|
|
|
|
this.currentFloor++;
|
|
return true;
|
|
}
|
|
|
|
update(dt, gameState) {
|
|
// 1. Relic: Lightning strike
|
|
const lightning = this.activeRelics.find(r => r.id === 'relic_lightning');
|
|
if (lightning) {
|
|
lightning.timer = (lightning.timer || 7) - dt;
|
|
if (lightning.timer <= 0) {
|
|
lightning.timer = 7;
|
|
const target = gameState.zombies.find(z => z.health > 0 && !z.toRemove);
|
|
if (target) {
|
|
target.takeDamage(160);
|
|
gameState.combat.createExplosionParticles(target.x, target.y);
|
|
if (window.PvZAudio) window.PvZAudio.playStrike();
|
|
}
|
|
}
|
|
}
|
|
|
|
// 2. Relic: Toxic soil poison damage
|
|
if (this.hasRelic('relic_toxic_soil')) {
|
|
for (const z of gameState.zombies) {
|
|
if (z.health > 0 && !z.toRemove) {
|
|
z.takeDamage(6 * dt);
|
|
z.speedModifier = Math.min(z.speedModifier, 0.85);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Draw active relic badges in HUD
|
|
draw(ctx) {
|
|
if (this.activeRelics.length === 0) return;
|
|
|
|
ctx.save();
|
|
let rx = 580;
|
|
const ry = 52;
|
|
for (const relic of this.activeRelics) {
|
|
ctx.fillStyle = 'rgba(0, 0, 0, 0.55)';
|
|
ctx.beginPath();
|
|
ctx.roundRect(rx, ry, 26, 26, 4);
|
|
ctx.fill();
|
|
ctx.strokeStyle = '#ffd700';
|
|
ctx.lineWidth = 1.5;
|
|
ctx.stroke();
|
|
|
|
ctx.font = '14px sans-serif';
|
|
ctx.textAlign = 'center';
|
|
ctx.textBaseline = 'middle';
|
|
ctx.fillText(relic.icon, rx + 13, ry + 13);
|
|
rx += 30;
|
|
}
|
|
ctx.restore();
|
|
}
|
|
};
|