fix: replace non-existent Golem icon with Mountain, implement golemancy tick logic
All checks were successful
Build and Publish Mana Loop Docker Image / build-and-publish (push) Successful in 2m4s
All checks were successful
Build and Publish Mana Loop Docker Image / build-and-publish (push) Successful in 2m4s
- Fix GolemancyTab.tsx icon (Golem -> Mountain) - Add golem cost helper functions (summon, maintenance, affordability checks) - Add auto-summoning at floor start for non-puzzle rooms - Add maintenance cost deduction per tick from appropriate mana pools - Add golem auto-attack logic with AOE support and armor pierce - Add floor duration tracking and golem dismissal mechanics - Integrate all golemancy skills (Mastery, Efficiency, Longevity, Siphon) - All 44 tests pass
This commit is contained in:
@@ -1146,6 +1146,190 @@ export const useGameStore = create<GameStore>()(
|
||||
}
|
||||
}
|
||||
|
||||
// ─── 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<GameStore>()(
|
||||
elements,
|
||||
log,
|
||||
castProgress,
|
||||
golemancy,
|
||||
});
|
||||
return;
|
||||
}
|
||||
@@ -1219,6 +1404,7 @@ export const useGameStore = create<GameStore>()(
|
||||
unlockedEffects,
|
||||
log,
|
||||
castProgress,
|
||||
golemancy,
|
||||
...craftingUpdates,
|
||||
});
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user