diff --git a/src/lib/game/store.ts b/src/lib/game/store.ts index 9d9ebbf..d650973 100755 --- a/src/lib/game/store.ts +++ b/src/lib/game/store.ts @@ -1146,6 +1146,190 @@ export const useGameStore = create()( } } + // ─── Golemancy Processing ───────────────────────────────────────────────── + let golemancy = state.golemancy; + const fabricatorLevel = state.attunements.fabricator?.level || 0; + const maxGolemSlots = getGolemSlots(fabricatorLevel); + + // Check if golems need to be summoned (floor changed or first summon) + const floorChanged = currentFloor !== golemancy.lastSummonFloor; + const inCombatRoom = currentRoom.roomType !== 'puzzle'; + + if (state.currentAction === 'climb' && inCombatRoom && floorChanged && maxGolemSlots > 0) { + // Determine which golems should be summoned + const unlockedElementIds = Object.entries(elements) + .filter(([, e]) => e.unlocked) + .map(([id]) => id); + + const enabledAndUnlocked = golemancy.enabledGolems.filter(golemId => + isGolemUnlocked(golemId, state.attunements, unlockedElementIds) + ); + + // Limit to available slots + const golemsToSummon = enabledAndUnlocked.slice(0, maxGolemSlots); + + // Summon golems that can be afforded + const summonedGolems: typeof golemancy.summonedGolems = []; + let summonCostsPaid = 0; + + for (const golemId of golemsToSummon) { + if (canAffordGolemSummon(golemId, rawMana, elements)) { + const afterCost = deductGolemSummonCost(golemId, rawMana, elements); + rawMana = afterCost.rawMana; + elements = afterCost.elements; + + summonedGolems.push({ + golemId, + summonedFloor: currentFloor, + attackProgress: 0, + }); + summonCostsPaid++; + } + } + + if (summonedGolems.length > 0) { + golemancy = { + ...golemancy, + summonedGolems, + lastSummonFloor: currentFloor, + }; + + if (summonCostsPaid > 0) { + log = [`🗿 Summoned ${summonedGolems.length} golem(s) on floor ${currentFloor}!`, ...log.slice(0, 49)]; + } + } else if (golemsToSummon.length > 0) { + log = [`⚠️ Could not afford to summon any golems!`, ...log.slice(0, 49)]; + } + } + + // Process golem maintenance and attacks each tick + if (golemancy.summonedGolems.length > 0 && state.currentAction === 'climb' && inCombatRoom) { + const floorDuration = getGolemFloorDuration(skills); + const survivingGolems: typeof golemancy.summonedGolems = []; + let anyGolemDismissed = false; + + for (const summonedGolem of golemancy.summonedGolems) { + const golemId = summonedGolem.golemId; + + // Check floor duration + const floorsActive = currentFloor - summonedGolem.summonedFloor; + if (floorsActive >= floorDuration) { + log = [`⏰ ${GOLEMS_DEF[golemId]?.name || golemId} returned to the earth after ${floorDuration} floor(s).`, ...log.slice(0, 49)]; + anyGolemDismissed = true; + continue; + } + + // Check and pay maintenance cost + if (!canAffordGolemMaintenance(golemId, rawMana, elements, skills)) { + log = [`💫 ${GOLEMS_DEF[golemId]?.name || golemId} dismissed - insufficient mana for maintenance!`, ...log.slice(0, 49)]; + anyGolemDismissed = true; + continue; + } + + const afterMaintenance = deductGolemMaintenance(golemId, rawMana, elements, skills); + rawMana = afterMaintenance.rawMana; + elements = afterMaintenance.elements; + + survivingGolems.push(summonedGolem); + } + + if (anyGolemDismissed) { + golemancy = { + ...golemancy, + summonedGolems: survivingGolems, + }; + } + + // Process golem attacks + for (const summonedGolem of golemancy.summonedGolems) { + const golemDef = GOLEMS_DEF[summonedGolem.golemId]; + if (!golemDef) continue; + + // Get attack speed (attacks per hour) + const attackSpeed = getGolemAttackSpeed(summonedGolem.golemId, skills); + const progressGain = HOURS_PER_TICK * attackSpeed; + + // Accumulate attack progress + summonedGolem.attackProgress = (summonedGolem.attackProgress || 0) + progressGain; + + // Process attacks when progress >= 1 + while (summonedGolem.attackProgress >= 1) { + // Find alive enemies + const aliveEnemies = currentRoom.enemies.filter(e => e.hp > 0); + if (aliveEnemies.length === 0) break; + + // Calculate damage + const baseDamage = getGolemDamage(summonedGolem.golemId, skills); + + // Determine targets (AOE vs single target) + const numTargets = golemDef.isAoe + ? Math.min(golemDef.aoeTargets, aliveEnemies.length) + : 1; + + const targets = aliveEnemies.slice(0, numTargets); + + for (const enemy of targets) { + let damage = baseDamage; + + // AOE damage falloff + if (golemDef.isAoe && numTargets > 1) { + damage *= (1 - 0.1 * (targets.indexOf(enemy))); + } + + // Apply armor reduction with pierce + const effectiveArmor = Math.max(0, enemy.armor - golemDef.armorPierce); + damage *= (1 - effectiveArmor); + + // Apply damage + enemy.hp = Math.max(0, enemy.hp - Math.floor(damage)); + } + + // Update currentRoom with damaged enemies + currentRoom = { ...currentRoom, enemies: [...currentRoom.enemies] }; + + // Reduce attack progress + summonedGolem.attackProgress -= 1; + + // Check if all enemies are dead + const allDead = currentRoom.enemies.every(e => e.hp <= 0); + if (allDead) { + // Floor cleared by golems - trigger floor change logic + const wasGuardian = GUARDIANS[currentFloor]; + if (wasGuardian && !signedPacts.includes(currentFloor)) { + signedPacts = [...signedPacts, currentFloor]; + log = [`⚔️ ${wasGuardian.name} defeated by golems! Pact signed! (${wasGuardian.pact}x)`, ...log.slice(0, 49)]; + } else if (!wasGuardian) { + const roomTypeName = currentRoom.roomType === 'swarm' ? 'Swarm' + : currentRoom.roomType === 'speed' ? 'Speed floor' + : currentRoom.roomType === 'puzzle' ? 'Puzzle' + : 'Floor'; + if (currentFloor % 5 === 0 || currentRoom.roomType !== 'combat') { + log = [`🗿 ${roomTypeName} ${currentFloor} cleared by golems!`, ...log.slice(0, 49)]; + } + } + + currentFloor = currentFloor + 1; + if (currentFloor > 100) currentFloor = 100; + currentRoom = generateFloorState(currentFloor); + floorMaxHP = getFloorMaxHP(currentFloor); + floorHP = currentRoom.enemies[0]?.hp || floorMaxHP; + maxFloorReached = Math.max(maxFloorReached, currentFloor); + castProgress = 0; + break; // Exit attack loop on floor change + } + } + } + } + + // Unsummon golems when not climbing or in puzzle room + if ((state.currentAction !== 'climb' || !inCombatRoom) && golemancy.summonedGolems.length > 0) { + log = [`🗿 Golems returned to the earth.`, ...log.slice(0, 49)]; + golemancy = { + ...golemancy, + summonedGolems: [], + }; + } + // Process crafting actions (design, prepare, enchant) const craftingUpdates = processCraftingTick( { @@ -1194,6 +1378,7 @@ export const useGameStore = create()( elements, log, castProgress, + golemancy, }); return; } @@ -1219,6 +1404,7 @@ export const useGameStore = create()( unlockedEffects, log, castProgress, + golemancy, ...craftingUpdates, }); },