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
+302
-11
@@ -386,8 +386,39 @@ canvas#gameCanvas {
|
|||||||
|
|
||||||
/* Shop Modal Styles */
|
/* Shop Modal Styles */
|
||||||
.shop-card {
|
.shop-card {
|
||||||
max-width: 520px;
|
max-width: 680px;
|
||||||
width: 90%;
|
width: 95%;
|
||||||
|
max-height: 90vh;
|
||||||
|
overflow-y: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.shop-tabs {
|
||||||
|
display: flex;
|
||||||
|
gap: 8px;
|
||||||
|
margin-bottom: 12px;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.shop-tab {
|
||||||
|
background: rgba(0, 0, 0, 0.4);
|
||||||
|
color: #cfd8dc;
|
||||||
|
border: 1.5px solid #8d6e63;
|
||||||
|
border-radius: 6px;
|
||||||
|
padding: 6px 14px;
|
||||||
|
font-size: 13px;
|
||||||
|
font-weight: bold;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all 0.2s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.shop-tab:hover {
|
||||||
|
background: rgba(255, 255, 255, 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.shop-tab.active {
|
||||||
|
background: #388e3c;
|
||||||
|
color: #fff;
|
||||||
|
border-color: #81c784;
|
||||||
}
|
}
|
||||||
|
|
||||||
.shop-items {
|
.shop-items {
|
||||||
@@ -397,30 +428,90 @@ canvas#gameCanvas {
|
|||||||
text-align: left;
|
text-align: left;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.shop-plants-grid {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
|
||||||
|
gap: 10px;
|
||||||
|
text-align: left;
|
||||||
|
}
|
||||||
|
|
||||||
.shop-item {
|
.shop-item {
|
||||||
background: rgba(0, 0, 0, 0.4);
|
background: rgba(0, 0, 0, 0.4);
|
||||||
border: 2px solid #8d6e63;
|
border: 2px solid #8d6e63;
|
||||||
border-radius: 8px;
|
border-radius: 8px;
|
||||||
padding: 12px 16px;
|
padding: 10px 14px;
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
gap: 12px;
|
gap: 12px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.shop-item-info h3 {
|
.shop-plant-card {
|
||||||
font-size: 15px;
|
background: rgba(0, 0, 0, 0.45);
|
||||||
color: #ffeb3b;
|
border: 1.5px solid #8d6e63;
|
||||||
margin-bottom: 4px;
|
border-radius: 8px;
|
||||||
|
padding: 10px 12px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
gap: 10px;
|
||||||
|
transition: border-color 0.2s, transform 0.15s;
|
||||||
}
|
}
|
||||||
|
|
||||||
.shop-item-info p {
|
.shop-plant-card:hover {
|
||||||
font-size: 12px;
|
border-color: #ffeb3b;
|
||||||
color: #ccc;
|
transform: translateY(-2px);
|
||||||
margin-bottom: 0;
|
}
|
||||||
|
|
||||||
|
.shop-plant-icon {
|
||||||
|
font-size: 28px;
|
||||||
|
width: 44px;
|
||||||
|
height: 44px;
|
||||||
|
border-radius: 6px;
|
||||||
|
background: rgba(255, 255, 255, 0.08);
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
border: 1px solid rgba(255, 255, 255, 0.2);
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.shop-plant-info {
|
||||||
|
flex: 1;
|
||||||
|
min-width: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.shop-plant-info h4 {
|
||||||
|
font-size: 14px;
|
||||||
|
color: #ffeb3b;
|
||||||
|
margin: 0 0 2px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.shop-plant-info p {
|
||||||
|
font-size: 11px;
|
||||||
|
color: #b0bec5;
|
||||||
|
margin: 0;
|
||||||
line-height: 1.2;
|
line-height: 1.2;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.shop-plant-cost {
|
||||||
|
font-size: 11px;
|
||||||
|
color: #ffd54f;
|
||||||
|
font-weight: bold;
|
||||||
|
margin-bottom: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.badge-purchased {
|
||||||
|
background: rgba(76, 175, 80, 0.25);
|
||||||
|
border: 1px solid #81c784;
|
||||||
|
color: #a5d6a7;
|
||||||
|
font-size: 11px;
|
||||||
|
font-weight: bold;
|
||||||
|
padding: 4px 8px;
|
||||||
|
border-radius: 4px;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
.btn-buy {
|
.btn-buy {
|
||||||
background: #4caf50;
|
background: #4caf50;
|
||||||
color: #fff;
|
color: #fff;
|
||||||
@@ -444,6 +535,206 @@ canvas#gameCanvas {
|
|||||||
cursor: not-allowed;
|
cursor: not-allowed;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Pre-Battle Seed Selection & Inventory Modal */
|
||||||
|
.seed-select-card {
|
||||||
|
max-width: 740px;
|
||||||
|
width: 95%;
|
||||||
|
max-height: 90vh;
|
||||||
|
overflow-y: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.seed-select-section-title {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
font-size: 13px;
|
||||||
|
font-weight: bold;
|
||||||
|
color: #ffeb3b;
|
||||||
|
margin-bottom: 6px;
|
||||||
|
border-bottom: 1px solid rgba(255, 235, 59, 0.3);
|
||||||
|
padding-bottom: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.seed-deck-bar {
|
||||||
|
display: flex;
|
||||||
|
gap: 8px;
|
||||||
|
padding: 8px 10px;
|
||||||
|
background: rgba(0, 0, 0, 0.4);
|
||||||
|
border: 1.5px solid #81c784;
|
||||||
|
border-radius: 8px;
|
||||||
|
min-height: 76px;
|
||||||
|
align-items: center;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
margin-bottom: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.seed-slot-card {
|
||||||
|
position: relative;
|
||||||
|
width: 62px;
|
||||||
|
height: 68px;
|
||||||
|
background: linear-gradient(135deg, #37474f, #263238);
|
||||||
|
border: 1.5px solid #81c784;
|
||||||
|
border-radius: 6px;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
cursor: pointer;
|
||||||
|
user-select: none;
|
||||||
|
transition: transform 0.15s, border-color 0.2s, box-shadow 0.2s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.seed-slot-card:hover {
|
||||||
|
transform: translateY(-3px) scale(1.04);
|
||||||
|
border-color: #ff5252;
|
||||||
|
box-shadow: 0 0 8px rgba(255, 82, 82, 0.5);
|
||||||
|
}
|
||||||
|
|
||||||
|
.seed-slot-card .slot-icon {
|
||||||
|
font-size: 24px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.seed-slot-card .slot-cost {
|
||||||
|
font-size: 10px;
|
||||||
|
font-weight: bold;
|
||||||
|
color: #ffeb3b;
|
||||||
|
background: rgba(0, 0, 0, 0.6);
|
||||||
|
padding: 1px 4px;
|
||||||
|
border-radius: 3px;
|
||||||
|
margin-top: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.seed-slot-card .slot-remove-badge {
|
||||||
|
position: absolute;
|
||||||
|
top: -5px;
|
||||||
|
right: -5px;
|
||||||
|
background: #f44336;
|
||||||
|
color: #fff;
|
||||||
|
font-size: 10px;
|
||||||
|
width: 16px;
|
||||||
|
height: 16px;
|
||||||
|
border-radius: 50%;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
font-weight: bold;
|
||||||
|
border: 1px solid #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.seed-slot-empty {
|
||||||
|
width: 62px;
|
||||||
|
height: 68px;
|
||||||
|
border: 2px dashed rgba(255, 255, 255, 0.25);
|
||||||
|
border-radius: 6px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
color: rgba(255, 255, 255, 0.35);
|
||||||
|
font-size: 11px;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
.seed-inventory-grid {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(auto-fill, minmax(130px, 1fr));
|
||||||
|
gap: 8px;
|
||||||
|
max-height: 300px;
|
||||||
|
overflow-y: auto;
|
||||||
|
padding: 6px;
|
||||||
|
background: rgba(0, 0, 0, 0.3);
|
||||||
|
border-radius: 8px;
|
||||||
|
border: 1px solid rgba(255, 255, 255, 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.seed-inv-item {
|
||||||
|
background: rgba(0, 0, 0, 0.4);
|
||||||
|
border: 1.5px solid #5d4037;
|
||||||
|
border-radius: 6px;
|
||||||
|
padding: 8px 6px;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all 0.2s;
|
||||||
|
user-select: none;
|
||||||
|
min-height: 94px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.seed-inv-item:hover:not(.locked) {
|
||||||
|
border-color: #69f0ae;
|
||||||
|
transform: translateY(-2px);
|
||||||
|
background: rgba(105, 240, 174, 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.seed-inv-item.in-deck {
|
||||||
|
border-color: #ffeb3b;
|
||||||
|
background: rgba(255, 235, 59, 0.15);
|
||||||
|
box-shadow: 0 0 6px rgba(255, 235, 59, 0.3);
|
||||||
|
}
|
||||||
|
|
||||||
|
.seed-inv-item.locked {
|
||||||
|
opacity: 0.65;
|
||||||
|
border-color: #424242;
|
||||||
|
cursor: default;
|
||||||
|
}
|
||||||
|
|
||||||
|
.seed-inv-icon {
|
||||||
|
font-size: 26px;
|
||||||
|
margin-bottom: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.seed-inv-name {
|
||||||
|
font-size: 11px;
|
||||||
|
font-weight: bold;
|
||||||
|
color: #fff;
|
||||||
|
text-align: center;
|
||||||
|
white-space: nowrap;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
max-width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.seed-inv-sun {
|
||||||
|
font-size: 10px;
|
||||||
|
color: #ffd54f;
|
||||||
|
margin-top: 1px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.seed-inv-status {
|
||||||
|
margin-top: 4px;
|
||||||
|
font-size: 10px;
|
||||||
|
font-weight: bold;
|
||||||
|
padding: 2px 6px;
|
||||||
|
border-radius: 3px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.seed-inv-status.status-deck {
|
||||||
|
background: #ffb300;
|
||||||
|
color: #000;
|
||||||
|
}
|
||||||
|
|
||||||
|
.seed-inv-status.status-ready {
|
||||||
|
background: rgba(76, 175, 80, 0.3);
|
||||||
|
color: #81c784;
|
||||||
|
border: 1px solid #81c784;
|
||||||
|
}
|
||||||
|
|
||||||
|
.seed-inv-status.status-lock {
|
||||||
|
background: rgba(244, 67, 54, 0.2);
|
||||||
|
color: #ef5350;
|
||||||
|
border: 1px solid #ef5350;
|
||||||
|
}
|
||||||
|
|
||||||
|
.seed-select-actions {
|
||||||
|
display: flex;
|
||||||
|
gap: 12px;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
margin-top: 14px;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
}
|
||||||
|
|
||||||
/* Auth Modal & Profile Dashboard Styles */
|
/* Auth Modal & Profile Dashboard Styles */
|
||||||
.auth-card {
|
.auth-card {
|
||||||
max-width: 440px;
|
max-width: 440px;
|
||||||
|
|||||||
+69
-17
@@ -58,6 +58,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="drawer-body">
|
<div class="drawer-body">
|
||||||
<button id="btn-drawer-main-screen" class="drawer-item">🏠 Главный экран (Режимы)</button>
|
<button id="btn-drawer-main-screen" class="drawer-item">🏠 Главный экран (Режимы)</button>
|
||||||
|
<button id="btn-drawer-inventory" class="drawer-item">🎒 Выбор растений / Инвентарь</button>
|
||||||
<button id="btn-drawer-campaign" class="drawer-item">🗺️ Карта Кампании (25 уровней)</button>
|
<button id="btn-drawer-campaign" class="drawer-item">🗺️ Карта Кампании (25 уровней)</button>
|
||||||
<button id="btn-drawer-workshop" class="drawer-item">🧪 Сад-Мастерская (Крафт и перки)</button>
|
<button id="btn-drawer-workshop" class="drawer-item">🧪 Сад-Мастерская (Крафт и перки)</button>
|
||||||
<button id="btn-drawer-zengarden" class="drawer-item">🪴 Дзен-Сад (Пассивный доход)</button>
|
<button id="btn-drawer-zengarden" class="drawer-item">🪴 Дзен-Сад (Пассивный доход)</button>
|
||||||
@@ -104,6 +105,7 @@
|
|||||||
|
|
||||||
<!-- Secondary Activities Row -->
|
<!-- Secondary Activities Row -->
|
||||||
<div class="start-activities-grid">
|
<div class="start-activities-grid">
|
||||||
|
<button id="btn-inventory-start" class="btn btn-inventory btn-act">🎒 Выбор растений (Колода)</button>
|
||||||
<button id="btn-shop" class="btn btn-shop btn-act">🏪 Магазин Дэйва</button>
|
<button id="btn-shop" class="btn btn-shop btn-act">🏪 Магазин Дэйва</button>
|
||||||
<button id="btn-workshop-start" class="btn btn-workshop btn-act">🧪 Сад-Мастерская</button>
|
<button id="btn-workshop-start" class="btn btn-workshop btn-act">🧪 Сад-Мастерская</button>
|
||||||
<button id="btn-zengarden-start" class="btn btn-zengarden btn-act">🪴 Дзен-Сад</button>
|
<button id="btn-zengarden-start" class="btn btn-zengarden btn-act">🪴 Дзен-Сад</button>
|
||||||
@@ -121,28 +123,78 @@
|
|||||||
<!-- Crazy Dave's Shop Modal -->
|
<!-- Crazy Dave's Shop Modal -->
|
||||||
<div id="shop-modal" class="overlay hidden">
|
<div id="shop-modal" class="overlay hidden">
|
||||||
<div class="banner-card shop-card">
|
<div class="banner-card shop-card">
|
||||||
<h1 style="color: #ffa000;">МАГАЗИН БЕЗУМНОГО ДЭЙВА</h1>
|
<h1 style="color: #ffa000; font-size: 24px; margin-bottom: 4px;">МАГАЗИН БЕЗУМНОГО ДЭЙВА</h1>
|
||||||
<div id="shop-coins" style="font-size: 18px; color: #ffd54f; font-weight: bold; margin-bottom: 16px;">💰 Монеты: 0</div>
|
<div id="shop-coins" style="font-size: 18px; color: #ffd54f; font-weight: bold; margin-bottom: 12px;">💰 Монеты: 0</div>
|
||||||
|
|
||||||
<div class="shop-items">
|
<!-- Shop Category Tabs -->
|
||||||
<div class="shop-item">
|
<div class="shop-tabs">
|
||||||
<div class="shop-item-info">
|
<button type="button" id="tab-shop-plants" class="shop-tab active">🌱 Растения</button>
|
||||||
<h3>+1 Слот под семена</h3>
|
<button type="button" id="tab-shop-upgrades" class="shop-tab">🛠️ Улучшения сада</button>
|
||||||
<p>Разблокирует 6-й слот для пакета растений в колоде.</p>
|
</div>
|
||||||
</div>
|
|
||||||
<button id="btn-buy-slot" class="btn-buy">Купить (500 💰)</button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="shop-item">
|
<!-- Tab 1: Plants Nursery Grid -->
|
||||||
<div class="shop-item-info">
|
<div id="shop-plants-panel" class="shop-tab-panel">
|
||||||
<h3>Садовые грабли</h3>
|
<div id="shop-plants-grid" class="shop-plants-grid">
|
||||||
<p>Автоматически уничтожают первого зомби на газоне в каждом раунде.</p>
|
<!-- Dynamically populated from PLANT_CATALOGUE -->
|
||||||
</div>
|
|
||||||
<button id="btn-buy-rake" class="btn-buy">Купить (150 💰)</button>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<button id="btn-close-shop" class="btn" style="margin-top: 18px;">НАЗАД</button>
|
<!-- Tab 2: Garden Upgrades (Slots, Rake) -->
|
||||||
|
<div id="shop-upgrades-panel" class="shop-tab-panel hidden">
|
||||||
|
<div class="shop-items">
|
||||||
|
<div class="shop-item">
|
||||||
|
<div class="shop-item-info">
|
||||||
|
<h3>+1 Слот под семена</h3>
|
||||||
|
<p>Разблокирует 6-й слот для пакета растений в колоде.</p>
|
||||||
|
</div>
|
||||||
|
<button id="btn-buy-slot" class="btn-buy">Купить (500 💰)</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="shop-item">
|
||||||
|
<div class="shop-item-info">
|
||||||
|
<h3>Садовые грабли</h3>
|
||||||
|
<p>Автоматически уничтожают первого зомби на газоне в каждом раунде.</p>
|
||||||
|
</div>
|
||||||
|
<button id="btn-buy-rake" class="btn-buy">Купить (150 💰)</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<button id="btn-close-shop" class="btn" style="margin-top: 16px;">НАЗАД</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Pre-Battle Seed Selection & Inventory Modal -->
|
||||||
|
<div id="seed-select-modal" class="overlay hidden">
|
||||||
|
<div class="banner-card seed-select-card">
|
||||||
|
<h1 style="color: #69f0ae; font-size: 24px; margin-bottom: 4px;">🌱 ВЫБОР РАСТЕНИЙ ДЛЯ БОЯ</h1>
|
||||||
|
<p style="font-size: 13px; color: #cfd8dc; margin-bottom: 12px;">
|
||||||
|
Соберите свою колоду для защиты газона. Нажмите на растение для добавления или удаления из слота.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<!-- Active Deck Bar -->
|
||||||
|
<div class="seed-select-section-title">
|
||||||
|
<span>ВЫБРАННАЯ КОЛОДА (<span id="seed-deck-count">0</span> / <span id="seed-deck-max">5</span>)</span>
|
||||||
|
<button type="button" id="btn-seed-auto-fill" class="btn btn-secondary btn-sm" style="font-size: 11px; padding: 4px 10px;">⚡ Быстрый выбор</button>
|
||||||
|
</div>
|
||||||
|
<div id="seed-active-deck" class="seed-deck-bar">
|
||||||
|
<!-- Dynamically populated -->
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Inventory Grid -->
|
||||||
|
<div class="seed-select-section-title" style="margin-top: 10px;">
|
||||||
|
<span>ИНВЕНТАРЬ РАСТЕНИЙ</span>
|
||||||
|
<button type="button" id="btn-seed-open-shop" class="btn btn-shop btn-sm" style="font-size: 11px; padding: 4px 10px;">🏪 Купить семена в магазине</button>
|
||||||
|
</div>
|
||||||
|
<div id="seed-inventory-grid" class="seed-inventory-grid">
|
||||||
|
<!-- Dynamically populated -->
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Actions -->
|
||||||
|
<div class="seed-select-actions">
|
||||||
|
<button type="button" id="btn-seed-start-battle" class="btn btn-hero" style="background: linear-gradient(135deg, #2e7d32, #43a047); font-size: 16px; padding: 10px 28px;">⚔️ В БОЙ!</button>
|
||||||
|
<button type="button" id="btn-seed-cancel" class="btn btn-secondary">Отмена</button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
+270
-4
@@ -422,7 +422,14 @@ window.GameEngine = class GameEngine {
|
|||||||
} else if (this.currentMode === 'roguelike') {
|
} else if (this.currentMode === 'roguelike') {
|
||||||
startLvl = this.roguelike ? this.roguelike.currentFloor : 1;
|
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) {
|
if (!this.loopStarted) {
|
||||||
this.loopStarted = true;
|
this.loopStarted = true;
|
||||||
requestAnimationFrame(this.loop.bind(this));
|
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 btnShop = document.getElementById('btn-shop');
|
||||||
const shopModal = document.getElementById('shop-modal');
|
const shopModal = document.getElementById('shop-modal');
|
||||||
const btnCloseShop = document.getElementById('btn-close-shop');
|
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) {
|
if (btnShop && shopModal) {
|
||||||
btnShop.addEventListener('click', () => {
|
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
|
// Shop Item Purchases
|
||||||
const btnBuySlot = document.getElementById('btn-buy-slot');
|
const btnBuySlot = document.getElementById('btn-buy-slot');
|
||||||
if (btnBuySlot) {
|
if (btnBuySlot) {
|
||||||
@@ -1128,6 +1224,7 @@ window.GameEngine = class GameEngine {
|
|||||||
if (window.PvZAudio) window.PvZAudio.playWin();
|
if (window.PvZAudio) window.PvZAudio.playWin();
|
||||||
this.saveUserData();
|
this.saveUserData();
|
||||||
this.updateShopUI();
|
this.updateShopUI();
|
||||||
|
this.renderSeedSelectionUI();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -1544,7 +1641,11 @@ window.GameEngine = class GameEngine {
|
|||||||
if (startModal) startModal.classList.add('hidden');
|
if (startModal) startModal.classList.add('hidden');
|
||||||
|
|
||||||
if (window.PvZAudio) window.PvZAudio.init();
|
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) {
|
if (!this.loopStarted) {
|
||||||
this.loopStarted = true;
|
this.loopStarted = true;
|
||||||
requestAnimationFrame(this.loop.bind(this));
|
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() {
|
updateShopUI() {
|
||||||
const elCoins = document.getElementById('shop-coins');
|
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');
|
const btnBuySlot = document.getElementById('btn-buy-slot');
|
||||||
if (btnBuySlot) {
|
if (btnBuySlot) {
|
||||||
@@ -1581,6 +1799,54 @@ window.GameEngine = class GameEngine {
|
|||||||
btnBuyRake.textContent = this.economy.upgrades.hasRake ? 'Куплено ✔️' : 'Купить (150 💰)';
|
btnBuyRake.textContent = this.economy.upgrades.hasRake ? 'Куплено ✔️' : 'Купить (150 💰)';
|
||||||
btnBuyRake.disabled = this.economy.upgrades.hasRake || this.economy.totalCoins < 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() {
|
updateWorkshopUI() {
|
||||||
|
|||||||
@@ -48,6 +48,8 @@ window.PvZAuth = (function () {
|
|||||||
highScore: 0,
|
highScore: 0,
|
||||||
theme: 'classic',
|
theme: 'classic',
|
||||||
upgrades: { extraSlot: false, hasRake: false },
|
upgrades: { extraSlot: false, hasRake: false },
|
||||||
|
unlockedPlants: ['sunflower', 'peashooter', 'wallnut'],
|
||||||
|
selectedDeck: ['sunflower', 'peashooter', 'wallnut'],
|
||||||
materials: { bioHumus: 10, scrapMetal: 5, sunEssence: 20, crystals: 0 },
|
materials: { bioHumus: 10, scrapMetal: 5, sunEssence: 20, crystals: 0 },
|
||||||
plantLevels: { peashooter: 1, sunflower: 1, wallnut: 1, potatomine: 1,
|
plantLevels: { peashooter: 1, sunflower: 1, wallnut: 1, potatomine: 1,
|
||||||
repeater: 1, snowpea: 1, chomper: 1 },
|
repeater: 1, snowpea: 1, chomper: 1 },
|
||||||
@@ -113,10 +115,12 @@ window.PvZAuth = (function () {
|
|||||||
return {
|
return {
|
||||||
...def,
|
...def,
|
||||||
...serverData,
|
...serverData,
|
||||||
upgrades: { ...def.upgrades, ...(serverData.upgrades || {}) },
|
upgrades: { ...def.upgrades, ...(serverData.upgrades || {}) },
|
||||||
materials: { ...def.materials, ...(serverData.materials || {}) },
|
unlockedPlants: serverData.unlockedPlants || def.unlockedPlants,
|
||||||
plantLevels: { ...def.plantLevels, ...(serverData.plantLevels || {}) },
|
selectedDeck: serverData.selectedDeck || def.selectedDeck,
|
||||||
perks: { ...def.perks, ...(serverData.perks || {}) },
|
materials: { ...def.materials, ...(serverData.materials || {}) },
|
||||||
|
plantLevels: { ...def.plantLevels, ...(serverData.plantLevels || {}) },
|
||||||
|
perks: { ...def.perks, ...(serverData.perks || {}) },
|
||||||
zenGarden: serverData.zenGarden
|
zenGarden: serverData.zenGarden
|
||||||
? {
|
? {
|
||||||
...def.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
|
// Economy, Sun, Coin Drops & Crazy Dave's Shop System
|
||||||
window.EconomySystem = class EconomySystem {
|
window.EconomySystem = class EconomySystem {
|
||||||
constructor() {
|
constructor() {
|
||||||
@@ -10,45 +30,156 @@ window.EconomySystem = class EconomySystem {
|
|||||||
this.isConveyor = false; // For Wall-nut bowling mini-game
|
this.isConveyor = false; // For Wall-nut bowling mini-game
|
||||||
this.isIZombie = false;
|
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
|
// Total coins & upgrades persistent storage
|
||||||
this.reloadProfileData();
|
this.reloadProfileData();
|
||||||
|
|
||||||
// Full Card Registry
|
// Full Card Registry
|
||||||
this.allCards = [
|
this.allCards = window.PLANT_CATALOGUE.map(p => ({
|
||||||
{ id: 'sunflower', name: 'Подсолнух', cost: 50, cooldown: 7.5, timer: 0 },
|
id: p.id,
|
||||||
{ id: 'peashooter', name: 'Горохострел', cost: 100, cooldown: 7.5, timer: 0 },
|
name: p.name,
|
||||||
{ id: 'potatomine', name: 'Мина', cost: 25, cooldown: 18.0, timer: 0 },
|
cost: p.cost,
|
||||||
{ id: 'wallnut', name: 'Стеноорех', cost: 50, cooldown: 20.0, timer: 0 },
|
cooldown: p.cooldown,
|
||||||
{ id: 'repeater', name: 'Повторитель', cost: 200, cooldown: 7.5, timer: 0 },
|
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.selectedCard = null;
|
this.selectedCard = null;
|
||||||
this.animTime = 0;
|
this.animTime = 0;
|
||||||
this.setDeckForLevel(1, 'day');
|
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() {
|
reloadProfileData() {
|
||||||
if (window.PvZAuth) {
|
if (window.PvZAuth) {
|
||||||
const uData = window.PvZAuth.getUserData();
|
const uData = window.PvZAuth.getUserData();
|
||||||
this.totalCoins = uData.coins !== undefined ? uData.coins : 250;
|
this.totalCoins = uData.coins !== undefined ? uData.coins : 250;
|
||||||
this.upgrades = uData.upgrades || { extraSlot: false, hasRake: false };
|
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 {
|
} else {
|
||||||
this.totalCoins = parseInt(localStorage.getItem('pvz_coins') || '250', 10);
|
this.totalCoins = parseInt(localStorage.getItem('pvz_coins') || '250', 10);
|
||||||
this.upgrades = {
|
this.upgrades = {
|
||||||
extraSlot: localStorage.getItem('pvz_upg_slot') === 'true',
|
extraSlot: localStorage.getItem('pvz_upg_slot') === 'true',
|
||||||
hasRake: localStorage.getItem('pvz_upg_rake') === '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_coins', this.totalCoins.toString());
|
||||||
localStorage.setItem('pvz_upg_slot', this.upgrades.extraSlot ? 'true' : 'false');
|
localStorage.setItem('pvz_upg_slot', this.upgrades.extraSlot ? 'true' : 'false');
|
||||||
localStorage.setItem('pvz_upg_rake', this.upgrades.hasRake ? '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()) {
|
if (window.PvZAuth && !window.PvZAuth.isGuest()) {
|
||||||
window.PvZAuth.saveUserData({
|
window.PvZAuth.saveUserData({
|
||||||
coins: this.totalCoins,
|
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.isConveyor = false;
|
||||||
this.isIZombie = false;
|
this.isIZombie = false;
|
||||||
|
|
||||||
// Card slots count (5 default, 6 with Dave's extra slot upgrade)
|
// Use player selected deck if configured and valid
|
||||||
const slotCount = this.upgrades.extraSlot ? 6 : 5;
|
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 = [];
|
let availableIds = [];
|
||||||
if (biome === 'roof') {
|
if (biome === 'roof') {
|
||||||
availableIds = ['flowerpot', 'cabbagepult', 'melonpult', 'wallnut', 'jalapeno', 'potatomine'];
|
availableIds = ['flowerpot', 'cabbagepult', 'melonpult', 'wallnut', 'jalapeno', 'potatomine'];
|
||||||
} else if (biome === 'fog') {
|
} else if (biome === 'fog') {
|
||||||
availableIds = ['sunshroom', 'fumeshroom', 'lilypad', 'cabbagepult', 'wallnut', 'snowpea'];
|
availableIds = ['sunshroom', 'fumeshroom', 'lilypad', 'cabbagepult', 'wallnut', 'snowpea'];
|
||||||
} else if (biome === 'night') {
|
} else if (biome === 'night') {
|
||||||
availableIds = ['sunshroom', 'fumeshroom', 'potatomine', 'wallnut', 'gravebuster', 'hypnoshroom'];
|
availableIds = ['sunshroom', 'fumeshroom', 'potatomine', 'wallnut', 'gravebuster'];
|
||||||
} else if (biome === 'pool') {
|
} else if (biome === 'pool') {
|
||||||
availableIds = ['sunflower', 'peashooter', 'lilypad', 'wallnut', 'repeater', 'snowpea'];
|
availableIds = ['sunflower', 'peashooter', 'lilypad', 'wallnut', 'repeater', 'snowpea'];
|
||||||
} else {
|
} else {
|
||||||
if (level === 1) availableIds = ['sunflower', 'peashooter', 'potatomine', 'wallnut', 'cherrybomb'];
|
availableIds = ['sunflower', 'peashooter', 'wallnut', 'potatomine', 'snowpea', 'repeater', 'chomper', 'cherrybomb'];
|
||||||
else if (level === 2) availableIds = ['sunflower', 'peashooter', 'repeater', 'wallnut', 'jalapeno'];
|
|
||||||
else availableIds = ['sunflower', 'peashooter', 'snowpea', 'chomper', 'wallnut', 'jalapeno'];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
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() {
|
setBossDeck() {
|
||||||
|
|||||||
+24
-3
@@ -88,7 +88,14 @@ def get_default_profile_dict():
|
|||||||
"maxUnlockedLevel": 1,
|
"maxUnlockedLevel": 1,
|
||||||
"highScore": 0,
|
"highScore": 0,
|
||||||
"theme": "classic",
|
"theme": "classic",
|
||||||
"upgrades": {"extraSlot": False, "hasRake": False},
|
"upgrades": {
|
||||||
|
"extraSlot": False,
|
||||||
|
"hasRake": False,
|
||||||
|
"unlockedPlants": ["sunflower", "peashooter", "wallnut"],
|
||||||
|
"selectedDeck": ["sunflower", "peashooter", "wallnut"]
|
||||||
|
},
|
||||||
|
"unlockedPlants": ["sunflower", "peashooter", "wallnut"],
|
||||||
|
"selectedDeck": ["sunflower", "peashooter", "wallnut"],
|
||||||
"materials": {"bioHumus": 10, "scrapMetal": 5, "sunEssence": 20, "crystals": 0},
|
"materials": {"bioHumus": 10, "scrapMetal": 5, "sunEssence": 20, "crystals": 0},
|
||||||
"plantLevels": {"peashooter": 1, "sunflower": 1, "wallnut": 1, "potatomine": 1, "repeater": 1, "snowpea": 1, "chomper": 1},
|
"plantLevels": {"peashooter": 1, "sunflower": 1, "wallnut": 1, "potatomine": 1, "repeater": 1, "snowpea": 1, "chomper": 1},
|
||||||
"perks": {"magnetism": 0, "startSun": 0, "fastCool": 0, "hardyRoots": 0, "luckyDrop": 0},
|
"perks": {"magnetism": 0, "startSun": 0, "fastCool": 0, "hardyRoots": 0, "luckyDrop": 0},
|
||||||
@@ -199,13 +206,19 @@ def authenticate_user(username, password_hash):
|
|||||||
if not p:
|
if not p:
|
||||||
profile_dict = get_default_profile_dict()
|
profile_dict = get_default_profile_dict()
|
||||||
else:
|
else:
|
||||||
|
upg_data = json.loads(p["upgrades"] or "{}")
|
||||||
|
unlocked_plants = upg_data.get("unlockedPlants", ["sunflower", "peashooter", "wallnut"])
|
||||||
|
selected_deck = upg_data.get("selectedDeck", ["sunflower", "peashooter", "wallnut"])
|
||||||
|
|
||||||
profile_dict = {
|
profile_dict = {
|
||||||
"coins": p["coins"],
|
"coins": p["coins"],
|
||||||
"level": p["level"],
|
"level": p["level"],
|
||||||
"maxUnlockedLevel": p["max_unlocked_level"],
|
"maxUnlockedLevel": p["max_unlocked_level"],
|
||||||
"highScore": p["high_score"],
|
"highScore": p["high_score"],
|
||||||
"theme": p["theme"],
|
"theme": p["theme"],
|
||||||
"upgrades": json.loads(p["upgrades"] or "{}"),
|
"upgrades": upg_data,
|
||||||
|
"unlockedPlants": unlocked_plants,
|
||||||
|
"selectedDeck": selected_deck,
|
||||||
"materials": json.loads(p["materials"] or "{}"),
|
"materials": json.loads(p["materials"] or "{}"),
|
||||||
"plantLevels": json.loads(p["plant_levels"] or "{}"),
|
"plantLevels": json.loads(p["plant_levels"] or "{}"),
|
||||||
"perks": json.loads(p["perks"] or "{}"),
|
"perks": json.loads(p["perks"] or "{}"),
|
||||||
@@ -247,7 +260,15 @@ def save_user_profile(username, data):
|
|||||||
cur_obj.update(new_val)
|
cur_obj.update(new_val)
|
||||||
return json.dumps(cur_obj)
|
return json.dumps(cur_obj)
|
||||||
|
|
||||||
upgrades_json = merge_json("upgrades", {})
|
upg_obj = json.loads(current["upgrades"] or "{}") if current and current["upgrades"] else {}
|
||||||
|
if "upgrades" in data and isinstance(data["upgrades"], dict):
|
||||||
|
upg_obj.update(data["upgrades"])
|
||||||
|
if "unlockedPlants" in data and isinstance(data["unlockedPlants"], list):
|
||||||
|
upg_obj["unlockedPlants"] = data["unlockedPlants"]
|
||||||
|
if "selectedDeck" in data and isinstance(data["selectedDeck"], list):
|
||||||
|
upg_obj["selectedDeck"] = data["selectedDeck"]
|
||||||
|
upgrades_json = json.dumps(upg_obj)
|
||||||
|
|
||||||
materials_json = merge_json("materials", {})
|
materials_json = merge_json("materials", {})
|
||||||
plant_levels_json = merge_json("plant_levels", {}) if "plant_levels" in data else (json.dumps(data["plantLevels"]) if "plantLevels" in data else (current["plant_levels"] if current else "{}"))
|
plant_levels_json = merge_json("plant_levels", {}) if "plant_levels" in data else (json.dumps(data["plantLevels"]) if "plantLevels" in data else (current["plant_levels"] if current else "{}"))
|
||||||
perks_json = merge_json("perks", {})
|
perks_json = merge_json("perks", {})
|
||||||
|
|||||||
@@ -736,6 +736,54 @@ def test_http_endpoint():
|
|||||||
except Exception as e:
|
except Exception as e:
|
||||||
record(f"HTTP {name} ({url})", "FAIL", str(e))
|
record(f"HTTP {name} ({url})", "FAIL", str(e))
|
||||||
|
|
||||||
|
def test_plant_shop_and_deck_selection():
|
||||||
|
print("\n--- 11b. Plant Shop, Locked Plants & Pre-Battle Deck Selection ---")
|
||||||
|
econ_path = os.path.join(GAME_DIR, "js/systems/economy.js")
|
||||||
|
with open(econ_path, "r", encoding="utf-8") as f:
|
||||||
|
econ_code = f.read()
|
||||||
|
|
||||||
|
record("PLANT_CATALOGUE defined in economy.js", "PASS" if "window.PLANT_CATALOGUE" in econ_code else "FAIL")
|
||||||
|
record("Sunflower default unlocked in PLANT_CATALOGUE", "PASS" if "id: 'sunflower'" in econ_code and "defaultUnlocked: true" in econ_code else "FAIL")
|
||||||
|
record("Peashooter default unlocked in PLANT_CATALOGUE", "PASS" if "id: 'peashooter'" in econ_code and "defaultUnlocked: true" in econ_code else "FAIL")
|
||||||
|
record("Wallnut default unlocked in PLANT_CATALOGUE", "PASS" if "id: 'wallnut'" in econ_code and "defaultUnlocked: true" in econ_code else "FAIL")
|
||||||
|
record("PotatoMine locked by default with price", "PASS" if "id: 'potatomine'" in econ_code and "price: 150" in econ_code and "defaultUnlocked: false" in econ_code else "FAIL")
|
||||||
|
record("Melonpult locked by default with price", "PASS" if "id: 'melonpult'" in econ_code and "price: 750" in econ_code and "defaultUnlocked: false" in econ_code else "FAIL")
|
||||||
|
|
||||||
|
econ_methods = [
|
||||||
|
"isPlantUnlocked",
|
||||||
|
"buyPlant",
|
||||||
|
"addToDeck",
|
||||||
|
"removeFromDeck",
|
||||||
|
"autoFillDeck",
|
||||||
|
"setDeckFromSelection",
|
||||||
|
"getMaxSlots"
|
||||||
|
]
|
||||||
|
for m in econ_methods:
|
||||||
|
if m in econ_code:
|
||||||
|
record(f"EconomySystem.{m} method implemented", "PASS")
|
||||||
|
else:
|
||||||
|
record(f"EconomySystem.{m} method implemented", "FAIL", f"Missing {m}")
|
||||||
|
|
||||||
|
index_html_path = os.path.join(GAME_DIR, "index.html")
|
||||||
|
with open(index_html_path, "r", encoding="utf-8") as f:
|
||||||
|
html_code = f.read()
|
||||||
|
|
||||||
|
record("Shop plants grid present in HTML", "PASS" if 'id="shop-plants-grid"' in html_code else "FAIL")
|
||||||
|
record("Pre-battle seed select modal present in HTML", "PASS" if 'id="seed-select-modal"' in html_code else "FAIL")
|
||||||
|
record("Seed active deck container present in HTML", "PASS" if 'id="seed-active-deck"' in html_code else "FAIL")
|
||||||
|
record("Seed inventory grid container present in HTML", "PASS" if 'id="seed-inventory-grid"' in html_code else "FAIL")
|
||||||
|
record("Start battle button in seed select modal present", "PASS" if 'id="btn-seed-start-battle"' in html_code else "FAIL")
|
||||||
|
record("Auto-fill button in seed select modal present", "PASS" if 'id="btn-seed-auto-fill"' in html_code else "FAIL")
|
||||||
|
record("Inventory start button on main menu present", "PASS" if 'id="btn-inventory-start"' in html_code else "FAIL")
|
||||||
|
|
||||||
|
game_js_path = os.path.join(GAME_DIR, "js/game.js")
|
||||||
|
with open(game_js_path, "r", encoding="utf-8") as f:
|
||||||
|
game_code = f.read()
|
||||||
|
|
||||||
|
record("GameEngine.openSeedSelectionModal implemented", "PASS" if "openSeedSelectionModal(" in game_code else "FAIL")
|
||||||
|
record("GameEngine.renderSeedSelectionUI implemented", "PASS" if "renderSeedSelectionUI(" in game_code else "FAIL")
|
||||||
|
record("Shop plants nursery population implemented in updateShopUI", "PASS" if "shop-plants-grid" in game_code and "PLANT_CATALOGUE" in game_code else "FAIL")
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
print("==================================================")
|
print("==================================================")
|
||||||
print("🌱 PLANTS VS. ZOMBIES — AUTONOMOUS TEST SUITE 🌱")
|
print("🌱 PLANTS VS. ZOMBIES — AUTONOMOUS TEST SUITE 🌱")
|
||||||
@@ -752,6 +800,7 @@ def main():
|
|||||||
test_v2_1_features()
|
test_v2_1_features()
|
||||||
test_campaign_system()
|
test_campaign_system()
|
||||||
test_feedback_and_layout_system()
|
test_feedback_and_layout_system()
|
||||||
|
test_plant_shop_and_deck_selection()
|
||||||
test_http_endpoint()
|
test_http_endpoint()
|
||||||
|
|
||||||
passed = sum(1 for r in TEST_RESULTS if r["status"] == "PASS")
|
passed = sum(1 for r in TEST_RESULTS if r["status"] == "PASS")
|
||||||
|
|||||||
Reference in new issue
Block a user