- 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
351 lines
14 KiB
JavaScript
351 lines
14 KiB
JavaScript
// Workshop System (Crafting, 3-Tier Plant Upgrades, and Passive Perks)
|
|
window.WorkshopSystem = class WorkshopSystem {
|
|
constructor(economy) {
|
|
this.economy = economy;
|
|
|
|
// Default Materials
|
|
this.materials = {
|
|
bioHumus: 10,
|
|
scrapMetal: 5,
|
|
sunEssence: 20,
|
|
crystals: 0
|
|
};
|
|
|
|
// Plant Upgrade Levels (1, 2, 3)
|
|
this.plantLevels = {
|
|
peashooter: 1,
|
|
sunflower: 1,
|
|
wallnut: 1,
|
|
potatomine: 1,
|
|
repeater: 1,
|
|
snowpea: 1,
|
|
chomper: 1
|
|
};
|
|
|
|
// Passive Perks (0, 1, 2, 3)
|
|
this.perks = {
|
|
magnetism: 0,
|
|
startSun: 0,
|
|
fastCool: 0,
|
|
hardyRoots: 0,
|
|
luckyDrop: 0
|
|
};
|
|
|
|
// Flying loot items on canvas
|
|
this.floatingLoot = [];
|
|
|
|
// Upgrade Definitions
|
|
this.plantDefs = {
|
|
peashooter: {
|
|
name: 'Горохострел',
|
|
icon: '🌱',
|
|
tiers: {
|
|
1: { title: 'Базовый', desc: 'Урон 20, интервал 1.4с' },
|
|
2: { title: 'Скорострел', desc: 'Урон 25, атака +20%, крит 15% (x2 урон)', cost: { coins: 150, bioHumus: 8 } },
|
|
3: { title: 'Плазменный стрелок', desc: 'Пробивает 1-го зомби насквозь и ранит следующего', cost: { coins: 400, bioHumus: 20, crystals: 1 } }
|
|
}
|
|
},
|
|
sunflower: {
|
|
name: 'Подсолнух',
|
|
icon: '🌻',
|
|
tiers: {
|
|
1: { title: 'Базовый', desc: 'Дает 25 ☀️ каждые 12с' },
|
|
2: { title: 'Крупный подсолнух', desc: 'Дает 35 ☀️ каждые 10с', cost: { coins: 120, sunEssence: 15 } },
|
|
3: { title: 'Благодатный свет', desc: 'При генерации лечит +30 HP соседним растениям', cost: { coins: 350, sunEssence: 35, crystals: 1 } }
|
|
}
|
|
},
|
|
wallnut: {
|
|
name: 'Стеноорех',
|
|
icon: '🌰',
|
|
tiers: {
|
|
1: { title: 'Базовый', desc: '400 HP' },
|
|
2: { title: 'Каменный монолит', desc: '700 HP + 15% снижения входящего урона', cost: { coins: 150, scrapMetal: 10 } },
|
|
3: { title: 'Шипованный бастион', desc: '1000 HP + возврат 30% урона кусающим зомби', cost: { coins: 400, scrapMetal: 25, crystals: 1 } }
|
|
}
|
|
},
|
|
potatomine: {
|
|
name: 'Картофельная мина',
|
|
icon: '🥔',
|
|
tiers: {
|
|
1: { title: 'Базовый', desc: 'Взведение 15с, взрыв в клетке' },
|
|
2: { title: 'Быстрый фитиль', desc: 'Взведение всего за 6 секунд!', cost: { coins: 120, bioHumus: 8, scrapMetal: 5 } },
|
|
3: { title: 'Атомный Spudow', desc: 'Взрыв 3x3 + горящая лужа лавы на 5с', cost: { coins: 350, bioHumus: 18, scrapMetal: 15, crystals: 1 } }
|
|
}
|
|
},
|
|
repeater: {
|
|
name: 'Повторитель',
|
|
icon: '🌿',
|
|
tiers: {
|
|
1: { title: 'Базовый', desc: '2 горошины за выстрел' },
|
|
2: { title: 'Тройной залп', desc: '2 горошины + 20% шанс выпустить 3-ю', cost: { coins: 250, bioHumus: 15, sunEssence: 10 } },
|
|
3: { title: 'Огненный повторитель', desc: 'Горошины пылают и наносят сплэш-урон', cost: { coins: 600, bioHumus: 30, sunEssence: 25, crystals: 2 } }
|
|
}
|
|
},
|
|
snowpea: {
|
|
name: 'Ледяной горох',
|
|
icon: '❄️',
|
|
tiers: {
|
|
1: { title: 'Базовый', desc: 'Замедление 4с, урон 20' },
|
|
2: { title: 'Вечная мерзлота', desc: 'Холод 6с + замедляет соседей сзади', cost: { coins: 250, bioHumus: 15, crystals: 1 } },
|
|
3: { title: 'Ледяная глыба', desc: 'Шанс 15% полностью обратить зомби в лед на 2с', cost: { coins: 600, bioHumus: 30, crystals: 3 } }
|
|
}
|
|
},
|
|
chomper: {
|
|
name: 'Зубастик',
|
|
icon: '🌺',
|
|
tiers: {
|
|
1: { title: 'Базовый', desc: 'Заглатывает зомби, жевание 30с' },
|
|
2: { title: 'Острые клыки', desc: 'Скорость жевания ускорена до 14с', cost: { coins: 200, bioHumus: 15, scrapMetal: 10 } },
|
|
3: { title: 'Двойной укус', desc: 'Способен проглотить сразу двух зомби подряд!', cost: { coins: 500, bioHumus: 30, scrapMetal: 20, crystals: 1 } }
|
|
}
|
|
}
|
|
};
|
|
|
|
this.perkDefs = {
|
|
magnetism: {
|
|
name: 'Магнитный газон',
|
|
icon: '🧲',
|
|
desc: 'Автоматически притягивает монеты, солнце и материалы',
|
|
tiers: {
|
|
1: { title: 'Ур. 1', desc: 'Радиус сбора +40px', cost: { coins: 150, scrapMetal: 5 } },
|
|
2: { title: 'Ур. 2', desc: 'Радиус сбора +90px', cost: { coins: 300, scrapMetal: 12 } },
|
|
3: { title: 'Ур. 3', desc: 'Полный автосбор со всего экрана!', cost: { coins: 600, scrapMetal: 25, crystals: 1 } }
|
|
}
|
|
},
|
|
startSun: {
|
|
name: 'Стартовый капитал',
|
|
icon: '☀️',
|
|
desc: 'Дополнительное солнце в начале каждого раунда',
|
|
tiers: {
|
|
1: { title: 'Ур. 1', desc: '+50 начального солнца', cost: { coins: 150, sunEssence: 15 } },
|
|
2: { title: 'Ур. 2', desc: '+100 начального солнца', cost: { coins: 350, sunEssence: 30 } },
|
|
3: { title: 'Ур. 3', desc: '+150 начального солнца', cost: { coins: 700, sunEssence: 60, crystals: 1 } }
|
|
}
|
|
},
|
|
fastCool: {
|
|
name: 'Ускоренный полив',
|
|
icon: '⏳',
|
|
desc: 'Сокращает время перезарядки всех пакетов семян',
|
|
tiers: {
|
|
1: { title: 'Ур. 1', desc: 'Перезарядка -10%', cost: { coins: 200, bioHumus: 10 } },
|
|
2: { title: 'Ур. 2', desc: 'Перезарядка -20%', cost: { coins: 450, bioHumus: 25 } },
|
|
3: { title: 'Ур. 3', desc: 'Перезарядка -30%', cost: { coins: 900, bioHumus: 50, crystals: 2 } }
|
|
}
|
|
},
|
|
hardyRoots: {
|
|
name: 'Упорные корни',
|
|
icon: '🛡️',
|
|
desc: 'Увеличивает максимальное здоровье всех растений на газоне',
|
|
tiers: {
|
|
1: { title: 'Ур. 1', desc: '+20% к запасу здоровья', cost: { coins: 180, scrapMetal: 10, bioHumus: 10 } },
|
|
2: { title: 'Ур. 2', desc: '+35% к запасу здоровья', cost: { coins: 400, scrapMetal: 20, bioHumus: 20 } },
|
|
3: { title: 'Ур. 3', desc: '+50% к запасу здоровья', cost: { coins: 800, scrapMetal: 40, bioHumus: 40, crystals: 1 } }
|
|
}
|
|
},
|
|
luckyDrop: {
|
|
name: 'Золотая рука',
|
|
icon: '🍀',
|
|
desc: 'Повышает шанс выпадения золотых монет и редких материалов',
|
|
tiers: {
|
|
1: { title: 'Ур. 1', desc: 'Шанс дропа +30%', cost: { coins: 200, crystals: 1 } },
|
|
2: { title: 'Ур. 2', desc: 'Шанс дропа +60%', cost: { coins: 500, crystals: 2 } },
|
|
3: { title: 'Ур. 3', desc: 'Шанс дропа +100%', cost: { coins: 1000, crystals: 4 } }
|
|
}
|
|
}
|
|
};
|
|
|
|
this.loadFromAuth();
|
|
}
|
|
|
|
loadFromAuth() {
|
|
const defaultMaterials = { bioHumus: 10, scrapMetal: 5, sunEssence: 20, crystals: 0 };
|
|
const defaultPlantLevels = { peashooter: 1, sunflower: 1, wallnut: 1, potatomine: 1, repeater: 1, snowpea: 1, chomper: 1 };
|
|
const defaultPerks = { magnetism: 0, startSun: 0, fastCool: 0, hardyRoots: 0, luckyDrop: 0 };
|
|
|
|
if (window.PvZAuth) {
|
|
const data = window.PvZAuth.getUserData();
|
|
this.materials = { ...defaultMaterials, ...(data.materials || {}) };
|
|
this.plantLevels = { ...defaultPlantLevels, ...(data.plantLevels || {}) };
|
|
this.perks = { ...defaultPerks, ...(data.perks || {}) };
|
|
} else {
|
|
this.materials = { ...defaultMaterials };
|
|
this.plantLevels = { ...defaultPlantLevels };
|
|
this.perks = { ...defaultPerks };
|
|
}
|
|
}
|
|
|
|
saveToAuth() {
|
|
if (window.PvZAuth && !window.PvZAuth.isGuest()) {
|
|
window.PvZAuth.saveUserData({
|
|
materials: this.materials,
|
|
plantLevels: this.plantLevels,
|
|
perks: this.perks,
|
|
coins: this.economy ? this.economy.totalCoins : undefined
|
|
});
|
|
}
|
|
}
|
|
|
|
getPlantLevel(plantType) {
|
|
return this.plantLevels[plantType] || 1;
|
|
}
|
|
|
|
getPerkLevel(perkId) {
|
|
return this.perks[perkId] || 0;
|
|
}
|
|
|
|
canAffordCost(cost) {
|
|
if (!cost) return false;
|
|
const coins = this.economy ? this.economy.totalCoins : 0;
|
|
if (cost.coins && coins < cost.coins) return false;
|
|
if (cost.bioHumus && this.materials.bioHumus < cost.bioHumus) return false;
|
|
if (cost.scrapMetal && this.materials.scrapMetal < cost.scrapMetal) return false;
|
|
if (cost.sunEssence && this.materials.sunEssence < cost.sunEssence) return false;
|
|
if (cost.crystals && this.materials.crystals < cost.crystals) return false;
|
|
return true;
|
|
}
|
|
|
|
payCost(cost) {
|
|
if (!this.canAffordCost(cost)) return false;
|
|
if (cost.coins && this.economy) this.economy.addCoins(-cost.coins);
|
|
if (cost.bioHumus) this.materials.bioHumus -= cost.bioHumus;
|
|
if (cost.scrapMetal) this.materials.scrapMetal -= cost.scrapMetal;
|
|
if (cost.sunEssence) this.materials.sunEssence -= cost.sunEssence;
|
|
if (cost.crystals) this.materials.crystals -= cost.crystals;
|
|
this.saveToAuth();
|
|
return true;
|
|
}
|
|
|
|
upgradePlant(plantId) {
|
|
const current = this.getPlantLevel(plantId);
|
|
if (current >= 3) return false;
|
|
|
|
const nextTier = current + 1;
|
|
const def = this.plantDefs[plantId];
|
|
if (!def || !def.tiers[nextTier]) return false;
|
|
|
|
const cost = def.tiers[nextTier].cost;
|
|
if (this.payCost(cost)) {
|
|
this.plantLevels[plantId] = nextTier;
|
|
this.saveToAuth();
|
|
if (nextTier === 3 && this.economy && this.economy.engine && this.economy.engine.achievements) {
|
|
this.economy.engine.achievements.trigger('tier3_unlocked');
|
|
}
|
|
if (window.PvZAudio) window.PvZAudio.playWin();
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
upgradePerk(perkId) {
|
|
const current = this.getPerkLevel(perkId);
|
|
if (current >= 3) return false;
|
|
|
|
const nextTier = current + 1;
|
|
const def = this.perkDefs[perkId];
|
|
if (!def || !def.tiers[nextTier]) return false;
|
|
|
|
const cost = def.tiers[nextTier].cost;
|
|
if (this.payCost(cost)) {
|
|
this.perks[perkId] = nextTier;
|
|
this.saveToAuth();
|
|
if (window.PvZAudio) window.PvZAudio.playWin();
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
addMaterial(type, amount = 1) {
|
|
if (this.materials[type] !== undefined) {
|
|
this.materials[type] += amount;
|
|
this.saveToAuth();
|
|
}
|
|
}
|
|
|
|
// Spawn visual drop on field
|
|
spawnLoot(x, y, type) {
|
|
this.floatingLoot.push({
|
|
x,
|
|
y,
|
|
startY: y,
|
|
targetY: Math.min(y + 25, 550),
|
|
type,
|
|
animTime: 0,
|
|
vy: -120,
|
|
collected: false,
|
|
alpha: 1.0
|
|
});
|
|
}
|
|
|
|
update(dt, gameState) {
|
|
// Check loot collection and animation
|
|
const mousePos = gameState.hoverCell ? gameState.grid.getCellBounds(gameState.hoverCell.col, gameState.hoverCell.row) : null;
|
|
const magLevel = this.getPerkLevel('magnetism');
|
|
const magnetRadius = magLevel === 3 ? 9999 : (magLevel === 2 ? 140 : (magLevel === 1 ? 80 : 35));
|
|
|
|
for (let i = this.floatingLoot.length - 1; i >= 0; i--) {
|
|
const loot = this.floatingLoot[i];
|
|
loot.animTime += dt;
|
|
|
|
if (!loot.collected) {
|
|
if (loot.vy < 0) {
|
|
loot.y += loot.vy * dt;
|
|
loot.vy += 300 * dt;
|
|
}
|
|
|
|
// Magnetism collection
|
|
if (gameState.hoverCell) {
|
|
const bounds = gameState.grid.getCellBounds(gameState.hoverCell.col, gameState.hoverCell.row);
|
|
const dist = Math.hypot(bounds.centerX - loot.x, bounds.centerY - loot.y);
|
|
if (dist <= magnetRadius) {
|
|
this.collectLoot(loot);
|
|
}
|
|
}
|
|
} else {
|
|
loot.y -= 100 * dt;
|
|
loot.alpha -= 2.0 * dt;
|
|
if (loot.alpha <= 0) {
|
|
this.floatingLoot.splice(i, 1);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
collectLoot(loot) {
|
|
if (loot.collected) return;
|
|
loot.collected = true;
|
|
this.addMaterial(loot.type, 1);
|
|
if (window.PvZAudio) window.PvZAudio.playCoin();
|
|
}
|
|
|
|
draw(ctx) {
|
|
for (const loot of this.floatingLoot) {
|
|
ctx.save();
|
|
ctx.globalAlpha = Math.max(0, loot.alpha);
|
|
ctx.translate(loot.x, loot.y);
|
|
|
|
// Bounce bobbing
|
|
const bob = Math.sin(loot.animTime * 6) * 3;
|
|
ctx.translate(0, bob);
|
|
|
|
// Glow circle
|
|
ctx.beginPath();
|
|
ctx.arc(0, 0, 16, 0, Math.PI * 2);
|
|
ctx.fillStyle = loot.type === 'crystals' ? 'rgba(0, 229, 255, 0.4)' :
|
|
(loot.type === 'sunEssence' ? 'rgba(255, 235, 59, 0.4)' :
|
|
(loot.type === 'scrapMetal' ? 'rgba(158, 158, 158, 0.4)' : 'rgba(139, 195, 74, 0.4)'));
|
|
ctx.fill();
|
|
|
|
// Icon
|
|
ctx.font = '16px sans-serif';
|
|
ctx.textAlign = 'center';
|
|
ctx.textBaseline = 'middle';
|
|
const icon = loot.type === 'crystals' ? '💎' :
|
|
(loot.type === 'sunEssence' ? '☀️' :
|
|
(loot.type === 'scrapMetal' ? '⚙️' : '🧪'));
|
|
ctx.fillText(icon, 0, 1);
|
|
|
|
ctx.restore();
|
|
}
|
|
}
|
|
};
|