forked from phenom/healer-man
Release Healer Man 0.1.37
This commit is contained in:
1 parent
03a4e3f51c
commit
361734bc41
41 files changed
+1714
-110
No files matched your search
@@ -0,0 +1,67 @@
|
||||
import type { StructuredCombatEvent } from "./combatEvents";
|
||||
|
||||
export interface DpsMeterActor {
|
||||
readonly id: string;
|
||||
readonly name: string;
|
||||
readonly color: string;
|
||||
}
|
||||
|
||||
export interface DpsMeterState {
|
||||
readonly startedAt: number | null;
|
||||
readonly endedAt: number | null;
|
||||
readonly updatedAt: number | null;
|
||||
readonly elapsedMs: number;
|
||||
readonly damage: Readonly<Record<string, DpsMeterActor & { total: number }>>;
|
||||
}
|
||||
|
||||
export interface DpsMeterRow extends DpsMeterActor {
|
||||
readonly total: number;
|
||||
readonly dps: number;
|
||||
readonly percent: number;
|
||||
}
|
||||
|
||||
export interface DpsMeterSnapshot {
|
||||
readonly active: boolean;
|
||||
readonly durationSeconds: number;
|
||||
readonly totalDamage: number;
|
||||
readonly totalDps: number;
|
||||
readonly rows: readonly DpsMeterRow[];
|
||||
}
|
||||
|
||||
export function emptyDpsMeter(): DpsMeterState {
|
||||
return { startedAt: null, endedAt: null, updatedAt: null, elapsedMs: 0, damage: {} };
|
||||
}
|
||||
|
||||
/** Keep the last pull visible until the next starts; menus do not add combat time. */
|
||||
export function advanceDpsMeter(state: DpsMeterState, now: number, inCombat: boolean, suspended = false): DpsMeterState {
|
||||
if (!Number.isFinite(now)) return state;
|
||||
if (state.startedAt === null || state.endedAt !== null) {
|
||||
return inCombat ? { ...emptyDpsMeter(), startedAt: now, updatedAt: now } : state;
|
||||
}
|
||||
const updatedAt = Math.max(state.updatedAt ?? now, now);
|
||||
return { ...state, updatedAt,
|
||||
elapsedMs: state.elapsedMs + (suspended ? 0 : updatedAt - (state.updatedAt ?? updatedAt)),
|
||||
endedAt: inCombat ? null : updatedAt };
|
||||
}
|
||||
|
||||
/** Consume the lossless stream, never the truncated combat-log history. */
|
||||
export function recordDpsDamage(state: DpsMeterState, event: StructuredCombatEvent, actor: DpsMeterActor): DpsMeterState {
|
||||
if (event.kind !== "damage" || !Number.isFinite(event.effectiveAmount) || event.effectiveAmount <= 0) return state;
|
||||
const next = advanceDpsMeter(state, event.occurredAt, true);
|
||||
return { ...next, damage: { ...next.damage,
|
||||
[actor.id]: { ...actor, total: (next.damage[actor.id]?.total ?? 0) + event.effectiveAmount } } };
|
||||
}
|
||||
|
||||
export function dpsMeterSnapshot(state: DpsMeterState, actors: readonly DpsMeterActor[] = []): DpsMeterSnapshot {
|
||||
const durationSeconds = state.elapsedMs / 1_000;
|
||||
const seconds = Math.max(1, durationSeconds);
|
||||
const damage = state.startedAt === null ? {} : {
|
||||
...Object.fromEntries(actors.map((actor) => [actor.id, { ...actor, total: 0 }])), ...state.damage,
|
||||
};
|
||||
const ordered = Object.values(damage).sort((a, b) => b.total - a.total || a.name.localeCompare(b.name));
|
||||
const maximum = Math.max(1, ordered[0]?.total ?? 0);
|
||||
const totalDamage = ordered.reduce((sum, actor) => sum + actor.total, 0);
|
||||
return { active: state.startedAt !== null && state.endedAt === null, durationSeconds, totalDamage,
|
||||
totalDps: totalDamage / seconds,
|
||||
rows: ordered.map((actor) => ({ ...actor, dps: actor.total / seconds, percent: actor.total / maximum * 100 })) };
|
||||
}
|
||||
Reference in new issue
Block a user