Update Healer Man server

This commit is contained in:
phenom committed 2026-09-27 14:31:15 -04:00
1 parent ab1434a3b8
commit e284f4fc76
60 files changed
+2704 -118

No files matched your search

+32 -15
View File
@@ -19,6 +19,8 @@ import {
} from "./abilityRules";
import type { MobCombatState } from "./combatStore";
import { useCombatStore } from "./combatStore";
import { raceAffixHasteMultiplier } from "./dungeonRaceAffixBuffs";
import { activeRaceOrbId } from "./dungeonRaceAffixRuntime";
import { companionResourceMaximum, memberFromCompanionActor } from "./combatActorAuthority";
import type { MobVector3 } from "./mobPopulation";
import { requireDungeonDefinition } from "./dungeonRegistry";
@@ -48,6 +50,7 @@ import { useWailingEncounterStore } from "./wailingCavernsEncounter";
import { resolvedEquipmentLoadout } from "./heirloomEquipment";
import {
emitCombatPresentation,
penancePulseVisualSpellId,
presentationDeliveryForAbility,
presentationSchoolForAbility,
type CombatPresentationDelivery,
@@ -664,7 +667,7 @@ function memberSwingMs(member: PartyMember): number {
|| member.specialization === "Feral (Cat)"
? 1_550
: 1_900;
return hastedDuration(
return Math.round(hastedDuration(
base,
ratingPercent(
member.gearStats.hasteRating,
@@ -675,7 +678,7 @@ function memberSwingMs(member: PartyMember): number {
member.classId,
),
500,
);
) / raceAffixHasteMultiplier());
}
function emitPartyAbility(
@@ -686,25 +689,33 @@ function emitPartyAbility(
now: number,
instantCast = false,
): void {
const penancePulse = ability.id === "wow335-priest-penance"
&& ability.effects.some((effect) => effect.kind === "heal" || effect.kind === "damage")
&& (phase === "release" || phase === "impact");
if (ability.id === "wow335-priest-penance" && phase === "impact" && !penancePulse) return;
const origin = getPartyRuntimePosition(member.id) ?? useCombatStore.getState().playerPosition;
const targetPosition = targetId === PLAYER_AGGRO_ID
? useCombatStore.getState().playerPosition
: getMobPosition(targetId) ?? getPartyRuntimePosition(targetId) ?? origin;
emitCombatPresentation({
occurredAt: now,
phase,
phase: penancePulse && phase === "release" ? "tick" : phase,
actorGroup: "party",
sourceActorId: member.id,
targetActorId: targetId,
abilityId: ability.id,
sourceSpellId: ability.dbcSpellId,
sourceSpellId: penancePulse
? penancePulseVisualSpellId(Boolean(useCombatStore.getState().mobs[targetId]))
: ability.dbcSpellId,
name: ability.name,
source: ability.source === "runewaker" ? "runewaker" : ability.source?.startsWith("coa") ? "ascension" : "wow335",
school: presentationSchoolForAbility(ability),
delivery: presentationDeliveryForAbility(ability),
delivery: penancePulse ? "projectile" : presentationDeliveryForAbility(ability),
origin,
targetPosition,
...(ability.radius === undefined ? {} : { radius: ability.radius }),
...(ability.id === "wow335-priest-penance" && phase === "cast-start"
? { durationMs: ability.castTimeMs } : {}),
...(instantCast ? { instantCast: true } : {}),
});
}
@@ -1686,6 +1697,7 @@ export function advanceDungeonPartyCombat(now = Date.now()): void {
party.activeMobId,
partyOrigin,
);
if (!targetId && party.command !== "stop") targetId = activeRaceOrbId();
members = members.map((member) => {
const profile = resourceProfileForClass(member.classId);
const passiveRates: Partial<Record<ResourceType, number>> = {
@@ -1867,7 +1879,7 @@ export function advanceDungeonPartyCombat(now = Date.now()): void {
status: desiredStatus,
nextActionAt: activeCast.mode === "channel"
? Math.max(now + 250, execution.member.globalCooldownEndsAt)
: now + Math.max(250, castingAbility.gcdMs),
: now + Math.max(250, Math.round(castingAbility.gcdMs / raceAffixHasteMultiplier(now))),
};
} else {
nextMembers[index] = {
@@ -1900,6 +1912,7 @@ export function advanceDungeonPartyCombat(now = Date.now()): void {
const instantOutOfCombatResurrection = !targetId
&& supportIntent.ability.effects.some((effect) => effect.kind === "resurrection");
if (supportIntent.ability.castTimeMs > 0 && !instantOutOfCombatResurrection) {
const castTimeMs = Math.round(supportIntent.ability.castTimeMs / raceAffixHasteMultiplier(now));
const castOrigin = getPartyRuntimePosition(member.id) ?? currentCombat.playerPosition;
nextMembers[index] = {
...committed,
@@ -1909,14 +1922,14 @@ export function advanceDungeonPartyCombat(now = Date.now()): void {
school: presentationSchoolForAbility(supportIntent.ability),
mode: supportIntent.ability.castMode === "channel" ? "channel" : "cast",
startedAt: now,
completesAt: now + supportIntent.ability.castTimeMs,
originalDurationMs: supportIntent.ability.castTimeMs,
completesAt: now + castTimeMs,
originalDurationMs: castTimeMs,
pushbackHits: 0,
origin: castOrigin,
targetId: supportIntent.targetId,
},
activeCastTargetId: supportIntent.targetId,
nextActionAt: now + supportIntent.ability.castTimeMs,
nextActionAt: now + castTimeMs,
};
emitPartyAbility(member, supportIntent.ability, supportIntent.targetId, "cast-start", now);
} else {
@@ -1930,7 +1943,7 @@ export function advanceDungeonPartyCombat(now = Date.now()): void {
nextMembers[index] = {
...execution.member,
status: "healing",
nextActionAt: now + Math.max(250, supportIntent.ability.gcdMs),
nextActionAt: now + Math.max(250, Math.round(supportIntent.ability.gcdMs / raceAffixHasteMultiplier(now))),
};
}
continue;
@@ -1942,6 +1955,7 @@ export function advanceDungeonPartyCombat(now = Date.now()): void {
targetId,
partyOrigin,
);
if (!targetId && party.command !== "stop") targetId = activeRaceOrbId();
if (!targetId) {
nextMembers[index] = member.status === "ready" && member.nextActionAt === 0
? member
@@ -1950,7 +1964,9 @@ export function advanceDungeonPartyCombat(now = Date.now()): void {
}
const desiredStatus = member.role === "tank" ? "guarding" : "attacking";
const memberPosition = getPartyRuntimePosition(member.id) ?? currentCombat.playerPosition;
const memberTargetId = member.role === "tank"
const memberTargetId = member.role === "damage" && party.command !== "stop" && activeRaceOrbId()
? activeRaceOrbId()
: member.role === "tank"
? tankPriorityTargetId(
currentCombat.mobs,
nextMembers,
@@ -2115,6 +2131,7 @@ export function advanceDungeonPartyCombat(now = Date.now()): void {
});
}
if (selectedAbility.castTimeMs > 0) {
const castTimeMs = Math.round(selectedAbility.castTimeMs / raceAffixHasteMultiplier(now));
nextMembers[index] = {
...committed,
status: desiredStatus,
@@ -2123,14 +2140,14 @@ export function advanceDungeonPartyCombat(now = Date.now()): void {
school: presentationSchoolForAbility(selectedAbility),
mode: selectedAbility.castMode === "channel" ? "channel" : "cast",
startedAt: now,
completesAt: now + selectedAbility.castTimeMs,
originalDurationMs: selectedAbility.castTimeMs,
completesAt: now + castTimeMs,
originalDurationMs: castTimeMs,
pushbackHits: 0,
origin: memberPosition,
targetId: abilityTargetId,
},
activeCastTargetId: abilityTargetId,
nextActionAt: now + selectedAbility.castTimeMs,
nextActionAt: now + castTimeMs,
};
emitPartyAbility(member, selectedAbility, abilityTargetId, "cast-start", now);
} else {
@@ -2138,7 +2155,7 @@ export function advanceDungeonPartyCombat(now = Date.now()): void {
nextMembers[index] = {
...execution.member,
status: desiredStatus,
nextActionAt: now + Math.max(250, selectedAbility.gcdMs),
nextActionAt: now + Math.max(250, Math.round(selectedAbility.gcdMs / raceAffixHasteMultiplier(now))),
};
}
}