100 lines
4.0 KiB
TypeScript
100 lines
4.0 KiB
TypeScript
// ─── 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<string, unknown>) => 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<string, { current: number; max: number; unlocked: boolean }>) => {
|
|
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 };
|
|
}
|