diff --git a/assets/avatars/merge_conflict_enemy.png b/assets/avatars/codegirl.png
similarity index 100%
rename from assets/avatars/merge_conflict_enemy.png
rename to assets/avatars/codegirl.png
diff --git a/src/data/enemies.js b/src/data/enemies.js
index ea24af7..6baffb1 100644
--- a/src/data/enemies.js
+++ b/src/data/enemies.js
@@ -1,64 +1,49 @@
export const ENEMIES = {
- chat_gremlin: {
- id: "chat_gremlin", name: "Old Man Judo", maxHp: 40,
+ old_man_judo: {
+ id: "old_man_judo", name: "Old Man Judo", maxHp: 40,
avatar: "assets/avatars/13.png", // Young person with cap
background: "assets/backgrounds/terrace.png",
ai: (turn) => turn % 3 === 0 ? { type: "block", value: 6 } : { type: "attack", value: turn % 2 === 0 ? 7 : 8 }
},
- type_checker: {
- id: "type_checker", name: "Type Checker", maxHp: 45,
- avatar: "assets/avatars/type_checker.png", // Scholar/mage with glasses
- background: "assets/backgrounds/castle.png",
- ai: (turn) => (turn % 3 === 0) ? { type: "debuff", value: 1 } : { type: "attack", value: 8 },
- onDebuff: (ctx) => ctx.applyWeak(ctx.player, 1)
- },
- js_blob: {
- id: "js_blob", name: "JS Blob", maxHp: 60,
- avatar: "assets/avatars/js_blob.png", // Mysterious hooded figure
- background: "assets/backgrounds/dead forest.png",
- ai: (turn) => (turn % 2 === 0) ? { type: "attack", value: 12 } : { type: "block", value: 6 },
- onBlock: (ctx, val) => ctx.enemy.block += val
- },
- infinite_loop: {
- id: "infinite_loop", name: "Beastco", maxHp: 35,
+ beastco: {
+ id: "beastco", name: "Beastco", maxHp: 35,
avatar: "assets/avatars/2.png", // Dizzy/confused character
background: "assets/backgrounds/throne room.png",
ai: (turn) => ({ type: "attack", value: 4 }),
},
- merge_conflict_enemy: {
- id: "merge_conflict_enemy", name: "Codegirl", maxHp: 50,
- avatar: "assets/avatars/merge_conflict_enemy.png", // Warrior with conflicted expression
+ codegirl: {
+ id: "codegirl", name: "Codegirl", maxHp: 50,
+ avatar: "assets/avatars/codegirl.png", // Warrior with conflicted expression
background: "assets/backgrounds/terrace.png", // Repeat background
ai: (turn) => turn <= 4 ? { type: "attack", value: 8 } : { type: "debuff", value: 1 },
onDebuff: (ctx) => {
-
ctx.enemy.hp = Math.min(ctx.enemy.maxHp, ctx.enemy.hp + 8);
ctx.log("Codegirl resolves the merge conflict and heals 8 HP!");
}
},
- bug_404: {
- id: "bug_404", name: "404 Bug", maxHp: 45,
- avatar: "assets/avatars/bug_404.png", // Elusive character
- background: "assets/backgrounds/castle.png", // Repeat background
- ai: (turn) => ({ type: "attack", value: 10 }),
-
- },
- elite_ts_demon: {
- id: "elite_ts_demon", name: "Nightshadedude", maxHp: 85,
+ nightshadedude: {
+ id: "nightshadedude", name: "Nightshadedude", maxHp: 85,
avatar: "assets/avatars/11.png", // Powerful demon/witch
background: "assets/backgrounds/dead forest.png", // Repeat background
ai: (turn) => turn % 3 === 1 ? { type: "debuff", value: 1 } : { type: "attack", value: 14 },
onDebuff: (ctx) => ctx.applyVulnerable(ctx.player, 1)
},
- elite_refactor: {
- id: "elite_refactor", name: "Refactor Dragon (Elite)", maxHp: 90,
- avatar: "assets/avatars/elite_refactor.png", // Regal/noble character
- background: "assets/backgrounds/throne room.png", // Repeat background
- ai: (turn) => ({ type: "attack", value: 10 + Math.floor(turn * 1.5) })
+ defyusall: {
+ id: "defyusall", name: "Defyusall", maxHp: 65,
+ avatar: "assets/avatars/bug_404.png", // Elusive character
+ background: "assets/backgrounds/castle.png",
+ ai: (turn) => turn % 3 === 0 ? { type: "block", value: 8 } : { type: "attack", value: 10 },
+ },
+ lithium: {
+ id: "lithium", name: "Lithium", maxHp: 55,
+ avatar: "assets/avatars/type_checker.png", // Scholar/mage with glasses
+ background: "assets/backgrounds/dead forest.png",
+ ai: (turn) => (turn % 2 === 0) ? { type: "debuff", value: 1 } : { type: "attack", value: 12 },
+ onDebuff: (ctx) => ctx.applyWeak(ctx.player, 1)
},
- boss_birthday_bug: {
- id: "boss_birthday_bug", name: "Teej", maxHp: 120,
+ teej: {
+ id: "teej", name: "Teej", maxHp: 120,
avatar: "assets/avatars/boss_birthday_bug.png", // Demanding/angry character
background: "assets/backgrounds/throne room.png", // Repeat background - fitting for boss
ai: (turn) => {
diff --git a/src/data/maps.js b/src/data/maps.js
index 27bed66..d125856 100644
--- a/src/data/maps.js
+++ b/src/data/maps.js
@@ -3,16 +3,20 @@ export const MAPS = {
act1: {
id: "act1", name: "Birthday Spire β Act I",
nodes: [
- { id: "n1", kind: "start", next: ["n2", "n3"] },
- { id: "n2", kind: "battle", enemy: "chat_gremlin", next: ["n4"] },
- { id: "n3", kind: "event", next: ["n4"] },
- { id: "n4", kind: "battle", enemy: "infinite_loop", next: ["n5", "n6"] },
- { id: "n5", kind: "rest", next: ["n7"] },
- { id: "n6", kind: "shop", next: ["n7"] },
- { id: "n7", kind: "battle", enemy: "merge_conflict_enemy", next: ["n8"] },
- { id: "n8", kind: "elite", enemy: "elite_ts_demon", next: ["n9"] },
- { id: "n9", kind: "rest", next: ["n10"] },
- { id: "n10", kind: "boss", enemy: "boss_birthday_bug", next: [] },
+ { id: "n1", kind: "start", next: ["n2", "n3"], x: 500, y: 760 },
+ { id: "n2", kind: "battle", enemy: "old_man_judo", next: ["n4"], x: 350, y: 695 },
+ { id: "n3", kind: "event", next: ["n4"], x: 650, y: 695 },
+ { id: "n4", kind: "battle", enemy: "beastco", next: ["n5", "n6"], x: 500, y: 600 },
+ { id: "n5", kind: "rest", next: ["n7"], x: 350, y: 525 },
+ { id: "n6", kind: "shop", next: ["n7"], x: 650, y: 525 },
+ { id: "n7", kind: "battle", enemy: "codegirl", next: ["n8"], x: 500, y: 460 },
+ { id: "n8", kind: "battle", enemy: "defyusall", next: ["n9", "n10"], x: 500, y: 395 },
+ { id: "n9", kind: "rest", next: ["n11"], x: 350, y: 330 },
+ { id: "n10", kind: "shop", next: ["n11"], x: 650, y: 330 },
+ { id: "n11", kind: "battle", enemy: "lithium", next: ["n12"], x: 500, y: 280 },
+ { id: "n12", kind: "elite", enemy: "nightshadedude", next: ["n13"], x: 500, y: 205 },
+ { id: "n13", kind: "rest", next: ["n14"], x: 500, y: 125 },
+ { id: "n14", kind: "boss", enemy: "teej", next: [], x: 500, y: 40 },
]
}
};
diff --git a/src/engine/battle.js b/src/engine/battle.js
index b3f02fb..dd3c47f 100644
--- a/src/engine/battle.js
+++ b/src/engine/battle.js
@@ -125,13 +125,6 @@ export function enemyTurn(ctx) {
}
function applyDamage(ctx, target, raw, label) {
-
- if (target === ctx.enemy && ctx.enemy.id === "bug_404" && ctx.enemy.turn % 3 === 0) {
- ctx.log(`${ctx.enemy.name} phases out and dodges your attack completely!`);
- return;
- }
-
-
let dmg = raw;
for (const r of ctx.relicStates) {
if (r.hooks?.onDamageTaken && target === ctx.player) dmg = r.hooks.onDamageTaken(ctx, dmg);
diff --git a/src/ui/render.js b/src/ui/render.js
index 49b4b1d..58af47e 100644
--- a/src/ui/render.js
+++ b/src/ui/render.js
@@ -1,13 +1,13 @@
// Simple audio utility
function playSound(soundFile) {
- try {
- const audio = new Audio(`assets/sounds/${soundFile}`);
- audio.volume = 0.3;
- audio.play().catch(e => {}); // Silently fail if no audio
- } catch (e) {
- // Silently fail if audio not available
- }
+ try {
+ const audio = new Audio(`assets/sounds/${soundFile}`);
+ audio.volume = 0.3;
+ audio.play().catch(e => { }); // Silently fail if no audio
+ } catch (e) {
+ // Silently fail if audio not available
+ }
}
export function showDamageNumber(damage, target, isPlayer = false) {
@@ -42,8 +42,8 @@ export function showDamageNumber(damage, target, isPlayer = false) {
}
export async function renderBattle(root) {
- const app = root.app;
- const p = root.player, e = root.enemy;
+ const app = root.app;
+ const p = root.player, e = root.enemy;
const { ENEMIES } = await import("../data/enemies.js");
@@ -51,13 +51,13 @@ export async function renderBattle(root) {
const backgroundImage = enemyData?.background || null;
- const intentInfo = {
+ const intentInfo = {
attack: { emoji: '', text: `Will attack for ${e.intent.value} damage`, color: 'danger' },
block: { emoji: '', text: `Will gain ${e.intent.value} block`, color: 'info' },
debuff: { emoji: '', text: 'Will apply a debuff', color: 'warning' }
}[e.intent.type] || { emoji: '', text: 'Unknown intent', color: 'neutral' };
- app.innerHTML = `
+ app.innerHTML = `
@@ -183,12 +183,12 @@ export async function renderBattle(root) {
- ${p.hand.length === 0 ?
- '
π΄ No cards in hand - End turn to draw new cards
' :
- p.hand.map((card, i) => {
- const canPlay = p.energy >= card.cost;
- const cardType = card.type === 'attack' ? 'attack' : card.type === 'skill' ? 'skill' : 'power';
- return `
+ ${p.hand.length === 0 ?
+ '
π΄ No cards in hand - End turn to draw new cards
' :
+ p.hand.map((card, i) => {
+ const canPlay = p.energy >= card.cost;
+ const cardType = card.type === 'attack' ? 'attack' : card.type === 'skill' ? 'skill' : 'power';
+ return `
@@ -209,8 +209,8 @@ export async function renderBattle(root) {
${!canPlay ? `
Need ${card.cost} energy
` : ''}
`;
- }).join('')
- }
+ }).join('')
+ }
@@ -235,96 +235,96 @@ export async function renderBattle(root) {
`;
- app.querySelectorAll("[data-play]").forEach(btn => {
- btn.addEventListener("mouseenter", () => {
- if (btn.classList.contains('playable')) {
- playSound('swipe.mp3');
- root.selectedCardIndex = null;
- updateCardSelection(root);
- }
- });
-
- btn.addEventListener("click", () => {
- const index = parseInt(btn.dataset.play, 10);
- const card = p.hand[index];
- if (p.energy >= card.cost) {
- root.play(index);
- // Clear selection when card is played via mouse
- root.selectedCardIndex = null;
- updateCardSelection(root);
- }
- });
+ app.querySelectorAll("[data-play]").forEach(btn => {
+ btn.addEventListener("mouseenter", () => {
+ if (btn.classList.contains('playable')) {
+ playSound('swipe.mp3');
+ root.selectedCardIndex = null;
+ updateCardSelection(root);
+ }
});
-
- const endTurnBtn = app.querySelector("[data-action='end']");
- if (endTurnBtn) {
- endTurnBtn.addEventListener("click", () => {
-
- try {
- root.end();
- } catch (error) {
- console.error("Error ending turn:", error);
- }
- });
- }
-
- // Initialize card selection state if not exists
- if (!root.selectedCardIndex) {
+ btn.addEventListener("click", () => {
+ const index = parseInt(btn.dataset.play, 10);
+ const card = p.hand[index];
+ if (p.energy >= card.cost) {
+ root.play(index);
+ // Clear selection when card is played via mouse
root.selectedCardIndex = null;
+ updateCardSelection(root);
+ }
+ });
+ });
+
+ const endTurnBtn = app.querySelector("[data-action='end']");
+ if (endTurnBtn) {
+
+ endTurnBtn.addEventListener("click", () => {
+
+ try {
+ root.end();
+ } catch (error) {
+ console.error("Error ending turn:", error);
+ }
+ });
+ }
+
+ // Initialize card selection state if not exists
+ if (!root.selectedCardIndex) {
+ root.selectedCardIndex = null;
+ }
+
+ window.onkeydown = (e) => {
+ if (e.key.toLowerCase() === "e") {
+ try {
+ root.end();
+ } catch (error) {
+ console.error("Error ending turn via keyboard:", error);
+ }
}
- window.onkeydown = (e) => {
- if (e.key.toLowerCase() === "e") {
- try {
- root.end();
- } catch (error) {
- console.error("Error ending turn via keyboard:", error);
- }
- }
-
- const n = parseInt(e.key, 10);
- if (n >= 1 && n <= p.hand.length) {
- const cardIndex = n - 1;
- const card = p.hand[cardIndex];
-
- if (root.selectedCardIndex === cardIndex) {
- // Second press of same key - play the card
- if (p.energy >= card.cost) {
- root.play(cardIndex);
- root.selectedCardIndex = null; // Clear selection
- updateCardSelection(root);
- }
- } else {
- // First press or different key - select the card
- root.selectedCardIndex = cardIndex;
- updateCardSelection(root);
- playSound('swipe.mp3'); // Play swipe sound on keyboard selection
- }
+ const n = parseInt(e.key, 10);
+ if (n >= 1 && n <= p.hand.length) {
+ const cardIndex = n - 1;
+ const card = p.hand[cardIndex];
+
+ if (root.selectedCardIndex === cardIndex) {
+ // Second press of same key - play the card
+ if (p.energy >= card.cost) {
+ root.play(cardIndex);
+ root.selectedCardIndex = null; // Clear selection
+ updateCardSelection(root);
}
- };
+ } else {
+ // First press or different key - select the card
+ root.selectedCardIndex = cardIndex;
+ updateCardSelection(root);
+ playSound('swipe.mp3'); // Play swipe sound on keyboard selection
+ }
+ }
+ };
// Auto-scroll fight log to bottom
const logContent = document.getElementById('fight-log-content');
if (logContent) {
logContent.scrollTop = logContent.scrollHeight;
- }
-
- // Apply initial card selection visual state
- updateCardSelection(root);
+ }
+
+ // Apply initial card selection visual state
+ updateCardSelection(root);
}
export async function renderMap(root) {
- const { CARDS } = await import("../data/cards.js");
- const { ENEMIES } = await import("../data/enemies.js");
- const m = root.map;
- const currentId = root.nodeId;
+ const { CARDS } = await import("../data/cards.js");
+ const { ENEMIES } = await import("../data/enemies.js");
+ const m = root.map;
+ const currentId = root.nodeId;
const currentNode = m.nodes.find(n => n.id === currentId);
const nextIds = currentNode ? currentNode.next : [];
- const getNodeEmoji = (kind) => {
- const emojis = {
+ const getNodeEmoji = (kind) => {
+ const emojis = {
start: '

',
battle: '

',
elite: '

',
@@ -332,49 +332,49 @@ export async function renderMap(root) {
rest: '

',
shop: '

',
event: '

'
- };
- return emojis[kind] || 'β';
};
+ return emojis[kind] || 'β';
+ };
- const getNodeDescription = (node) => {
+ const getNodeDescription = (node) => {
switch (node.kind) {
- case 'start':
+ case 'start':
return '
Starting Point\nBegin your journey up ThePrimeagen Spire';
- case 'battle':
- const enemy = ENEMIES[node.enemy];
+ case 'battle':
+ const enemy = ENEMIES[node.enemy];
return `
Battle\nFight: ${enemy?.name || 'Unknown Enemy'}\nHP: ${enemy?.maxHp || '?'}`;
- case 'elite':
- const elite = ENEMIES[node.enemy];
+ case 'elite':
+ const elite = ENEMIES[node.enemy];
return `
Elite Battle\nFight: ${elite?.name || 'Unknown Elite'}\nHP: ${elite?.maxHp || '?'}\nTough enemy with better rewards`;
- case 'boss':
- const boss = ENEMIES[node.enemy];
+ case 'boss':
+ const boss = ENEMIES[node.enemy];
return `
Boss Battle\nFight: ${boss?.name || 'Unknown Boss'}\nHP: ${boss?.maxHp || '?'}\nFinal challenge of the act`;
- case 'rest':
+ case 'rest':
return '
Rest Site\nHeal up to 30% max HP\nor upgrade a card';
- case 'shop':
+ case 'shop':
return '
Shop\nSpend your hard-earned gold';
- case 'event':
+ case 'event':
return '
Random Event\nBirthday-themed encounter\nUnknown outcome\nPotential rewards or challenges';
- default:
+ default:
return '
Unknown\nMysterious node';
- }
- };
+ }
+ };
- const getNodeTooltipData = (node) => {
- const description = getNodeDescription(node);
- let avatarPath = null;
-
- if (['battle', 'elite', 'boss'].includes(node.kind) && node.enemy) {
- const enemy = ENEMIES[node.enemy];
- if (enemy?.avatar) {
- avatarPath = enemy.avatar;
- }
- }
-
- return { description, avatarPath };
- };
+ const getNodeTooltipData = (node) => {
+ const description = getNodeDescription(node);
+ let avatarPath = null;
- root.app.innerHTML = `
+ if (['battle', 'elite', 'boss'].includes(node.kind) && node.enemy) {
+ const enemy = ENEMIES[node.enemy];
+ if (enemy?.avatar) {
+ avatarPath = enemy.avatar;
+ }
+ }
+
+ return { description, avatarPath };
+ };
+
+ root.app.innerHTML = `
@@ -588,17 +568,17 @@ May this birthday bring joy in each moment youβve got.
${Object.entries(
- root.player.deck.reduce((acc, cardId) => {
- acc[cardId] = (acc[cardId] || 0) + 1;
- return acc;
- }, {})
+ root.player.deck.reduce((acc, cardId) => {
+ acc[cardId] = (acc[cardId] || 0) + 1;
+ return acc;
+ }, {})
).map(([cardId, count], index) => {
- const card = CARDS[cardId];
- if (!card) return '';
-
- const cardType = card.type === 'attack' ? 'attack' : card.type === 'skill' ? 'skill' : 'power';
-
- return `
+ const card = CARDS[cardId];
+ if (!card) return '';
+
+ const cardType = card.type === 'attack' ? 'attack' : card.type === 'skill' ? 'skill' : 'power';
+
+ return `
`;
- }).join('')}
+ }).join('')}
@@ -622,20 +602,20 @@ May this birthday bring joy in each moment youβve got.
`;
- root.app.querySelectorAll("[data-node]").forEach(el => {
- if (!el.dataset.node) return;
- el.addEventListener("click", () => root.go(el.dataset.node));
- });
+ root.app.querySelectorAll("[data-node]").forEach(el => {
+ if (!el.dataset.node) return;
+ el.addEventListener("click", () => root.go(el.dataset.node));
+ });
window.showTooltip = function (event) {
- const tooltip = document.getElementById('custom-tooltip');
- const node = event.target.closest('.spire-node');
- const content = node.dataset.tooltip;
- const avatarPath = node.dataset.avatar;
-
- let tooltipHTML = '';
- if (avatarPath) {
- tooltipHTML = `
+ const tooltip = document.getElementById('custom-tooltip');
+ const node = event.target.closest('.spire-node');
+ const content = node.dataset.tooltip;
+ const avatarPath = node.dataset.avatar;
+
+ let tooltipHTML = '';
+ if (avatarPath) {
+ tooltipHTML = `