feat: Initial commit Plants vs Zombies Web Edition v2.4

- 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
This commit is contained in:
2026-09-17 09:30:52 +00:00
commit 6457a8904e
50 changed files with 14486 additions and 0 deletions
+204
View File
@@ -0,0 +1,204 @@
// 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();
}
};