fix: record control driven cast cancellations (#16)

This commit is contained in:
Codex Bot authored and Codex (Root combat coordinator) committed 2026-10-15 09:12:08 -04:00
1 parent 6a716fb8ca
commit 9be6f54c52
3 files changed
+214 -5

No files matched your search

+52 -5
View File
@@ -1999,10 +1999,16 @@ function emitPlayerAbilityPresentation(
if (penancePulse) emitCombatPresentation({ ...event, phase: "impact" });
}
function emitPlayerAbilityCancellation(abilityId: string, occurredAt: number, origin: CombatPosition): void {
function emitPlayerAbilityCancellation(
abilityId: string,
occurredAt: number,
origin: CombatPosition,
targetId: string | null = null,
targetPosition: CombatPosition | null = null,
): void {
const ability = abilityById(abilityId);
if (!ability) return;
emitPlayerAbilityPresentation(ability, "cast-cancel", occurredAt, origin, null, origin);
emitPlayerAbilityPresentation(ability, "cast-cancel", occurredAt, origin, targetId, targetPosition ?? origin);
}
function emitPlayerAbilityResolutionPresentation(
@@ -20632,7 +20638,12 @@ export const useCombatStore = create<CombatState>((set, get) => ({
if (remainingTicks > 0) enemyPeriodicEffects.push({ ...periodic, remainingTicks, nextTickAt });
}
const enemyHealing: Array<{ readonly targetId: string; readonly amount: number }> = [];
let playerControl: { readonly mechanic: EnemyControlMechanic; readonly endsAt: number } | null = null;
let playerControl: {
readonly mechanic: EnemyControlMechanic;
readonly endsAt: number;
readonly sourceActorId: string;
readonly causeAbilityId: string;
} | null = null;
const memberControls: Array<{
readonly memberId: string;
readonly mechanic: EnemyControlMechanic;
@@ -21516,7 +21527,9 @@ export const useCombatStore = create<CombatState>((set, get) => ({
const control = resolveEnemyTriggeredShoutControl(namedAuras, actorId, now);
if (!control) continue;
if (actorId === PLAYER_AGGRO_ID) {
if (!playerControl || control.endsAt >= playerControl.endsAt) playerControl = control;
if (!playerControl || control.endsAt >= playerControl.endsAt) {
playerControl = { ...control, sourceActorId: id, causeAbilityId: attack.id };
}
} else memberControls.push({ memberId: actorId, ...control });
}
} else if (effect.kind === "control") {
@@ -21577,7 +21590,9 @@ export const useCombatStore = create<CombatState>((set, get) => ({
: auraMasteryDurationMs;
const control = { mechanic: effect.mechanic, endsAt: now + controlDurationMs };
if (actorId === PLAYER_AGGRO_ID) {
if (!playerControl || control.endsAt >= playerControl.endsAt) playerControl = control;
if (!playerControl || control.endsAt >= playerControl.endsAt) {
playerControl = { ...control, sourceActorId: id, causeAbilityId: attack.id };
}
}
else memberControls.push({ memberId: actorId, ...control });
namedAuras = [...applyCombatAura(namedAuras, {
@@ -21788,11 +21803,31 @@ export const useCombatStore = create<CombatState>((set, get) => ({
};
changed = true;
}
const controlInterruptedCast = playerControl && state.activeCast
? { cast: state.activeCast, control: playerControl }
: null;
if (changed || playerControl || state.enemyPeriodicEffects.length || enemyPeriodicEffects.length) {
set({
mobs,
enemyPeriodicEffects,
auras: namedAuras,
...(controlInterruptedCast ? {
combatEvents: recordCombatEvent(state.combatEvents, {
occurredAt: now,
kind: "interrupt",
sourceActorId: controlInterruptedCast.control.sourceActorId,
targetActorId: controlInterruptedCast.cast.targetId ?? PLAYER_AURA_ENTITY_ID,
abilityId: controlInterruptedCast.cast.abilityId,
causeAbilityId: controlInterruptedCast.control.causeAbilityId,
school: controlInterruptedCast.cast.school,
rawAmount: 0,
effectiveAmount: 0,
overheal: 0,
absorbed: 0,
critical: false,
outcome: `control:${controlInterruptedCast.control.mechanic}`,
}),
} : {}),
...(playerControl
? {
...(state.activeCast
@@ -21812,6 +21847,18 @@ export const useCombatStore = create<CombatState>((set, get) => ({
: {}),
});
}
if (controlInterruptedCast) {
emitPlayerAbilityCancellation(
controlInterruptedCast.cast.abilityId,
now,
state.playerPosition,
controlInterruptedCast.cast.targetId,
controlInterruptedCast.cast.targetId
? getMobPosition(controlInterruptedCast.cast.targetId)
?? actorPosition(state, controlInterruptedCast.cast.targetId)
: null,
);
}
let appliedDamage = 0;
for (const hit of incomingHits) {
if (hit.actorId === PLAYER_AURA_ENTITY_ID