fix(gameplay & db): fix casino spin freeze, bank deposit sync, and plant selection menu (fixes #8, #9, #10)
This commit is contained in:
1 parent
351d5f1cca
commit
bd314730ff
7 files changed
+247
-33
No files matched your search
+16
-1
@@ -746,6 +746,16 @@ canvas#gameCanvas {
|
|||||||
width: 95%;
|
width: 95%;
|
||||||
max-height: 90vh;
|
max-height: 90vh;
|
||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.seed-select-card .seed-select-section-title:first-of-type {
|
||||||
|
position: sticky;
|
||||||
|
top: 0;
|
||||||
|
background: #2a160c;
|
||||||
|
z-index: 12;
|
||||||
|
padding-top: 6px;
|
||||||
|
padding-bottom: 4px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.seed-select-section-title {
|
.seed-select-section-title {
|
||||||
@@ -761,16 +771,21 @@ canvas#gameCanvas {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.seed-deck-bar {
|
.seed-deck-bar {
|
||||||
|
position: sticky;
|
||||||
|
top: 28px;
|
||||||
|
z-index: 11;
|
||||||
display: flex;
|
display: flex;
|
||||||
gap: 8px;
|
gap: 8px;
|
||||||
padding: 8px 10px;
|
padding: 8px 10px;
|
||||||
background: rgba(0, 0, 0, 0.4);
|
background: rgba(35, 18, 11, 0.96);
|
||||||
|
backdrop-filter: blur(6px);
|
||||||
border: 1.5px solid #81c784;
|
border: 1.5px solid #81c784;
|
||||||
border-radius: 8px;
|
border-radius: 8px;
|
||||||
min-height: 76px;
|
min-height: 76px;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
margin-bottom: 12px;
|
margin-bottom: 12px;
|
||||||
|
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.5);
|
||||||
}
|
}
|
||||||
|
|
||||||
.seed-slot-card {
|
.seed-slot-card {
|
||||||
|
|||||||
+35
-20
@@ -448,7 +448,8 @@ window.GameEngine = class GameEngine {
|
|||||||
// Close any game-phase modals that might have been left open
|
// Close any game-phase modals that might have been left open
|
||||||
['game-over-modal', 'victory-modal', 'draft-modal', 'zengarden-modal',
|
['game-over-modal', 'victory-modal', 'draft-modal', 'zengarden-modal',
|
||||||
'workshop-modal', 'leaderboard-modal', 'achievement-modal', 'almanac-modal',
|
'workshop-modal', 'leaderboard-modal', 'achievement-modal', 'almanac-modal',
|
||||||
'bugreport-modal', 'campaign-modal', 'tree-modal', 'casino-modal'].forEach(id => {
|
'bugreport-modal', 'campaign-modal', 'tree-modal', 'casino-modal',
|
||||||
|
'seed-select-modal', 'dave-modal', 'shop-modal'].forEach(id => {
|
||||||
const el = document.getElementById(id);
|
const el = document.getElementById(id);
|
||||||
if (el) el.classList.add('hidden');
|
if (el) el.classList.add('hidden');
|
||||||
});
|
});
|
||||||
@@ -492,6 +493,12 @@ window.GameEngine = class GameEngine {
|
|||||||
this.currentLevel = level;
|
this.currentLevel = level;
|
||||||
this.currentMode = mode;
|
this.currentMode = mode;
|
||||||
|
|
||||||
|
// Ensure all pre-battle and menu overlays are hidden
|
||||||
|
['seed-select-modal', 'dave-modal', 'start-modal', 'campaign-modal', 'victory-modal', 'game-over-modal', 'shop-modal'].forEach(id => {
|
||||||
|
const el = document.getElementById(id);
|
||||||
|
if (el) el.classList.add('hidden');
|
||||||
|
});
|
||||||
|
|
||||||
// Pre-set economy mode flags BEFORE reset() so economy.reset() computes
|
// Pre-set economy mode flags BEFORE reset() so economy.reset() computes
|
||||||
// the correct baseSun (avoids stale isConveyor/isIZombie from previous game)
|
// the correct baseSun (avoids stale isConveyor/isIZombie from previous game)
|
||||||
this.economy.isConveyor = (mode === 'bowling' || mode === 'boss' || mode === 'bossrush');
|
this.economy.isConveyor = (mode === 'bowling' || mode === 'boss' || mode === 'bossrush');
|
||||||
@@ -1876,6 +1883,7 @@ window.GameEngine = class GameEngine {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Animate spin
|
// Animate spin
|
||||||
|
this.casino.isSpinning = true;
|
||||||
[reel1, reel2, reel3].forEach(r => { if (r) r.classList.add('spinning'); });
|
[reel1, reel2, reel3].forEach(r => { if (r) r.classList.add('spinning'); });
|
||||||
if (btnSpin) btnSpin.disabled = true;
|
if (btnSpin) btnSpin.disabled = true;
|
||||||
if (resultBanner) {
|
if (resultBanner) {
|
||||||
@@ -1884,27 +1892,34 @@ window.GameEngine = class GameEngine {
|
|||||||
}
|
}
|
||||||
|
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
if (this.casino) {
|
try {
|
||||||
this.casino.isSpinning = false;
|
if (this.casino) {
|
||||||
const res = this.casino.spin(bet);
|
this.casino.isSpinning = false;
|
||||||
|
const res = this.casino.spin(bet);
|
||||||
|
this.casino.isSpinning = false;
|
||||||
|
|
||||||
|
if (res && res.icons) {
|
||||||
|
if (reel1) reel1.innerHTML = `<span class="reel-symbol">${res.icons[0]}</span>`;
|
||||||
|
if (reel2) reel2.innerHTML = `<span class="reel-symbol">${res.icons[1]}</span>`;
|
||||||
|
if (reel3) reel3.innerHTML = `<span class="reel-symbol">${res.icons[2]}</span>`;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (resultBanner && res) {
|
||||||
|
resultBanner.textContent = res.message || '';
|
||||||
|
if (res.isJackpot) resultBanner.style.color = '#ffd700';
|
||||||
|
else if (res.payout > 0) resultBanner.style.color = '#69f0ae';
|
||||||
|
else resultBanner.style.color = '#b0bec5';
|
||||||
|
}
|
||||||
|
|
||||||
|
this.updateCasinoUI();
|
||||||
|
this.updateRecordDisplay();
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
console.error('Casino spin error:', e);
|
||||||
|
} finally {
|
||||||
|
if (this.casino) this.casino.isSpinning = false;
|
||||||
[reel1, reel2, reel3].forEach(r => { if (r) r.classList.remove('spinning'); });
|
[reel1, reel2, reel3].forEach(r => { if (r) r.classList.remove('spinning'); });
|
||||||
if (btnSpin) btnSpin.disabled = false;
|
if (btnSpin) btnSpin.disabled = false;
|
||||||
|
|
||||||
if (res.icons) {
|
|
||||||
if (reel1) reel1.innerHTML = `<span class="reel-symbol">${res.icons[0]}</span>`;
|
|
||||||
if (reel2) reel2.innerHTML = `<span class="reel-symbol">${res.icons[1]}</span>`;
|
|
||||||
if (reel3) reel3.innerHTML = `<span class="reel-symbol">${res.icons[2]}</span>`;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (resultBanner) {
|
|
||||||
resultBanner.textContent = res.message || '';
|
|
||||||
if (res.isJackpot) resultBanner.style.color = '#ffd700';
|
|
||||||
else if (res.payout > 0) resultBanner.style.color = '#69f0ae';
|
|
||||||
else resultBanner.style.color = '#b0bec5';
|
|
||||||
}
|
|
||||||
|
|
||||||
this.updateCasinoUI();
|
|
||||||
this.updateRecordDisplay();
|
|
||||||
}
|
}
|
||||||
}, 500);
|
}, 500);
|
||||||
});
|
});
|
||||||
|
|||||||
+45
-2
@@ -117,7 +117,20 @@ window.PvZAuth = (function () {
|
|||||||
lastVisit: Date.now()
|
lastVisit: Date.now()
|
||||||
},
|
},
|
||||||
treeOfWisdom: { height: 0, cheats: {} },
|
treeOfWisdom: { height: 0, cheats: {} },
|
||||||
achievements: {}
|
achievements: {},
|
||||||
|
casino: {
|
||||||
|
deposit: 0,
|
||||||
|
depositStartTime: 0,
|
||||||
|
loan: 0,
|
||||||
|
spinsTotal: 0,
|
||||||
|
totalWon: 0,
|
||||||
|
jackpotsWon: 0
|
||||||
|
},
|
||||||
|
zombieLab: {
|
||||||
|
unlockedStage: 1,
|
||||||
|
levels: { normal: 1, conehead: 1, buckethead: 1, football: 1, dancing: 1 },
|
||||||
|
mutations: {}
|
||||||
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -301,7 +314,37 @@ window.PvZAuth = (function () {
|
|||||||
achievements: {
|
achievements: {
|
||||||
...(loc.achievements || {}),
|
...(loc.achievements || {}),
|
||||||
...(base.achievements || {})
|
...(base.achievements || {})
|
||||||
}
|
},
|
||||||
|
casino: (base.casino || loc.casino)
|
||||||
|
? {
|
||||||
|
...def.casino,
|
||||||
|
...(loc.casino || {}),
|
||||||
|
...(base.casino || {}),
|
||||||
|
deposit: (base.casino && base.casino.deposit !== undefined) ? Number(base.casino.deposit) : (Number(loc.casino?.deposit) || 0),
|
||||||
|
depositStartTime: (base.casino && base.casino.depositStartTime) ? Number(base.casino.depositStartTime) : (Number(loc.casino?.depositStartTime) || 0),
|
||||||
|
loan: (base.casino && base.casino.loan !== undefined) ? Number(base.casino.loan) : (Number(loc.casino?.loan) || 0),
|
||||||
|
spinsTotal: Math.max(Number(base.casino?.spinsTotal) || 0, Number(loc.casino?.spinsTotal) || 0),
|
||||||
|
totalWon: Math.max(Number(base.casino?.totalWon) || 0, Number(loc.casino?.totalWon) || 0),
|
||||||
|
jackpotsWon: Math.max(Number(base.casino?.jackpotsWon) || 0, Number(loc.casino?.jackpotsWon) || 0)
|
||||||
|
}
|
||||||
|
: def.casino,
|
||||||
|
zombieLab: (base.zombieLab || loc.zombieLab)
|
||||||
|
? {
|
||||||
|
...def.zombieLab,
|
||||||
|
...(loc.zombieLab || {}),
|
||||||
|
...(base.zombieLab || {}),
|
||||||
|
unlockedStage: Math.max(Number(base.zombieLab?.unlockedStage) || 1, Number(loc.zombieLab?.unlockedStage) || 1),
|
||||||
|
levels: {
|
||||||
|
...def.zombieLab.levels,
|
||||||
|
...(loc.zombieLab?.levels || {}),
|
||||||
|
...(base.zombieLab?.levels || {})
|
||||||
|
},
|
||||||
|
mutations: {
|
||||||
|
...(loc.zombieLab?.mutations || {}),
|
||||||
|
...(base.zombieLab?.mutations || {})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
: def.zombieLab
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -138,6 +138,7 @@ window.CasinoSystem = class CasinoSystem {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this.isSpinning = false;
|
||||||
this.saveToAuth();
|
this.saveToAuth();
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
|||||||
@@ -130,11 +130,21 @@ window.EconomySystem = class EconomySystem {
|
|||||||
this.selectedDeck = deckIds.filter(id => this.isPlantUnlocked(id)).slice(0, this.getMaxSlots());
|
this.selectedDeck = deckIds.filter(id => this.isPlantUnlocked(id)).slice(0, this.getMaxSlots());
|
||||||
this.saveProfileData();
|
this.saveProfileData();
|
||||||
}
|
}
|
||||||
const chosen = this.selectedDeck.length > 0 ? this.selectedDeck : this.defaultUnlocked;
|
const chosen = (this.selectedDeck && this.selectedDeck.length > 0) ? this.selectedDeck : this.defaultUnlocked;
|
||||||
this.cards = chosen.map(id => {
|
this.cards = chosen.map(id => {
|
||||||
const found = this.allCards.find(c => c.id === id);
|
const found = this.allCards.find(c => c.id === id);
|
||||||
return found ? { ...found, timer: 0 } : null;
|
return found ? { ...found, timer: 0 } : null;
|
||||||
}).filter(Boolean);
|
}).filter(Boolean);
|
||||||
|
|
||||||
|
// Guaranteed fallback: If this.cards is empty, restore default starter cards so top menu never disappears
|
||||||
|
if (!this.cards || this.cards.length === 0) {
|
||||||
|
this.selectedDeck = [...this.defaultUnlocked];
|
||||||
|
this.cards = this.defaultUnlocked.map(id => {
|
||||||
|
const found = this.allCards.find(c => c.id === id);
|
||||||
|
return found ? { ...found, timer: 0 } : null;
|
||||||
|
}).filter(Boolean);
|
||||||
|
this.saveProfileData();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
reloadProfileData() {
|
reloadProfileData() {
|
||||||
@@ -720,6 +730,9 @@ window.EconomySystem = class EconomySystem {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 2. Draw Cards
|
// 2. Draw Cards
|
||||||
|
if (!this.isConveyor && !this.isIZombie && (!this.cards || this.cards.length === 0)) {
|
||||||
|
this.setDeckFromSelection();
|
||||||
|
}
|
||||||
const cardStartX = 105;
|
const cardStartX = 105;
|
||||||
const cardY = 8;
|
const cardY = 8;
|
||||||
const cardW = 66;
|
const cardW = 66;
|
||||||
|
|||||||
+82
-9
@@ -498,6 +498,10 @@ def init_db():
|
|||||||
zen_garden TEXT DEFAULT '{}',
|
zen_garden TEXT DEFAULT '{}',
|
||||||
tree_of_wisdom TEXT DEFAULT '{}',
|
tree_of_wisdom TEXT DEFAULT '{}',
|
||||||
achievements TEXT DEFAULT '{}',
|
achievements TEXT DEFAULT '{}',
|
||||||
|
stats TEXT DEFAULT '{}',
|
||||||
|
casino TEXT DEFAULT '{}',
|
||||||
|
zombie_lab TEXT DEFAULT '{}',
|
||||||
|
admin_updated_at TIMESTAMP DEFAULT NULL,
|
||||||
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||||
FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE
|
FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE
|
||||||
)
|
)
|
||||||
@@ -608,6 +612,10 @@ def init_db():
|
|||||||
cursor.execute("ALTER TABLE profiles ADD COLUMN stats TEXT DEFAULT '{}'")
|
cursor.execute("ALTER TABLE profiles ADD COLUMN stats TEXT DEFAULT '{}'")
|
||||||
if "admin_updated_at" not in prof_cols:
|
if "admin_updated_at" not in prof_cols:
|
||||||
cursor.execute("ALTER TABLE profiles ADD COLUMN admin_updated_at TIMESTAMP DEFAULT NULL")
|
cursor.execute("ALTER TABLE profiles ADD COLUMN admin_updated_at TIMESTAMP DEFAULT NULL")
|
||||||
|
if "casino" not in prof_cols:
|
||||||
|
cursor.execute("ALTER TABLE profiles ADD COLUMN casino TEXT DEFAULT '{}'")
|
||||||
|
if "zombie_lab" not in prof_cols:
|
||||||
|
cursor.execute("ALTER TABLE profiles ADD COLUMN zombie_lab TEXT DEFAULT '{}'")
|
||||||
|
|
||||||
conn.commit()
|
conn.commit()
|
||||||
conn.close()
|
conn.close()
|
||||||
@@ -1177,6 +1185,19 @@ def get_default_profile_dict():
|
|||||||
"plantsPlanted": 0,
|
"plantsPlanted": 0,
|
||||||
"levelsCompleted": 0,
|
"levelsCompleted": 0,
|
||||||
"coinsEarned": 0
|
"coinsEarned": 0
|
||||||
|
},
|
||||||
|
"casino": {
|
||||||
|
"deposit": 0,
|
||||||
|
"depositStartTime": 0,
|
||||||
|
"loan": 0,
|
||||||
|
"spinsTotal": 0,
|
||||||
|
"totalWon": 0,
|
||||||
|
"jackpotsWon": 0
|
||||||
|
},
|
||||||
|
"zombieLab": {
|
||||||
|
"unlockedStage": 1,
|
||||||
|
"mutations": {},
|
||||||
|
"levels": {"normal": 1, "conehead": 1, "buckethead": 1, "football": 1, "dancing": 1}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1212,8 +1233,8 @@ def register_user(username, password_hash, initial_data=None, ip_address="", use
|
|||||||
cursor.execute("""
|
cursor.execute("""
|
||||||
INSERT INTO profiles (
|
INSERT INTO profiles (
|
||||||
user_id, coins, level, max_unlocked_level, high_score, theme,
|
user_id, coins, level, max_unlocked_level, high_score, theme,
|
||||||
upgrades, materials, plant_levels, perks, zen_garden, tree_of_wisdom, achievements, updated_at
|
upgrades, materials, plant_levels, perks, zen_garden, tree_of_wisdom, achievements, stats, casino, zombie_lab, updated_at
|
||||||
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
||||||
""", (
|
""", (
|
||||||
user_id,
|
user_id,
|
||||||
profile_data.get("coins", 150),
|
profile_data.get("coins", 150),
|
||||||
@@ -1228,6 +1249,9 @@ def register_user(username, password_hash, initial_data=None, ip_address="", use
|
|||||||
json.dumps(profile_data.get("zenGarden", {})),
|
json.dumps(profile_data.get("zenGarden", {})),
|
||||||
json.dumps(profile_data.get("treeOfWisdom", {})),
|
json.dumps(profile_data.get("treeOfWisdom", {})),
|
||||||
json.dumps(profile_data.get("achievements", {})),
|
json.dumps(profile_data.get("achievements", {})),
|
||||||
|
json.dumps(profile_data.get("stats", {})),
|
||||||
|
json.dumps(profile_data.get("casino", {})),
|
||||||
|
json.dumps(profile_data.get("zombieLab", {})),
|
||||||
now_str
|
now_str
|
||||||
))
|
))
|
||||||
conn.commit()
|
conn.commit()
|
||||||
@@ -1319,6 +1343,12 @@ def authenticate_user(username, password_hash, ip_address="", user_agent="", dev
|
|||||||
"achievements": json.loads(p["achievements"] or "{}"),
|
"achievements": json.loads(p["achievements"] or "{}"),
|
||||||
"stats": json.loads(p["stats"] or "{}") if "stats" in p.keys() and p["stats"] else {
|
"stats": json.loads(p["stats"] or "{}") if "stats" in p.keys() and p["stats"] else {
|
||||||
"zombiesKilled": 0, "sunProduced": 0, "plantsPlanted": 0, "levelsCompleted": 0, "coinsEarned": 0
|
"zombiesKilled": 0, "sunProduced": 0, "plantsPlanted": 0, "levelsCompleted": 0, "coinsEarned": 0
|
||||||
|
},
|
||||||
|
"casino": json.loads(p["casino"] or "{}") if "casino" in p.keys() and p["casino"] else {
|
||||||
|
"deposit": 0, "depositStartTime": 0, "loan": 0, "spinsTotal": 0, "totalWon": 0, "jackpotsWon": 0
|
||||||
|
},
|
||||||
|
"zombieLab": json.loads(p["zombie_lab"] or "{}") if "zombie_lab" in p.keys() and p["zombie_lab"] else {
|
||||||
|
"unlockedStage": 1, "mutations": {}, "levels": {"normal": 1, "conehead": 1, "buckethead": 1, "football": 1, "dancing": 1}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1369,6 +1399,12 @@ def get_user_profile(username):
|
|||||||
"achievements": json.loads(p["achievements"] or "{}"),
|
"achievements": json.loads(p["achievements"] or "{}"),
|
||||||
"stats": json.loads(p["stats"] or "{}") if "stats" in p.keys() and p["stats"] else {
|
"stats": json.loads(p["stats"] or "{}") if "stats" in p.keys() and p["stats"] else {
|
||||||
"zombiesKilled": 0, "sunProduced": 0, "plantsPlanted": 0, "levelsCompleted": 0, "coinsEarned": 0
|
"zombiesKilled": 0, "sunProduced": 0, "plantsPlanted": 0, "levelsCompleted": 0, "coinsEarned": 0
|
||||||
|
},
|
||||||
|
"casino": json.loads(p["casino"] or "{}") if "casino" in p.keys() and p["casino"] else {
|
||||||
|
"deposit": 0, "depositStartTime": 0, "loan": 0, "spinsTotal": 0, "totalWon": 0, "jackpotsWon": 0
|
||||||
|
},
|
||||||
|
"zombieLab": json.loads(p["zombie_lab"] or "{}") if "zombie_lab" in p.keys() and p["zombie_lab"] else {
|
||||||
|
"unlockedStage": 1, "mutations": {}, "levels": {"normal": 1, "conehead": 1, "buckethead": 1, "football": 1, "dancing": 1}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1509,29 +1545,56 @@ def save_user_profile(username, data, token=None, is_admin_override=False, ip_ad
|
|||||||
cur_stats[sk] = max(cur_stats.get(sk, 0), int(sv))
|
cur_stats[sk] = max(cur_stats.get(sk, 0), int(sv))
|
||||||
stats_json = json.dumps(cur_stats)
|
stats_json = json.dumps(cur_stats)
|
||||||
|
|
||||||
|
# Casino merge (preserve deposit, spins, won, loan)
|
||||||
|
cur_casino = json.loads(current["casino"] or "{}") if current and "casino" in current.keys() and current["casino"] else {
|
||||||
|
"deposit": 0, "depositStartTime": 0, "loan": 0, "spinsTotal": 0, "totalWon": 0, "jackpotsWon": 0
|
||||||
|
}
|
||||||
|
if "casino" in data and isinstance(data["casino"], dict):
|
||||||
|
for ck, cv in data["casino"].items():
|
||||||
|
if ck in ("deposit", "depositStartTime", "loan", "spinsTotal", "totalWon", "jackpotsWon"):
|
||||||
|
cur_casino[ck] = int(cv) if isinstance(cv, (int, float)) else cv
|
||||||
|
casino_json = json.dumps(cur_casino)
|
||||||
|
|
||||||
|
# Zombie Lab merge
|
||||||
|
cur_zombie_lab = json.loads(current["zombie_lab"] or "{}") if current and "zombie_lab" in current.keys() and current["zombie_lab"] else {
|
||||||
|
"unlockedStage": 1, "mutations": {}, "levels": {"normal": 1, "conehead": 1, "buckethead": 1, "football": 1, "dancing": 1}
|
||||||
|
}
|
||||||
|
new_zombie_lab = data.get("zombieLab") or data.get("zombie_lab")
|
||||||
|
if new_zombie_lab and isinstance(new_zombie_lab, dict):
|
||||||
|
if "unlockedStage" in new_zombie_lab:
|
||||||
|
cur_zombie_lab["unlockedStage"] = max(cur_zombie_lab.get("unlockedStage", 1), int(new_zombie_lab["unlockedStage"]))
|
||||||
|
if "levels" in new_zombie_lab and isinstance(new_zombie_lab["levels"], dict):
|
||||||
|
cur_zombie_lab.setdefault("levels", {}).update(new_zombie_lab["levels"])
|
||||||
|
if "mutations" in new_zombie_lab and isinstance(new_zombie_lab["mutations"], dict):
|
||||||
|
cur_zombie_lab.setdefault("mutations", {}).update(new_zombie_lab["mutations"])
|
||||||
|
zombie_lab_json = json.dumps(cur_zombie_lab)
|
||||||
|
|
||||||
if current:
|
if current:
|
||||||
cursor.execute("""
|
cursor.execute("""
|
||||||
UPDATE profiles SET
|
UPDATE profiles SET
|
||||||
coins = ?, level = ?, max_unlocked_level = ?, high_score = ?, theme = ?,
|
coins = ?, level = ?, max_unlocked_level = ?, high_score = ?, theme = ?,
|
||||||
upgrades = ?, materials = ?, plant_levels = ?, perks = ?,
|
upgrades = ?, materials = ?, plant_levels = ?, perks = ?,
|
||||||
zen_garden = ?, tree_of_wisdom = ?, achievements = ?, stats = ?, admin_updated_at = ?, updated_at = ?
|
zen_garden = ?, tree_of_wisdom = ?, achievements = ?, stats = ?,
|
||||||
|
casino = ?, zombie_lab = ?, admin_updated_at = ?, updated_at = ?
|
||||||
WHERE user_id = ?
|
WHERE user_id = ?
|
||||||
""", (
|
""", (
|
||||||
coins, level, max_unlocked, high_score, theme,
|
coins, level, max_unlocked, high_score, theme,
|
||||||
upgrades_json, materials_json, plant_levels_json, perks_json,
|
upgrades_json, materials_json, plant_levels_json, perks_json,
|
||||||
zen_garden_json, tree_json, achievements_json, stats_json, admin_updated_val, now_str,
|
zen_garden_json, tree_json, achievements_json, stats_json,
|
||||||
|
casino_json, zombie_lab_json, admin_updated_val, now_str,
|
||||||
user_id
|
user_id
|
||||||
))
|
))
|
||||||
else:
|
else:
|
||||||
cursor.execute("""
|
cursor.execute("""
|
||||||
INSERT INTO profiles (
|
INSERT INTO profiles (
|
||||||
user_id, coins, level, max_unlocked_level, high_score, theme,
|
user_id, coins, level, max_unlocked_level, high_score, theme,
|
||||||
upgrades, materials, plant_levels, perks, zen_garden, tree_of_wisdom, achievements, stats, admin_updated_at, updated_at
|
upgrades, materials, plant_levels, perks, zen_garden, tree_of_wisdom, achievements, stats, casino, zombie_lab, admin_updated_at, updated_at
|
||||||
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
||||||
""", (
|
""", (
|
||||||
user_id, coins, level, max_unlocked, high_score, theme,
|
user_id, coins, level, max_unlocked, high_score, theme,
|
||||||
upgrades_json, materials_json, plant_levels_json, perks_json,
|
upgrades_json, materials_json, plant_levels_json, perks_json,
|
||||||
zen_garden_json, tree_json, achievements_json, stats_json, admin_updated_val, now_str
|
zen_garden_json, tree_json, achievements_json, stats_json,
|
||||||
|
casino_json, zombie_lab_json, admin_updated_val, now_str
|
||||||
))
|
))
|
||||||
|
|
||||||
conn.commit()
|
conn.commit()
|
||||||
@@ -1765,6 +1828,12 @@ def admin_get_user_detail(user_id):
|
|||||||
"stats": json.loads(p["stats"] or "{}") if "stats" in p.keys() and p["stats"] else {
|
"stats": json.loads(p["stats"] or "{}") if "stats" in p.keys() and p["stats"] else {
|
||||||
"zombiesKilled": 0, "sunProduced": 0, "plantsPlanted": 0, "levelsCompleted": 0, "coinsEarned": 0
|
"zombiesKilled": 0, "sunProduced": 0, "plantsPlanted": 0, "levelsCompleted": 0, "coinsEarned": 0
|
||||||
},
|
},
|
||||||
|
"casino": json.loads(p["casino"] or "{}") if "casino" in p.keys() and p["casino"] else {
|
||||||
|
"deposit": 0, "depositStartTime": 0, "loan": 0, "spinsTotal": 0, "totalWon": 0, "jackpotsWon": 0
|
||||||
|
},
|
||||||
|
"zombieLab": json.loads(p["zombie_lab"] or "{}") if "zombie_lab" in p.keys() and p["zombie_lab"] else {
|
||||||
|
"unlockedStage": 1, "mutations": {}, "levels": {"normal": 1, "conehead": 1, "buckethead": 1, "football": 1, "dancing": 1}
|
||||||
|
},
|
||||||
"updatedAt": p["updated_at"]
|
"updatedAt": p["updated_at"]
|
||||||
}
|
}
|
||||||
else:
|
else:
|
||||||
@@ -2087,8 +2156,8 @@ def admin_import_db(data):
|
|||||||
cursor.execute("""
|
cursor.execute("""
|
||||||
INSERT INTO profiles (
|
INSERT INTO profiles (
|
||||||
user_id, coins, level, max_unlocked_level, high_score, theme,
|
user_id, coins, level, max_unlocked_level, high_score, theme,
|
||||||
upgrades, materials, plant_levels, perks, zen_garden, tree_of_wisdom, achievements, updated_at
|
upgrades, materials, plant_levels, perks, zen_garden, tree_of_wisdom, achievements, stats, casino, zombie_lab, admin_updated_at, updated_at
|
||||||
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
||||||
""", (
|
""", (
|
||||||
p.get("user_id"),
|
p.get("user_id"),
|
||||||
p.get("coins", 150),
|
p.get("coins", 150),
|
||||||
@@ -2103,6 +2172,10 @@ def admin_import_db(data):
|
|||||||
p.get("zen_garden", "{}"),
|
p.get("zen_garden", "{}"),
|
||||||
p.get("tree_of_wisdom", "{}"),
|
p.get("tree_of_wisdom", "{}"),
|
||||||
p.get("achievements", "{}"),
|
p.get("achievements", "{}"),
|
||||||
|
p.get("stats", "{}"),
|
||||||
|
p.get("casino", "{}"),
|
||||||
|
p.get("zombie_lab", "{}"),
|
||||||
|
p.get("admin_updated_at"),
|
||||||
p.get("updated_at")
|
p.get("updated_at")
|
||||||
))
|
))
|
||||||
|
|
||||||
|
|||||||
@@ -1320,6 +1320,59 @@ def test_v2_7_features():
|
|||||||
record("POST /api/admin/system/cleanup authorized request", "FAIL", str(e))
|
record("POST /api/admin/system/cleanup authorized request", "FAIL", str(e))
|
||||||
|
|
||||||
|
|
||||||
|
def test_gitea_critical_bugfixes():
|
||||||
|
"""Verify bugfixes for Gitea issues #8, #9, and #10."""
|
||||||
|
print("\n--- 19. Gitea Critical Bugfixes (#8, #9, #10) ---")
|
||||||
|
|
||||||
|
# Issue #8: Casino allows multiple spins without freezing
|
||||||
|
casino_path = os.path.join(GAME_DIR, "js/systems/casino.js")
|
||||||
|
with open(casino_path, "r", encoding="utf-8") as f:
|
||||||
|
casino_code = f.read()
|
||||||
|
game_path = os.path.join(GAME_DIR, "js/game.js")
|
||||||
|
with open(game_path, "r", encoding="utf-8") as f:
|
||||||
|
game_code = f.read()
|
||||||
|
|
||||||
|
record("Issue #8: casino.js spin resets isSpinning to false", "PASS" if "this.isSpinning = false;" in casino_code else "FAIL")
|
||||||
|
record("Issue #8: game.js btnSpin handler releases isSpinning on completion", "PASS" if "this.casino.isSpinning = false" in game_code and "btnSpin.disabled = false" in game_code else "FAIL")
|
||||||
|
|
||||||
|
# Issue #9: Bank deposit persistence in SQLite and Auth
|
||||||
|
auth_path = os.path.join(GAME_DIR, "js/systems/auth.js")
|
||||||
|
with open(auth_path, "r", encoding="utf-8") as f:
|
||||||
|
auth_code = f.read()
|
||||||
|
record("Issue #9: auth.js merges and stores casino deposit state", "PASS" if "casino:" in auth_code and "deposit:" in auth_code else "FAIL")
|
||||||
|
record("Issue #9: auth.js merges and stores zombieLab state", "PASS" if "zombieLab:" in auth_code and "unlockedStage" in auth_code else "FAIL")
|
||||||
|
|
||||||
|
try:
|
||||||
|
conn = db.get_connection()
|
||||||
|
cursor = conn.cursor()
|
||||||
|
cursor.execute("PRAGMA table_info(profiles)")
|
||||||
|
cols = [r["name"] for r in cursor.fetchall()]
|
||||||
|
conn.close()
|
||||||
|
record("Issue #9: SQLite profiles table has casino and zombie_lab columns", "PASS" if "casino" in cols and "zombie_lab" in cols else "FAIL")
|
||||||
|
except Exception as e:
|
||||||
|
record("Issue #9: SQLite profiles table has casino and zombie_lab columns", "FAIL", str(e))
|
||||||
|
|
||||||
|
# Check kostyan restored deposit
|
||||||
|
try:
|
||||||
|
kostyan_prof = db.get_user_profile("kostyan")
|
||||||
|
has_deposit = kostyan_prof and isinstance(kostyan_prof.get("casino"), dict) and kostyan_prof["casino"].get("deposit", 0) > 0
|
||||||
|
record("Issue #9: Player kostyan deposit restored and retrievable", "PASS" if has_deposit else "FAIL", f"Deposit: {kostyan_prof.get('casino', {}).get('deposit')}")
|
||||||
|
except Exception as e:
|
||||||
|
record("Issue #9: Player kostyan deposit restored and retrievable", "FAIL", str(e))
|
||||||
|
|
||||||
|
# Issue #10: Seed selection top menu and sticky HUD
|
||||||
|
css_path = os.path.join(GAME_DIR, "css/style.css")
|
||||||
|
with open(css_path, "r", encoding="utf-8") as f:
|
||||||
|
css_code = f.read()
|
||||||
|
record("Issue #10: style.css has sticky seed deck selection bar", "PASS" if ".seed-deck-bar" in css_code and "position: sticky" in css_code else "FAIL")
|
||||||
|
|
||||||
|
economy_path = os.path.join(GAME_DIR, "js/systems/economy.js")
|
||||||
|
with open(economy_path, "r", encoding="utf-8") as f:
|
||||||
|
economy_code = f.read()
|
||||||
|
record("Issue #10: economy.js guaranteed fallback for cards array in draw()", "PASS" if "this.setDeckFromSelection()" in economy_code else "FAIL")
|
||||||
|
record("Issue #10: game.js hides pre-battle modals on startLevel", "PASS" if "seed-select-modal" in game_code and "dave-modal" 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 🌱")
|
||||||
@@ -1343,6 +1396,7 @@ def main():
|
|||||||
test_admin_panel_and_configurator()
|
test_admin_panel_and_configurator()
|
||||||
test_http_endpoint()
|
test_http_endpoint()
|
||||||
test_v2_7_features()
|
test_v2_7_features()
|
||||||
|
test_gitea_critical_bugfixes()
|
||||||
|
|
||||||
passed = sum(1 for r in TEST_RESULTS if r["status"] == "PASS")
|
passed = sum(1 for r in TEST_RESULTS if r["status"] == "PASS")
|
||||||
total = len(TEST_RESULTS)
|
total = len(TEST_RESULTS)
|
||||||
|
|||||||
Reference in new issue
Block a user