// ─── Combat Tick Callback Builder ───────────────────────────────────────────── // Extracts the large combat callback lambdas from gameStore.ts tick() // to keep the coordinator under the 400-line file limit. import { HOURS_PER_TICK } from '../../constants'; import { getGuardianForFloor } from '../../data/guardian-encounters'; import { hasSpecial, SPECIAL_EFFECTS } from '../../effects/special-effects'; import type { ComputedEffects } from '../../effects/upgrade-effects.types'; interface BuildCombatCallbacksParams { ctx: { combat: { floorHP: number; floorMaxHP: number; currentFloor: number; guardianShield: number; guardianShieldMax: number; guardianBarrier: number; guardianBarrierMax: number; }; }; effects: ComputedEffects; maxMana: number; addLog: (msg: string) => void; useCombatStore: { setState: (s: Record) => void }; usePrestigeStore: { getState: () => { addDefeatedGuardian: (floor: number) => void } }; } export function buildCombatCallbacks(params: BuildCombatCallbacksParams) { const { ctx, effects, maxMana, addLog, useCombatStore, usePrestigeStore } = params; const onFloorCleared = (floor: number, wasGuardian: boolean) => { if (wasGuardian) { const defeatedGuardian = getGuardianForFloor(floor); addLog((defeatedGuardian?.name || 'Unknown') + ' defeated! Visit the Grimoire to sign a pact.'); usePrestigeStore.getState().addDefeatedGuardian(floor); } else if (floor % 5 === 0) { addLog('Floor ' + floor + ' cleared!'); } useCombatStore.setState({ guardianShield: 0, guardianShieldMax: 0, guardianBarrier: 0, guardianBarrierMax: 0 }); }; // Returns a function matching the processCombatTick onDamageDealt signature. // The returned function closes over the current tick's rawMana/elements references. const makeOnDamageDealt = (rawManaRef: () => number, elementsRef: () => Record) => { return (damage: number) => { const rawMana = rawManaRef(); const elements = elementsRef(); let dmg = damage; if (hasSpecial(effects, SPECIAL_EFFECTS.EXECUTIONER) && ctx.combat.floorHP / ctx.combat.floorMaxHP < 0.25) { dmg *= 2; } if (hasSpecial(effects, SPECIAL_EFFECTS.BERSERKER) && rawMana < maxMana * 0.5) { dmg *= 1.5; } const guardian = getGuardianForFloor(ctx.combat.currentFloor); if (guardian && (guardian.shield || guardian.barrier || guardian.healthRegen)) { let shield = ctx.combat.guardianShield; const shieldMax = ctx.combat.guardianShieldMax; let barrier = ctx.combat.guardianBarrier; const barrierMax = ctx.combat.guardianBarrierMax; if (guardian.shieldRegen && shield < shieldMax) { shield = Math.min(shieldMax, shield + guardian.shieldRegen * HOURS_PER_TICK); } if (guardian.barrierRegen && barrier < barrierMax) { barrier = Math.min(barrierMax, barrier + guardian.barrierRegen * HOURS_PER_TICK); } if (shield > 0 && dmg > 0) { const absorb = Math.min(shield, dmg); shield -= absorb; dmg -= absorb; } if (barrier > 0 && dmg > 0) { dmg *= (1 - barrier); } if (guardian.healthRegen && guardian.healthRegen > 0) { const healAmount = guardian.healthRegenIsPercent ? Math.floor(ctx.combat.floorMaxHP * guardian.healthRegen / 100 * HOURS_PER_TICK) : Math.floor(guardian.healthRegen * HOURS_PER_TICK); dmg -= healAmount; } useCombatStore.setState({ guardianShield: shield, guardianShieldMax: shieldMax, guardianBarrier: barrier, guardianBarrierMax: barrierMax, }); } return { rawMana, elements, modifiedDamage: dmg }; }; }; return { onFloorCleared, makeOnDamageDealt }; }