feat: implement non-combat room gameplay (Library, Recovery, Treasure, Puzzle)
Build and Publish Mana Loop Docker Image / build-and-publish (push) Failing after 1m4s

This commit is contained in:
2026-06-04 19:28:25 +02:00
parent 40a50d34f4
commit ee24227d62
12 changed files with 539 additions and 124 deletions
+55 -1
View File
@@ -2,7 +2,9 @@
// Spire-specific utility functions for room generation, enemy stat scaling, etc.
import type { RoomType, FloorState, EnemyState } from '../types';
import type { LootDrop } from '../types/game';
import { PUZZLE_ROOMS } from '../constants';
import { getAvailableDrops, rollLootDrops } from '../data/loot-drops';
import { getFloorMaxHP, getFloorElement } from './floor-utils';
import { getEnemyName } from './enemy-utils';
import { getGuardianForFloor, isGuardianFloor } from '../data/guardian-encounters';
@@ -159,11 +161,17 @@ export function generateSpireFloorState(floor: number, roomIndex: number, totalR
libraryRequired: 1,
};
case 'treasure':
case 'treasure': {
const loot = generateTreasureLoot(floor);
return {
roomType: 'treasure',
enemies: [],
treasureProgress: 0,
treasureRequired: 1,
treasureLoot: loot,
treasureLootClaimed: [],
};
}
default:
return generateCombatRoom(floor, element, baseHP);
@@ -267,6 +275,52 @@ export function calcInsight(floor: number, isGuardian: boolean): number {
// ─── Room Type Display ────────────────────────────────────────────────────────
/**
* Generate treasure loot for a treasure room based on floor.
* Returns pre-generated loot drops that are progressively revealed.
*/
export function generateTreasureLoot(floor: number): LootDrop[] {
const available = getAvailableDrops(floor, false);
const drops: LootDrop[] = [];
// Determine item count based on floor
let itemCount: number;
if (floor <= 10) {
itemCount = 2 + Math.floor(Math.random() * 2); // 2-3
} else if (floor <= 50) {
itemCount = 4 + Math.floor(Math.random() * 4); // 4-7
} else {
itemCount = 8 + Math.floor(Math.random() * 8); // 8-15
}
// Roll for each item
for (let i = 0; i < itemCount; i++) {
const rolled = rollLootDrops(floor, false, 0);
for (const { drop, amount } of rolled) {
// For materials, add amount; for others, just add the drop
const existing = drops.find(d => d.id === drop.id);
if (existing && existing.amount) {
existing.amount = {
min: existing.amount.min + (drop.amount?.min ?? 1),
max: existing.amount.max + (drop.amount?.max ?? amount),
};
} else {
drops.push({ ...drop, amount: drop.amount || { min: amount, max: amount } });
}
}
}
// Ensure at least one item
if (drops.length === 0) {
const fallback = available.find(d => d.type === 'material' && d.rarity === 'common');
if (fallback) {
drops.push({ ...fallback, amount: { min: 1, max: 3 } });
}
}
return drops;
}
export function getSpireRoomTypeDisplay(roomType: SpireRoomType): { label: string; icon: string; color: string } {
const displays: Record<string, { label: string; icon: string; color: string }> = {
combat: { label: 'Combat', icon: '⚔️', color: '#EF4444' },