Update Healer Man server

This commit is contained in:
phenom committed 2026-10-04 22:51:21 -04:00
1 parent 6cc1b9217f
commit 6114a2295f
67 files changed
+6241 -347

No files matched your search

+174 -13
View File
@@ -565,6 +565,15 @@ import {
ROM_KNIGHT_PANTHERS_BLESSING_ABILITY_ID,
romKnightPanthersBlessingRestoreOnDodge,
} from "./romKnightPanthersBlessingRuntime";
import {
hasOwnedRomScoutBloodArrow,
isRomBloodArrowOwner,
isRomScoutBloodArrowAbility,
romBloodArrowCastCosts,
ROM_SCOUT_BLOOD_ARROW_ABILITY_ID,
ROM_SCOUT_BLOOD_ARROW_AURA_TAG,
ROM_SCOUT_BLOOD_ARROW_DEACTIVATION_COOLDOWN_MS,
} from "./romBloodArrow";
import {
ROM_COA_KNIGHT_ROGUE_GODS_SWORD_ID,
romCoaKnightRogueGodsSwordBonusLightDamage,
@@ -1562,6 +1571,7 @@ export interface CombatState {
selectFriendlyActor: (id: string | null) => boolean;
setPlayerPosition: (position: CombatPosition) => void;
castAbility: (abilityId: string, requestOrOrigin?: CastRequest | CombatPosition, now?: number, completingCast?: ActiveCast) => CastResult;
deactivateRomScoutBloodArrow: (ownerId: string, now?: number) => boolean;
beginGroundTarget: (abilityId: string) => boolean;
moveGroundTarget: (moveX: number, moveForward: number, yaw: number, deltaSeconds: number) => void;
confirmGroundTarget: (position?: CombatPosition, now?: number) => CastResult;
@@ -10958,13 +10968,26 @@ function damageCompanionActorInWork(work: MutableCastWork, id: string, amount: n
if (!actor || actor.health <= 0 || requested <= 0) return 0;
const applied = Math.min(actor.health, requested);
const health = actor.health - applied;
const next = Object.freeze({ ...actor, health, alive: health > 0, cast: health > 0 ? actor.cast : null });
const next = Object.freeze({
...actor,
health,
alive: health > 0,
cast: health > 0 ? actor.cast : null,
...(health <= 0 ? {
cooldowns: Object.fromEntries(Object.entries(actor.cooldowns)
.filter(([abilityId]) => abilityId !== ROM_SCOUT_BLOOD_ARROW_ABILITY_ID)),
} : {}),
});
work.actors[id] = next;
mirrorCompanionActor(next);
if (health <= 0) {
work.auras = work.auras.filter((aura) => !(aura.targetId === id
&& aura.definition.utilityTags?.includes(ROM_SCOUT_BLOOD_ARROW_AURA_TAG)));
usePartyStore.setState((party) => ({
members: party.members.map((member) => member.id === id
? { ...member, status: "down", activeCast: null, activeCastTargetId: null }
? { ...member, status: "down", activeCast: null, activeCastTargetId: null,
cooldowns: Object.fromEntries(Object.entries(member.cooldowns)
.filter(([abilityId]) => abilityId !== ROM_SCOUT_BLOOD_ARROW_ABILITY_ID)) }
: member),
}));
}
@@ -11985,13 +12008,38 @@ export const useCombatStore = create<CombatState>((set, get) => ({
cancelGroundTarget: () => set({ pendingGroundTarget: null }),
synchronizeActors: () => {
const partyMembers = usePartyStore.getState().members;
const livingCompanionIds = new Set(partyMembers.filter((member) => member.health > 0).map((member) => member.id));
let actors: Readonly<Record<string, CombatActorState>> = {};
set((state) => {
actors = combatActorSnapshots(state);
return { actors };
const activeBloodArrowOwners = new Set(livingCompanionIds);
if (state.health > 0) activeBloodArrowOwners.add(PLAYER_AURA_ENTITY_ID);
actors = Object.freeze(Object.fromEntries(Object.entries(combatActorSnapshots(state)).map(([id, actor]) => (
activeBloodArrowOwners.has(id) ? [id, actor] : [id, {
...actor,
cooldowns: Object.fromEntries(Object.entries(actor.cooldowns)
.filter(([abilityId]) => abilityId !== ROM_SCOUT_BLOOD_ARROW_ABILITY_ID)),
}]
))));
const auras = state.auras.filter((aura) => !aura.definition.utilityTags?.includes(ROM_SCOUT_BLOOD_ARROW_AURA_TAG)
|| aura.sourceId !== aura.targetId
|| (aura.sourceId === PLAYER_AURA_ENTITY_ID
? state.health > 0
: livingCompanionIds.has(aura.sourceId)));
const cooldowns = state.health > 0
? state.cooldowns
: Object.fromEntries(Object.entries(state.cooldowns).filter(([id]) => id !== ROM_SCOUT_BLOOD_ARROW_ABILITY_ID));
return { actors, auras, cooldowns };
});
usePartyStore.setState((party) => ({
members: party.members.map((member) => memberFromCompanionActor(member, actors[member.id])),
members: party.members.map((member) => {
const runtime = memberFromCompanionActor(member, actors[member.id]);
return member.health > 0 ? runtime : {
...runtime,
cooldowns: Object.fromEntries(Object.entries(runtime.cooldowns)
.filter(([id]) => id !== ROM_SCOUT_BLOOD_ARROW_ABILITY_ID)),
};
}),
}));
},
applyDefaultPartyBuffs: (now = combatNow()) => {
@@ -12226,6 +12274,7 @@ export const useCombatStore = create<CombatState>((set, get) => ({
get().synchronizeActors();
},
commitCompanionActors: (members) => {
const livingCompanionIds = new Set(members.filter((member) => member.health > 0).map((member) => member.id));
let actors: Readonly<Record<string, CombatActorState>> = {};
set((state) => {
const next = { ...state.actors };
@@ -12234,15 +12283,40 @@ export const useCombatStore = create<CombatState>((set, get) => ({
member,
entityAbsorbAmount(state.auras, member.id),
);
next[member.id] = companionActorWithMemberRuntime(existing, member);
const runtime = companionActorWithMemberRuntime(existing, member);
next[member.id] = member.health > 0 ? runtime : {
...runtime,
cooldowns: Object.fromEntries(Object.entries(runtime.cooldowns)
.filter(([id]) => id !== ROM_SCOUT_BLOOD_ARROW_ABILITY_ID)),
};
}
const activeBloodArrowOwners = new Set(livingCompanionIds);
if (state.health > 0) activeBloodArrowOwners.add(PLAYER_AURA_ENTITY_ID);
for (const [id, actor] of Object.entries(next)) {
if (activeBloodArrowOwners.has(id) || actor.cooldowns[ROM_SCOUT_BLOOD_ARROW_ABILITY_ID] === undefined) continue;
next[id] = {
...actor,
cooldowns: Object.fromEntries(Object.entries(actor.cooldowns)
.filter(([abilityId]) => abilityId !== ROM_SCOUT_BLOOD_ARROW_ABILITY_ID)),
};
}
actors = Object.freeze(next);
return { actors };
const auras = state.auras.filter((aura) => !aura.definition.utilityTags?.includes(ROM_SCOUT_BLOOD_ARROW_AURA_TAG)
|| aura.sourceId !== aura.targetId
|| (aura.sourceId === PLAYER_AURA_ENTITY_ID
? state.health > 0
: livingCompanionIds.has(aura.sourceId)));
return { actors, auras };
});
usePartyStore.setState((party) => ({
members: party.members.map((member) => {
const runtime = members.find((candidate) => candidate.id === member.id) ?? member;
return memberFromCompanionActor(runtime, actors[member.id]);
const projected = memberFromCompanionActor(runtime, actors[member.id]);
return projected.health > 0 ? projected : {
...projected,
cooldowns: Object.fromEntries(Object.entries(projected.cooldowns)
.filter(([id]) => id !== ROM_SCOUT_BLOOD_ARROW_ABILITY_ID)),
};
}),
}));
},
@@ -12267,6 +12341,20 @@ export const useCombatStore = create<CombatState>((set, get) => ({
const suddenDoomFreeDeathCoil = request[WOW335_DEATH_KNIGHT_SUDDEN_DOOM_FREE_CAST] === true
&& ability?.id === WOW335_DEATH_KNIGHT_SUDDEN_DOOM_DEATH_COIL_ABILITY_ID;
const activePlayerAuras = aurasForEntity(state.auras, PLAYER_AURA_ENTITY_ID);
if (!completingCast && ability && isRomScoutBloodArrowAbility(ability)
&& isRomBloodArrowOwner({ classId: state.classId, secondaryClassId: state.secondaryClassId })
&& state.abilities.some((entry) => entry.id === ability!.id)
&& hasOwnedRomScoutBloodArrow(state.auras, PLAYER_AURA_ENTITY_ID, now)
&& get().deactivateRomScoutBloodArrow(PLAYER_AURA_ENTITY_ID, now)) {
return {
ok: true,
abilityId,
affectedMobIds: [],
damage: 0,
healing: 0,
levelsGained: 0,
};
}
const druidMasterShapeshifterHealingModifiers = state.classId === "druid"
? wow335DruidMasterShapeshifterModifiersForForm(
state.talentRanks[WOW335_DRUID_MASTER_SHAPESHIFTER_TALENT_ID] ?? 0,
@@ -12691,16 +12779,29 @@ export const useCombatStore = create<CombatState>((set, get) => ({
: state.classId === "warrior" && ability?.name === "Execute"
? Math.min(3, Math.max(0, Math.trunc(state.talentRanks["talent-1542"] ?? 0))) * 2
: 0;
const costs = ability && !consumesInnerFocus && !consumesDruidClearcasting && !consumesSurgeOfLight
? resolvedAbilityCosts(ability, modifiers, { ...state, now }).map((cost) => {
const costs = [
...(ability && !consumesInnerFocus && !consumesDruidClearcasting && !consumesSurgeOfLight
? resolvedAbilityCosts(ability, modifiers, { ...state, now }).map((cost) => {
if (suddenDoomFreeDeathCoil && cost.resource === "runic-power") return { ...cost, amount: 0 };
if (consumesHunterLockAndLoad && cost.resource === "mana") return { ...cost, amount: 0 };
if (state.classId !== "warrior" || cost.resource !== "rage") return cost;
return warriorFlatRageCostReduction > 0
? { ...cost, amount: Math.max(0, cost.amount - warriorFlatRageCostReduction) }
: cost;
})
: [];
})
: []),
...(ability ? romBloodArrowCastCosts(
ability,
{ classId: state.classId, secondaryClassId: state.secondaryClassId },
activePlayerAuras,
PLAYER_AURA_ENTITY_ID,
state.maxHealth,
resourceProfileForClass(state.classId).type === "mana"
? state.maxResource
: state.maxResourcePools.mana ?? 0,
now,
) : []),
];
const resourceCost = ability && !suddenDoomFreeDeathCoil
&& !consumesInnerFocus && !consumesDruidClearcasting && !consumesSurgeOfLight
? Math.max(0, talentAdjustedResourceCost(ability, modifiers)
@@ -17103,6 +17204,45 @@ export const useCombatStore = create<CombatState>((set, get) => ({
return result;
},
deactivateRomScoutBloodArrow: (ownerId, now = combatNow()) => {
const state = get();
const playerOwner = ownerId === PLAYER_AURA_ENTITY_ID;
const member = playerOwner
? null
: usePartyStore.getState().members.find((candidate) => candidate.id === ownerId) ?? null;
const ownerBuild = playerOwner ? state : member;
if (!ownerBuild || !isRomBloodArrowOwner(ownerBuild)
|| !hasOwnedRomScoutBloodArrow(state.auras, ownerId, now)) return false;
if ((playerOwner && state.health <= 0) || (!playerOwner && (!member || member.health <= 0))) return false;
const readyAt = now + ROM_SCOUT_BLOOD_ARROW_DEACTIVATION_COOLDOWN_MS;
const auras = state.auras.filter((aura) => !(aura.sourceId === ownerId
&& aura.targetId === ownerId
&& aura.definition.utilityTags?.includes(ROM_SCOUT_BLOOD_ARROW_AURA_TAG)));
const actors = state.actors[ownerId]
? { ...state.actors, [ownerId]: {
...state.actors[ownerId],
cooldowns: { ...state.actors[ownerId].cooldowns, [ROM_SCOUT_BLOOD_ARROW_ABILITY_ID]: readyAt },
} }
: state.actors;
if (playerOwner) {
set({
auras,
actors,
cooldowns: { ...state.cooldowns, [ROM_SCOUT_BLOOD_ARROW_ABILITY_ID]: readyAt },
});
return true;
}
usePartyStore.setState((party) => ({
members: party.members.map((candidate) => candidate.id === ownerId
? { ...candidate, cooldowns: { ...candidate.cooldowns, [ROM_SCOUT_BLOOD_ARROW_ABILITY_ID]: readyAt } }
: candidate),
}));
set({ auras, actors });
return true;
},
consumeActorAuraTag: (actorId, tag) => {
set((state) => ({
auras: state.auras.filter((aura) => !(aura.targetId === actorId
@@ -18944,7 +19084,12 @@ export const useCombatStore = create<CombatState>((set, get) => ({
}
set({
auras: health <= 0 ? nextAuras.filter((aura) => !(aura.targetId === PLAYER_AURA_ENTITY_ID
&& aura.definition.utilityTags?.includes("wow335-rogue-killing-spree"))) : nextAuras,
&& (aura.definition.utilityTags?.includes("wow335-rogue-killing-spree")
|| aura.definition.utilityTags?.includes(ROM_SCOUT_BLOOD_ARROW_AURA_TAG)))) : nextAuras,
cooldowns: health <= 0
? Object.fromEntries(Object.entries(state.cooldowns)
.filter(([id]) => id !== ROM_SCOUT_BLOOD_ARROW_ABILITY_ID))
: state.cooldowns,
killingSpreeWindows: health <= 0
? state.killingSpreeWindows.filter((window) => window.ownerActorId !== PLAYER_AURA_ENTITY_ID)
: state.killingSpreeWindows,
@@ -21036,6 +21181,11 @@ export const useCombatStore = create<CombatState>((set, get) => ({
combatResult.dodged ? "dodge" : combatResult.landed && combatResult.rawDamage > 0 ? "hit" : "other",
now,
)];
if (procWork.health <= 0) {
procWork.auras = procWork.auras.filter((aura) => !(aura.targetId === PLAYER_AURA_ENTITY_ID
&& aura.definition.utilityTags?.includes(ROM_SCOUT_BLOOD_ARROW_AURA_TAG)));
delete procWork.cooldowns[ROM_SCOUT_BLOOD_ARROW_ABILITY_ID];
}
const playerPushbackReduction = healthDamage > 0
? paladinConcentrationAuraPushbackReductionForTarget(
procWork.auras,
@@ -22078,6 +22228,17 @@ export const useCombatStore = create<CombatState>((set, get) => ({
combatResult.dodged ? "dodge" : combatResult.landed && combatResult.rawDamage > 0 ? "hit" : "other",
now,
)];
if (nextActor.health <= 0) {
aurasAfterProcs = aurasAfterProcs.filter((aura) => !(aura.targetId === member.id
&& aura.definition.utilityTags?.includes(ROM_SCOUT_BLOOD_ARROW_AURA_TAG)));
nextActor = {
...nextActor,
cooldowns: Object.fromEntries(Object.entries(nextActor.cooldowns)
.filter(([abilityId]) => abilityId !== ROM_SCOUT_BLOOD_ARROW_ABILITY_ID)),
};
actorsAfterHit = { ...actorsAfterHit, [nextActor.id]: nextActor };
mirrorCompanionActor(nextActor);
}
set({
actors: actorsAfterHit,
summons: nextSummons,