@kar-mi/spirit-vale-tools-combat 3.3.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +56 -0
- package/dist/actor-directory.d.ts +88 -0
- package/dist/actor-directory.d.ts.map +1 -0
- package/dist/boss-catalog.d.ts +6 -0
- package/dist/boss-catalog.d.ts.map +1 -0
- package/dist/combat-tracker.d.ts +295 -0
- package/dist/combat-tracker.d.ts.map +1 -0
- package/dist/dps-meter.d.ts +115 -0
- package/dist/dps-meter.d.ts.map +1 -0
- package/dist/effect-display.d.ts +16 -0
- package/dist/effect-display.d.ts.map +1 -0
- package/dist/history/domain.d.ts +6 -0
- package/dist/history/domain.d.ts.map +1 -0
- package/dist/history/importer.d.ts +12 -0
- package/dist/history/importer.d.ts.map +1 -0
- package/dist/history/store.d.ts +90 -0
- package/dist/history/store.d.ts.map +1 -0
- package/dist/index.d.ts +34 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +3998 -0
- package/dist/live-combat.d.ts +80 -0
- package/dist/live-combat.d.ts.map +1 -0
- package/dist/live-log.d.ts +50 -0
- package/dist/live-log.d.ts.map +1 -0
- package/dist/position-tracker.d.ts +43 -0
- package/dist/position-tracker.d.ts.map +1 -0
- package/dist/reducers/damage.d.ts +168 -0
- package/dist/reducers/damage.d.ts.map +1 -0
- package/dist/reducers/meter.d.ts +29 -0
- package/dist/reducers/meter.d.ts.map +1 -0
- package/dist/reducers/rows.d.ts +37 -0
- package/dist/reducers/rows.d.ts.map +1 -0
- package/dist/reducers/timeline.d.ts +29 -0
- package/dist/reducers/timeline.d.ts.map +1 -0
- package/dist/replay-capture.d.ts +24 -0
- package/dist/replay-capture.d.ts.map +1 -0
- package/dist/replay-summary.d.ts +14 -0
- package/dist/replay-summary.d.ts.map +1 -0
- package/dist/replay.d.ts +11 -0
- package/dist/replay.d.ts.map +1 -0
- package/dist/snapshot.d.ts +67 -0
- package/dist/snapshot.d.ts.map +1 -0
- package/dist/status-tracker.d.ts +61 -0
- package/dist/status-tracker.d.ts.map +1 -0
- package/dist/summon-calibration.d.ts +8 -0
- package/dist/summon-calibration.d.ts.map +1 -0
- package/package.json +33 -0
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import type { FishNetActorIdentityEvent } from "./actor-directory.ts";
|
|
2
|
+
import type { FishNetCombatEvent } from "./combat-tracker.ts";
|
|
3
|
+
import type { FishNetDpsEncounterSnapshot } from "./snapshot.ts";
|
|
4
|
+
export interface MeterRow {
|
|
5
|
+
displayName: string;
|
|
6
|
+
actorIds: number[];
|
|
7
|
+
amount: number;
|
|
8
|
+
rate: number;
|
|
9
|
+
hits: number;
|
|
10
|
+
firstAtMs?: number;
|
|
11
|
+
lastAtMs?: number;
|
|
12
|
+
}
|
|
13
|
+
export interface MeterEncounterSnapshot {
|
|
14
|
+
id: string;
|
|
15
|
+
startedAtMs: number;
|
|
16
|
+
lastEventAtMs: number;
|
|
17
|
+
endedAtMs?: number;
|
|
18
|
+
durationMs: number;
|
|
19
|
+
total: number;
|
|
20
|
+
rate: number;
|
|
21
|
+
rows: MeterRow[];
|
|
22
|
+
/** The same encounter rendered with the detail the DPS snapshot carries: per-skill rows, timeline buckets, crit rates, contribution shares and the personal row. */
|
|
23
|
+
detail: FishNetDpsEncounterSnapshot;
|
|
24
|
+
}
|
|
25
|
+
export interface CombatEncounterRecord {
|
|
26
|
+
dps: FishNetDpsEncounterSnapshot;
|
|
27
|
+
tps: MeterEncounterSnapshot;
|
|
28
|
+
hps: MeterEncounterSnapshot;
|
|
29
|
+
}
|
|
30
|
+
export interface LiveCombatOptions {
|
|
31
|
+
/** Decay constant for every meter's current rate. Defaults to 2.5 seconds. */
|
|
32
|
+
currentTauSeconds?: number;
|
|
33
|
+
timelinePoints?: number;
|
|
34
|
+
retainedFinishedEncounters?: number;
|
|
35
|
+
idleGapMs?: number;
|
|
36
|
+
minimumDurationMs?: number;
|
|
37
|
+
/** Names the local player, so every meter can resolve its `personal` row. */
|
|
38
|
+
personalName?: string;
|
|
39
|
+
/** Selects the local player by actor id, overriding {@link personalName} when set. */
|
|
40
|
+
personalActorId?: number;
|
|
41
|
+
onEncounterFinished?: (encounter: CombatEncounterRecord) => void | Promise<void>;
|
|
42
|
+
}
|
|
43
|
+
export interface LiveCombatState {
|
|
44
|
+
revision: number;
|
|
45
|
+
current?: CombatEncounterRecord;
|
|
46
|
+
latestFinished?: CombatEncounterRecord;
|
|
47
|
+
}
|
|
48
|
+
/** Bounded live combat state for overlays and other polling consumers. */
|
|
49
|
+
export declare class LiveCombatService {
|
|
50
|
+
private readonly reducer;
|
|
51
|
+
private readonly currentTauSeconds;
|
|
52
|
+
private readonly minimumDurationMs;
|
|
53
|
+
private readonly retainedFinishedEncounters;
|
|
54
|
+
private readonly onEncounterFinished?;
|
|
55
|
+
private readonly tanked;
|
|
56
|
+
private readonly healing;
|
|
57
|
+
private meterId?;
|
|
58
|
+
private lastEventAtMs?;
|
|
59
|
+
/** The finished encounter is retained as aggregates rather than a rendered record, so changing the personal actor re-renders it too. */
|
|
60
|
+
private latestFinished?;
|
|
61
|
+
private personalName;
|
|
62
|
+
private personalActorId?;
|
|
63
|
+
private revision;
|
|
64
|
+
constructor(options?: LiveCombatOptions);
|
|
65
|
+
consumeIdentity(event: FishNetActorIdentityEvent, observedAtMs: number): void;
|
|
66
|
+
private identityChanged;
|
|
67
|
+
consumeCombat(event: FishNetCombatEvent, observedAtMs: number): void;
|
|
68
|
+
setPersonalName(personalName: string): void;
|
|
69
|
+
setPersonalActorId(personalActorId: number | undefined): void;
|
|
70
|
+
getPersonalActorId(): number | undefined;
|
|
71
|
+
/** Advances the idle clock. */
|
|
72
|
+
advance(observedAtMs: number): void;
|
|
73
|
+
reset(observedAtMs: number): void;
|
|
74
|
+
getState(nowMs?: number): LiveCombatState;
|
|
75
|
+
private ensureMeter;
|
|
76
|
+
private recordMeterEvent;
|
|
77
|
+
private finishMeter;
|
|
78
|
+
private renderRecord;
|
|
79
|
+
}
|
|
80
|
+
//# sourceMappingURL=live-combat.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"live-combat.d.ts","sourceRoot":"","sources":["../src/live-combat.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import type { JsonlTailReadResult, LiveLogSessionFollowerOptions } from "@kar-mi/spirit-vale-tools-logging";
|
|
2
|
+
import type { FishNetActorIdentityEvent } from "./actor-directory.ts";
|
|
3
|
+
import type { FishNetCombatEvent } from "./combat-tracker.ts";
|
|
4
|
+
export interface TimedDpsLogEvent {
|
|
5
|
+
event: FishNetActorIdentityEvent | FishNetCombatEvent;
|
|
6
|
+
observedAtMs: number;
|
|
7
|
+
}
|
|
8
|
+
export interface DpsLogBatch {
|
|
9
|
+
events: TimedDpsLogEvent[];
|
|
10
|
+
invalidLines: number;
|
|
11
|
+
missing: boolean;
|
|
12
|
+
reset: boolean;
|
|
13
|
+
/** Whether this batch carries anything a consumer has to act on. */
|
|
14
|
+
changed: boolean;
|
|
15
|
+
/** Bumped once per changed batch, so a consumer can skip re-projecting on an unchanged one. */
|
|
16
|
+
revision: number;
|
|
17
|
+
path?: string;
|
|
18
|
+
sessionId?: string;
|
|
19
|
+
}
|
|
20
|
+
/** Incrementally reads an actively-written combat JSON Lines file. */
|
|
21
|
+
export declare class DpsLogFollower {
|
|
22
|
+
private readonly ticksPerSecond;
|
|
23
|
+
private readonly reader;
|
|
24
|
+
private recordedAtOriginMs?;
|
|
25
|
+
private lastObservedAtMs;
|
|
26
|
+
private originTick?;
|
|
27
|
+
private lastTick?;
|
|
28
|
+
private revision;
|
|
29
|
+
constructor(path: string, ticksPerSecond?: number);
|
|
30
|
+
poll(): Promise<DpsLogBatch>;
|
|
31
|
+
consumeRead({ missing, reset, lines }: JsonlTailReadResult): DpsLogBatch;
|
|
32
|
+
private consume;
|
|
33
|
+
private observedAt;
|
|
34
|
+
private resetState;
|
|
35
|
+
}
|
|
36
|
+
export type DpsSessionLogFollowerOptions = Pick<LiveLogSessionFollowerOptions<DpsLogFollower, DpsLogBatch>, "fallbackPollMs" | "debounceMs" | "persistent"> & {
|
|
37
|
+
ticksPerSecond?: number;
|
|
38
|
+
};
|
|
39
|
+
/** Follows whichever combat session is named by the shared current-stream pointer. */
|
|
40
|
+
export declare class DpsSessionLogFollower {
|
|
41
|
+
private readonly inner;
|
|
42
|
+
constructor(logDirectory?: string, ticksPerSecond?: number, options?: DpsSessionLogFollowerOptions);
|
|
43
|
+
/** Follows the stream without polling: the returned follower wakes on a watcher event and yields only batches that carry something. */
|
|
44
|
+
static watch(logDirectory?: string, options?: DpsSessionLogFollowerOptions): DpsSessionLogFollower;
|
|
45
|
+
poll(): Promise<DpsLogBatch>;
|
|
46
|
+
next(): Promise<DpsLogBatch>;
|
|
47
|
+
[Symbol.asyncIterator](): AsyncIterator<DpsLogBatch>;
|
|
48
|
+
close(): void;
|
|
49
|
+
}
|
|
50
|
+
//# sourceMappingURL=live-log.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"live-log.d.ts","sourceRoot":"","sources":["../src/live-log.ts"],"names":[],"mappings":"AAOA,OAAO,KAAK,EAAE,mBAAmB,EAAE,6BAA6B,EAAE,MAAM,mCAAmC,CAAC;AAC5G,OAAO,KAAK,EAAE,yBAAyB,EAAE,MAAM,sBAAsB,CAAC;AACtE,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,qBAAqB,CAAC;AAG9D,MAAM,WAAW,gBAAgB;IAC/B,KAAK,EAAE,yBAAyB,GAAG,kBAAkB,CAAC;IACtD,YAAY,EAAE,MAAM,CAAC;CACtB;AAED,MAAM,WAAW,WAAW;IAC1B,MAAM,EAAE,gBAAgB,EAAE,CAAC;IAC3B,YAAY,EAAE,MAAM,CAAC;IACrB,OAAO,EAAE,OAAO,CAAC;IACjB,KAAK,EAAE,OAAO,CAAC;IACf,oEAAoE;IACpE,OAAO,EAAE,OAAO,CAAC;IACjB,+FAA+F;IAC/F,QAAQ,EAAE,MAAM,CAAC;IACjB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,sEAAsE;AACtE,qBAAa,cAAc;IAQC,OAAO,CAAC,QAAQ,CAAC,cAAc;IAPzD,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAkB;IACzC,OAAO,CAAC,kBAAkB,CAAC,CAAS;IACpC,OAAO,CAAC,gBAAgB,CAAK;IAC7B,OAAO,CAAC,UAAU,CAAC,CAAS;IAC5B,OAAO,CAAC,QAAQ,CAAC,CAAS;IAC1B,OAAO,CAAC,QAAQ,CAAK;IAErB,YAAY,IAAI,EAAE,MAAM,EAAmB,cAAc,SAAK,EAE7D;IAEK,IAAI,IAAI,OAAO,CAAC,WAAW,CAAC,CAEjC;IAED,WAAW,CAAC,EAAE,OAAO,EAAE,KAAK,EAAE,KAAK,EAAE,EAAE,mBAAmB,GAAG,WAAW,CASvE;IAED,OAAO,CAAC,OAAO;IA8Bf,OAAO,CAAC,UAAU;IAkBlB,OAAO,CAAC,UAAU;CAMnB;AAED,MAAM,MAAM,4BAA4B,GACtC,IAAI,CAAC,6BAA6B,CAAC,cAAc,EAAE,WAAW,CAAC,EAAE,gBAAgB,GAAG,YAAY,GAAG,YAAY,CAAC,GAC9G;IAAE,cAAc,CAAC,EAAE,MAAM,CAAA;CAAE,CAAC;AAEhC,sFAAsF;AACtF,qBAAa,qBAAqB;IAChC,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAsD;IAE5E,YAAY,YAAY,CAAC,EAAE,MAAM,EAAE,cAAc,SAAK,EAAE,OAAO,GAAE,4BAAiC,EAgBjG;IAED,uIAAuI;IACvI,MAAM,CAAC,KAAK,CAAC,YAAY,CAAC,EAAE,MAAM,EAAE,OAAO,GAAE,4BAAiC,GAAG,qBAAqB,CAErG;IAED,IAAI,IAAI,OAAO,CAAC,WAAW,CAAC,CAE3B;IAED,IAAI,IAAI,OAAO,CAAC,WAAW,CAAC,CAE3B;IAED,CAAC,MAAM,CAAC,aAAa,CAAC,IAAI,aAAa,CAAC,WAAW,CAAC,CAEnD;IAED,KAAK,IAAI,IAAI,CAEZ;CACF"}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import type { DecodedFishNetPacket } from "@kar-mi/spirit-vale-tools-capture";
|
|
2
|
+
import type { FishNetActorDirectory } from "./actor-directory.ts";
|
|
3
|
+
export interface FishNetPosition {
|
|
4
|
+
x: number;
|
|
5
|
+
y: number;
|
|
6
|
+
z: number;
|
|
7
|
+
/** Yaw (radians, about the world up axis) the object was last known to be facing. */
|
|
8
|
+
heading?: number;
|
|
9
|
+
}
|
|
10
|
+
export interface FishNetPositionEvent {
|
|
11
|
+
kind: "position";
|
|
12
|
+
tick: number;
|
|
13
|
+
objectId: number;
|
|
14
|
+
position: FishNetPosition;
|
|
15
|
+
/** Display name of the object's owner, when the actor directory knows one. */
|
|
16
|
+
displayName?: string;
|
|
17
|
+
/** True when this object is the local player's, per the identity the caller supplied. */
|
|
18
|
+
self: boolean;
|
|
19
|
+
}
|
|
20
|
+
export interface FishNetPositionTrackerOptions {
|
|
21
|
+
/** Names positions. The directory is not driven from here — feed it the same packets. */
|
|
22
|
+
directory?: FishNetActorDirectory;
|
|
23
|
+
}
|
|
24
|
+
/** Tracks where every observed object is. */
|
|
25
|
+
export declare class FishNetPositionTracker {
|
|
26
|
+
private readonly options;
|
|
27
|
+
private readonly positions;
|
|
28
|
+
private localObjectId?;
|
|
29
|
+
constructor(options?: FishNetPositionTrackerOptions);
|
|
30
|
+
/** Marks which object is the local player's, as decided by the caller's existing identity source. */
|
|
31
|
+
setLocalObjectId(objectId: number | undefined): void;
|
|
32
|
+
/** Last known position of the local player's object, when one has been established. */
|
|
33
|
+
self(): FishNetPosition | undefined;
|
|
34
|
+
get(objectId: number): FishNetPosition | undefined;
|
|
35
|
+
snapshot(): Array<{
|
|
36
|
+
objectId: number;
|
|
37
|
+
position: FishNetPosition;
|
|
38
|
+
}>;
|
|
39
|
+
reset(): void;
|
|
40
|
+
consume(packet: DecodedFishNetPacket): FishNetPositionEvent[];
|
|
41
|
+
private event;
|
|
42
|
+
}
|
|
43
|
+
//# sourceMappingURL=position-tracker.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"position-tracker.d.ts","sourceRoot":"","sources":["../src/position-tracker.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,mCAAmC,CAAC;AAC9E,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,sBAAsB,CAAC;AAElE,MAAM,WAAW,eAAe;IAC9B,CAAC,EAAE,MAAM,CAAC;IACV,CAAC,EAAE,MAAM,CAAC;IACV,CAAC,EAAE,MAAM,CAAC;IACV,qFAAqF;IACrF,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,WAAW,oBAAoB;IACnC,IAAI,EAAE,UAAU,CAAC;IACjB,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,eAAe,CAAC;IAC1B,8EAA8E;IAC9E,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,yFAAyF;IACzF,IAAI,EAAE,OAAO,CAAC;CACf;AAED,MAAM,WAAW,6BAA6B;IAC5C,yFAAyF;IACzF,SAAS,CAAC,EAAE,qBAAqB,CAAC;CACnC;AAED,6CAA6C;AAC7C,qBAAa,sBAAsB;IAIrB,OAAO,CAAC,QAAQ,CAAC,OAAO;IAHpC,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAsC;IAChE,OAAO,CAAC,aAAa,CAAC,CAAS;IAE/B,YAA6B,OAAO,GAAE,6BAAkC,EAAI;IAE5E,qGAAqG;IACrG,gBAAgB,CAAC,QAAQ,EAAE,MAAM,GAAG,SAAS,GAAG,IAAI,CAEnD;IAED,uFAAuF;IACvF,IAAI,IAAI,eAAe,GAAG,SAAS,CAElC;IAED,GAAG,CAAC,QAAQ,EAAE,MAAM,GAAG,eAAe,GAAG,SAAS,CAEjD;IAED,QAAQ,IAAI,KAAK,CAAC;QAAE,QAAQ,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,eAAe,CAAA;KAAE,CAAC,CAEjE;IAED,KAAK,IAAI,IAAI,CAGZ;IAED,OAAO,CAAC,MAAM,EAAE,oBAAoB,GAAG,oBAAoB,EAAE,CAkC5D;IAED,OAAO,CAAC,KAAK;CAWd"}
|
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
import { EwmaRate } from "@kar-mi/spirit-vale-tools-metrics";
|
|
2
|
+
import type { FishNetActorIdentityEvent } from "../actor-directory.ts";
|
|
3
|
+
import type { FishNetCombatDamageEvent, FishNetCombatDeathEvent, FishNetCombatEvent } from "../combat-tracker.ts";
|
|
4
|
+
import type { BucketSeries } from "./timeline.ts";
|
|
5
|
+
export declare const DEFAULT_MINIMUM_DURATION_MS = 1000;
|
|
6
|
+
/** Current DPS is an exponentially-weighted rate rather than a flat window of recent hits. */
|
|
7
|
+
export declare const DEFAULT_CURRENT_TAU_SECONDS = 2.5;
|
|
8
|
+
export interface CombatIdentity {
|
|
9
|
+
displayName: string;
|
|
10
|
+
archetype?: number;
|
|
11
|
+
ownerConnectionId?: number;
|
|
12
|
+
uid?: string;
|
|
13
|
+
}
|
|
14
|
+
export interface SkillAggregate {
|
|
15
|
+
sourceId: string;
|
|
16
|
+
sourceLabel: string;
|
|
17
|
+
damage: number;
|
|
18
|
+
hits: number;
|
|
19
|
+
criticalHits: number;
|
|
20
|
+
}
|
|
21
|
+
/** Per-actor totals for one encounter. */
|
|
22
|
+
export interface ActorAggregate {
|
|
23
|
+
actorId: number;
|
|
24
|
+
actorIds: number[];
|
|
25
|
+
displayName?: string;
|
|
26
|
+
archetype?: number;
|
|
27
|
+
ownerConnectionId?: number;
|
|
28
|
+
uid?: string;
|
|
29
|
+
activeIdentity: boolean;
|
|
30
|
+
damage: number;
|
|
31
|
+
firstDamageAtMs?: number;
|
|
32
|
+
lastDamageAtMs?: number;
|
|
33
|
+
hits: number;
|
|
34
|
+
criticalHits: number;
|
|
35
|
+
kills: number;
|
|
36
|
+
targetIds: Set<number>;
|
|
37
|
+
targetDamage: Map<number, number>;
|
|
38
|
+
/** Enemy target id -> skill id -> outgoing damage attributed to this actor lifetime. */
|
|
39
|
+
enemySkills: Map<number, Map<string, EnemySkillStats>>;
|
|
40
|
+
skills: Map<string, SkillAggregate>;
|
|
41
|
+
encounterSeries: BucketSeries;
|
|
42
|
+
actorSeries: BucketSeries;
|
|
43
|
+
/** Exponentially-weighted recent rate behind `currentDps`. Two numbers, whatever the encounter length. */
|
|
44
|
+
currentRate: EwmaRate;
|
|
45
|
+
}
|
|
46
|
+
export interface EnemySkillStats {
|
|
47
|
+
sourceLabel: string;
|
|
48
|
+
damage: number;
|
|
49
|
+
hits: number;
|
|
50
|
+
criticalHits: number;
|
|
51
|
+
}
|
|
52
|
+
export interface DeathHitRecord {
|
|
53
|
+
/** Milliseconds before the death; zero is the lethal hit. */
|
|
54
|
+
beforeDeathMs: number;
|
|
55
|
+
attackerActorId: number;
|
|
56
|
+
attackerLabel: string;
|
|
57
|
+
attackerIsMonster: boolean;
|
|
58
|
+
sourceLabel: string;
|
|
59
|
+
damage: number;
|
|
60
|
+
critical: boolean;
|
|
61
|
+
}
|
|
62
|
+
export interface DeathRecord {
|
|
63
|
+
victimName: string;
|
|
64
|
+
targetId: number;
|
|
65
|
+
diedAtMs: number;
|
|
66
|
+
totalDamage: number;
|
|
67
|
+
hits: DeathHitRecord[];
|
|
68
|
+
}
|
|
69
|
+
export interface EncounterAggregate {
|
|
70
|
+
id: string;
|
|
71
|
+
startedAtMs: number;
|
|
72
|
+
lastDamageAtMs: number;
|
|
73
|
+
endedAtMs?: number;
|
|
74
|
+
actors: ActorAggregate[];
|
|
75
|
+
activeActors: Map<number, ActorAggregate>;
|
|
76
|
+
/** First time each enemy was hit, which orders the enemy picker. */
|
|
77
|
+
enemyFirstSeenAtMs: Map<number, number>;
|
|
78
|
+
/** Enemy display names, captured when the hit lands rather than looked up when the encounter is written. */
|
|
79
|
+
enemyNames: Map<number, string>;
|
|
80
|
+
deaths: DeathRecord[];
|
|
81
|
+
}
|
|
82
|
+
export interface DamageReducerOptions {
|
|
83
|
+
idleGapMs?: number;
|
|
84
|
+
/** Decay constant for `currentDps`. Defaults to {@link DEFAULT_CURRENT_TAU_SECONDS}. */
|
|
85
|
+
currentTauSeconds?: number;
|
|
86
|
+
/** Caps buckets per series; the read model leaves this unbounded to keep full resolution. */
|
|
87
|
+
maxTimelineBuckets?: number;
|
|
88
|
+
/** Supplies the encounter id when one begins. Defaults to a sequential counter. */
|
|
89
|
+
createEncounterId?: (startedAtMs: number) => string;
|
|
90
|
+
onEncounterFinished?: (encounter: EncounterAggregate) => void;
|
|
91
|
+
}
|
|
92
|
+
/** Windows combat events into encounters and accumulates per-actor totals. */
|
|
93
|
+
/** Marks an activation record the capture coordinator uses to publish a monster's display name. */
|
|
94
|
+
export declare const MOB_IDENTITY_PREFIX = "__spiritvaleMobIdentity:";
|
|
95
|
+
export declare class DamageReducer {
|
|
96
|
+
readonly identities: Map<number, CombatIdentity>;
|
|
97
|
+
readonly mobIdentities: Map<number, string>;
|
|
98
|
+
current?: EncounterAggregate;
|
|
99
|
+
/** Recent positive hits per target, trimmed to the death lookback. */
|
|
100
|
+
readonly recentHits: Map<number, {
|
|
101
|
+
atMs: number;
|
|
102
|
+
hit: DeathHitRecord;
|
|
103
|
+
}[]>;
|
|
104
|
+
private lastSweepAtMs?;
|
|
105
|
+
private readonly idleGapMs;
|
|
106
|
+
private readonly currentTauSeconds;
|
|
107
|
+
private readonly maxTimelineBuckets;
|
|
108
|
+
private readonly createEncounterId;
|
|
109
|
+
private readonly onEncounterFinished?;
|
|
110
|
+
private nextEncounter;
|
|
111
|
+
constructor(options?: DamageReducerOptions);
|
|
112
|
+
/** Adopts an encounter left open by an earlier indexing pass. */
|
|
113
|
+
resume(encounter: EncounterAggregate): void;
|
|
114
|
+
consumeIdentity(event: FishNetActorIdentityEvent, _observedAtMs: number): void;
|
|
115
|
+
consumeCombat(event: FishNetCombatEvent, observedAtMs: number): void;
|
|
116
|
+
/** Finalizes an encounter that has been idle long enough. */
|
|
117
|
+
advance(observedAtMs: number): void;
|
|
118
|
+
/** Finalizes the current encounter; the next qualifying hit starts a new one. */
|
|
119
|
+
reset(observedAtMs: number): void;
|
|
120
|
+
private finish;
|
|
121
|
+
/** Keeps the rolling window of recent positive hits per target that the death log draws from. */
|
|
122
|
+
private trackRecentHit;
|
|
123
|
+
/**
|
|
124
|
+
* Trims every target to the lookback immediately, regardless of when the last sweep ran.
|
|
125
|
+
*
|
|
126
|
+
* Used before persisting the map: the periodic sweep leaves up to two windows in memory, but only
|
|
127
|
+
* one is ever read back, and the persisted copy is rewritten on every batch.
|
|
128
|
+
*/
|
|
129
|
+
pruneRecentHits(observedAtMs: number): void;
|
|
130
|
+
/**
|
|
131
|
+
* Drops targets whose hits have all aged out.
|
|
132
|
+
*
|
|
133
|
+
* {@link trackRecentHit} only trims the target it is currently touching, so a target hit once and
|
|
134
|
+
* never again would otherwise be retained for the rest of the session. Runs at most once per
|
|
135
|
+
* lookback window of log time, which makes it a rare pass over a map that stays small.
|
|
136
|
+
*/
|
|
137
|
+
private sweepRecentHits;
|
|
138
|
+
/** Records a player's identity, evicting the least recently seen once the cap is reached. */
|
|
139
|
+
private rememberIdentity;
|
|
140
|
+
/** Records a monster's display name, evicting the least recently seen once the cap is reached. */
|
|
141
|
+
private rememberMobIdentity;
|
|
142
|
+
/** Attributes a hit to the open encounter's enemy breakdown and, on death, its log. */
|
|
143
|
+
private recordEncounterHit;
|
|
144
|
+
private actorFor;
|
|
145
|
+
}
|
|
146
|
+
/** One positive hit's contribution to a row, shared by the damage reducer and the tanked/healing meters. */
|
|
147
|
+
export interface RecordedHit {
|
|
148
|
+
value: number;
|
|
149
|
+
atMs: number;
|
|
150
|
+
critical: boolean;
|
|
151
|
+
sourceId: string;
|
|
152
|
+
sourceLabel: string;
|
|
153
|
+
}
|
|
154
|
+
/**
|
|
155
|
+
* Folds one hit into an actor's totals, both bucket series, its current-rate estimator and its
|
|
156
|
+
* per-skill row. Target attribution stays with the caller: the damage reducer counts enemies it can
|
|
157
|
+
* distinguish from party members, while the meters count their hit's counterpart unconditionally.
|
|
158
|
+
*/
|
|
159
|
+
export declare function recordHit(actor: ActorAggregate, hit: RecordedHit, maxTimelineBuckets: number): void;
|
|
160
|
+
/** Rejected at construction rather than when the first actor is created, so a bad value fails fast. */
|
|
161
|
+
export declare function positiveTau(value: number): number;
|
|
162
|
+
export declare function createActor(actorId: number, encounterStartedAtMs: number, currentRate?: number | EwmaRate): ActorAggregate;
|
|
163
|
+
/**
|
|
164
|
+
* The enemy breakdown and death log count a wider set than the DPS aggregation: any positive hit,
|
|
165
|
+
* regardless of team or self-targeting, minus deaths that merely restate a damage event.
|
|
166
|
+
*/
|
|
167
|
+
export declare function isPositiveHit(event: FishNetCombatDamageEvent | FishNetCombatDeathEvent): boolean;
|
|
168
|
+
//# sourceMappingURL=damage.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"damage.d.ts","sourceRoot":"","sources":["../../src/reducers/damage.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import type { FishNetActorIdentityEvent } from "../actor-directory.ts";
|
|
2
|
+
import type { FishNetCombatEvent } from "../combat-tracker.ts";
|
|
3
|
+
import type { CombatIdentity, EncounterAggregate } from "./damage.ts";
|
|
4
|
+
/** Which side of an encounter a meter measures. */
|
|
5
|
+
export type MeterKind = "tanked" | "healing";
|
|
6
|
+
export interface MeterReducerOptions {
|
|
7
|
+
kind: MeterKind;
|
|
8
|
+
/** Decay constant for the meter's current rate. Defaults to {@link DEFAULT_CURRENT_TAU_SECONDS}. */
|
|
9
|
+
currentTauSeconds?: number;
|
|
10
|
+
maxTimelineBuckets?: number;
|
|
11
|
+
}
|
|
12
|
+
export declare class MeterReducer {
|
|
13
|
+
readonly kind: MeterKind;
|
|
14
|
+
current?: EncounterAggregate;
|
|
15
|
+
private readonly currentTauSeconds;
|
|
16
|
+
private readonly maxTimelineBuckets;
|
|
17
|
+
constructor(options: MeterReducerOptions);
|
|
18
|
+
/** Starts the meter's own aggregate for an encounter the damage reducer has opened. */
|
|
19
|
+
begin(id: string, startedAtMs: number): EncounterAggregate;
|
|
20
|
+
/** Adopts an aggregate left open by an earlier indexing pass. */
|
|
21
|
+
resume(encounter: EncounterAggregate): void;
|
|
22
|
+
finish(endedAtMs: number): EncounterAggregate | undefined;
|
|
23
|
+
reset(): void;
|
|
24
|
+
/** Keeps a display name current on a row that is already accumulating. */
|
|
25
|
+
consumeIdentity(event: FishNetActorIdentityEvent): void;
|
|
26
|
+
consumeCombat(event: FishNetCombatEvent, observedAtMs: number, identities: ReadonlyMap<number, CombatIdentity>): void;
|
|
27
|
+
private apply;
|
|
28
|
+
}
|
|
29
|
+
//# sourceMappingURL=meter.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"meter.d.ts","sourceRoot":"","sources":["../../src/reducers/meter.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,yBAAyB,EAAE,MAAM,uBAAuB,CAAC;AACvE,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,sBAAsB,CAAC;AAE/D,OAAO,KAAK,EAAE,cAAc,EAAE,kBAAkB,EAAE,MAAM,aAAa,CAAC;AAEtE,mDAAmD;AACnD,MAAM,MAAM,SAAS,GAAG,QAAQ,GAAG,SAAS,CAAC;AAE7C,MAAM,WAAW,mBAAmB;IAClC,IAAI,EAAE,SAAS,CAAC;IAChB,oGAAoG;IACpG,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,kBAAkB,CAAC,EAAE,MAAM,CAAC;CAC7B;AAED,qBAAa,YAAY;IACvB,QAAQ,CAAC,IAAI,EAAE,SAAS,CAAC;IACzB,OAAO,CAAC,EAAE,kBAAkB,CAAC;IAC7B,OAAO,CAAC,QAAQ,CAAC,iBAAiB,CAAS;IAC3C,OAAO,CAAC,QAAQ,CAAC,kBAAkB,CAAS;IAE5C,YAAY,OAAO,EAAE,mBAAmB,EAIvC;IAED,uFAAuF;IACvF,KAAK,CAAC,EAAE,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,GAAG,kBAAkB,CAYzD;IAED,iEAAiE;IACjE,MAAM,CAAC,SAAS,EAAE,kBAAkB,GAAG,IAAI,CAE1C;IAED,MAAM,CAAC,SAAS,EAAE,MAAM,GAAG,kBAAkB,GAAG,SAAS,CAMxD;IAED,KAAK,IAAI,IAAI,CAEZ;IAED,0EAA0E;IAC1E,eAAe,CAAC,KAAK,EAAE,yBAAyB,GAAG,IAAI,CAOtD;IAED,aAAa,CACX,KAAK,EAAE,kBAAkB,EACzB,YAAY,EAAE,MAAM,EACpB,UAAU,EAAE,WAAW,CAAC,MAAM,EAAE,cAAc,CAAC,GAC9C,IAAI,CAKN;IAED,OAAO,CAAC,KAAK;CAoBd"}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import type { FishNetDpsEncounterSnapshot } from "../snapshot.ts";
|
|
2
|
+
import type { ActorAggregate, EncounterAggregate } from "./damage.ts";
|
|
3
|
+
export interface RenderOptions {
|
|
4
|
+
nowMs?: number;
|
|
5
|
+
minimumDurationMs?: number;
|
|
6
|
+
anonymousIdentityGraceMs?: number;
|
|
7
|
+
personalName?: string;
|
|
8
|
+
personalActorId?: number;
|
|
9
|
+
}
|
|
10
|
+
/** Renders an aggregate into an encounter snapshot. */
|
|
11
|
+
export declare function renderEncounter(encounter: EncounterAggregate, options?: RenderOptions): FishNetDpsEncounterSnapshot;
|
|
12
|
+
export interface DisplayActorAggregate {
|
|
13
|
+
rowId: string;
|
|
14
|
+
actor: ActorAggregate;
|
|
15
|
+
}
|
|
16
|
+
export interface DisplayActorOptions {
|
|
17
|
+
snapshotNowMs?: number;
|
|
18
|
+
anonymousIdentityGraceMs?: number;
|
|
19
|
+
personalActorId?: number;
|
|
20
|
+
/**
|
|
21
|
+
* Keeps every anonymous actor regardless of the grace period. The grace period stops a row
|
|
22
|
+
* flickering into the live meter before its identity arrives, which is a rendering concern; a
|
|
23
|
+
* caller reading totals wants the damage either way.
|
|
24
|
+
*/
|
|
25
|
+
includeAllAnonymous?: boolean;
|
|
26
|
+
}
|
|
27
|
+
/** Applies the same identity merging and row visibility used by the rendered encounter. */
|
|
28
|
+
export declare function displayActorAggregates(encounter: EncounterAggregate, options?: DisplayActorOptions): DisplayActorAggregate[];
|
|
29
|
+
export declare function actorRowId(actor: ActorAggregate): string;
|
|
30
|
+
/**
|
|
31
|
+
* The single identity key for a display name. NFC folds the two Unicode spellings of the same
|
|
32
|
+
* text onto one key - Hangul in particular arrives both precomposed and as conjoining jamo,
|
|
33
|
+
* which render identically but compare unequal and would otherwise split one player into two
|
|
34
|
+
* rows. `toLowerCase` rather than `toLocaleLowerCase` keeps the key independent of host locale.
|
|
35
|
+
*/
|
|
36
|
+
export declare function normalizeName(name: string): string;
|
|
37
|
+
//# sourceMappingURL=rows.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"rows.d.ts","sourceRoot":"","sources":["../../src/reducers/rows.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAsB,2BAA2B,EAA4C,MAAM,gBAAgB,CAAC;AAEhI,OAAO,KAAK,EAAE,cAAc,EAAE,kBAAkB,EAAmB,MAAM,aAAa,CAAC;AAGvF,MAAM,WAAW,aAAa;IAC5B,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,wBAAwB,CAAC,EAAE,MAAM,CAAC;IAClC,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,eAAe,CAAC,EAAE,MAAM,CAAC;CAC1B;AAID,uDAAuD;AACvD,wBAAgB,eAAe,CAC7B,SAAS,EAAE,kBAAkB,EAC7B,OAAO,GAAE,aAAkB,GAC1B,2BAA2B,CAoF7B;AAiDD,MAAM,WAAW,qBAAqB;IACpC,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,cAAc,CAAC;CACvB;AAED,MAAM,WAAW,mBAAmB;IAClC,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,wBAAwB,CAAC,EAAE,MAAM,CAAC;IAClC,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB;;;;OAIG;IACH,mBAAmB,CAAC,EAAE,OAAO,CAAC;CAC/B;AAED,2FAA2F;AAC3F,wBAAgB,sBAAsB,CACpC,SAAS,EAAE,kBAAkB,EAC7B,OAAO,GAAE,mBAAwB,GAChC,qBAAqB,EAAE,CAEzB;AAsBD,wBAAgB,UAAU,CAAC,KAAK,EAAE,cAAc,GAAG,MAAM,CAMxD;AA0GD;;;;;GAKG;AACH,wBAAgB,aAAa,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAElD"}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/** Width the legacy meter used for analysis buckets; the read model stores this resolution. */
|
|
2
|
+
export declare const ANALYSIS_BUCKET_MS = 5000;
|
|
3
|
+
export interface TimelinePoint {
|
|
4
|
+
/** Milliseconds elapsed since the series origin. */
|
|
5
|
+
elapsedMs: number;
|
|
6
|
+
/** Damage recorded during this time bucket. */
|
|
7
|
+
damage: number;
|
|
8
|
+
/** Damage recorded from the origin through this time bucket. */
|
|
9
|
+
cumulativeDamage: number;
|
|
10
|
+
/** Damage per second for this time bucket. */
|
|
11
|
+
dps: number;
|
|
12
|
+
}
|
|
13
|
+
/** Damage accumulated into fixed-width buckets as events arrive, instead of a per-hit array that is re-bucketed on every read. */
|
|
14
|
+
export interface BucketSeries {
|
|
15
|
+
originMs: number;
|
|
16
|
+
widthMs: number;
|
|
17
|
+
buckets: number[];
|
|
18
|
+
/** Lowest bucket index changed since the last {@link takeDirtyFrom}, or `Infinity` when none is. */
|
|
19
|
+
dirtyFrom: number;
|
|
20
|
+
}
|
|
21
|
+
export declare function createSeries(originMs: number, widthMs?: number): BucketSeries;
|
|
22
|
+
/** Claims the dirty range and marks the series clean, returning the lowest index a writer must persist from. */
|
|
23
|
+
export declare function takeDirtyFrom(series: BucketSeries): number;
|
|
24
|
+
export declare function addToSeries(series: BucketSeries, atMs: number, damage: number, maxBuckets?: number): void;
|
|
25
|
+
/** Element-wise sum of two series that share an origin and width, e.g. when merging actor rows. */
|
|
26
|
+
export declare function addSeries(target: BucketSeries, source: BucketSeries): void;
|
|
27
|
+
/** Renders the series over `durationMs`: a leading zero point, then one point per bucket, padded with zeroes to cover the full duration. */
|
|
28
|
+
export declare function seriesPoints(series: BucketSeries, durationMs: number): TimelinePoint[];
|
|
29
|
+
//# sourceMappingURL=timeline.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"timeline.d.ts","sourceRoot":"","sources":["../../src/reducers/timeline.ts"],"names":[],"mappings":"AAAA,+FAA+F;AAC/F,eAAO,MAAM,kBAAkB,OAAQ,CAAC;AAExC,MAAM,WAAW,aAAa;IAC5B,oDAAoD;IACpD,SAAS,EAAE,MAAM,CAAC;IAClB,+CAA+C;IAC/C,MAAM,EAAE,MAAM,CAAC;IACf,gEAAgE;IAChE,gBAAgB,EAAE,MAAM,CAAC;IACzB,8CAA8C;IAC9C,GAAG,EAAE,MAAM,CAAC;CACb;AAED,kIAAkI;AAClI,MAAM,WAAW,YAAY;IAC3B,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,oGAAoG;IACpG,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,wBAAgB,YAAY,CAAC,QAAQ,EAAE,MAAM,EAAE,OAAO,SAAqB,GAAG,YAAY,CAEzF;AAED,gHAAgH;AAChH,wBAAgB,aAAa,CAAC,MAAM,EAAE,YAAY,GAAG,MAAM,CAI1D;AAED,wBAAgB,WAAW,CACzB,MAAM,EAAE,YAAY,EACpB,IAAI,EAAE,MAAM,EACZ,MAAM,EAAE,MAAM,EACd,UAAU,SAA2B,GACpC,IAAI,CAUN;AAcD,mGAAmG;AACnG,wBAAgB,SAAS,CAAC,MAAM,EAAE,YAAY,EAAE,MAAM,EAAE,YAAY,GAAG,IAAI,CAa1E;AAED,4IAA4I;AAC5I,wBAAgB,YAAY,CAAC,MAAM,EAAE,YAAY,EAAE,UAAU,EAAE,MAAM,GAAG,aAAa,EAAE,CAiBtF"}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { FishNetActorDirectory } from "./actor-directory.ts";
|
|
2
|
+
import type { FishNetActorIdentityEvent } from "./actor-directory.ts";
|
|
3
|
+
import { FishNetCombatTracker } from "./combat-tracker.ts";
|
|
4
|
+
import type { FishNetCombatEvent } from "./combat-tracker.ts";
|
|
5
|
+
export interface CombatCaptureReplayOptions {
|
|
6
|
+
tracker: FishNetCombatTracker;
|
|
7
|
+
directory: FishNetActorDirectory;
|
|
8
|
+
/** Receives every decoded event in wire order, so a caller can drive reducers as it replays. */
|
|
9
|
+
onEvent?: (event: FishNetActorIdentityEvent | FishNetCombatEvent, observedAtMs: number) => void;
|
|
10
|
+
}
|
|
11
|
+
export interface CombatCaptureReplayResult {
|
|
12
|
+
datagrams: number;
|
|
13
|
+
combatEvents: number;
|
|
14
|
+
identityEvents: number;
|
|
15
|
+
invalidLines: number;
|
|
16
|
+
decodeWarnings: number;
|
|
17
|
+
}
|
|
18
|
+
/** Replays structured transport records without retaining endpoint metadata. */
|
|
19
|
+
export declare function decodeCombatCaptureJsonLines(text: string, options: CombatCaptureReplayOptions): CombatCaptureReplayResult;
|
|
20
|
+
/** Re-runs a raw packet capture through the combat trackers. */
|
|
21
|
+
export declare function replayCombatCapture(path: string, options: CombatCaptureReplayOptions): Promise<CombatCaptureReplayResult>;
|
|
22
|
+
/** Replays ordered capture-file splits through one FishNet decoder and one identity lifetime. */
|
|
23
|
+
export declare function replayCombatCaptures(paths: readonly string[], options: CombatCaptureReplayOptions): Promise<CombatCaptureReplayResult>;
|
|
24
|
+
//# sourceMappingURL=replay-capture.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"replay-capture.d.ts","sourceRoot":"","sources":["../src/replay-capture.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,qBAAqB,EAAE,MAAM,sBAAsB,CAAC;AAC7D,OAAO,KAAK,EAAE,yBAAyB,EAAE,MAAM,sBAAsB,CAAC;AACtE,OAAO,EAAE,oBAAoB,EAAE,MAAM,qBAAqB,CAAC;AAC3D,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,qBAAqB,CAAC;AAE9D,MAAM,WAAW,0BAA0B;IACzC,OAAO,EAAE,oBAAoB,CAAC;IAC9B,SAAS,EAAE,qBAAqB,CAAC;IACjC,gGAAgG;IAChG,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,yBAAyB,GAAG,kBAAkB,EAAE,YAAY,EAAE,MAAM,KAAK,IAAI,CAAC;CACjG;AAED,MAAM,WAAW,yBAAyB;IACxC,SAAS,EAAE,MAAM,CAAC;IAClB,YAAY,EAAE,MAAM,CAAC;IACrB,cAAc,EAAE,MAAM,CAAC;IACvB,YAAY,EAAE,MAAM,CAAC;IACrB,cAAc,EAAE,MAAM,CAAC;CACxB;AAED,gFAAgF;AAChF,wBAAgB,4BAA4B,CAC1C,IAAI,EAAE,MAAM,EACZ,OAAO,EAAE,0BAA0B,GAClC,yBAAyB,CAI3B;AAED,gEAAgE;AAChE,wBAAsB,mBAAmB,CACvC,IAAI,EAAE,MAAM,EACZ,OAAO,EAAE,0BAA0B,GAClC,OAAO,CAAC,yBAAyB,CAAC,CAEpC;AAED,iGAAiG;AACjG,wBAAsB,oBAAoB,CACxC,KAAK,EAAE,SAAS,MAAM,EAAE,EACxB,OAAO,EAAE,0BAA0B,GAClC,OAAO,CAAC,yBAAyB,CAAC,CAMpC"}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export interface CombatReplaySummary {
|
|
2
|
+
encounters: number;
|
|
3
|
+
totalDamage: number;
|
|
4
|
+
durationMs: number;
|
|
5
|
+
invalidLines: number;
|
|
6
|
+
}
|
|
7
|
+
export interface CombatReplayInspection {
|
|
8
|
+
recordCount: number;
|
|
9
|
+
summary: string;
|
|
10
|
+
}
|
|
11
|
+
export declare function readCombatReplaySummary(path: string): Promise<CombatReplaySummary>;
|
|
12
|
+
export declare function formatCombatReplaySummary(path: string): Promise<string>;
|
|
13
|
+
export declare function inspectCombatReplaySummary(path: string): Promise<CombatReplayInspection>;
|
|
14
|
+
//# sourceMappingURL=replay-summary.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"replay-summary.d.ts","sourceRoot":"","sources":["../src/replay-summary.ts"],"names":[],"mappings":"AAGA,MAAM,WAAW,mBAAmB;IAClC,UAAU,EAAE,MAAM,CAAC;IACnB,WAAW,EAAE,MAAM,CAAC;IACpB,UAAU,EAAE,MAAM,CAAC;IACnB,YAAY,EAAE,MAAM,CAAC;CACtB;AAED,MAAM,WAAW,sBAAsB;IACrC,WAAW,EAAE,MAAM,CAAC;IACpB,OAAO,EAAE,MAAM,CAAC;CACjB;AAED,wBAAsB,uBAAuB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,mBAAmB,CAAC,CASxF;AAED,wBAAsB,yBAAyB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAE7E;AAED,wBAAsB,0BAA0B,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,sBAAsB,CAAC,CAG9F"}
|
package/dist/replay.d.ts
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { FishNetActorIdentityEvent } from "./actor-directory.ts";
|
|
2
|
+
import type { FishNetCombatEvent } from "./combat-tracker.ts";
|
|
3
|
+
import type { FishNetDpsEncounterSnapshot } from "./snapshot.ts";
|
|
4
|
+
export interface DpsReplayResult {
|
|
5
|
+
snapshots: FishNetDpsEncounterSnapshot[];
|
|
6
|
+
invalidLines: number;
|
|
7
|
+
}
|
|
8
|
+
/** Loads combat JSON Lines without retaining raw records or file contents. */
|
|
9
|
+
export declare function loadDpsReplay(path: string, personalName?: string): Promise<DpsReplayResult>;
|
|
10
|
+
export declare function parseDpsLogRecord(type: string, data: Record<string, unknown>): FishNetActorIdentityEvent | FishNetCombatEvent | undefined | null;
|
|
11
|
+
//# sourceMappingURL=replay.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"replay.d.ts","sourceRoot":"","sources":["../src/replay.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,yBAAyB,EAAE,MAAM,sBAAsB,CAAC;AACtE,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,qBAAqB,CAAC;AAI9D,OAAO,KAAK,EAAE,2BAA2B,EAAE,MAAM,eAAe,CAAC;AAGjE,MAAM,WAAW,eAAe;IAC9B,SAAS,EAAE,2BAA2B,EAAE,CAAC;IACzC,YAAY,EAAE,MAAM,CAAC;CACtB;AAED,8EAA8E;AAC9E,wBAAsB,aAAa,CAAC,IAAI,EAAE,MAAM,EAAE,YAAY,SAAK,GAAG,OAAO,CAAC,eAAe,CAAC,CAuC7F;AAwFD,wBAAgB,iBAAiB,CAC/B,IAAI,EAAE,MAAM,EACZ,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAC5B,yBAAyB,GAAG,kBAAkB,GAAG,SAAS,GAAG,IAAI,CAOnE"}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
/** The rendered shape of one encounter, shared by every producer of it: the live service, the history read model, and a whole-log replay. */
|
|
2
|
+
export interface FishNetDpsSkillRow {
|
|
3
|
+
sourceId: string;
|
|
4
|
+
sourceLabel: string;
|
|
5
|
+
damage: number;
|
|
6
|
+
dps: number;
|
|
7
|
+
contribution: number;
|
|
8
|
+
hits: number;
|
|
9
|
+
criticalHits: number;
|
|
10
|
+
/** Critical hits as a fraction of total hits, when the skill has hit. */
|
|
11
|
+
critRate?: number;
|
|
12
|
+
}
|
|
13
|
+
export interface FishNetDpsTimelinePoint {
|
|
14
|
+
/** Milliseconds elapsed since the encounter began. */
|
|
15
|
+
elapsedMs: number;
|
|
16
|
+
/** Damage dealt during this time bucket. */
|
|
17
|
+
damage: number;
|
|
18
|
+
/** Damage dealt from encounter start through this time bucket. */
|
|
19
|
+
cumulativeDamage: number;
|
|
20
|
+
/** Damage per second for this time bucket. */
|
|
21
|
+
dps: number;
|
|
22
|
+
}
|
|
23
|
+
export interface FishNetDpsActorRow {
|
|
24
|
+
/** Stable identity for this rendered row, including merged and unidentified rows. */
|
|
25
|
+
rowId: string;
|
|
26
|
+
actorIds: number[];
|
|
27
|
+
displayName: string;
|
|
28
|
+
archetype?: number;
|
|
29
|
+
/** Milliseconds used as the DPS divisor and timeline span for this row. */
|
|
30
|
+
durationMs?: number;
|
|
31
|
+
/** Timestamp of this actor's most recent positive damage in the encounter. */
|
|
32
|
+
lastDamageAtMs?: number;
|
|
33
|
+
damage: number;
|
|
34
|
+
dps: number;
|
|
35
|
+
/** Recent damage per second: an exponentially-weighted rate, not a flat window of the last few seconds. */
|
|
36
|
+
currentDps: number;
|
|
37
|
+
contribution: number;
|
|
38
|
+
hits: number;
|
|
39
|
+
criticalHits: number;
|
|
40
|
+
/** Critical hits as a fraction of total hits, when the actor has hit. */
|
|
41
|
+
critRate?: number;
|
|
42
|
+
kills: number;
|
|
43
|
+
/** Number of distinct enemy object IDs damaged by this actor during the encounter. */
|
|
44
|
+
mobsHit: number;
|
|
45
|
+
skills: FishNetDpsSkillRow[];
|
|
46
|
+
timeline: FishNetDpsTimelinePoint[];
|
|
47
|
+
/** True when the row contains damage that has not been verified to a player identity. */
|
|
48
|
+
isUnidentified?: boolean;
|
|
49
|
+
}
|
|
50
|
+
export type FishNetPersonalMatch = "unconfigured" | "missing" | "matched" | "ambiguous";
|
|
51
|
+
export interface FishNetDpsEncounterSnapshot {
|
|
52
|
+
id: string;
|
|
53
|
+
startedAtMs: number;
|
|
54
|
+
lastDamageAtMs: number;
|
|
55
|
+
endedAtMs?: number;
|
|
56
|
+
durationMs: number;
|
|
57
|
+
totalDamage: number;
|
|
58
|
+
partyDps: number;
|
|
59
|
+
partyCurrentDps: number;
|
|
60
|
+
actors: FishNetDpsActorRow[];
|
|
61
|
+
/** Actor ids on rows with no verified player identity. Each such actor keeps its own row. */
|
|
62
|
+
unidentifiedActorIds: number[];
|
|
63
|
+
personalName: string;
|
|
64
|
+
personalMatch: FishNetPersonalMatch;
|
|
65
|
+
personal?: FishNetDpsActorRow;
|
|
66
|
+
}
|
|
67
|
+
//# sourceMappingURL=snapshot.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"snapshot.d.ts","sourceRoot":"","sources":["../src/snapshot.ts"],"names":[],"mappings":"AAAA,6IAA6I;AAC7I,MAAM,WAAW,kBAAkB;IACjC,QAAQ,EAAE,MAAM,CAAC;IACjB,WAAW,EAAE,MAAM,CAAC;IACpB,MAAM,EAAE,MAAM,CAAC;IACf,GAAG,EAAE,MAAM,CAAC;IACZ,YAAY,EAAE,MAAM,CAAC;IACrB,IAAI,EAAE,MAAM,CAAC;IACb,YAAY,EAAE,MAAM,CAAC;IACrB,yEAAyE;IACzE,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,uBAAuB;IACtC,sDAAsD;IACtD,SAAS,EAAE,MAAM,CAAC;IAClB,4CAA4C;IAC5C,MAAM,EAAE,MAAM,CAAC;IACf,kEAAkE;IAClE,gBAAgB,EAAE,MAAM,CAAC;IACzB,8CAA8C;IAC9C,GAAG,EAAE,MAAM,CAAC;CACb;AAED,MAAM,WAAW,kBAAkB;IACjC,qFAAqF;IACrF,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,MAAM,EAAE,CAAC;IACnB,WAAW,EAAE,MAAM,CAAC;IACpB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,2EAA2E;IAC3E,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,8EAA8E;IAC9E,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,MAAM,EAAE,MAAM,CAAC;IACf,GAAG,EAAE,MAAM,CAAC;IACZ,2GAA2G;IAC3G,UAAU,EAAE,MAAM,CAAC;IACnB,YAAY,EAAE,MAAM,CAAC;IACrB,IAAI,EAAE,MAAM,CAAC;IACb,YAAY,EAAE,MAAM,CAAC;IACrB,yEAAyE;IACzE,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,KAAK,EAAE,MAAM,CAAC;IACd,sFAAsF;IACtF,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,kBAAkB,EAAE,CAAC;IAC7B,QAAQ,EAAE,uBAAuB,EAAE,CAAC;IACpC,yFAAyF;IACzF,cAAc,CAAC,EAAE,OAAO,CAAC;CAC1B;AAED,MAAM,MAAM,oBAAoB,GAAG,cAAc,GAAG,SAAS,GAAG,SAAS,GAAG,WAAW,CAAC;AAExF,MAAM,WAAW,2BAA2B;IAC1C,EAAE,EAAE,MAAM,CAAC;IACX,WAAW,EAAE,MAAM,CAAC;IACpB,cAAc,EAAE,MAAM,CAAC;IACvB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,UAAU,EAAE,MAAM,CAAC;IACnB,WAAW,EAAE,MAAM,CAAC;IACpB,QAAQ,EAAE,MAAM,CAAC;IACjB,eAAe,EAAE,MAAM,CAAC;IACxB,MAAM,EAAE,kBAAkB,EAAE,CAAC;IAC7B,6FAA6F;IAC7F,oBAAoB,EAAE,MAAM,EAAE,CAAC;IAC/B,YAAY,EAAE,MAAM,CAAC;IACrB,aAAa,EAAE,oBAAoB,CAAC;IACpC,QAAQ,CAAC,EAAE,kBAAkB,CAAC;CAC/B"}
|