feat(plants): Plant purchase system in Dave's Shop & pre-battle deck selection screen
This commit is contained in:
1 parent
4e450d758b
commit
84c28addfe
7 files changed
+889
-66
No files matched your search
@@ -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
@@ -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() {
|
||||
|
||||
Reference in new issue
Block a user