forked from phenom/healer-man
Correct minion sacrifice and passive talent semantics (refs #19)
Coordinator Root /root, session 01a12399-cfab-7c72-8455-e0e4cb7e156c. Independently reviewed by /root/combat_source_finish_luna (GPT-6 Luna xhigh). 31 focused DK tests, required release check and complete source type check pass.
This commit is contained in:
1 parent
a9322a3897
commit
3687c423e2
11 files changed
+284
-10
No files matched your search
+54
-1
@@ -1,3 +1,4 @@
|
||||
import { eligibleSacrificeMinion } from "./sacrificeMinion";
|
||||
import { healerManaRecoveryAmount, synchronizeHealerManaRecovery } from "./healerManaRecovery";
|
||||
import { nativeDamageCasterAmount, nativeHolyHealCasterAmount, nativeHolyHealTargetAmount, nativePeriodicHealSnapshot, nativePeriodicDamageCasterSnapshot, nativeTrainingAutoAttackProfile, nativeDisarmedMainHandAutoAttackProfile, matchesNativeTrainingGear, type RomNativeDamageTargetContext } from "./romNativeCombatAmount";
|
||||
import { romNativeNpcCombatant, romNativeTrainingCombatant, romNativePlayerHitProfile, romNativeNpcHitProfile, resolveRomNativeCombatResult,
|
||||
@@ -1712,6 +1713,7 @@ export interface CombatState {
|
||||
controlMob: (id: string, sourceAbilityId: string, status: MobStatusKind, durationMs: number, now?: number, breakOnDamage?: boolean, sourceActorId?: string, magnitude?: number) => boolean;
|
||||
dispelActor: (id: string, categories: readonly DispelCategory[], maxCount?: number, now?: number) => number;
|
||||
healActor: (id: string, amount: number, request: ActorHealingRequest) => HealingResolution;
|
||||
sacrificeActorMinion: (ownerActorId: string, abilityId: string, effect: Extract<AbilityEffect, { kind: "sacrifice-minion" }>, origin: CombatPosition, now?: number) => boolean;
|
||||
/** Applies the CoA Holy Candle self-damage trigger to a companion or channel caster. */
|
||||
triggerRomPriestHolyCandle: (ownerActorId: string, healingAbilityId: string, now?: number) => number;
|
||||
reviveActor: (id: string, percentMaxHealth: number, sourceActorId: string, abilityId?: string, now?: number) => number;
|
||||
@@ -1822,6 +1824,30 @@ let summonSequence = 0;
|
||||
let areaEffectSequence = 0;
|
||||
let combatEventSequence = 0;
|
||||
|
||||
function sacrificeMinionInWork(
|
||||
work: Pick<MutableCastWork, "summons" | "actors" | "auras" | "effects" | "combatEvents">,
|
||||
ownerActorId: string,
|
||||
abilityId: string,
|
||||
effect: Extract<AbilityEffect, { kind: "sacrifice-minion" }>,
|
||||
origin: CombatPosition,
|
||||
now: number,
|
||||
): boolean {
|
||||
const minion = eligibleSacrificeMinion(work.summons, ownerActorId, effect, origin, now);
|
||||
if (!minion) return false;
|
||||
work.summons = work.summons.map((summon) => summon.id === minion.id
|
||||
? { ...summon, health: 0, targetId: null, cast: null } : summon);
|
||||
const actor = work.actors[minion.id];
|
||||
if (actor) work.actors[minion.id] = { ...actor, health: 0, alive: false, cast: null };
|
||||
work.auras = work.auras.filter((aura) => aura.targetId !== minion.id);
|
||||
work.effects = work.effects.filter((candidate) => candidate.targetId !== minion.id);
|
||||
recordCombatEventInWork(work, {
|
||||
occurredAt: now, kind: "death", sourceActorId: ownerActorId,
|
||||
targetActorId: minion.id, abilityId, rawAmount: 0, effectiveAmount: 0,
|
||||
overheal: 0, absorbed: 0, critical: false, outcome: "sacrificed",
|
||||
});
|
||||
return true;
|
||||
}
|
||||
|
||||
function initialSummonCombatRuntime(): Pick<CombatSummon,
|
||||
"abilityIds" | "resources" | "cooldowns" | "globalCooldownEndsAt" | "cast" | "controlledUntil" | "schoolLockedUntil"
|
||||
> {
|
||||
@@ -1908,7 +1934,7 @@ function recordCombatEvent(
|
||||
return appendCombatEvent(events, input, combatEventSequence);
|
||||
}
|
||||
|
||||
function recordCombatEventInWork(work: MutableCastWork, input: CombatEventInput): void {
|
||||
function recordCombatEventInWork(work: Pick<MutableCastWork, "combatEvents">, input: CombatEventInput): void {
|
||||
work.combatEvents = [...recordCombatEvent(work.combatEvents, input)];
|
||||
}
|
||||
|
||||
@@ -13665,6 +13691,8 @@ export const useCombatStore = create<CombatState>((set, get) => ({
|
||||
&& Object.values(state.mobs).some((mob) => mob.engaged && !mob.dead)) failure = "condition-not-met";
|
||||
else if (ability.name === "Enraged Regeneration"
|
||||
&& !state.auras.some((aura) => consumableEnrage(aura, PLAYER_AURA_ENTITY_ID, now))) failure = "condition-not-met";
|
||||
else if (ability.effects.some((effect) => effect.kind === "sacrifice-minion"
|
||||
&& !eligibleSacrificeMinion(state.summons, PLAYER_AGGRO_ID, effect, currentOrigin(state, origin), now))) failure = "condition-not-met";
|
||||
else if (state.classId === "hunter" && ability.name === "Revive Pet"
|
||||
&& !state.summons.some((summon) => summon.health <= 0
|
||||
&& (summon.id.startsWith("player-pet-")
|
||||
@@ -16879,6 +16907,22 @@ export const useCombatStore = create<CombatState>((set, get) => ({
|
||||
]);
|
||||
}
|
||||
}
|
||||
} else if (effect.kind === "sacrifice-minion") {
|
||||
if (!sacrificeMinionInWork(work, PLAYER_AGGRO_ID, ability.id, effect, playerOrigin, now)) continue;
|
||||
const amount = Math.round(resolveAuraHealingValue(
|
||||
resolveAuraHealingValue(Math.round(work.maxHealth * effect.percentMaxHealth),
|
||||
aurasForEntity(work.auras, PLAYER_AURA_ENTITY_ID), "done"),
|
||||
aurasForEntity(work.auras, PLAYER_AURA_ENTITY_ID), "taken",
|
||||
));
|
||||
const healed = healPlayerInWork(work, amount);
|
||||
work.totalHealing += healed;
|
||||
recordCombatEventInWork(work, {
|
||||
occurredAt: now, kind: "healing", sourceActorId: PLAYER_AGGRO_ID,
|
||||
targetActorId: PLAYER_AURA_ENTITY_ID, abilityId: ability.id, school: spellSchool,
|
||||
rawAmount: amount, effectiveAmount: healed, overheal: amount - healed,
|
||||
absorbed: 0, critical: false,
|
||||
});
|
||||
addHealingThreatInWork(work, castThreatSource, healed, now);
|
||||
} else if (effect.kind === "execute") {
|
||||
for (const id of affectedMobIds) {
|
||||
const mob = work.mobs[id];
|
||||
@@ -18676,6 +18720,15 @@ export const useCombatStore = create<CombatState>((set, get) => ({
|
||||
return applied;
|
||||
},
|
||||
|
||||
sacrificeActorMinion: (ownerActorId, abilityId, effect, origin, now = combatNow()) => {
|
||||
const state = get();
|
||||
const work = { summons: [...state.summons], actors: { ...state.actors },
|
||||
auras: [...state.auras], effects: [...state.effects], combatEvents: [...state.combatEvents] };
|
||||
if (!sacrificeMinionInWork(work, ownerActorId, abilityId, effect, origin, now)) return false;
|
||||
set(work);
|
||||
return true;
|
||||
},
|
||||
|
||||
healActor: (id, amount, request) => {
|
||||
const now = request.now ?? combatNow();
|
||||
const state = get();
|
||||
|
||||
Reference in new issue
Block a user