Add SCUM log sessions and trajectory projections

This commit is contained in:
npc0-hue
2026-08-27 12:34:07 +08:00
parent 0940780058
commit 316efbe780
38 changed files with 1549 additions and 84 deletions
@@ -111,11 +111,12 @@ export type SCUMSurfaceData = {
mapSettings: RecordMap[];
vehicles: RecordMap[];
flags: RecordMap[];
trajectories: RecordMap[];
};
export const emptySCUMSurfaceData: SCUMSurfaceData = {
players: [], squads: [], members: [], events: [], eventProduces: [], eventRuns: [], nativeEventRounds: [], tasks: [], activityEvents: [],
gifts: [], giftClaims: [], pendingGifts: [], giftDeliveries: [], timedGiftEvents: [], mapPoints: [], mapRegions: [], mapSettings: [], vehicles: [], flags: []
gifts: [], giftClaims: [], pendingGifts: [], giftDeliveries: [], timedGiftEvents: [], mapPoints: [], mapRegions: [], mapSettings: [], vehicles: [], flags: [], trajectories: []
};
export const scumCollections = {
@@ -137,16 +138,17 @@ export const scumCollections = {
mapRegions: "scum_map_regions",
mapSettings: "scum_map_settings",
vehicles: "scum_vehicles",
flags: "scum_flags"
flags: "scum_flags",
trajectories: "scum_trajectories"
} as const;
type SurfaceKey = keyof SCUMSurfaceData;
type PageKey = "players" | "squads" | "live-map" | "gifts" | "workflows";
const pageCollections: Record<PageKey, SurfaceKey[]> = {
players: ["players", "members", "activityEvents", "giftClaims", "pendingGifts", "giftDeliveries"],
players: ["players", "members", "activityEvents", "giftClaims", "pendingGifts", "giftDeliveries", "trajectories", "vehicles"],
squads: ["squads", "members", "flags"],
"live-map": ["mapPoints", "mapRegions", "mapSettings", "players", "vehicles", "flags"],
"live-map": ["mapPoints", "mapRegions", "mapSettings", "players", "vehicles", "flags", "trajectories"],
gifts: ["gifts", "giftClaims", "pendingGifts", "giftDeliveries", "timedGiftEvents", "players"],
workflows: ["events", "eventProduces", "eventRuns", "nativeEventRounds", "tasks", "activityEvents"]
};
@@ -161,6 +163,10 @@ export async function loadSCUMSurface(actions: SCUMWorkspaceActions, pageKey: st
const playersSnapshot = await actions.gameClient.snapshots({ profileKey: "scum-client-manager", type: "players", streamKey: "current", limit: 1 }).catch(() => undefined);
data.players = mergePlayerSnapshots(data.players, playersSnapshot);
}
if (keys.includes("vehicles") && actions.gameClient) {
const vehiclesSnapshot = await actions.gameClient.snapshots({ profileKey: "scum-client-manager", type: "vehicles", streamKey: "current", limit: 1 }).catch(() => undefined);
data.vehicles = mergeVehicleSnapshots(data.vehicles, vehiclesSnapshot);
}
return data;
}
@@ -184,6 +190,25 @@ export function mergePlayerSnapshots(players: RecordMap[], playersResponse: unkn
return merged;
}
export function mergeVehicleSnapshots(vehicles: RecordMap[], vehiclesResponse: unknown): RecordMap[] {
const vehicleSnapshot = latestSnapshotPayload(vehiclesResponse);
const merged = vehicles.map((vehicle) => ({ ...vehicle }));
const snapshotVehicles = Array.isArray(vehicleSnapshot?.vehicles) ? vehicleSnapshot.vehicles.filter(isRecord) : [];
if (!snapshotVehicles.length) return merged;
const byIdentity = vehicleIndex(merged);
for (const snapshotVehicle of snapshotVehicles) {
const match = findVehicle(byIdentity, snapshotVehicle);
const value = { ...(match ? merged[match.index] : {}), ...snapshotVehicle, vehicleObservedAt: textValue(vehicleSnapshot?.observedAt) };
if (match) merged[match.index] = value;
else {
const created = { ...value, vehicleId: firstText(snapshotVehicle, "vehicleId", "entityId", "id") };
merged.push(created);
addVehicleToIndex(byIdentity, created, merged.length - 1);
}
}
return merged;
}
export async function saveGiftDefinition(actions: SCUMWorkspaceActions, gift: RecordMap): Promise<unknown> {
const key = requiredKey(gift, "code", "礼包编号");
return requirePluginData(actions).transact(scumCollections.gifts, [{ operation: "put", key, value: gift }]);
@@ -342,6 +367,10 @@ function snapshotOrder(snapshot: RecordMap): number { const observed = Date.pars
function playerIndex(players: RecordMap[]): Map<string, number> { const result = new Map<string, number>(); players.forEach((player, index) => addPlayerToIndex(result, player, index)); return result; }
function addPlayerToIndex(index: Map<string, number>, player: RecordMap, playerIndex: number): void { playerIdentities(player).forEach((identity) => index.set(identity, playerIndex)); }
function findPlayer(index: Map<string, number>, player: RecordMap): { index: number } | undefined { for (const identity of playerIdentities(player)) { const found = index.get(identity); if (found !== undefined) return { index: found }; } return undefined; }
function vehicleIndex(vehicles: RecordMap[]): Map<string, number> { const result = new Map<string, number>(); vehicles.forEach((vehicle, index) => addVehicleToIndex(result, vehicle, index)); return result; }
function addVehicleToIndex(index: Map<string, number>, vehicle: RecordMap, vehicleIndex: number): void { vehicleIdentities(vehicle).forEach((identity) => index.set(identity, vehicleIndex)); }
function findVehicle(index: Map<string, number>, vehicle: RecordMap): { index: number } | undefined { for (const identity of vehicleIdentities(vehicle)) { const found = index.get(identity); if (found !== undefined) return { index: found }; } return undefined; }
function vehicleIdentities(vehicle: RecordMap): string[] { return ["vehicleId", "entityId", "id"].map((key) => textValue(vehicle[key])).filter(Boolean).map((value) => `vehicle:${value}`); }
function playerIdentities(player: RecordMap): string[] {
const identities = new Set<string>();
for (const key of ["gamePlayerId", "playerId", "steamId", "id"]) { const value = textValue(player[key]); if (value) identities.add(`player:${value}`); }