@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
package/README.md
ADDED
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
# @kar-mi/spirit-vale-tools-combat
|
|
2
|
+
|
|
3
|
+
Spirit Vale combat event tracking and replay utilities.
|
|
4
|
+
|
|
5
|
+
## Install
|
|
6
|
+
|
|
7
|
+
```sh
|
|
8
|
+
bun add @kar-mi/spirit-vale-tools-combat
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Usage
|
|
12
|
+
|
|
13
|
+
```ts
|
|
14
|
+
import { FishNetCombatTracker, LiveCombatService } from "@kar-mi/spirit-vale-tools-combat";
|
|
15
|
+
|
|
16
|
+
const tracker = new FishNetCombatTracker();
|
|
17
|
+
const meter = new LiveCombatService({ personalName: "MyCharacter" });
|
|
18
|
+
|
|
19
|
+
// packet: DecodedFishNetPacket from @kar-mi/spirit-vale-tools-capture
|
|
20
|
+
for (const event of tracker.consume(packet)) {
|
|
21
|
+
meter.consumeCombat(event, Date.now());
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const { current } = meter.getState();
|
|
25
|
+
if (current) console.log(current.dps.partyDps, current.dps.actors);
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
To build encounters yourself rather than through the live service, drive
|
|
29
|
+
`DamageReducer` and render each finished encounter with `renderEncounter`.
|
|
30
|
+
|
|
31
|
+
Feed the tracker every decoded FishNet packet from live capture or replay. Use
|
|
32
|
+
`FishNetActorDirectory` to resolve actor identities and `loadDpsReplay` /
|
|
33
|
+
`DpsSessionLogFollower` to rebuild encounters from recorded log sessions.
|
|
34
|
+
|
|
35
|
+
`DpsSessionLogFollower` can be followed rather than polled, which is what an
|
|
36
|
+
overlay should do — it wakes on a filesystem event instead of on a timer, and
|
|
37
|
+
yields only batches that carry something:
|
|
38
|
+
|
|
39
|
+
```ts
|
|
40
|
+
const follower = DpsSessionLogFollower.watch();
|
|
41
|
+
for await (const batch of follower) {
|
|
42
|
+
if (batch.changed) apply(batch.events);
|
|
43
|
+
}
|
|
44
|
+
follower.close();
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
Each batch carries `changed` and `revision`, so a consumer can skip re-projecting
|
|
48
|
+
when the reducer state did not move. `FishNetStatusTracker` exposes the same
|
|
49
|
+
`revision` alongside `nextExpiryAtMs()`, which says when the next status is due
|
|
50
|
+
to lapse — enough to sleep until a chip actually disappears rather than ticking.
|
|
51
|
+
|
|
52
|
+
For bounded live UI state, use `LiveCombatService`. It exposes the current and
|
|
53
|
+
latest finished encounter with DPS, incoming damage-per-second (TPS), and
|
|
54
|
+
healing-per-second (HPS) rows without retaining the full session history.
|
|
55
|
+
|
|
56
|
+
See the [package guide](https://github.com/kar-mi/spirit-vale-tools/blob/main/docs/packages.md) for registry setup and usage.
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import type { DecodedFishNetPacket } from "@kar-mi/spirit-vale-tools-capture";
|
|
2
|
+
export interface FishNetActorIdentity {
|
|
3
|
+
readonly actorId: number;
|
|
4
|
+
readonly displayName: string;
|
|
5
|
+
readonly archetype?: number;
|
|
6
|
+
/** Session-local FishNet owner used to group related player objects. */
|
|
7
|
+
readonly ownerConnectionId?: number;
|
|
8
|
+
readonly uid?: string;
|
|
9
|
+
}
|
|
10
|
+
export interface FishNetActorIdentityUpsertEvent extends FishNetActorIdentity {
|
|
11
|
+
kind: "actorIdentity";
|
|
12
|
+
operation: "upsert";
|
|
13
|
+
tick: number;
|
|
14
|
+
}
|
|
15
|
+
export interface FishNetActorIdentityRemoveEvent {
|
|
16
|
+
kind: "actorIdentity";
|
|
17
|
+
operation: "remove";
|
|
18
|
+
tick: number;
|
|
19
|
+
actorId: number;
|
|
20
|
+
}
|
|
21
|
+
export interface FishNetActorIdentityResetEvent {
|
|
22
|
+
kind: "actorIdentity";
|
|
23
|
+
operation: "reset";
|
|
24
|
+
tick: number;
|
|
25
|
+
}
|
|
26
|
+
export type FishNetActorIdentityEvent = FishNetActorIdentityUpsertEvent | FishNetActorIdentityRemoveEvent | FishNetActorIdentityResetEvent;
|
|
27
|
+
export interface FishNetLocalIdentity {
|
|
28
|
+
displayName: string;
|
|
29
|
+
uid?: string;
|
|
30
|
+
archetype?: number;
|
|
31
|
+
accountId?: string;
|
|
32
|
+
}
|
|
33
|
+
/** A durable, uid-keyed identity learned in a prior session, used to seed a fresh directory. */
|
|
34
|
+
export interface FishNetKnownIdentity {
|
|
35
|
+
readonly uid: string;
|
|
36
|
+
readonly displayName: string;
|
|
37
|
+
readonly archetype?: number;
|
|
38
|
+
}
|
|
39
|
+
export interface FishNetActorDirectoryOptions {
|
|
40
|
+
/** Persisted local-player identity. */
|
|
41
|
+
localIdentity?: FishNetLocalIdentity;
|
|
42
|
+
/** Invoked whenever the local player's identity is decoded from CharacterData RPCs. */
|
|
43
|
+
onLocalIdentity?: (identity: FishNetLocalIdentity) => void;
|
|
44
|
+
knownIdentities?: readonly FishNetKnownIdentity[];
|
|
45
|
+
/** Invoked whenever a uid's cached displayName/archetype is newly learned or changed. */
|
|
46
|
+
onIdentityLearned?: (identity: FishNetKnownIdentity) => void;
|
|
47
|
+
}
|
|
48
|
+
/** Tracks public display names by the FishNet object IDs used by combat events. */
|
|
49
|
+
export declare class FishNetActorDirectory {
|
|
50
|
+
private readonly options;
|
|
51
|
+
private readonly identities;
|
|
52
|
+
private readonly objects;
|
|
53
|
+
private readonly ownerObjects;
|
|
54
|
+
private readonly identitySources;
|
|
55
|
+
private readonly sourceRevisions;
|
|
56
|
+
/** Combat attacker IDs that should inherit the identity of another object with the same owner. */
|
|
57
|
+
private readonly observedPlayerActors;
|
|
58
|
+
/** UID-keyed names survive map-change resets when delta respawns repeat the UID. */
|
|
59
|
+
private readonly uidIdentities;
|
|
60
|
+
private nextSourceRevision;
|
|
61
|
+
private localIdentity?;
|
|
62
|
+
constructor(options?: FishNetActorDirectoryOptions);
|
|
63
|
+
consume(packet: DecodedFishNetPacket): FishNetActorIdentityEvent[];
|
|
64
|
+
private applyIdentitySource;
|
|
65
|
+
get(actorId: number): FishNetActorIdentity | undefined;
|
|
66
|
+
/** Resolves a combat attacker even when the hit came from a newly allocated player object. */
|
|
67
|
+
getAttribution(actorId: number): FishNetActorIdentity | undefined;
|
|
68
|
+
/** Snapshots every currently known identity, for seeding a freshly rotated log with resolved names. */
|
|
69
|
+
snapshot(): FishNetActorIdentity[];
|
|
70
|
+
/** Marks an attacker ID from a player-team combat event as eligible for owner-based identity propagation. */
|
|
71
|
+
observePlayerActor(actorId: number, tick: number): FishNetActorIdentityEvent[];
|
|
72
|
+
/** Clears per-connection state (spawned objects, owner mappings, in-session identity sources). */
|
|
73
|
+
reset(): void;
|
|
74
|
+
setLocalIdentity(identity: FishNetLocalIdentity): void;
|
|
75
|
+
private seedLocalIdentity;
|
|
76
|
+
private updateLocalIdentity;
|
|
77
|
+
private resolveSpawnIdentity;
|
|
78
|
+
private learnUidIdentity;
|
|
79
|
+
private changeOwner;
|
|
80
|
+
private removeObject;
|
|
81
|
+
private clearPlayerIdentity;
|
|
82
|
+
private refreshOwner;
|
|
83
|
+
private reconcile;
|
|
84
|
+
private addOwnerObject;
|
|
85
|
+
private removeOwnerObject;
|
|
86
|
+
private clear;
|
|
87
|
+
}
|
|
88
|
+
//# sourceMappingURL=actor-directory.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"actor-directory.d.ts","sourceRoot":"","sources":["../src/actor-directory.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,oBAAoB,EAA4C,MAAM,mCAAmC,CAAC;AAIxH,MAAM,WAAW,oBAAoB;IACnC,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,CAAC;IAC5B,wEAAwE;IACxE,QAAQ,CAAC,iBAAiB,CAAC,EAAE,MAAM,CAAC;IACpC,QAAQ,CAAC,GAAG,CAAC,EAAE,MAAM,CAAC;CACvB;AAED,MAAM,WAAW,+BAAgC,SAAQ,oBAAoB;IAC3E,IAAI,EAAE,eAAe,CAAC;IACtB,SAAS,EAAE,QAAQ,CAAC;IACpB,IAAI,EAAE,MAAM,CAAC;CACd;AAED,MAAM,WAAW,+BAA+B;IAC9C,IAAI,EAAE,eAAe,CAAC;IACtB,SAAS,EAAE,QAAQ,CAAC;IACpB,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,8BAA8B;IAC7C,IAAI,EAAE,eAAe,CAAC;IACtB,SAAS,EAAE,OAAO,CAAC;IACnB,IAAI,EAAE,MAAM,CAAC;CACd;AAED,MAAM,MAAM,yBAAyB,GACjC,+BAA+B,GAC/B,+BAA+B,GAC/B,8BAA8B,CAAC;AAEnC,MAAM,WAAW,oBAAoB;IACnC,WAAW,EAAE,MAAM,CAAC;IACpB,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,gGAAgG;AAChG,MAAM,WAAW,oBAAoB;IACnC,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,CAAC;CAC7B;AAED,MAAM,WAAW,4BAA4B;IAC3C,uCAAuC;IACvC,aAAa,CAAC,EAAE,oBAAoB,CAAC;IACrC,uFAAuF;IACvF,eAAe,CAAC,EAAE,CAAC,QAAQ,EAAE,oBAAoB,KAAK,IAAI,CAAC;IAC3D,eAAe,CAAC,EAAE,SAAS,oBAAoB,EAAE,CAAC;IAClD,yFAAyF;IACzF,iBAAiB,CAAC,EAAE,CAAC,QAAQ,EAAE,oBAAoB,KAAK,IAAI,CAAC;CAC9D;AAED,mFAAmF;AACnF,qBAAa,qBAAqB;IAapB,OAAO,CAAC,QAAQ,CAAC,OAAO;IAZpC,OAAO,CAAC,QAAQ,CAAC,UAAU,CAA2C;IACtE,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAgF;IACxG,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAkC;IAC/D,OAAO,CAAC,QAAQ,CAAC,eAAe,CAA2C;IAC3E,OAAO,CAAC,QAAQ,CAAC,eAAe,CAA6B;IAC7D,kGAAkG;IAClG,OAAO,CAAC,QAAQ,CAAC,oBAAoB,CAAqB;IAC1D,oFAAoF;IACpF,OAAO,CAAC,QAAQ,CAAC,aAAa,CAAkE;IAChG,OAAO,CAAC,kBAAkB,CAAK;IAC/B,OAAO,CAAC,aAAa,CAAC,CAAuB;IAE7C,YAA6B,OAAO,GAAE,4BAAiC,EAStE;IAED,OAAO,CAAC,MAAM,EAAE,oBAAoB,GAAG,yBAAyB,EAAE,CAsGjE;IAED,OAAO,CAAC,mBAAmB;IAmB3B,GAAG,CAAC,OAAO,EAAE,MAAM,GAAG,oBAAoB,GAAG,SAAS,CAGrD;IAED,8FAA8F;IAC9F,cAAc,CAAC,OAAO,EAAE,MAAM,GAAG,oBAAoB,GAAG,SAAS,CAoBhE;IAED,uGAAuG;IACvG,QAAQ,IAAI,oBAAoB,EAAE,CAEjC;IAED,6GAA6G;IAC7G,kBAAkB,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,yBAAyB,EAAE,CAQ7E;IAED,kGAAkG;IAClG,KAAK,IAAI,IAAI,CAGZ;IAED,gBAAgB,CAAC,QAAQ,EAAE,oBAAoB,GAAG,IAAI,CAGrD;IAED,OAAO,CAAC,iBAAiB;IAUzB,OAAO,CAAC,mBAAmB;IAS3B,OAAO,CAAC,oBAAoB;IAI5B,OAAO,CAAC,gBAAgB;IAaxB,OAAO,CAAC,WAAW;IAiBnB,OAAO,CAAC,YAAY;IAapB,OAAO,CAAC,mBAAmB;IAW3B,OAAO,CAAC,YAAY;IA0DpB,OAAO,CAAC,SAAS;IAkBjB,OAAO,CAAC,cAAc;IAMtB,OAAO,CAAC,iBAAiB;IAOzB,OAAO,CAAC,KAAK;CASd"}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import type { FishNetBossCatalog } from "./combat-tracker.ts";
|
|
2
|
+
/** Makes the deliberately small, manually maintained boss registry explicit at each call site. */
|
|
3
|
+
export declare function createBossCatalog(entries: Readonly<Record<string, string>>): FishNetBossCatalog;
|
|
4
|
+
/** Registry for the bundled game build. */
|
|
5
|
+
export declare const CURRENT_BOSS_SKILL_NAMES: FishNetBossCatalog;
|
|
6
|
+
//# sourceMappingURL=boss-catalog.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"boss-catalog.d.ts","sourceRoot":"","sources":["../src/boss-catalog.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,qBAAqB,CAAC;AAE9D,kGAAkG;AAClG,wBAAgB,iBAAiB,CAAC,OAAO,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,GAAG,kBAAkB,CAG/F;AAED,2CAA2C;AAC3C,eAAO,MAAM,wBAAwB,oBAAwB,CAAC"}
|
|
@@ -0,0 +1,295 @@
|
|
|
1
|
+
import type { DecodedFishNetPacket, FishNetDecodedValue, FishNetRecoveryStyle, FishNetSemanticMap } from "@kar-mi/spirit-vale-tools-capture";
|
|
2
|
+
import type { FishNetSkillCatalog } from "@kar-mi/spirit-vale-tools-skills";
|
|
3
|
+
export type FishNetCombatActionKind = "skill" | "basicAttack" | "inferred";
|
|
4
|
+
export type FishNetCombatActionPhase = "begin" | "complete" | "interrupt" | "cancel" | "inferred";
|
|
5
|
+
export type FishNetDamageAttribution = "exact" | "ambiguous" | "inferred";
|
|
6
|
+
export type FishNetHitResult = "normal" | "critical" | "miss" | "blocked" | "dodged" | number;
|
|
7
|
+
export interface FishNetCombatActorIdentity {
|
|
8
|
+
readonly displayName: string;
|
|
9
|
+
readonly archetype?: number;
|
|
10
|
+
readonly ownerConnectionId?: number;
|
|
11
|
+
readonly uid?: string;
|
|
12
|
+
}
|
|
13
|
+
/** Names a monster type. */
|
|
14
|
+
export interface FishNetMonsterCatalog {
|
|
15
|
+
get(mobId: string): {
|
|
16
|
+
readonly level: number;
|
|
17
|
+
readonly displayName: string;
|
|
18
|
+
} | undefined;
|
|
19
|
+
}
|
|
20
|
+
/** Curated, build-scoped boss names keyed by a skill unique to that boss. */
|
|
21
|
+
export interface FishNetBossCatalog {
|
|
22
|
+
get(skillId: string): {
|
|
23
|
+
readonly displayName: string;
|
|
24
|
+
} | undefined;
|
|
25
|
+
}
|
|
26
|
+
export interface FishNetCombatTrackerOptions {
|
|
27
|
+
/** Ticks to retain a completed activation for trailing hits. Defaults to 30. */
|
|
28
|
+
hitGraceTicks?: number;
|
|
29
|
+
/** Maximum age of an activation that never completes. Defaults to 900 ticks. */
|
|
30
|
+
activationMaxAgeTicks?: number;
|
|
31
|
+
/** Semantic labels for skill wire identifiers. Defaults to the current bundled build. */
|
|
32
|
+
semanticMap?: FishNetSemanticMap;
|
|
33
|
+
/** Extracted public skill metadata. Defaults to the current bundled build when available. */
|
|
34
|
+
skillCatalog?: FishNetSkillCatalog;
|
|
35
|
+
buildFingerprint?: string;
|
|
36
|
+
/** Resolves an attacker to a known player identity, including owner/UID continuity. */
|
|
37
|
+
actorIdentityResolver?: (actorId: number) => FishNetCombatActorIdentity | undefined;
|
|
38
|
+
/** Resolves healing mechanics for actors whose local character build is visible. */
|
|
39
|
+
healingTraitsResolver?: (actorId: number) => FishNetHealingTraits | undefined;
|
|
40
|
+
/** The local player's network object id, when known. */
|
|
41
|
+
localActorIdResolver?: () => number | undefined;
|
|
42
|
+
/** Names monsters seen spawning, emitting identity lifecycle events keyed by network object id. */
|
|
43
|
+
monsterCatalog?: FishNetMonsterCatalog;
|
|
44
|
+
/** Fallback names for otherwise-anonymous bosses; game-provided identities always win. */
|
|
45
|
+
bossCatalog?: FishNetBossCatalog;
|
|
46
|
+
}
|
|
47
|
+
export type FishNetCombatMonsterIdentityEvent = {
|
|
48
|
+
kind: "monsterIdentity";
|
|
49
|
+
operation: "upsert";
|
|
50
|
+
tick: number;
|
|
51
|
+
actorId: number;
|
|
52
|
+
mobId: string;
|
|
53
|
+
displayName: string;
|
|
54
|
+
actorIdentity?: never;
|
|
55
|
+
} | {
|
|
56
|
+
kind: "monsterIdentity";
|
|
57
|
+
operation: "remove";
|
|
58
|
+
tick: number;
|
|
59
|
+
actorId: number;
|
|
60
|
+
actorIdentity?: never;
|
|
61
|
+
} | {
|
|
62
|
+
kind: "monsterIdentity";
|
|
63
|
+
operation: "reset";
|
|
64
|
+
tick: number;
|
|
65
|
+
actorId?: never;
|
|
66
|
+
actorIdentity?: never;
|
|
67
|
+
};
|
|
68
|
+
export interface FishNetHealingTraits {
|
|
69
|
+
readonly hasSiphonHealth: boolean;
|
|
70
|
+
readonly hasHealthLeech: boolean;
|
|
71
|
+
}
|
|
72
|
+
export interface FishNetCombatActivationEvent {
|
|
73
|
+
kind: "activation";
|
|
74
|
+
rpc: string;
|
|
75
|
+
tick: number;
|
|
76
|
+
payloadBytes: number;
|
|
77
|
+
fields: Record<string, FishNetDecodedValue>;
|
|
78
|
+
actorId: number;
|
|
79
|
+
activationId?: string;
|
|
80
|
+
actionKind: FishNetCombatActionKind;
|
|
81
|
+
phase: FishNetCombatActionPhase;
|
|
82
|
+
sourceId?: string;
|
|
83
|
+
sourceLabel?: string;
|
|
84
|
+
targetId?: number;
|
|
85
|
+
level?: number;
|
|
86
|
+
attackIndex?: number;
|
|
87
|
+
inferred?: boolean;
|
|
88
|
+
actorIdentity?: FishNetCombatActorIdentity;
|
|
89
|
+
}
|
|
90
|
+
export interface FishNetCombatDamageEvent {
|
|
91
|
+
kind: "damage";
|
|
92
|
+
rpc: "ApplyDamage_C";
|
|
93
|
+
tick: number;
|
|
94
|
+
payloadBytes: number;
|
|
95
|
+
fields: Record<string, FishNetDecodedValue>;
|
|
96
|
+
actorId: number;
|
|
97
|
+
targetId: number;
|
|
98
|
+
sourceId: string;
|
|
99
|
+
sourceLabel: string;
|
|
100
|
+
value: number;
|
|
101
|
+
hitResult: FishNetHitResult;
|
|
102
|
+
wireHits: number;
|
|
103
|
+
damageType: number;
|
|
104
|
+
team: number;
|
|
105
|
+
element: number;
|
|
106
|
+
weaponType: number;
|
|
107
|
+
range: number;
|
|
108
|
+
isClone: boolean;
|
|
109
|
+
isSummon: boolean;
|
|
110
|
+
position: number[];
|
|
111
|
+
origin: number[];
|
|
112
|
+
attribution: FishNetDamageAttribution;
|
|
113
|
+
activationId?: string;
|
|
114
|
+
candidateActivationIds?: string[];
|
|
115
|
+
actorIdentity?: FishNetCombatActorIdentity;
|
|
116
|
+
}
|
|
117
|
+
export interface FishNetCombatDeathEvent {
|
|
118
|
+
kind: "death";
|
|
119
|
+
rpc: "Death_C";
|
|
120
|
+
tick: number;
|
|
121
|
+
payloadBytes: number;
|
|
122
|
+
fields: Record<string, FishNetDecodedValue>;
|
|
123
|
+
actorId: number;
|
|
124
|
+
targetId: number;
|
|
125
|
+
sourceId: string;
|
|
126
|
+
sourceLabel: string;
|
|
127
|
+
value: number;
|
|
128
|
+
hitResult: FishNetHitResult;
|
|
129
|
+
wireHits: number;
|
|
130
|
+
damageType: number;
|
|
131
|
+
team: number;
|
|
132
|
+
element: number;
|
|
133
|
+
weaponType: number;
|
|
134
|
+
range: number;
|
|
135
|
+
isClone: boolean;
|
|
136
|
+
isSummon: boolean;
|
|
137
|
+
attribution: FishNetDamageAttribution;
|
|
138
|
+
activationId?: string;
|
|
139
|
+
candidateActivationIds?: string[];
|
|
140
|
+
/** True when an identical ApplyDamage_C was already emitted at this tick. */
|
|
141
|
+
duplicatesDamageEvent: boolean;
|
|
142
|
+
actorIdentity?: FishNetCombatActorIdentity;
|
|
143
|
+
}
|
|
144
|
+
export interface FishNetCombatStatusEvent {
|
|
145
|
+
kind: "status";
|
|
146
|
+
rpc: "ApplyEffect_T" | "RemoveEffect_T" | "ApplyEffectDisplays_O" | "ApplySkillDisplay_O" | "RemoveSkillDisplay_O";
|
|
147
|
+
tick: number;
|
|
148
|
+
payloadBytes: number;
|
|
149
|
+
fields: Record<string, FishNetDecodedValue>;
|
|
150
|
+
actorId: number;
|
|
151
|
+
statusId: string;
|
|
152
|
+
/** Absent on `ApplyEffectDisplays_O`, which carries no level; consumers keep the last known one. */
|
|
153
|
+
level?: number;
|
|
154
|
+
action: "applied" | "removed";
|
|
155
|
+
/** Server-reported time left, from `ApplyEffectDisplays_O` only. */
|
|
156
|
+
remainingSeconds?: number;
|
|
157
|
+
stacks?: number;
|
|
158
|
+
actorIdentity?: FishNetCombatActorIdentity;
|
|
159
|
+
}
|
|
160
|
+
export interface FishNetCombatSummonEvent {
|
|
161
|
+
kind: "summon";
|
|
162
|
+
rpc: "CalibrateSummons_T";
|
|
163
|
+
tick: number;
|
|
164
|
+
payloadBytes: number;
|
|
165
|
+
fields: Record<string, FishNetDecodedValue>;
|
|
166
|
+
actorId: number;
|
|
167
|
+
skillId: string;
|
|
168
|
+
stacks: number;
|
|
169
|
+
/** True when the packet arrived unnamed and was recovered heuristically. See `recoverSummons`. */
|
|
170
|
+
recovered?: boolean;
|
|
171
|
+
actorIdentity?: FishNetCombatActorIdentity;
|
|
172
|
+
}
|
|
173
|
+
/** A `PlayerController.FullHeal_C`, which restores an actor outright. */
|
|
174
|
+
interface FishNetCombatFullHealEvent {
|
|
175
|
+
kind: "fullHeal";
|
|
176
|
+
rpc: "FullHeal_C";
|
|
177
|
+
tick: number;
|
|
178
|
+
payloadBytes: number;
|
|
179
|
+
fields: Record<string, FishNetDecodedValue>;
|
|
180
|
+
/** The restored actor: the RPC's own object. There is no healer on the wire. */
|
|
181
|
+
targetId: number;
|
|
182
|
+
/** No one performed this heal, so no actor may be credited for it. */
|
|
183
|
+
actorId?: never;
|
|
184
|
+
actorIdentity?: FishNetCombatActorIdentity;
|
|
185
|
+
}
|
|
186
|
+
export type FishNetHealAttribution = FishNetDamageAttribution | "unattributed";
|
|
187
|
+
export interface FishNetCombatHealEvent {
|
|
188
|
+
kind: "heal";
|
|
189
|
+
/** `ApplyDamage_C` is authoritative when its signed damage value is negative. */
|
|
190
|
+
rpc: "ApplyDamage_C" | "Recover_C";
|
|
191
|
+
tick: number;
|
|
192
|
+
payloadBytes: number;
|
|
193
|
+
fields: Record<string, FishNetDecodedValue>;
|
|
194
|
+
/** The healed entity — always known (it's the RPC's objectId). */
|
|
195
|
+
targetId: number;
|
|
196
|
+
/** The inferred healer, when attribution succeeds. Absent when fully unattributed or ambiguous. */
|
|
197
|
+
actorId?: number;
|
|
198
|
+
sourceId?: string;
|
|
199
|
+
sourceLabel?: string;
|
|
200
|
+
value: number;
|
|
201
|
+
/** Semantic style derived from the build-specific Recover_C FloaterSettings. */
|
|
202
|
+
recoveryStyle?: FishNetRecoveryStyle;
|
|
203
|
+
attribution: FishNetHealAttribution;
|
|
204
|
+
activationId?: string;
|
|
205
|
+
candidateActivationIds?: string[];
|
|
206
|
+
actorIdentity?: FishNetCombatActorIdentity;
|
|
207
|
+
}
|
|
208
|
+
export type FishNetCombatEvent = FishNetCombatMonsterIdentityEvent | FishNetCombatActivationEvent | FishNetCombatDamageEvent | FishNetCombatDeathEvent | FishNetCombatStatusEvent | FishNetCombatSummonEvent | FishNetCombatHealEvent | FishNetCombatFullHealEvent;
|
|
209
|
+
/** Converts decoded FishNet RPCs into actor-grouped combat events and summaries. */
|
|
210
|
+
export declare class FishNetCombatTracker {
|
|
211
|
+
private readonly hitGraceTicks;
|
|
212
|
+
private readonly activationMaxAgeTicks;
|
|
213
|
+
private readonly skillLabels;
|
|
214
|
+
private readonly actorIdentityResolver?;
|
|
215
|
+
private readonly healingTraitsResolver?;
|
|
216
|
+
private readonly localActorIdResolver?;
|
|
217
|
+
private readonly monsterCatalog?;
|
|
218
|
+
private readonly bossCatalog?;
|
|
219
|
+
private readonly monsters?;
|
|
220
|
+
private readonly semanticMap?;
|
|
221
|
+
private readonly activations;
|
|
222
|
+
/** Curated boss names are valid only for the lifetime of their network object id. */
|
|
223
|
+
private readonly bossIdentities;
|
|
224
|
+
private readonly activeRegenSources;
|
|
225
|
+
private readonly summonStacks;
|
|
226
|
+
private readonly recentDamageSignatures;
|
|
227
|
+
private recentDamageTick;
|
|
228
|
+
private nextActivation;
|
|
229
|
+
constructor(options?: FishNetCombatTrackerOptions);
|
|
230
|
+
consume(packet: DecodedFishNetPacket): FishNetCombatEvent[];
|
|
231
|
+
reset(): void;
|
|
232
|
+
private monsterIdentity;
|
|
233
|
+
/** Uses the normal monster-identity path so all consumers receive the curated boss name. */
|
|
234
|
+
private bossIdentity;
|
|
235
|
+
/**
|
|
236
|
+
* FishNet may reuse an object id in a later zone. Drop a curated boss name at every object
|
|
237
|
+
* lifetime boundary so the next boss cannot inherit the old one's identity before it casts.
|
|
238
|
+
*/
|
|
239
|
+
private bossIdentityLifecycle;
|
|
240
|
+
/**
|
|
241
|
+
* Turns the observers-facing status broadcast into the same status events as the owner-only
|
|
242
|
+
* `ApplyEffect_T`/`RemoveEffect_T` pair.
|
|
243
|
+
*
|
|
244
|
+
* This feed reports every actor in range rather than just the local player, and it repeats
|
|
245
|
+
* periodically instead of only on change, so it both widens coverage and self-heals after a
|
|
246
|
+
* dropped packet. It reports real remaining time, which the owner-only path can only approximate
|
|
247
|
+
* from the catalog. It carries no level, so the events omit one and consumers keep whatever the
|
|
248
|
+
* owner-only feed last reported.
|
|
249
|
+
*/
|
|
250
|
+
private consumeEffectDisplays;
|
|
251
|
+
/** Recovers only current-build payloads whose domain codec and component position both agree. */
|
|
252
|
+
private recoverAmbiguousCombat;
|
|
253
|
+
/**
|
|
254
|
+
* Turns the skill-icon display feed into status events.
|
|
255
|
+
*
|
|
256
|
+
* `ApplySkillDisplay_O`/`RemoveSkillDisplay_O` announce a *skill* shown on an actor - stances and
|
|
257
|
+
* auras such as `SilentEdge` that the effect feed does not report. Ids do overlap it in places
|
|
258
|
+
* (`FlowState`, `AngelicBlessing` appear on both), and this feed carries no timing at all, so it
|
|
259
|
+
* must never overwrite an expiry the effect feed established. `consumeStatus` enforces that by
|
|
260
|
+
* keeping a known expiry when an event brings none.
|
|
261
|
+
*/
|
|
262
|
+
private consumeSkillDisplay;
|
|
263
|
+
/**
|
|
264
|
+
* Last-resort recovery for a `CalibrateSummons_T` the capture layer could not name. Two paths lose
|
|
265
|
+
* it. An rpcLink whose registration never arrived carries no object id, no hash and no name at all
|
|
266
|
+
* - links are only ever learned from an `objectSpawn`, so if the player object's spawn is missed on
|
|
267
|
+
* a connection, every link on that object is dead for the connection's whole life. A plain
|
|
268
|
+
* targetRpc on an object with no bound components fares little better: wire hash 0 is shared by
|
|
269
|
+
* several behaviours, and with nothing bound there is nothing to eliminate against. Either way the
|
|
270
|
+
* summon tile stays blank until a map change respawns the player object and re-registers its links.
|
|
271
|
+
*
|
|
272
|
+
* This is a heuristic and deliberately narrow. `decodeSummonCalibration` must consume the payload
|
|
273
|
+
* exactly, at least one entry must decode, every skill id must be one the catalog knows, and the
|
|
274
|
+
* result is attributed to the local player because `CalibrateSummons_T` is a targetRpc no other
|
|
275
|
+
* client receives. An empty calibration - the "all summons gone" snapshot - is *not* recovered: it
|
|
276
|
+
* encodes as a single 0x01 byte, which carries no signature worth trusting.
|
|
277
|
+
*/
|
|
278
|
+
private recoverSummons;
|
|
279
|
+
private consumeSummonCalibration;
|
|
280
|
+
/** Diffs one summon snapshot against the actor's last known stacks and emits only the changes. */
|
|
281
|
+
private applySummonSnapshot;
|
|
282
|
+
private consumeSkill;
|
|
283
|
+
private beginAttack;
|
|
284
|
+
private consumeDamage;
|
|
285
|
+
private consumeRecover;
|
|
286
|
+
private createSelfRecovery;
|
|
287
|
+
private eligibleHealActivations;
|
|
288
|
+
private consumeStatus;
|
|
289
|
+
private createActivation;
|
|
290
|
+
private findLifecycleActivation;
|
|
291
|
+
private eligibleActivations;
|
|
292
|
+
private pruneExpired;
|
|
293
|
+
}
|
|
294
|
+
export {};
|
|
295
|
+
//# sourceMappingURL=combat-tracker.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"combat-tracker.d.ts","sourceRoot":"","sources":["../src/combat-tracker.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
import type { FishNetActorIdentityEvent } from "./actor-directory.ts";
|
|
2
|
+
import type { FishNetCombatEvent } from "./combat-tracker.ts";
|
|
3
|
+
export interface FishNetDpsMeterOptions {
|
|
4
|
+
/** Milliseconds without positive player damage before a new encounter. Defaults to 30 seconds. */
|
|
5
|
+
idleGapMs?: number;
|
|
6
|
+
/** Minimum duration used as the DPS divisor. Defaults to one second. */
|
|
7
|
+
minimumDurationMs?: number;
|
|
8
|
+
/** Decay constant for current DPS. Defaults to 2.5 seconds, which matches a 5-second window's smoothing. */
|
|
9
|
+
currentTauSeconds?: number;
|
|
10
|
+
/** Milliseconds to wait for a late identity before showing unidentified damage. Defaults to ten seconds. */
|
|
11
|
+
anonymousIdentityGraceMs?: number;
|
|
12
|
+
/** FishNet ticks per second used for replay records without wall-clock timestamps. Defaults to 30. */
|
|
13
|
+
replayTicksPerSecond?: number;
|
|
14
|
+
personalName?: string;
|
|
15
|
+
personalActorId?: number;
|
|
16
|
+
}
|
|
17
|
+
export interface FishNetDpsSkillRow {
|
|
18
|
+
sourceId: string;
|
|
19
|
+
sourceLabel: string;
|
|
20
|
+
damage: number;
|
|
21
|
+
dps: number;
|
|
22
|
+
contribution: number;
|
|
23
|
+
hits: number;
|
|
24
|
+
criticalHits: number;
|
|
25
|
+
/** Critical hits as a fraction of total hits, when the skill has hit. */
|
|
26
|
+
critRate?: number;
|
|
27
|
+
}
|
|
28
|
+
export interface FishNetDpsTimelinePoint {
|
|
29
|
+
/** Milliseconds elapsed since the encounter began. */
|
|
30
|
+
elapsedMs: number;
|
|
31
|
+
/** Damage dealt during this time bucket. */
|
|
32
|
+
damage: number;
|
|
33
|
+
/** Damage dealt from encounter start through this time bucket. */
|
|
34
|
+
cumulativeDamage: number;
|
|
35
|
+
/** Damage per second for this time bucket. */
|
|
36
|
+
dps: number;
|
|
37
|
+
}
|
|
38
|
+
export interface FishNetDpsActorRow {
|
|
39
|
+
actorIds: number[];
|
|
40
|
+
displayName: string;
|
|
41
|
+
archetype?: number;
|
|
42
|
+
/** Milliseconds used as the DPS divisor and timeline span for this row. */
|
|
43
|
+
durationMs?: number;
|
|
44
|
+
/** Timestamp of this actor's most recent positive damage in the encounter. */
|
|
45
|
+
lastDamageAtMs?: number;
|
|
46
|
+
damage: number;
|
|
47
|
+
dps: number;
|
|
48
|
+
/**
|
|
49
|
+
* Recent damage per second: an exponentially-weighted rate, not a flat window of the last few
|
|
50
|
+
* seconds. It rises the instant a hit lands and then fades, so it never steps discontinuously as
|
|
51
|
+
* a hit ages out and never reads exactly zero after one.
|
|
52
|
+
*/
|
|
53
|
+
currentDps: number;
|
|
54
|
+
contribution: number;
|
|
55
|
+
hits: number;
|
|
56
|
+
criticalHits: number;
|
|
57
|
+
/** Critical hits as a fraction of total hits, when the actor has hit. */
|
|
58
|
+
critRate?: number;
|
|
59
|
+
kills: number;
|
|
60
|
+
/** Number of distinct enemy object IDs damaged by this actor during the encounter. */
|
|
61
|
+
mobsHit: number;
|
|
62
|
+
skills: FishNetDpsSkillRow[];
|
|
63
|
+
timeline: FishNetDpsTimelinePoint[];
|
|
64
|
+
/** True when the row contains damage that has not been verified to a player identity. */
|
|
65
|
+
isUnidentified?: boolean;
|
|
66
|
+
}
|
|
67
|
+
export type FishNetPersonalMatch = "unconfigured" | "missing" | "matched" | "ambiguous";
|
|
68
|
+
export interface FishNetDpsEncounterSnapshot {
|
|
69
|
+
id: string;
|
|
70
|
+
startedAtMs: number;
|
|
71
|
+
lastDamageAtMs: number;
|
|
72
|
+
endedAtMs?: number;
|
|
73
|
+
durationMs: number;
|
|
74
|
+
totalDamage: number;
|
|
75
|
+
partyDps: number;
|
|
76
|
+
partyCurrentDps: number;
|
|
77
|
+
actors: FishNetDpsActorRow[];
|
|
78
|
+
personalName: string;
|
|
79
|
+
personalMatch: FishNetPersonalMatch;
|
|
80
|
+
personal?: FishNetDpsActorRow;
|
|
81
|
+
}
|
|
82
|
+
/** Aggregates structured FishNet identity and combat events into encounter DPS summaries. */
|
|
83
|
+
export declare class FishNetDpsMeter {
|
|
84
|
+
private readonly idleGapMs;
|
|
85
|
+
private readonly minimumDurationMs;
|
|
86
|
+
private readonly currentTauSeconds;
|
|
87
|
+
private readonly anonymousIdentityGraceMs;
|
|
88
|
+
private readonly replayTicksPerSecond;
|
|
89
|
+
private readonly finished;
|
|
90
|
+
private readonly identities;
|
|
91
|
+
private current?;
|
|
92
|
+
private nextEncounter;
|
|
93
|
+
private personalName;
|
|
94
|
+
private personalActorId?;
|
|
95
|
+
constructor(options?: FishNetDpsMeterOptions);
|
|
96
|
+
consumeIdentity(event: FishNetActorIdentityEvent, observedAtMs: number): void;
|
|
97
|
+
consumeCombat(event: FishNetCombatEvent, observedAtMs: number): void;
|
|
98
|
+
/** Finalizes an idle encounter. Calling this regularly keeps live status current. */
|
|
99
|
+
advance(observedAtMs: number): void;
|
|
100
|
+
/** Finalizes the current encounter; the next qualifying hit starts a new one. */
|
|
101
|
+
reset(observedAtMs?: number): void;
|
|
102
|
+
/** Discards all encounter statistics while retaining actor and personal configuration. */
|
|
103
|
+
clearEncounters(): void;
|
|
104
|
+
setPersonalName(name: string): void;
|
|
105
|
+
getPersonalName(): string;
|
|
106
|
+
setPersonalActorId(actorId: number | undefined): void;
|
|
107
|
+
getPersonalActorId(): number | undefined;
|
|
108
|
+
getSnapshots(nowMs?: number): FishNetDpsEncounterSnapshot[];
|
|
109
|
+
getLatestSnapshot(nowMs?: number): FishNetDpsEncounterSnapshot | undefined;
|
|
110
|
+
/** Maps a FishNet tick onto replay time relative to a caller-selected origin. */
|
|
111
|
+
replayTimeMs(tick: number, originTick: number, originTimeMs?: number): number;
|
|
112
|
+
private finishCurrent;
|
|
113
|
+
private snapshot;
|
|
114
|
+
}
|
|
115
|
+
//# sourceMappingURL=dps-meter.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"dps-meter.d.ts","sourceRoot":"","sources":["../src/dps-meter.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
interface FishNetEffectDisplay {
|
|
2
|
+
statusId: string;
|
|
3
|
+
/** Seconds left on the status. */
|
|
4
|
+
remainingSeconds?: number;
|
|
5
|
+
stacks: number;
|
|
6
|
+
/** Server-declared stack ceiling; 0 where the status declares none. */
|
|
7
|
+
maxStacks: number;
|
|
8
|
+
}
|
|
9
|
+
export interface FishNetEffectDisplayBatch {
|
|
10
|
+
applies: FishNetEffectDisplay[];
|
|
11
|
+
removes: string[];
|
|
12
|
+
}
|
|
13
|
+
/** Decodes the batched status snapshot carried by `ApplyEffectDisplays_O`. */
|
|
14
|
+
export declare function decodeEffectDisplays(payload: Buffer): FishNetEffectDisplayBatch;
|
|
15
|
+
export {};
|
|
16
|
+
//# sourceMappingURL=effect-display.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"effect-display.d.ts","sourceRoot":"","sources":["../src/effect-display.ts"],"names":[],"mappings":"AAGA,UAAU,oBAAoB;IAC5B,QAAQ,EAAE,MAAM,CAAC;IACjB,kCAAkC;IAClC,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,MAAM,EAAE,MAAM,CAAC;IACf,uEAAuE;IACvE,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,yBAAyB;IACxC,OAAO,EAAE,oBAAoB,EAAE,CAAC;IAChC,OAAO,EAAE,MAAM,EAAE,CAAC;CACnB;AAED,8EAA8E;AAC9E,wBAAgB,oBAAoB,CAAC,OAAO,EAAE,MAAM,GAAG,yBAAyB,CA2C/E"}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import type { ReadModelDomain } from "@kar-mi/spirit-vale-tools-sqlite";
|
|
2
|
+
/** Bump whenever the tables below change; only combat is dropped and re-indexed. */
|
|
3
|
+
export declare const COMBAT_DOMAIN_VERSION = 7;
|
|
4
|
+
export declare const COMBAT_DOMAIN_NAME = "combat";
|
|
5
|
+
export declare function createCombatDomain(): ReadModelDomain;
|
|
6
|
+
//# sourceMappingURL=domain.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"domain.d.ts","sourceRoot":"","sources":["../../src/history/domain.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,kCAAkC,CAAC;AAExE,oFAAoF;AACpF,eAAO,MAAM,qBAAqB,IAAI,CAAC;AACvC,eAAO,MAAM,kBAAkB,WAAW,CAAC;AA0J3C,wBAAgB,kBAAkB,IAAI,eAAe,CAwBpD"}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { IndexStreamResult, ReadModel } from "@kar-mi/spirit-vale-tools-sqlite";
|
|
2
|
+
import type { DamageReducerOptions } from "../reducers/damage.ts";
|
|
3
|
+
export interface IndexCombatStreamOptions extends Pick<DamageReducerOptions, "idleGapMs" | "currentTauSeconds"> {
|
|
4
|
+
sessionId: string;
|
|
5
|
+
sourcePath: string;
|
|
6
|
+
batchBytes?: number;
|
|
7
|
+
/** Closes the encounter still in progress at the end of the log. */
|
|
8
|
+
finalize?: boolean;
|
|
9
|
+
}
|
|
10
|
+
/** Indexes one combat log into the read model. */
|
|
11
|
+
export declare function indexCombatStream(model: ReadModel, options: IndexCombatStreamOptions): Promise<IndexStreamResult>;
|
|
12
|
+
//# sourceMappingURL=importer.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"importer.d.ts","sourceRoot":"","sources":["../../src/history/importer.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAsB,iBAAiB,EAAE,SAAS,EAAE,MAAM,kCAAkC,CAAC;AAKzG,OAAO,KAAK,EAAkB,oBAAoB,EAAmD,MAAM,uBAAuB,CAAC;AAcnI,MAAM,WAAW,wBAAyB,SAAQ,IAAI,CAAC,oBAAoB,EAAE,WAAW,GAAG,mBAAmB,CAAC;IAC7G,SAAS,EAAE,MAAM,CAAC;IAClB,UAAU,EAAE,MAAM,CAAC;IACnB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,oEAAoE;IACpE,QAAQ,CAAC,EAAE,OAAO,CAAC;CACpB;AAED,kDAAkD;AAClD,wBAAsB,iBAAiB,CAAC,KAAK,EAAE,SAAS,EAAE,OAAO,EAAE,wBAAwB,GAAG,OAAO,CAAC,iBAAiB,CAAC,CA0GvH"}
|