@kar-mi/spirit-vale-tools-combat 3.3.5 → 3.3.6
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/dist/combat-tracker.d.ts +39 -20
- package/dist/combat-tracker.d.ts.map +1 -1
- package/dist/index.js +138 -77
- package/package.json +9 -9
- package/dist/dps-meter.d.ts +0 -115
- package/dist/dps-meter.d.ts.map +0 -1
- package/dist/summon-calibration.d.ts +0 -8
- package/dist/summon-calibration.d.ts.map +0 -1
package/dist/combat-tracker.d.ts
CHANGED
|
@@ -37,7 +37,7 @@ export interface FishNetCombatTrackerOptions {
|
|
|
37
37
|
actorIdentityResolver?: (actorId: number) => FishNetCombatActorIdentity | undefined;
|
|
38
38
|
/** Resolves healing mechanics for actors whose local character build is visible. */
|
|
39
39
|
healingTraitsResolver?: (actorId: number) => FishNetHealingTraits | undefined;
|
|
40
|
-
/**
|
|
40
|
+
/** @deprecated Summon packets now require a verified object id; retained as an unused compatibility option. */
|
|
41
41
|
localActorIdResolver?: () => number | undefined;
|
|
42
42
|
/** Names monsters seen spawning, emitting identity lifecycle events keyed by network object id. */
|
|
43
43
|
monsterCatalog?: FishNetMonsterCatalog;
|
|
@@ -143,7 +143,8 @@ export interface FishNetCombatDeathEvent {
|
|
|
143
143
|
}
|
|
144
144
|
export interface FishNetCombatStatusEvent {
|
|
145
145
|
kind: "status";
|
|
146
|
-
|
|
146
|
+
/** `LoadCharacter_T` is a login-restore fallback: an effect already active when the client connects, from the character save's own snapshot rather than a live apply. */
|
|
147
|
+
rpc: "ApplyEffect_T" | "RemoveEffect_T" | "ApplyEffectDisplays_O" | "ApplySkillDisplay_O" | "RemoveSkillDisplay_O" | "LoadCharacter_T";
|
|
147
148
|
tick: number;
|
|
148
149
|
payloadBytes: number;
|
|
149
150
|
fields: Record<string, FishNetDecodedValue>;
|
|
@@ -152,22 +153,21 @@ export interface FishNetCombatStatusEvent {
|
|
|
152
153
|
/** Absent on `ApplyEffectDisplays_O`, which carries no level; consumers keep the last known one. */
|
|
153
154
|
level?: number;
|
|
154
155
|
action: "applied" | "removed";
|
|
155
|
-
/** Server-reported time left, from `ApplyEffectDisplays_O` only. */
|
|
156
|
+
/** Server-reported time left, from `ApplyEffectDisplays_O`/`LoadCharacter_T` only. */
|
|
156
157
|
remainingSeconds?: number;
|
|
157
158
|
stacks?: number;
|
|
158
159
|
actorIdentity?: FishNetCombatActorIdentity;
|
|
159
160
|
}
|
|
160
161
|
export interface FishNetCombatSummonEvent {
|
|
161
162
|
kind: "summon";
|
|
162
|
-
|
|
163
|
+
/** `SummonSkillSync` is a login-restore fallback: one active summon reported via SyncType rather than the batch RPC. */
|
|
164
|
+
rpc: "CalibrateSummons_T" | "SummonSkillSync";
|
|
163
165
|
tick: number;
|
|
164
166
|
payloadBytes: number;
|
|
165
167
|
fields: Record<string, FishNetDecodedValue>;
|
|
166
168
|
actorId: number;
|
|
167
169
|
skillId: string;
|
|
168
170
|
stacks: number;
|
|
169
|
-
/** True when the packet arrived unnamed and was recovered heuristically. See `recoverSummons`. */
|
|
170
|
-
recovered?: boolean;
|
|
171
171
|
actorIdentity?: FishNetCombatActorIdentity;
|
|
172
172
|
}
|
|
173
173
|
/** A `PlayerController.FullHeal_C`, which restores an actor outright. */
|
|
@@ -213,7 +213,6 @@ export declare class FishNetCombatTracker {
|
|
|
213
213
|
private readonly skillLabels;
|
|
214
214
|
private readonly actorIdentityResolver?;
|
|
215
215
|
private readonly healingTraitsResolver?;
|
|
216
|
-
private readonly localActorIdResolver?;
|
|
217
216
|
private readonly monsterCatalog?;
|
|
218
217
|
private readonly bossCatalog?;
|
|
219
218
|
private readonly monsters?;
|
|
@@ -223,6 +222,10 @@ export declare class FishNetCombatTracker {
|
|
|
223
222
|
private readonly bossIdentities;
|
|
224
223
|
private readonly activeRegenSources;
|
|
225
224
|
private readonly summonStacks;
|
|
225
|
+
/** Pending `SummonSkillSync`/`SummonerSync` state per summoned object's own network id, until both are known. */
|
|
226
|
+
private readonly summonSkillSyncState;
|
|
227
|
+
/** Actors for whom a `CalibrateSummons_T` snapshot has been seen; the `SummonSkillSync` fallback stops touching their stacks from then on, since the batch RPC now fully owns them. */
|
|
228
|
+
private readonly authoritativeSummonActors;
|
|
226
229
|
private readonly recentDamageSignatures;
|
|
227
230
|
private recentDamageTick;
|
|
228
231
|
private nextActivation;
|
|
@@ -261,21 +264,37 @@ export declare class FishNetCombatTracker {
|
|
|
261
264
|
*/
|
|
262
265
|
private consumeSkillDisplay;
|
|
263
266
|
/**
|
|
264
|
-
*
|
|
265
|
-
*
|
|
266
|
-
*
|
|
267
|
-
*
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
267
|
+
* Login restores whichever effects were active at save time through `PlayerSave.LoadCharacter_T`'s
|
|
268
|
+
* own `State.Effects` snapshot - no other packet reports them. Unlike `ApplyEffectDisplays_O`
|
|
269
|
+
* (which repeats and self-heals), this fires once per login, so a status it lists that never gets
|
|
270
|
+
* an explicit apply afterward would otherwise never appear at all.
|
|
271
|
+
*/
|
|
272
|
+
private consumeLoginEffects;
|
|
273
|
+
/**
|
|
274
|
+
* Login restores an existing summon through `SummonSkillSync` (a `SummoningComponent` SyncType),
|
|
275
|
+
* not `CalibrateSummons_T` - that RPC only fires on a later change, so without this the overlay
|
|
276
|
+
* shows nothing for a summon that was already active when the client connected.
|
|
271
277
|
*
|
|
272
|
-
*
|
|
273
|
-
*
|
|
274
|
-
*
|
|
275
|
-
*
|
|
276
|
-
*
|
|
278
|
+
* `SummonSkillSync` lives on the summoned object's own `SummoningComponent`, not the owner's - the
|
|
279
|
+
* packet's `objectId` names the summon (e.g. one skeleton), not the actor to credit. `SummonerSync`,
|
|
280
|
+
* a sibling SyncType on that same component, is the owner reference. FishNet sends every dirty
|
|
281
|
+
* SyncType on login together, so both usually arrive in the same packet, but not always in the same
|
|
282
|
+
* order, and only a change re-sends a SyncType afterward - so `summonSkillSyncState` accumulates
|
|
283
|
+
* whichever field arrives first, per summon object, until both are known. Each summon object
|
|
284
|
+
* contributes its own +1 once (never more), so two objects reporting the same skill both count -
|
|
285
|
+
* this is per-object state, not a "have I seen this skill" flag.
|
|
286
|
+
*/
|
|
287
|
+
private consumeSummonSkillSync;
|
|
288
|
+
/**
|
|
289
|
+
* Clears a summon object's `SummonSkillSync` bookkeeping on despawn (or a respawn reusing the same
|
|
290
|
+
* network object id), so a later reused id starts fresh rather than inheriting a stale owner/skill.
|
|
291
|
+
* Also corrects the stack count for an object this tracker itself counted in, unless a
|
|
292
|
+
* `CalibrateSummons_T` snapshot has since taken over that actor's stacks (which will correct the
|
|
293
|
+
* count on its own).
|
|
277
294
|
*/
|
|
278
|
-
private
|
|
295
|
+
private consumeSummonObjectLifecycle;
|
|
296
|
+
/** Adjusts one actor's stack count for one skill by `delta` and reports the resulting total. */
|
|
297
|
+
private applySummonDelta;
|
|
279
298
|
private consumeSummonCalibration;
|
|
280
299
|
/** Diffs one summon snapshot against the actor's last known stacks and emits only the changes. */
|
|
281
300
|
private applySummonSnapshot;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"combat-tracker.d.ts","sourceRoot":"","sources":["../src/combat-tracker.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAE,oBAAoB,EAAE,mBAAmB,EAAiC,oBAAoB,EAAE,kBAAkB,EAAE,MAAM,mCAAmC,CAAC;AAG5K,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,kCAAkC,CAAC;
|
|
1
|
+
{"version":3,"file":"combat-tracker.d.ts","sourceRoot":"","sources":["../src/combat-tracker.ts"],"names":[],"mappings":"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"}
|
package/dist/index.js
CHANGED
|
@@ -6,7 +6,7 @@ import {
|
|
|
6
6
|
FishNetMonsterDirectory,
|
|
7
7
|
loadBundledFishNetSemanticMap
|
|
8
8
|
} from "@kar-mi/spirit-vale-tools-capture";
|
|
9
|
-
import { readSignedPackedWhole as
|
|
9
|
+
import { readSignedPackedWhole as readSignedPackedWhole2 } from "@kar-mi/spirit-vale-tools-capture/wire-reader";
|
|
10
10
|
import { loadBundledSkillCatalog } from "@kar-mi/spirit-vale-tools-skills";
|
|
11
11
|
|
|
12
12
|
// src/effect-display.ts
|
|
@@ -69,51 +69,6 @@ function readBoolean(buffer, offset, description) {
|
|
|
69
69
|
return { value: value === 1, nextOffset: offset + 1 };
|
|
70
70
|
}
|
|
71
71
|
|
|
72
|
-
// src/summon-calibration.ts
|
|
73
|
-
import { FishNetProtocolError as FishNetProtocolError2 } from "@kar-mi/spirit-vale-tools-capture";
|
|
74
|
-
import { checkedEnd as checkedEnd2, readSignedPackedWhole as readSignedPackedWhole2, requireBytes as requireBytes2 } from "@kar-mi/spirit-vale-tools-capture/wire-reader";
|
|
75
|
-
function decodeSummonCalibration(payload) {
|
|
76
|
-
const count = readSignedPackedWhole2(payload, 0);
|
|
77
|
-
if (count.value === -1) {
|
|
78
|
-
requireComplete(payload, count.nextOffset);
|
|
79
|
-
return [];
|
|
80
|
-
}
|
|
81
|
-
if (count.value < 0)
|
|
82
|
-
throw new FishNetProtocolError2("invalid summon calibration array length");
|
|
83
|
-
const entries = [];
|
|
84
|
-
let offset = count.nextOffset;
|
|
85
|
-
for (let index = 0;index < count.value; index += 1) {
|
|
86
|
-
const skill = readString2(payload, offset);
|
|
87
|
-
offset = skill.nextOffset;
|
|
88
|
-
const primary = readBoolean2(payload, offset, "summon primary flag");
|
|
89
|
-
offset = primary.nextOffset;
|
|
90
|
-
const mounted = readBoolean2(payload, offset, "summon mounted flag");
|
|
91
|
-
offset = mounted.nextOffset;
|
|
92
|
-
entries.push({ skillId: skill.value, isPrimary: primary.value, isMountedSummon: mounted.value });
|
|
93
|
-
}
|
|
94
|
-
requireComplete(payload, offset);
|
|
95
|
-
return entries;
|
|
96
|
-
}
|
|
97
|
-
function readString2(buffer, offset) {
|
|
98
|
-
const length = readSignedPackedWhole2(buffer, offset);
|
|
99
|
-
if (length.value < 0)
|
|
100
|
-
throw new FishNetProtocolError2("summon skill id must not be null");
|
|
101
|
-
const nextOffset = checkedEnd2(buffer, length.nextOffset, length.value);
|
|
102
|
-
return { value: buffer.toString("utf8", length.nextOffset, nextOffset), nextOffset };
|
|
103
|
-
}
|
|
104
|
-
function readBoolean2(buffer, offset, description) {
|
|
105
|
-
requireBytes2(buffer, offset, 1, description);
|
|
106
|
-
const value = buffer[offset];
|
|
107
|
-
if (value !== 0 && value !== 1)
|
|
108
|
-
throw new FishNetProtocolError2(`invalid ${description}`);
|
|
109
|
-
return { value: value === 1, nextOffset: offset + 1 };
|
|
110
|
-
}
|
|
111
|
-
function requireComplete(buffer, offset) {
|
|
112
|
-
if (offset !== buffer.length) {
|
|
113
|
-
throw new FishNetProtocolError2(`summon calibration left ${buffer.length - offset} undecoded bytes`);
|
|
114
|
-
}
|
|
115
|
-
}
|
|
116
|
-
|
|
117
72
|
// src/combat-tracker.ts
|
|
118
73
|
var HIT_RESULTS = {
|
|
119
74
|
0: "normal",
|
|
@@ -125,7 +80,6 @@ var HIT_RESULTS = {
|
|
|
125
80
|
var SKILL_RPC_NAMES = new Set(["CastBegin_C", "AutoCast_C", "ToggleBegin_C", "CastComplete_C", "CastInterrupt_C", "CastCancel_C"]);
|
|
126
81
|
var CURRENT_STATUS_COMPONENT_INDICES = new Set([5, 6]);
|
|
127
82
|
var CURRENT_HEALTH_COMPONENT_INDICES = new Set([2, 3]);
|
|
128
|
-
var MAX_RECOVERED_SUMMON_BYTES = 256;
|
|
129
83
|
var HEALING_SKILL_IDS = new Set(["Heal", "HighHeal", "FieldHealing"]);
|
|
130
84
|
var REGEN_SKILL_IDS = new Set(["HealAll", "Sanctuary", "GuardianBond", "SanctuaryField"]);
|
|
131
85
|
var REGEN_STATUS_ID = "Regeneration";
|
|
@@ -136,7 +90,6 @@ class FishNetCombatTracker {
|
|
|
136
90
|
skillLabels;
|
|
137
91
|
actorIdentityResolver;
|
|
138
92
|
healingTraitsResolver;
|
|
139
|
-
localActorIdResolver;
|
|
140
93
|
monsterCatalog;
|
|
141
94
|
bossCatalog;
|
|
142
95
|
monsters;
|
|
@@ -145,6 +98,8 @@ class FishNetCombatTracker {
|
|
|
145
98
|
bossIdentities = new Map;
|
|
146
99
|
activeRegenSources = new Map;
|
|
147
100
|
summonStacks = new Map;
|
|
101
|
+
summonSkillSyncState = new Map;
|
|
102
|
+
authoritativeSummonActors = new Set;
|
|
148
103
|
recentDamageSignatures = new Set;
|
|
149
104
|
recentDamageTick;
|
|
150
105
|
nextActivation = 1;
|
|
@@ -168,7 +123,6 @@ class FishNetCombatTracker {
|
|
|
168
123
|
this.skillLabels.set(value, label);
|
|
169
124
|
this.actorIdentityResolver = options.actorIdentityResolver;
|
|
170
125
|
this.healingTraitsResolver = options.healingTraitsResolver;
|
|
171
|
-
this.localActorIdResolver = options.localActorIdResolver;
|
|
172
126
|
this.monsterCatalog = options.monsterCatalog;
|
|
173
127
|
if (options.monsterCatalog)
|
|
174
128
|
this.monsters = new FishNetMonsterDirectory(options.monsterCatalog);
|
|
@@ -177,6 +131,7 @@ class FishNetCombatTracker {
|
|
|
177
131
|
consume(packet) {
|
|
178
132
|
const monsterIdentity = this.monsterIdentity(packet.tick, this.monsters?.consume(packet));
|
|
179
133
|
const bossLifecycle = this.bossIdentityLifecycle(packet);
|
|
134
|
+
const summonLifecycle = this.consumeSummonObjectLifecycle(packet);
|
|
180
135
|
if (packet.packetName === "authenticated" || packet.packetName === "disconnect") {
|
|
181
136
|
this.reset();
|
|
182
137
|
return uniqueMonsterIdentityEvents([
|
|
@@ -191,11 +146,15 @@ class FishNetCombatTracker {
|
|
|
191
146
|
}
|
|
192
147
|
const events = [
|
|
193
148
|
...monsterIdentity ? [monsterIdentity] : [],
|
|
194
|
-
...bossLifecycle ? [bossLifecycle] : []
|
|
149
|
+
...bossLifecycle ? [bossLifecycle] : [],
|
|
150
|
+
...summonLifecycle ? [summonLifecycle] : []
|
|
195
151
|
];
|
|
152
|
+
if (packet.packetName === "syncType" && matchesBehaviour(packet, "SummoningComponent")) {
|
|
153
|
+
events.push(...this.consumeSummonSkillSync(packet));
|
|
154
|
+
return events;
|
|
155
|
+
}
|
|
196
156
|
if (packet.objectId === undefined || !packet.rpcName) {
|
|
197
157
|
events.push(...this.recoverAmbiguousCombat(packet));
|
|
198
|
-
events.push(...this.recoverSummons(packet));
|
|
199
158
|
return events;
|
|
200
159
|
}
|
|
201
160
|
if (SKILL_RPC_NAMES.has(packet.rpcName) && matchesBehaviour(packet, "SkillsComponent")) {
|
|
@@ -255,10 +214,14 @@ class FishNetCombatTracker {
|
|
|
255
214
|
}
|
|
256
215
|
return events;
|
|
257
216
|
}
|
|
258
|
-
if (packet.rpcName === "CalibrateSummons_T" &&
|
|
217
|
+
if (packet.rpcName === "CalibrateSummons_T" && packet.rpcResolution === "verified" && packet.networkBehaviourType === "SummoningComponent") {
|
|
259
218
|
events.push(...this.consumeSummonCalibration(packet));
|
|
260
219
|
return events;
|
|
261
220
|
}
|
|
221
|
+
if (packet.rpcName === "LoadCharacter_T" && packet.rpcResolution === "verified" && packet.networkBehaviourType === "PlayerSave") {
|
|
222
|
+
events.push(...this.consumeLoginEffects(packet));
|
|
223
|
+
return events;
|
|
224
|
+
}
|
|
262
225
|
return events;
|
|
263
226
|
}
|
|
264
227
|
reset() {
|
|
@@ -266,6 +229,8 @@ class FishNetCombatTracker {
|
|
|
266
229
|
this.bossIdentities.clear();
|
|
267
230
|
this.activeRegenSources.clear();
|
|
268
231
|
this.summonStacks.clear();
|
|
232
|
+
this.summonSkillSyncState.clear();
|
|
233
|
+
this.authoritativeSummonActors.clear();
|
|
269
234
|
this.recentDamageSignatures.clear();
|
|
270
235
|
this.recentDamageTick = undefined;
|
|
271
236
|
this.monsters?.reset();
|
|
@@ -359,7 +324,7 @@ class FishNetCombatTracker {
|
|
|
359
324
|
return [];
|
|
360
325
|
let amount;
|
|
361
326
|
try {
|
|
362
|
-
amount =
|
|
327
|
+
amount = readSignedPackedWhole2(packet.payload, 0);
|
|
363
328
|
} catch {
|
|
364
329
|
return [];
|
|
365
330
|
}
|
|
@@ -397,35 +362,92 @@ class FishNetCombatTracker {
|
|
|
397
362
|
actorIdentity: this.actorIdentityResolver?.(actorId)
|
|
398
363
|
};
|
|
399
364
|
}
|
|
400
|
-
|
|
401
|
-
|
|
365
|
+
consumeLoginEffects(packet) {
|
|
366
|
+
const entries = decodedLoginEffects(packet);
|
|
367
|
+
if (!entries)
|
|
402
368
|
return [];
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
369
|
+
const actorId = packet.objectId;
|
|
370
|
+
return entries.map((entry) => ({
|
|
371
|
+
kind: "status",
|
|
372
|
+
rpc: "LoadCharacter_T",
|
|
373
|
+
tick: packet.tick,
|
|
374
|
+
payloadBytes: packet.payload.length,
|
|
375
|
+
fields: {
|
|
376
|
+
Id: entry.statusId,
|
|
377
|
+
Level: entry.level,
|
|
378
|
+
Duration: entry.remainingSeconds ?? null,
|
|
379
|
+
Stacks: entry.stacks
|
|
380
|
+
},
|
|
381
|
+
actorId,
|
|
382
|
+
statusId: entry.statusId,
|
|
383
|
+
level: entry.level,
|
|
384
|
+
action: "applied",
|
|
385
|
+
...entry.remainingSeconds === undefined ? {} : { remainingSeconds: entry.remainingSeconds },
|
|
386
|
+
stacks: entry.stacks,
|
|
387
|
+
actorIdentity: this.actorIdentityResolver?.(actorId)
|
|
388
|
+
}));
|
|
389
|
+
}
|
|
390
|
+
consumeSummonSkillSync(packet) {
|
|
391
|
+
const summonObjectId = packet.objectId;
|
|
392
|
+
const summonerSync = packet.syncEntries?.find((candidate) => candidate.name === "SummonerSync")?.fields.find((entryField) => entryField.name === "SummonerSync")?.value;
|
|
393
|
+
const skillId = packet.syncEntries?.find((candidate) => candidate.name === "SummonSkillSync")?.fields.find((entryField) => entryField.name === "SkillId")?.value;
|
|
394
|
+
if (typeof summonerSync !== "number" && typeof skillId !== "string")
|
|
406
395
|
return [];
|
|
407
|
-
const
|
|
408
|
-
if (
|
|
396
|
+
const state = this.summonSkillSyncState.get(summonObjectId) ?? { counted: false };
|
|
397
|
+
if (typeof summonerSync === "number")
|
|
398
|
+
state.ownerActorId = summonerSync;
|
|
399
|
+
if (typeof skillId === "string")
|
|
400
|
+
state.skillId = skillId;
|
|
401
|
+
this.summonSkillSyncState.set(summonObjectId, state);
|
|
402
|
+
if (state.counted || state.ownerActorId === undefined || state.skillId === undefined)
|
|
409
403
|
return [];
|
|
410
|
-
|
|
411
|
-
try {
|
|
412
|
-
entries = decodeSummonCalibration(packet.payload);
|
|
413
|
-
} catch {
|
|
404
|
+
if (this.authoritativeSummonActors.has(state.ownerActorId))
|
|
414
405
|
return [];
|
|
406
|
+
state.counted = true;
|
|
407
|
+
return [this.applySummonDelta(state.ownerActorId, state.skillId, 1, packet, "SummonSkillSync")];
|
|
408
|
+
}
|
|
409
|
+
consumeSummonObjectLifecycle(packet) {
|
|
410
|
+
if (packet.packetName !== "objectDespawn" && packet.packetName !== "objectSpawn" || packet.objectId === undefined) {
|
|
411
|
+
return;
|
|
415
412
|
}
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
413
|
+
const state = this.summonSkillSyncState.get(packet.objectId);
|
|
414
|
+
if (!state)
|
|
415
|
+
return;
|
|
416
|
+
this.summonSkillSyncState.delete(packet.objectId);
|
|
417
|
+
if (!state.counted || state.ownerActorId === undefined || state.skillId === undefined)
|
|
418
|
+
return;
|
|
419
|
+
if (this.authoritativeSummonActors.has(state.ownerActorId))
|
|
420
|
+
return;
|
|
421
|
+
return this.applySummonDelta(state.ownerActorId, state.skillId, -1, packet, "SummonSkillSync");
|
|
422
|
+
}
|
|
423
|
+
applySummonDelta(actorId, skillId, delta, packet, rpc) {
|
|
424
|
+
const stacks = new Map(this.summonStacks.get(actorId));
|
|
425
|
+
const nextCount = Math.max(0, (stacks.get(skillId) ?? 0) + delta);
|
|
426
|
+
if (nextCount === 0)
|
|
427
|
+
stacks.delete(skillId);
|
|
428
|
+
else
|
|
429
|
+
stacks.set(skillId, nextCount);
|
|
430
|
+
if (stacks.size === 0)
|
|
431
|
+
this.summonStacks.delete(actorId);
|
|
432
|
+
else
|
|
433
|
+
this.summonStacks.set(actorId, stacks);
|
|
434
|
+
return {
|
|
435
|
+
kind: "summon",
|
|
436
|
+
rpc,
|
|
437
|
+
tick: packet.tick,
|
|
438
|
+
payloadBytes: packet.payload.length,
|
|
439
|
+
fields: decodedFieldRecord(packet),
|
|
440
|
+
actorId,
|
|
441
|
+
skillId,
|
|
442
|
+
stacks: nextCount,
|
|
443
|
+
actorIdentity: this.actorIdentityResolver?.(actorId)
|
|
444
|
+
};
|
|
421
445
|
}
|
|
422
446
|
consumeSummonCalibration(packet) {
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
entries = decodeSummonCalibration(packet.payload);
|
|
426
|
-
} catch {
|
|
447
|
+
const entries = decodedSummonCalibration(packet);
|
|
448
|
+
if (!entries)
|
|
427
449
|
return [];
|
|
428
|
-
|
|
450
|
+
this.authoritativeSummonActors.add(packet.objectId);
|
|
429
451
|
return this.applySummonSnapshot(packet.objectId, packet, entries);
|
|
430
452
|
}
|
|
431
453
|
applySummonSnapshot(actorId, packet, entries) {
|
|
@@ -446,7 +468,7 @@ class FishNetCombatTracker {
|
|
|
446
468
|
rpc: "CalibrateSummons_T",
|
|
447
469
|
tick: packet.tick,
|
|
448
470
|
payloadBytes: packet.payload.length,
|
|
449
|
-
fields:
|
|
471
|
+
fields: decodedFieldRecord(packet),
|
|
450
472
|
actorId,
|
|
451
473
|
skillId,
|
|
452
474
|
stacks: current.get(skillId) ?? 0,
|
|
@@ -811,6 +833,45 @@ function field(packet, name) {
|
|
|
811
833
|
function decodedFieldRecord(packet) {
|
|
812
834
|
return Object.fromEntries(packet.decodedFields?.map(({ name, value }) => [name, value]) ?? []);
|
|
813
835
|
}
|
|
836
|
+
function decodedSummonCalibration(packet) {
|
|
837
|
+
if (packet.undecodedPayload && packet.undecodedPayload.length > 0)
|
|
838
|
+
return;
|
|
839
|
+
const length = numberField(packet, "data.length");
|
|
840
|
+
if (length === undefined || !Number.isInteger(length) || length < 0)
|
|
841
|
+
return;
|
|
842
|
+
const entries = [];
|
|
843
|
+
for (let index = 0;index < length; index += 1) {
|
|
844
|
+
const skillId = stringField(packet, `data[${index}].SkillId`);
|
|
845
|
+
const level = numberField(packet, `data[${index}].Level`);
|
|
846
|
+
if (skillId === undefined || level === undefined || !Number.isInteger(level) || level < 0)
|
|
847
|
+
return;
|
|
848
|
+
entries.push({ skillId, level });
|
|
849
|
+
}
|
|
850
|
+
return entries;
|
|
851
|
+
}
|
|
852
|
+
function decodedLoginEffects(packet) {
|
|
853
|
+
if (packet.undecodedPayload && packet.undecodedPayload.length > 0)
|
|
854
|
+
return;
|
|
855
|
+
const length = numberField(packet, "data.State.Effects.length");
|
|
856
|
+
if (length === undefined || !Number.isInteger(length) || length < 0)
|
|
857
|
+
return;
|
|
858
|
+
const entries = [];
|
|
859
|
+
for (let index = 0;index < length; index += 1) {
|
|
860
|
+
const statusId = stringField(packet, `data.State.Effects[${index}].Id`);
|
|
861
|
+
const level = numberField(packet, `data.State.Effects[${index}].Level`);
|
|
862
|
+
const duration = numberField(packet, `data.State.Effects[${index}].Duration`);
|
|
863
|
+
const stacks = numberField(packet, `data.State.Effects[${index}].Stacks`);
|
|
864
|
+
if (statusId === undefined || level === undefined || !Number.isInteger(level) || level < 0 || duration === undefined || stacks === undefined || !Number.isInteger(stacks) || stacks < 0)
|
|
865
|
+
return;
|
|
866
|
+
entries.push({
|
|
867
|
+
statusId,
|
|
868
|
+
level,
|
|
869
|
+
...duration < 0 || !Number.isFinite(duration) ? {} : { remainingSeconds: duration },
|
|
870
|
+
stacks
|
|
871
|
+
});
|
|
872
|
+
}
|
|
873
|
+
return entries;
|
|
874
|
+
}
|
|
814
875
|
function matchesBehaviour(packet, expected) {
|
|
815
876
|
return packet.networkBehaviourType === undefined || packet.networkBehaviourType === expected;
|
|
816
877
|
}
|
|
@@ -909,7 +970,7 @@ function createBossCatalog(entries) {
|
|
|
909
970
|
var CURRENT_BOSS_SKILL_NAMES = createBossCatalog({});
|
|
910
971
|
// src/actor-directory.ts
|
|
911
972
|
import { characterDataParameter, decodeFieldRun, loadBundledFishNetRpcMap } from "@kar-mi/spirit-vale-tools-capture";
|
|
912
|
-
import { readSignedPackedWhole as
|
|
973
|
+
import { readSignedPackedWhole as readSignedPackedWhole3 } from "@kar-mi/spirit-vale-tools-capture/wire-reader";
|
|
913
974
|
|
|
914
975
|
class FishNetActorDirectory {
|
|
915
976
|
options;
|
|
@@ -1313,7 +1374,7 @@ function decodeCharacterDataName(payload) {
|
|
|
1313
1374
|
try {
|
|
1314
1375
|
let offset = 0;
|
|
1315
1376
|
if (skipEnum)
|
|
1316
|
-
offset =
|
|
1377
|
+
offset = readSignedPackedWhole3(payload, offset).nextOffset;
|
|
1317
1378
|
const run = decodeFieldRun(payload, [characterNameParameter()], offset);
|
|
1318
1379
|
const values = new Map(run.fields.map((field2) => [field2.name, field2.value]));
|
|
1319
1380
|
const uid = values.get("data.UID");
|
package/package.json
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@kar-mi/spirit-vale-tools-combat",
|
|
3
|
-
"version": "3.3.
|
|
4
|
-
"description": "Spirit Vale combat event tracking and replay utilities.",
|
|
5
|
-
"license": "AGPL-3.0-only",
|
|
6
|
-
"repository": {
|
|
7
|
-
"type": "git",
|
|
8
|
-
"url": "git+https://github.com/kar-mi/spirit-vale-tools.git",
|
|
9
|
-
"directory": "packages/combat"
|
|
10
|
-
},
|
|
3
|
+
"version": "3.3.6",
|
|
4
|
+
"description": "Spirit Vale combat event tracking and replay utilities.",
|
|
5
|
+
"license": "AGPL-3.0-only",
|
|
6
|
+
"repository": {
|
|
7
|
+
"type": "git",
|
|
8
|
+
"url": "git+https://github.com/kar-mi/spirit-vale-tools.git",
|
|
9
|
+
"directory": "packages/combat"
|
|
10
|
+
},
|
|
11
11
|
"type": "module",
|
|
12
12
|
"files": [
|
|
13
13
|
"dist",
|
|
@@ -23,7 +23,7 @@
|
|
|
23
23
|
}
|
|
24
24
|
},
|
|
25
25
|
"dependencies": {
|
|
26
|
-
"@kar-mi/spirit-vale-tools-capture": "^2.
|
|
26
|
+
"@kar-mi/spirit-vale-tools-capture": "^2.4.2",
|
|
27
27
|
"@kar-mi/spirit-vale-tools-logging": "^0.9.0",
|
|
28
28
|
"@kar-mi/spirit-vale-tools-metrics": "^0.2.0",
|
|
29
29
|
"@kar-mi/spirit-vale-tools-skills": "^0.2.1",
|
package/dist/dps-meter.d.ts
DELETED
|
@@ -1,115 +0,0 @@
|
|
|
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
|
package/dist/dps-meter.d.ts.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"dps-meter.d.ts","sourceRoot":"","sources":["../src/dps-meter.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,8 +0,0 @@
|
|
|
1
|
-
export interface FishNetSummonCalibrationEntry {
|
|
2
|
-
skillId: string;
|
|
3
|
-
isPrimary: boolean;
|
|
4
|
-
isMountedSummon: boolean;
|
|
5
|
-
}
|
|
6
|
-
/** Decodes the complete summon snapshot carried by CalibrateSummons_T. */
|
|
7
|
-
export declare function decodeSummonCalibration(payload: Buffer): FishNetSummonCalibrationEntry[];
|
|
8
|
-
//# sourceMappingURL=summon-calibration.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"summon-calibration.d.ts","sourceRoot":"","sources":["../src/summon-calibration.ts"],"names":[],"mappings":"AAGA,MAAM,WAAW,6BAA6B;IAC5C,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,EAAE,OAAO,CAAC;IACnB,eAAe,EAAE,OAAO,CAAC;CAC1B;AAED,0EAA0E;AAC1E,wBAAgB,uBAAuB,CAAC,OAAO,EAAE,MAAM,GAAG,6BAA6B,EAAE,CAqBxF"}
|