Update Healer Man server
This commit is contained in:
1 parent
39458d5de6
commit
6cc1b9217f
203 files changed
+133929
-31271
No files matched your search
@@ -0,0 +1,140 @@
|
||||
import type { CharacterStatRuleset } from "./characterStatRuleset";
|
||||
import type { ActiveAura } from "./combatAuras";
|
||||
import type { RomNativeDerivedStats } from "./romNativeStatsTypes";
|
||||
|
||||
export type RomNativeRecoveryActorKind = "player" | "party-npc";
|
||||
|
||||
/** Runtime remainder for the source's 2 s player / 6 s owned-NPC recovery pulses. */
|
||||
export interface RomNativeRecoveryClock {
|
||||
readonly ruleset: "rom";
|
||||
readonly actorKind: RomNativeRecoveryActorKind;
|
||||
/** Elapsed milliseconds since the last pulse, always below the actor period. */
|
||||
readonly elapsedMs: number;
|
||||
}
|
||||
|
||||
export interface RomNativeRecoveryClockAdvance {
|
||||
readonly clock: RomNativeRecoveryClock | undefined;
|
||||
/** Pulse locations measured from the start of this update interval. */
|
||||
readonly pulseOffsetsMs: readonly number[];
|
||||
}
|
||||
|
||||
export interface RomNativeRecoveryPulseAmounts {
|
||||
readonly health: number;
|
||||
readonly mana: number;
|
||||
}
|
||||
|
||||
export const ROM_NATIVE_PLAYER_RECOVERY_PERIOD_MS = 2_000;
|
||||
export const ROM_NATIVE_OWNED_NPC_RECOVERY_PERIOD_MS = 6_000;
|
||||
|
||||
export function romNativeRecoveryPeriodMs(actorKind: RomNativeRecoveryActorKind): number {
|
||||
return actorKind === "player"
|
||||
? ROM_NATIVE_PLAYER_RECOVERY_PERIOD_MS
|
||||
: ROM_NATIVE_OWNED_NPC_RECOVERY_PERIOD_MS;
|
||||
}
|
||||
|
||||
/**
|
||||
* Advances the source pulse clock without borrowing time across ruleset or actor changes.
|
||||
* An uninitialized clock starts accumulating immediately; a mismatched clock is reset
|
||||
* and deliberately does not credit the interval that crossed the mode boundary.
|
||||
*/
|
||||
export function advanceRomNativeRecoveryClock(
|
||||
previous: RomNativeRecoveryClock | undefined,
|
||||
statRuleset: CharacterStatRuleset,
|
||||
actorKind: RomNativeRecoveryActorKind,
|
||||
deltaMs: number,
|
||||
): RomNativeRecoveryClockAdvance {
|
||||
if (statRuleset !== "rom") return { clock: undefined, pulseOffsetsMs: [] };
|
||||
|
||||
const period = romNativeRecoveryPeriodMs(actorKind);
|
||||
const compatible = previous?.ruleset === "rom" && previous.actorKind === actorKind;
|
||||
if (previous && !compatible) {
|
||||
return {
|
||||
clock: Object.freeze({ ruleset: "rom", actorKind, elapsedMs: 0 }),
|
||||
pulseOffsetsMs: [],
|
||||
};
|
||||
}
|
||||
|
||||
const elapsedBefore = compatible && Number.isFinite(previous.elapsedMs)
|
||||
? Math.max(0, Math.min(period - Number.EPSILON, previous.elapsedMs))
|
||||
: 0;
|
||||
const safeDelta = Number.isFinite(deltaMs) ? Math.max(0, deltaMs) : 0;
|
||||
const total = elapsedBefore + safeDelta;
|
||||
const pulseCount = Math.floor(total / period);
|
||||
const pulseOffsetsMs = Array.from({ length: pulseCount }, (_, index) => (
|
||||
Math.max(0, period - elapsedBefore) + index * period
|
||||
));
|
||||
return {
|
||||
clock: Object.freeze({ ruleset: "rom", actorKind, elapsedMs: total % period }),
|
||||
pulseOffsetsMs,
|
||||
};
|
||||
}
|
||||
|
||||
/** One native HP/MP pulse; point values intentionally remain fractional. */
|
||||
export function romNativeRecoveryPulseAmounts(
|
||||
stats: Pick<RomNativeDerivedStats, "healthRecovery" | "manaRecovery" | "usesMana">,
|
||||
actorKind: RomNativeRecoveryActorKind,
|
||||
inCombat: boolean,
|
||||
sitting = false,
|
||||
): RomNativeRecoveryPulseAmounts {
|
||||
const sourceHealthRecovery = Number.isFinite(stats.healthRecovery) ? Math.max(0, stats.healthRecovery) : 0;
|
||||
const sourceManaRecovery = stats.usesMana && Number.isFinite(stats.manaRecovery)
|
||||
? Math.max(0, stats.manaRecovery)
|
||||
: 0;
|
||||
if (inCombat) return Object.freeze({ health: 0, mana: sourceManaRecovery / 24 });
|
||||
const idleFactor = actorKind === "party-npc" || sitting ? 0.66 : 1 / 3;
|
||||
return Object.freeze({
|
||||
health: sourceHealthRecovery * idleFactor,
|
||||
mana: sourceManaRecovery * idleFactor,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Simulator representation of explicit source RoM resource-over-time bindings.
|
||||
* These are kept separate from the character's source ReMP pulse calculation.
|
||||
* Unmarked/shared WoW regeneration auras do not pass this source-ID gate.
|
||||
*/
|
||||
export function romNativePeriodicManaEffectPerSecond(
|
||||
auras: readonly ActiveAura[],
|
||||
maximumMana: number,
|
||||
): number {
|
||||
let flatPointsPerSecond = 0;
|
||||
let percentOfMaximumPerSecond = 0;
|
||||
for (const aura of auras) {
|
||||
if (!aura.definition.id.startsWith("rom-resource-over-time-")) continue;
|
||||
for (const modifier of aura.definition.modifiers ?? []) {
|
||||
const amount = modifier.value * (modifier.perStack === false ? 1 : aura.stacks);
|
||||
if (modifier.kind === "resource"
|
||||
&& modifier.resource === "mana"
|
||||
&& modifier.property === "regeneration"
|
||||
&& modifier.operation === "flat") {
|
||||
flatPointsPerSecond += amount;
|
||||
} else if (modifier.kind === "stat"
|
||||
&& modifier.stat === "mana-regeneration-max-percent-per-second"
|
||||
&& modifier.operation === "flat") {
|
||||
percentOfMaximumPerSecond += amount;
|
||||
}
|
||||
}
|
||||
}
|
||||
const safeMaximum = Number.isFinite(maximumMana) ? Math.max(0, maximumMana) : 0;
|
||||
return flatPointsPerSecond + safeMaximum * percentOfMaximumPerSecond;
|
||||
}
|
||||
|
||||
/** Integrates explicit RoM periodic Mana effects (including drains) over their live interval. */
|
||||
export function romNativePeriodicManaEffectDuring(
|
||||
auras: readonly ActiveAura[],
|
||||
intervalStartAt: number,
|
||||
intervalEndAt: number,
|
||||
maximumMana: number,
|
||||
): number {
|
||||
if (!Number.isFinite(intervalStartAt) || !Number.isFinite(intervalEndAt) || intervalEndAt <= intervalStartAt) return 0;
|
||||
let total = 0;
|
||||
for (const aura of auras) {
|
||||
if (!aura.definition.id.startsWith("rom-resource-over-time-")) continue;
|
||||
const activeStart = Math.max(intervalStartAt, aura.appliedAt);
|
||||
const activeEnd = Math.min(intervalEndAt, aura.expiresAt ?? intervalEndAt);
|
||||
if (activeEnd <= activeStart) continue;
|
||||
const perSecond = romNativePeriodicManaEffectPerSecond([aura], maximumMana);
|
||||
total += perSecond * (activeEnd - activeStart) / 1_000;
|
||||
}
|
||||
return total;
|
||||
}
|
||||
Reference in new issue
Block a user