Browse Source

Fix misc bugs

main
Stephanie Gredell 4 months ago
parent
commit
a007568d84
  1. 2
      src/data/cards.js
  2. 48
      src/engine/battle.js
  3. 95
      src/main.js

2
src/data/cards.js

@ -331,7 +331,7 @@ export const CARDS = {
ctx.draw(1); ctx.draw(1);
ctx.log(`Clean code heals the soul! Heal ${healAmount} HP and draw 1.`); ctx.log(`Clean code heals the soul! Heal ${healAmount} HP and draw 1.`);
} }
}, }
}; };

48
src/engine/battle.js

@ -74,8 +74,14 @@ export function playCard(ctx, handIndex) {
return; return;
} }
card.effect(ctx); try {
card._used = true; card.effect(ctx);
card._used = true;
} catch (error) {
console.error('Card effect error:', error, 'Card:', card);
ctx.log(`Error playing ${card.name || 'Unknown card'}: ${error.message}`);
return;
}
if (card.type !== "power") { if (card.type !== "power") {
@ -105,14 +111,29 @@ export function enemyTurn(ctx) {
if (e.weak > 0) dmg = Math.floor(dmg * 0.75); if (e.weak > 0) dmg = Math.floor(dmg * 0.75);
applyDamage(ctx, ctx.player, dmg, `${e.name} attacks`); applyDamage(ctx, ctx.player, dmg, `${e.name} attacks`);
} else if (e.intent.type === "block") { } else if (e.intent.type === "block") {
ENEMIES[e.id].onBlock?.(ctx, e.intent.value); try {
e.block += e.intent.value; ENEMIES[e.id].onBlock?.(ctx, e.intent.value);
ctx.log(`${e.name} defends and gains ${e.intent.value} block.`); e.block += e.intent.value;
ctx.log(`${e.name} defends and gains ${e.intent.value} block.`);
} catch (error) {
console.error('Enemy block effect error:', error, 'Enemy:', e.id);
ctx.log(`${e.name} tries to defend but fumbles!`);
}
} else if (e.intent.type === "debuff") { } else if (e.intent.type === "debuff") {
ENEMIES[e.id].onDebuff?.(ctx, e.intent.value); try {
ctx.log(`${e.name} casts a debuffing spell.`); ENEMIES[e.id].onDebuff?.(ctx, e.intent.value);
ctx.log(`${e.name} casts a debuffing spell.`);
} catch (error) {
console.error('Enemy debuff effect error:', error, 'Enemy:', e.id);
ctx.log(`${e.name} tries to cast a spell but it fizzles!`);
}
} else if (e.intent.type === "heal") { } else if (e.intent.type === "heal") {
ENEMIES[e.id].onHeal?.(ctx, e.intent.value); try {
ENEMIES[e.id].onHeal?.(ctx, e.intent.value);
} catch (error) {
console.error('Enemy heal effect error:', error, 'Enemy:', e.id);
ctx.log(`${e.name} tries to heal but something goes wrong!`);
}
} }
@ -123,7 +144,16 @@ export function enemyTurn(ctx) {
if (ctx.player.hp <= 0) { ctx.onLose(); return; } if (ctx.player.hp <= 0) { ctx.onLose(); return; }
e.turn++; e.turn++;
e.intent = ENEMIES[e.id].ai(e.turn); try {
e.intent = ENEMIES[e.id].ai(e.turn);
if (!e.intent || !e.intent.type) {
throw new Error('Invalid enemy intent returned');
}
} catch (error) {
console.error('Enemy AI error:', error, 'Enemy:', e.id);
ctx.log(`Enemy AI malfunction! ${e.name} does nothing this turn.`);
e.intent = { type: "block", value: 0 }; // Safe fallback
}
startPlayerTurn(ctx); startPlayerTurn(ctx);
} }

95
src/main.js

@ -18,7 +18,7 @@ const root = {
enemy: null, enemy: null,
log(m) { this.logs.push(m); this.logs = this.logs.slice(-200); }, log(m) { this.logs.push(m); this.logs = this.logs.slice(-200); },
render() { renderBattle(this); }, async render() { await renderBattle(this); },
play(i) { playCard(this, i); }, play(i) { playCard(this, i); },
showDamageNumber: showDamageNumber, showDamageNumber: showDamageNumber,
end() { endTurn(this); }, end() { endTurn(this); },
@ -44,7 +44,7 @@ const root = {
} else if (node.kind === "event") { } else if (node.kind === "event") {
renderEvent(this); renderEvent(this);
} else if (node.kind === "start") { } else if (node.kind === "start") {
renderMap(this); await renderMap(this);
} }
} }
}, },
@ -66,10 +66,10 @@ const root = {
await renderWin(this); return; await renderWin(this); return;
} }
renderMap(this); await renderMap(this);
}, },
takeReward(idx) { async takeReward(idx) {
const card = this._pendingChoices[idx]; const card = this._pendingChoices[idx];
if (card) { if (card) {
this.player.deck.push(card.id); this.player.deck.push(card.id);
@ -77,12 +77,12 @@ const root = {
} }
this._pendingChoices = null; this._pendingChoices = null;
this.save(); this.save();
renderMap(this); await renderMap(this);
}, },
skipReward() { async skipReward() {
this._pendingChoices = null; this._pendingChoices = null;
this.save(); this.save();
renderMap(this); await renderMap(this);
}, },
async onWin() { async onWin() {
@ -107,7 +107,7 @@ const root = {
this.completedNodes = []; this.completedNodes = [];
this.log(`🎉 Act ${this.currentAct === "act2" ? "II" : "I"} Complete! Advancing to the next challenge...`); this.log(`🎉 Act ${this.currentAct === "act2" ? "II" : "I"} Complete! Advancing to the next challenge...`);
this.save(); this.save();
renderMap(this); await renderMap(this);
} else { } else {
// Final victory // Final victory
this.save(); // Save progress before clearing on victory this.save(); // Save progress before clearing on victory
@ -140,11 +140,11 @@ const root = {
renderRelicSelection(this); renderRelicSelection(this);
}, },
selectStartingRelic(relicId) { async selectStartingRelic(relicId) {
attachRelics(this, [relicId]); attachRelics(this, [relicId]);
this.log(`Selected starting relic: ${relicId}`); this.log(`Selected starting relic: ${relicId}`);
this.save(); this.save();
renderMap(this); await renderMap(this);
}, },
save() { save() {
@ -170,16 +170,57 @@ const root = {
const saveData = localStorage.getItem('birthday-spire-save'); const saveData = localStorage.getItem('birthday-spire-save');
if (saveData) { if (saveData) {
const data = JSON.parse(saveData); const data = JSON.parse(saveData);
this.player = data.player;
this.nodeId = data.nodeId; // Validate essential save data
this.currentAct = data.currentAct || "act1"; if (!data || typeof data !== 'object') {
throw new Error('Invalid save data format');
}
if (!data.player || typeof data.player !== 'object') {
throw new Error('Invalid player data');
}
if (!data.nodeId || typeof data.nodeId !== 'string') {
throw new Error('Invalid node ID');
}
// Validate current act and ensure map exists
const actId = data.currentAct || "act1";
if (!MAPS[actId]) {
console.warn(`Invalid act ${actId}, falling back to act1`);
this.currentAct = "act1";
} else {
this.currentAct = actId;
}
this.map = MAPS[this.currentAct]; this.map = MAPS[this.currentAct];
this.relicStates = data.relicStates || [];
this.completedNodes = data.completedNodes || [];
this.logs = data.logs || [];
this._battleInProgress = data.battleInProgress || false;
// Validate that the nodeId exists in the current map
const nodeExists = this.map.nodes.some(n => n.id === data.nodeId);
if (!nodeExists) {
console.warn(`Node ${data.nodeId} not found in ${this.currentAct}, starting from beginning`);
this.nodeId = this.map.nodes.find(n => n.kind === "start").id;
} else {
this.nodeId = data.nodeId;
}
// Validate player data has required fields
if (typeof data.player.hp !== 'number' || data.player.hp < 0) {
throw new Error('Invalid player HP');
}
if (typeof data.player.maxHp !== 'number' || data.player.maxHp <= 0) {
throw new Error('Invalid player max HP');
}
if (!Array.isArray(data.player.deck)) {
throw new Error('Invalid player deck');
}
this.player = data.player;
this.relicStates = Array.isArray(data.relicStates) ? data.relicStates : [];
this.completedNodes = Array.isArray(data.completedNodes) ? data.completedNodes : [];
this.logs = Array.isArray(data.logs) ? data.logs : [];
this._battleInProgress = Boolean(data.battleInProgress);
this.restoreCardEffects(); this.restoreCardEffects();
this.log('Game loaded from save.'); this.log('Game loaded from save.');
@ -187,6 +228,8 @@ const root = {
} }
} catch (e) { } catch (e) {
console.warn('Failed to load game:', e); console.warn('Failed to load game:', e);
console.warn('Clearing corrupted save data');
this.clearSave();
} }
return false; return false;
}, },
@ -227,8 +270,8 @@ function pickCards(n) {
function shuffle(a) { for (let i = a.length - 1; i > 0; i--) { const j = Math.floor(Math.random() * (i + 1));[a[i], a[j]] = [a[j], a[i]] } return a; } function shuffle(a) { for (let i = a.length - 1; i > 0; i--) { const j = Math.floor(Math.random() * (i + 1));[a[i], a[j]] = [a[j], a[i]] } return a; }
const _createBattle = root.go.bind(root); const _createBattle = root.go.bind(root);
root.go = function(nextId) { root.go = async function(nextId) {
_createBattle(nextId); await _createBattle(nextId);
const node = this.map.nodes.find(n => n.id === this.nodeId); const node = this.map.nodes.find(n => n.id === this.nodeId);
if (node && (node.kind === "battle" || node.kind === "elite" || node.kind === "boss")) { if (node && (node.kind === "battle" || node.kind === "elite" || node.kind === "boss")) {
const ctx = makeBattleContext(this); const ctx = makeBattleContext(this);
@ -304,11 +347,11 @@ async function initializeGame() {
return; return;
default: default:
console.warn(`Unknown screen: ${screenParam}. Loading normal game.`); console.warn(`Unknown screen: ${screenParam}. Loading normal game.`);
loadNormalGame(); await loadNormalGame();
return; return;
} }
} else { } else {
loadNormalGame(); await loadNormalGame();
} }
} }
@ -406,21 +449,21 @@ function showCountdown(birthday) {
}, 1000); }, 1000);
} }
function loadNormalGame() { async function loadNormalGame() {
const hasLoadedData = root.load(); const hasLoadedData = root.load();
if (hasLoadedData) { if (hasLoadedData) {
// If we were in a battle, resume it // If we were in a battle, resume it
if (root._battleInProgress) { if (root._battleInProgress) {
const node = root.map.nodes.find(n => n.id === root.nodeId); const node = root.map.nodes.find(n => n.id === root.nodeId);
if (node && (node.kind === "battle" || node.kind === "elite" || node.kind === "boss")) { if (node && (node.kind === "battle" || node.kind === "elite" || node.kind === "boss")) {
root.go(root.nodeId); await root.go(root.nodeId);
} else { } else {
// Battle state inconsistent, go to map // Battle state inconsistent, go to map
root._battleInProgress = false; root._battleInProgress = false;
renderMap(root); await renderMap(root);
} }
} else { } else {
renderMap(root); await renderMap(root);
} }
} else { } else {
root.reset(); root.reset();

Loading…
Cancel
Save