feat(plants): Plant purchase system in Dave's Shop & pre-battle deck selection screen

This commit is contained in:
Antigravity committed 2026-09-17 10:30:57 +00:00
1 parent 4e450d758b
commit 84c28addfe
7 files changed
+889 -66

No files matched your search

+270 -4
View File
@@ -422,7 +422,14 @@ window.GameEngine = class GameEngine {
} else if (this.currentMode === 'roguelike') {
startLvl = this.roguelike ? this.roguelike.currentFloor : 1;
}
this.startLevel(startLvl, this.currentMode);
// Fixed-conveyor mini-games launch immediately, standard modes open deck builder
if (this.currentMode === 'bowling' || this.currentMode === 'izombie' || this.currentMode === 'boss' || this.currentMode === 'bossrush') {
this.startLevel(startLvl, this.currentMode);
} else {
this.openSeedSelectionModal(startLvl, this.currentMode);
}
if (!this.loopStarted) {
this.loopStarted = true;
requestAnimationFrame(this.loop.bind(this));
@@ -1102,10 +1109,32 @@ window.GameEngine = class GameEngine {
}
// Shop Button
// Inventory / Deck Builder Buttons
const btnInvStart = document.getElementById('btn-inventory-start');
const btnDrawerInv = document.getElementById('btn-drawer-inventory');
if (btnInvStart) {
btnInvStart.addEventListener('click', () => {
this.openSeedSelectionModal(this.currentLevel || 1, this.currentMode || 'adventure');
});
}
if (btnDrawerInv) {
btnDrawerInv.addEventListener('click', () => {
const drawer = document.getElementById('mobile-drawer');
const backdrop = document.getElementById('drawer-backdrop');
if (drawer) drawer.classList.add('hidden');
if (backdrop) backdrop.classList.add('hidden');
this.openSeedSelectionModal(this.currentLevel || 1, this.currentMode || 'adventure');
});
}
// Shop Button & Tabs
const btnShop = document.getElementById('btn-shop');
const shopModal = document.getElementById('shop-modal');
const btnCloseShop = document.getElementById('btn-close-shop');
const tabShopPlants = document.getElementById('tab-shop-plants');
const tabShopUpgrades = document.getElementById('tab-shop-upgrades');
const panelShopPlants = document.getElementById('shop-plants-panel');
const panelShopUpgrades = document.getElementById('shop-upgrades-panel');
if (btnShop && shopModal) {
btnShop.addEventListener('click', () => {
@@ -1120,6 +1149,73 @@ window.GameEngine = class GameEngine {
});
}
if (tabShopPlants && tabShopUpgrades && panelShopPlants && panelShopUpgrades) {
tabShopPlants.addEventListener('click', () => {
tabShopPlants.classList.add('active');
tabShopUpgrades.classList.remove('active');
panelShopPlants.classList.remove('hidden');
panelShopUpgrades.classList.add('hidden');
});
tabShopUpgrades.addEventListener('click', () => {
tabShopUpgrades.classList.add('active');
tabShopPlants.classList.remove('active');
panelShopUpgrades.classList.remove('hidden');
panelShopPlants.classList.add('hidden');
});
}
// Pre-Battle Seed Selection & Inventory Modal Handlers
const seedSelectModal = document.getElementById('seed-select-modal');
const btnSeedStart = document.getElementById('btn-seed-start-battle');
const btnSeedAuto = document.getElementById('btn-seed-auto-fill');
const btnSeedShop = document.getElementById('btn-seed-open-shop');
const btnSeedCancel = document.getElementById('btn-seed-cancel');
if (btnSeedStart) {
btnSeedStart.addEventListener('click', () => {
if (!this.economy.selectedDeck || this.economy.selectedDeck.length === 0) {
this.economy.autoFillDeck();
}
if (seedSelectModal) seedSelectModal.classList.add('hidden');
const startModal = document.getElementById('start-modal');
if (startModal) startModal.classList.add('hidden');
if (window.PvZAudio) window.PvZAudio.init();
const lvl = this.pendingLevel || this.currentLevel || 1;
const mode = this.pendingMode || this.currentMode || 'adventure';
this.startLevel(lvl, mode);
if (!this.loopStarted) {
this.loopStarted = true;
requestAnimationFrame(this.loop.bind(this));
}
});
}
if (btnSeedAuto) {
btnSeedAuto.addEventListener('click', () => {
this.economy.autoFillDeck();
this.renderSeedSelectionUI();
if (window.PvZAudio) window.PvZAudio.playButtonClick();
});
}
if (btnSeedShop) {
btnSeedShop.addEventListener('click', () => {
if (seedSelectModal) seedSelectModal.classList.add('hidden');
this.updateShopUI();
if (shopModal) shopModal.classList.remove('hidden');
});
}
if (btnSeedCancel) {
btnSeedCancel.addEventListener('click', () => {
if (seedSelectModal) seedSelectModal.classList.add('hidden');
const startModal = document.getElementById('start-modal');
if (startModal) startModal.classList.remove('hidden');
});
}
// Shop Item Purchases
const btnBuySlot = document.getElementById('btn-buy-slot');
if (btnBuySlot) {
@@ -1128,6 +1224,7 @@ window.GameEngine = class GameEngine {
if (window.PvZAudio) window.PvZAudio.playWin();
this.saveUserData();
this.updateShopUI();
this.renderSeedSelectionUI();
}
});
}
@@ -1544,7 +1641,11 @@ window.GameEngine = class GameEngine {
if (startModal) startModal.classList.add('hidden');
if (window.PvZAudio) window.PvZAudio.init();
this.startLevel(targetLevel, 'adventure');
if (targetLevel === 25) {
this.startLevel(targetLevel, 'adventure');
} else {
this.openSeedSelectionModal(targetLevel, 'adventure');
}
if (!this.loopStarted) {
this.loopStarted = true;
requestAnimationFrame(this.loop.bind(this));
@@ -1566,9 +1667,126 @@ window.GameEngine = class GameEngine {
});
}
openSeedSelectionModal(targetLevel = null, targetMode = null) {
this.pendingLevel = targetLevel !== null ? targetLevel : (this.currentLevel || 1);
this.pendingMode = targetMode !== null ? targetMode : (this.currentMode || 'adventure');
this.renderSeedSelectionUI();
const modal = document.getElementById('seed-select-modal');
if (modal) modal.classList.remove('hidden');
const startModal = document.getElementById('start-modal');
if (startModal) startModal.classList.add('hidden');
const campaignModal = document.getElementById('campaign-modal');
if (campaignModal) campaignModal.classList.add('hidden');
}
renderSeedSelectionUI() {
const activeDeckContainer = document.getElementById('seed-active-deck');
const inventoryContainer = document.getElementById('seed-inventory-grid');
const deckCountEl = document.getElementById('seed-deck-count');
const deckMaxEl = document.getElementById('seed-deck-max');
const maxSlots = this.economy.getMaxSlots();
const selectedDeck = this.economy.selectedDeck || [];
if (deckCountEl) deckCountEl.textContent = selectedDeck.length;
if (deckMaxEl) deckMaxEl.textContent = maxSlots;
// 1. Render Active Deck Slots
if (activeDeckContainer) {
activeDeckContainer.innerHTML = '';
selectedDeck.forEach(plantId => {
const p = window.PLANT_CATALOGUE.find(item => item.id === plantId);
if (!p) return;
const slot = document.createElement('div');
slot.className = 'seed-slot-card';
slot.title = `Нажмите, чтобы убрать ${p.name} из колоды`;
slot.innerHTML = `
<div class="slot-remove-badge">✕</div>
<div class="slot-icon">${p.icon}</div>
<div class="slot-cost">☀️ ${p.cost}</div>
`;
slot.addEventListener('click', () => {
this.economy.removeFromDeck(plantId);
this.renderSeedSelectionUI();
if (window.PvZAudio) window.PvZAudio.playButtonClick();
});
activeDeckContainer.appendChild(slot);
});
// Render Empty Slots up to maxSlots
for (let i = selectedDeck.length; i < maxSlots; i++) {
const emptySlot = document.createElement('div');
emptySlot.className = 'seed-slot-empty';
emptySlot.textContent = `+ Слот ${i + 1}`;
activeDeckContainer.appendChild(emptySlot);
}
}
// 2. Render Inventory Grid
if (inventoryContainer && window.PLANT_CATALOGUE) {
inventoryContainer.innerHTML = '';
window.PLANT_CATALOGUE.forEach(p => {
const isUnlocked = this.economy.isPlantUnlocked(p.id);
const isInDeck = selectedDeck.includes(p.id);
const canAfford = this.economy.totalCoins >= (p.price || 0);
const item = document.createElement('div');
item.className = `seed-inv-item ${isUnlocked ? (isInDeck ? 'in-deck' : 'unlocked') : 'locked'}`;
let statusBadge = '';
if (isInDeck) {
statusBadge = '<span class="seed-inv-status status-deck">✓ В колоде</span>';
} else if (isUnlocked) {
statusBadge = '<span class="seed-inv-status status-ready">+ Выбрать</span>';
} else {
statusBadge = `<button type="button" class="btn btn-sm btn-shop" style="font-size: 10px; padding: 2px 6px; margin-top: 3px;" ${canAfford ? '' : 'disabled'}>🔒 ${p.price} 💰</button>`;
}
item.innerHTML = `
<div class="seed-inv-icon">${p.icon}</div>
<div class="seed-inv-name" title="${p.name}">${p.name}</div>
<div class="seed-inv-sun">☀️ ${p.cost}</div>
${statusBadge}
`;
if (isUnlocked) {
item.addEventListener('click', () => {
if (isInDeck) {
this.economy.removeFromDeck(p.id);
} else {
this.economy.addToDeck(p.id);
}
this.renderSeedSelectionUI();
if (window.PvZAudio) window.PvZAudio.playButtonClick();
});
} else {
const buyBtn = item.querySelector('button');
if (buyBtn) {
buyBtn.addEventListener('click', (e) => {
e.stopPropagation();
const res = this.economy.buyPlant(p.id);
if (res.success) {
if (window.PvZAudio) window.PvZAudio.playCoin();
this.saveUserData();
this.updateShopUI();
this.renderSeedSelectionUI();
}
});
}
}
inventoryContainer.appendChild(item);
});
}
}
updateShopUI() {
const elCoins = document.getElementById('shop-coins');
if (elCoins) elCoins.textContent = `💰 Монеты: ${this.economy.totalCoins}`;
if (elCoins) elCoins.textContent = `💰 Монеты: ${this.economy.totalCoins.toLocaleString('ru-RU')}`;
const btnBuySlot = document.getElementById('btn-buy-slot');
if (btnBuySlot) {
@@ -1581,6 +1799,54 @@ window.GameEngine = class GameEngine {
btnBuyRake.textContent = this.economy.upgrades.hasRake ? 'Куплено ✔️' : 'Купить (150 💰)';
btnBuyRake.disabled = this.economy.upgrades.hasRake || this.economy.totalCoins < 150;
}
// Populate Shop Plants Grid
const shopPlantsGrid = document.getElementById('shop-plants-grid');
if (shopPlantsGrid && window.PLANT_CATALOGUE) {
shopPlantsGrid.innerHTML = '';
window.PLANT_CATALOGUE.forEach(p => {
const isBasic = this.economy.defaultUnlocked.includes(p.id);
const isUnlocked = this.economy.isPlantUnlocked(p.id);
const canAfford = this.economy.totalCoins >= (p.price || 0);
const card = document.createElement('div');
card.className = 'shop-plant-card';
let actionHtml = '';
if (isBasic) {
actionHtml = '<span class="badge-purchased" style="background: rgba(33, 150, 243, 0.25); border-color: #64b5f6; color: #90caf9;">Базовое ✅</span>';
} else if (isUnlocked) {
actionHtml = '<span class="badge-purchased">Куплено ✅</span>';
} else {
actionHtml = `<button type="button" class="btn-buy" data-plant-id="${p.id}" ${canAfford ? '' : 'disabled'}>Купить (${p.price} 💰)</button>`;
}
card.innerHTML = `
<div class="shop-plant-icon">${p.icon}</div>
<div class="shop-plant-info">
<h4>${p.name}</h4>
<div class="shop-plant-cost">☀️ ${p.cost} солнца</div>
<p>${p.desc}</p>
</div>
<div>${actionHtml}</div>
`;
const buyBtn = card.querySelector('button.btn-buy');
if (buyBtn) {
buyBtn.addEventListener('click', () => {
const res = this.economy.buyPlant(p.id);
if (res.success) {
if (window.PvZAudio) window.PvZAudio.playCoin();
this.saveUserData();
this.updateShopUI();
this.renderSeedSelectionUI();
}
});
}
shopPlantsGrid.appendChild(card);
});
}
}
updateWorkshopUI() {
+8 -4
View File
@@ -48,6 +48,8 @@ window.PvZAuth = (function () {
highScore: 0,
theme: 'classic',
upgrades: { extraSlot: false, hasRake: false },
unlockedPlants: ['sunflower', 'peashooter', 'wallnut'],
selectedDeck: ['sunflower', 'peashooter', 'wallnut'],
materials: { bioHumus: 10, scrapMetal: 5, sunEssence: 20, crystals: 0 },
plantLevels: { peashooter: 1, sunflower: 1, wallnut: 1, potatomine: 1,
repeater: 1, snowpea: 1, chomper: 1 },
@@ -113,10 +115,12 @@ window.PvZAuth = (function () {
return {
...def,
...serverData,
upgrades: { ...def.upgrades, ...(serverData.upgrades || {}) },
materials: { ...def.materials, ...(serverData.materials || {}) },
plantLevels: { ...def.plantLevels, ...(serverData.plantLevels || {}) },
perks: { ...def.perks, ...(serverData.perks || {}) },
upgrades: { ...def.upgrades, ...(serverData.upgrades || {}) },
unlockedPlants: serverData.unlockedPlants || def.unlockedPlants,
selectedDeck: serverData.selectedDeck || def.selectedDeck,
materials: { ...def.materials, ...(serverData.materials || {}) },
plantLevels: { ...def.plantLevels, ...(serverData.plantLevels || {}) },
perks: { ...def.perks, ...(serverData.perks || {}) },
zenGarden: serverData.zenGarden
? {
...def.zenGarden,
+167 -27
View File
@@ -1,3 +1,23 @@
// Plant Catalogue for Purchase & Inventory Selection
window.PLANT_CATALOGUE = [
{ id: 'sunflower', name: 'Подсолнух', icon: '🌻', cost: 50, cooldown: 7.5, price: 0, defaultUnlocked: true, desc: 'Производит дополнительное солнце для посадки растений.' },
{ id: 'peashooter', name: 'Горохострел', icon: '🌱', cost: 100, cooldown: 7.5, price: 0, defaultUnlocked: true, desc: 'Базовый защитник, стреляет горошинами по зомби.' },
{ id: 'wallnut', name: 'Стеноорех', icon: '🥜', cost: 50, cooldown: 20.0, price: 0, defaultUnlocked: true, desc: 'Прочная преграда, задерживает натиск зомби.' },
{ id: 'potatomine', name: 'Картофельная мина', icon: '🥔', cost: 25, cooldown: 18.0, price: 150, defaultUnlocked: false, desc: 'Взрывает первого наступившего зомби (взводится 14с).' },
{ id: 'snowpea', name: 'Ледяной горох', icon: '❄️', cost: 175, cooldown: 10.0, price: 350, defaultUnlocked: false, desc: 'Стреляет ледяным горохом, замедляет зомби на 50%.' },
{ id: 'repeater', name: 'Повторитель', icon: '🌿', cost: 200, cooldown: 7.5, price: 500, defaultUnlocked: false, desc: 'Выпускает по 2 горошины за каждый выстрел.' },
{ id: 'chomper', name: 'Зубастик', icon: '🐊', cost: 150, cooldown: 10.0, price: 450, defaultUnlocked: false, desc: 'Заглатывает зомби целиком, затем долго пережевывает.' },
{ id: 'cherrybomb', name: 'Вишневая бомба', icon: '🍒', cost: 150, cooldown: 25.0, price: 400, defaultUnlocked: false, desc: 'Взрывает всех врагов в области 3х3 клетки.' },
{ id: 'jalapeno', name: 'Халапеньо', icon: '🌶️', cost: 125, cooldown: 30.0, price: 450, defaultUnlocked: false, desc: 'Испепеляет всех зомби на целой линии газона.' },
{ id: 'sunshroom', name: 'Солнцегриб', icon: '☀️', cost: 25, cooldown: 7.5, price: 250, defaultUnlocked: false, desc: 'Дает маленькое солнце, а вырастая — полноценное.' },
{ id: 'fumeshroom', name: 'Дымогриб', icon: '🍄', cost: 75, cooldown: 7.5, price: 300, defaultUnlocked: false, desc: 'Стреляет пробивающими спорами через броню и щиты.' },
{ id: 'lilypad', name: 'Кувшинка', icon: '🍃', cost: 25, cooldown: 7.5, price: 200, defaultUnlocked: false, desc: 'Позволяет сажать наземные растения на водной линии.' },
{ id: 'gravebuster', name: 'Могилоед', icon: '🪦', cost: 75, cooldown: 15.0, price: 250, defaultUnlocked: false, desc: 'Пожирает могилы на ночном газоне.' },
{ id: 'flowerpot', name: 'Цветочный горшок', icon: '🪴', cost: 25, cooldown: 7.5, price: 200, defaultUnlocked: false, desc: 'Необходим для высадки растений на скатной крыше.' },
{ id: 'cabbagepult', name: 'Капустопульта', icon: '🥬', cost: 100, cooldown: 7.5, price: 400, defaultUnlocked: false, desc: 'Перебрасывает капусту через препятствия и щиты.' },
{ id: 'melonpult', name: 'Арбузопульта', icon: '🍉', cost: 300, cooldown: 15.0, price: 750, defaultUnlocked: false, desc: 'Наносит мощнейший сплэш-урон тяжелыми арбузами.' }
];
// Economy, Sun, Coin Drops & Crazy Dave's Shop System
window.EconomySystem = class EconomySystem {
constructor() {
@@ -10,45 +30,156 @@ window.EconomySystem = class EconomySystem {
this.isConveyor = false; // For Wall-nut bowling mini-game
this.isIZombie = false;
// Default unlocked starter plants (Sunflower, Peashooter, Wallnut)
this.defaultUnlocked = ['sunflower', 'peashooter', 'wallnut'];
this.unlockedPlants = [...this.defaultUnlocked];
this.selectedDeck = [...this.defaultUnlocked];
// Total coins & upgrades persistent storage
this.reloadProfileData();
// Full Card Registry
this.allCards = [
{ id: 'sunflower', name: 'Подсолнух', cost: 50, cooldown: 7.5, timer: 0 },
{ id: 'peashooter', name: 'Горохострел', cost: 100, cooldown: 7.5, timer: 0 },
{ id: 'potatomine', name: 'Мина', cost: 25, cooldown: 18.0, timer: 0 },
{ id: 'wallnut', name: 'Стеноорех', cost: 50, cooldown: 20.0, timer: 0 },
{ id: 'repeater', name: 'Повторитель', cost: 200, cooldown: 7.5, timer: 0 },
{ id: 'snowpea', name: 'Ледяной', cost: 175, cooldown: 10.0, timer: 0 },
{ id: 'chomper', name: 'Зубастик', cost: 150, cooldown: 10.0, timer: 0 },
{ id: 'cherrybomb', name: 'Вишня', cost: 150, cooldown: 25.0, timer: 0 },
{ id: 'jalapeno', name: 'Халапеньо', cost: 125, cooldown: 30.0, timer: 0 },
{ id: 'sunshroom', name: 'Солнцегриб', cost: 25, cooldown: 7.5, timer: 0 },
{ id: 'fumeshroom', name: 'Дымогриб', cost: 75, cooldown: 7.5, timer: 0 },
{ id: 'lilypad', name: 'Кувшинка', cost: 25, cooldown: 7.5, timer: 0 },
{ id: 'gravebuster', name: 'Могилоед', cost: 75, cooldown: 15.0, timer: 0 },
{ id: 'flowerpot', name: 'Горшок', cost: 25, cooldown: 7.5, timer: 0 },
{ id: 'cabbagepult', name: 'Капуста', cost: 100, cooldown: 7.5, timer: 0 },
{ id: 'melonpult', name: 'Арбуз', cost: 300, cooldown: 15.0, timer: 0 }
];
this.allCards = window.PLANT_CATALOGUE.map(p => ({
id: p.id,
name: p.name,
cost: p.cost,
cooldown: p.cooldown,
timer: 0
}));
this.selectedCard = null;
this.animTime = 0;
this.setDeckForLevel(1, 'day');
}
getMaxSlots() {
return this.upgrades && this.upgrades.extraSlot ? 6 : 5;
}
isPlantUnlocked(plantId) {
if (this.defaultUnlocked.includes(plantId)) return true;
return this.unlockedPlants && this.unlockedPlants.includes(plantId);
}
buyPlant(plantId) {
const p = window.PLANT_CATALOGUE.find(item => item.id === plantId);
if (!p) return { success: false, message: 'Растение не найдено' };
if (this.isPlantUnlocked(plantId)) return { success: false, message: 'Растение уже куплено' };
const price = p.price || 0;
if (this.totalCoins < price) {
return { success: false, message: `Недостаточно монет! Требуется ${price} 💰 (у вас: ${this.totalCoins} 💰)` };
}
this.addCoins(-price);
if (!this.unlockedPlants.includes(plantId)) {
this.unlockedPlants.push(plantId);
}
if (this.selectedDeck.length < this.getMaxSlots() && !this.selectedDeck.includes(plantId)) {
this.selectedDeck.push(plantId);
}
this.saveProfileData();
return { success: true, plant: p };
}
addToDeck(plantId) {
if (!this.isPlantUnlocked(plantId)) {
return { success: false, message: 'Сначала купите это растение в магазине!' };
}
if (this.selectedDeck.includes(plantId)) {
return { success: false, message: 'Растение уже добавлено в колоду' };
}
if (this.selectedDeck.length >= this.getMaxSlots()) {
return { success: false, message: `Колода заполнена (максимум ${this.getMaxSlots()} слотов)` };
}
this.selectedDeck.push(plantId);
this.saveProfileData();
return { success: true };
}
removeFromDeck(plantId) {
const idx = this.selectedDeck.indexOf(plantId);
if (idx !== -1) {
this.selectedDeck.splice(idx, 1);
this.saveProfileData();
return { success: true };
}
return { success: false, message: 'Растение не найдено в колоде' };
}
autoFillDeck() {
const priority = [
'sunflower', 'peashooter', 'wallnut', 'potatomine', 'repeater',
'snowpea', 'chomper', 'cherrybomb', 'jalapeno', 'cabbagepult',
'melonpult', 'fumeshroom', 'sunshroom', 'lilypad', 'flowerpot', 'gravebuster'
];
for (const id of priority) {
if (this.selectedDeck.length >= this.getMaxSlots()) break;
if (this.isPlantUnlocked(id) && !this.selectedDeck.includes(id)) {
this.selectedDeck.push(id);
}
}
this.saveProfileData();
}
setDeckFromSelection(deckIds = null) {
if (Array.isArray(deckIds) && deckIds.length > 0) {
this.selectedDeck = deckIds.filter(id => this.isPlantUnlocked(id)).slice(0, this.getMaxSlots());
this.saveProfileData();
}
const chosen = this.selectedDeck.length > 0 ? this.selectedDeck : this.defaultUnlocked;
this.cards = chosen.map(id => {
const found = this.allCards.find(c => c.id === id);
return found ? { ...found, timer: 0 } : null;
}).filter(Boolean);
}
reloadProfileData() {
if (window.PvZAuth) {
const uData = window.PvZAuth.getUserData();
this.totalCoins = uData.coins !== undefined ? uData.coins : 250;
this.upgrades = uData.upgrades || { extraSlot: false, hasRake: false };
if (Array.isArray(uData.unlockedPlants) && uData.unlockedPlants.length > 0) {
this.unlockedPlants = Array.from(new Set([...this.defaultUnlocked, ...uData.unlockedPlants]));
} else {
// Check localStorage fallback
try {
const raw = localStorage.getItem('pvz_unlocked_plants');
this.unlockedPlants = raw ? Array.from(new Set([...this.defaultUnlocked, ...JSON.parse(raw)])) : [...this.defaultUnlocked];
} catch (_) {
this.unlockedPlants = [...this.defaultUnlocked];
}
}
if (Array.isArray(uData.selectedDeck) && uData.selectedDeck.length > 0) {
this.selectedDeck = uData.selectedDeck.filter(id => this.isPlantUnlocked(id)).slice(0, this.getMaxSlots());
} else {
try {
const rawDeck = localStorage.getItem('pvz_selected_deck');
this.selectedDeck = rawDeck ? JSON.parse(rawDeck).slice(0, this.getMaxSlots()) : [...this.defaultUnlocked];
} catch (_) {
this.selectedDeck = [...this.defaultUnlocked];
}
}
} else {
this.totalCoins = parseInt(localStorage.getItem('pvz_coins') || '250', 10);
this.upgrades = {
extraSlot: localStorage.getItem('pvz_upg_slot') === 'true',
hasRake: localStorage.getItem('pvz_upg_rake') === 'true'
};
try {
const raw = localStorage.getItem('pvz_unlocked_plants');
this.unlockedPlants = raw ? Array.from(new Set([...this.defaultUnlocked, ...JSON.parse(raw)])) : [...this.defaultUnlocked];
} catch (_) {
this.unlockedPlants = [...this.defaultUnlocked];
}
try {
const rawDeck = localStorage.getItem('pvz_selected_deck');
this.selectedDeck = rawDeck ? JSON.parse(rawDeck).slice(0, this.getMaxSlots()) : [...this.defaultUnlocked];
} catch (_) {
this.selectedDeck = [...this.defaultUnlocked];
}
}
}
@@ -56,10 +187,15 @@ window.EconomySystem = class EconomySystem {
localStorage.setItem('pvz_coins', this.totalCoins.toString());
localStorage.setItem('pvz_upg_slot', this.upgrades.extraSlot ? 'true' : 'false');
localStorage.setItem('pvz_upg_rake', this.upgrades.hasRake ? 'true' : 'false');
localStorage.setItem('pvz_unlocked_plants', JSON.stringify(this.unlockedPlants));
localStorage.setItem('pvz_selected_deck', JSON.stringify(this.selectedDeck));
if (window.PvZAuth && !window.PvZAuth.isGuest()) {
window.PvZAuth.saveUserData({
coins: this.totalCoins,
upgrades: this.upgrades
upgrades: this.upgrades,
unlockedPlants: this.unlockedPlants,
selectedDeck: this.selectedDeck
});
}
}
@@ -69,26 +205,30 @@ window.EconomySystem = class EconomySystem {
this.isConveyor = false;
this.isIZombie = false;
// Card slots count (5 default, 6 with Dave's extra slot upgrade)
const slotCount = this.upgrades.extraSlot ? 6 : 5;
// Use player selected deck if configured and valid
if (this.selectedDeck && this.selectedDeck.length > 0) {
this.setDeckFromSelection();
return;
}
// Choose relevant deck based on biome and progression
// Fallback: choose relevant deck based on unlocked plants and biome
const slotCount = this.getMaxSlots();
let availableIds = [];
if (biome === 'roof') {
availableIds = ['flowerpot', 'cabbagepult', 'melonpult', 'wallnut', 'jalapeno', 'potatomine'];
} else if (biome === 'fog') {
availableIds = ['sunshroom', 'fumeshroom', 'lilypad', 'cabbagepult', 'wallnut', 'snowpea'];
} else if (biome === 'night') {
availableIds = ['sunshroom', 'fumeshroom', 'potatomine', 'wallnut', 'gravebuster', 'hypnoshroom'];
availableIds = ['sunshroom', 'fumeshroom', 'potatomine', 'wallnut', 'gravebuster'];
} else if (biome === 'pool') {
availableIds = ['sunflower', 'peashooter', 'lilypad', 'wallnut', 'repeater', 'snowpea'];
} else {
if (level === 1) availableIds = ['sunflower', 'peashooter', 'potatomine', 'wallnut', 'cherrybomb'];
else if (level === 2) availableIds = ['sunflower', 'peashooter', 'repeater', 'wallnut', 'jalapeno'];
else availableIds = ['sunflower', 'peashooter', 'snowpea', 'chomper', 'wallnut', 'jalapeno'];
availableIds = ['sunflower', 'peashooter', 'wallnut', 'potatomine', 'snowpea', 'repeater', 'chomper', 'cherrybomb'];
}
this.cards = this.allCards.filter(c => availableIds.includes(c.id)).slice(0, slotCount);
const filtered = availableIds.filter(id => this.isPlantUnlocked(id));
const deckIds = filtered.length > 0 ? filtered.slice(0, slotCount) : this.defaultUnlocked;
this.setDeckFromSelection(deckIds);
}
setBossDeck() {