forked from phenom/healer-man
138 lines
4.5 KiB
TypeScript
138 lines
4.5 KiB
TypeScript
import type { ClassId } from "../app/characterCatalog";
|
|
import type {
|
|
OnlineMobTransform,
|
|
OnlineRelayEventInput,
|
|
OnlineSessionPlayer,
|
|
} from "../app/onlineSessionTypes";
|
|
import {
|
|
normalizeActorResourcePool,
|
|
type CombatActorState,
|
|
} from "./combatActors";
|
|
|
|
const ONLINE_PLAYER_PREFIX = "online-player:";
|
|
let active = false;
|
|
let authority = true;
|
|
let localAccountId: string | null = null;
|
|
let playerActors = new Map<string, CombatActorState>();
|
|
let players = new Map<string, OnlineSessionPlayer>();
|
|
let mobTransforms = new Map<string, OnlineMobTransform>();
|
|
let interactionSequence = 0;
|
|
let interactions: OnlineRelayEventInput[] = [];
|
|
|
|
export function onlinePlayerActorId(accountId: string): string {
|
|
return `${ONLINE_PLAYER_PREFIX}${accountId}`;
|
|
}
|
|
|
|
export function onlineAccountIdFromActor(actorId: string): string | null {
|
|
return actorId.startsWith(ONLINE_PLAYER_PREFIX)
|
|
? actorId.slice(ONLINE_PLAYER_PREFIX.length)
|
|
: null;
|
|
}
|
|
|
|
export function isOnlinePlayerActorId(actorId: string): boolean {
|
|
return actorId.startsWith(ONLINE_PLAYER_PREFIX);
|
|
}
|
|
|
|
export function configureOnlineSessionRuntime(input: {
|
|
readonly active: boolean;
|
|
readonly authority: boolean;
|
|
readonly localAccountId: string | null;
|
|
readonly players?: readonly OnlineSessionPlayer[];
|
|
readonly mobTransforms?: Readonly<Record<string, OnlineMobTransform>>;
|
|
}): void {
|
|
active = input.active;
|
|
authority = !input.active || input.authority;
|
|
localAccountId = input.localAccountId;
|
|
players = new Map((input.players ?? []).map((player) => [player.accountId, player]));
|
|
mobTransforms = new Map(Object.entries(input.mobTransforms ?? {}));
|
|
const nextActors = new Map<string, CombatActorState>();
|
|
for (const player of input.players ?? []) {
|
|
if (player.accountId === input.localAccountId || !player.character) continue;
|
|
const id = onlinePlayerActorId(player.accountId);
|
|
const maximum = Math.max(0, player.maxResource);
|
|
const resourceId = player.resourceName.toLowerCase() || "resource";
|
|
nextActors.set(id, {
|
|
id,
|
|
threatActorId: id,
|
|
kind: "companion",
|
|
ownerId: player.accountId,
|
|
classId: player.character.classId as ClassId,
|
|
level: player.character.level,
|
|
health: player.health,
|
|
maxHealth: player.maxHealth,
|
|
absorb: 0,
|
|
resources: maximum > 0
|
|
? { [resourceId]: normalizeActorResourcePool(resourceId, player.resource, maximum) }
|
|
: {},
|
|
cooldowns: {},
|
|
globalCooldownEndsAt: 0,
|
|
cast: player.activeCast ? {
|
|
abilityId: player.activeCast.abilityId,
|
|
school: player.activeCast.school,
|
|
mode: player.activeCast.mode,
|
|
startedAt: player.activeCast.startedAt,
|
|
completesAt: player.activeCast.completesAt,
|
|
originalDurationMs: player.activeCast.originalDurationMs,
|
|
pushbackHits: player.activeCast.pushbackHits,
|
|
origin: player.activeCast.origin,
|
|
targetId: player.activeCast.targetId,
|
|
} : null,
|
|
controlledUntil: 0,
|
|
schoolLockedUntil: {},
|
|
comboPoints: 0,
|
|
comboTargetId: null,
|
|
runes: [],
|
|
alive: player.health > 0,
|
|
});
|
|
}
|
|
playerActors = nextActors;
|
|
if (!input.active) interactions = [];
|
|
}
|
|
|
|
export function onlineSessionIsActive(): boolean {
|
|
return active;
|
|
}
|
|
|
|
export function onlineSessionOwnsWorldSimulation(): boolean {
|
|
return !active || authority;
|
|
}
|
|
|
|
export function onlineSessionLocalAccountId(): string | null {
|
|
return localAccountId;
|
|
}
|
|
|
|
export function onlinePlayerRuntimeActors(): readonly CombatActorState[] {
|
|
return [...playerActors.values()];
|
|
}
|
|
|
|
export function onlinePlayerRuntimeActor(actorId: string): CombatActorState | null {
|
|
return playerActors.get(actorId) ?? null;
|
|
}
|
|
|
|
export function onlineSessionPlayer(accountId: string): OnlineSessionPlayer | null {
|
|
return players.get(accountId) ?? null;
|
|
}
|
|
|
|
export function onlineSynchronizedMobTransform(id: string): OnlineMobTransform | null {
|
|
return active && !authority ? mobTransforms.get(id) ?? null : null;
|
|
}
|
|
|
|
export function requestOnlineInteraction(kind: "loot" | "portal", targetActorId = ""): boolean {
|
|
if (!active || authority) return false;
|
|
interactionSequence += 1;
|
|
interactions.push({
|
|
clientEventId: `interaction:${kind}:${Date.now()}:${interactionSequence}`,
|
|
kind,
|
|
sourceActorId: localAccountId ? onlinePlayerActorId(localAccountId) : "",
|
|
targetActorId,
|
|
occurredAt: Date.now(),
|
|
});
|
|
return true;
|
|
}
|
|
|
|
export function drainOnlineInteractions(): readonly OnlineRelayEventInput[] {
|
|
const pending = interactions;
|
|
interactions = [];
|
|
return pending;
|
|
}
|