forked from phenom/healer-man
Release Healer Man 0.1.6
This commit is contained in:
1 parent
55cfd43d66
commit
b12fb84859
96 files changed
+10874
-609
No files matched your search
@@ -0,0 +1,85 @@
|
||||
import { useEffect } from "react";
|
||||
import { selectedCharacter, useShellStore } from "./shellStore";
|
||||
import { useOnlineGroupStore } from "./onlineGroupStore";
|
||||
import {
|
||||
classCanFillPartyRole,
|
||||
defaultPartyRoleForClass,
|
||||
type PartyRole,
|
||||
} from "../game/partyRoles";
|
||||
import { isManastormPartySize } from "../game/manastormProgress";
|
||||
|
||||
const GROUP_POLL_MS = 2_500;
|
||||
const ACTIVITY_JOIN_WINDOW_MS = 10 * 60 * 1000;
|
||||
|
||||
export function OnlineGroupCoordinator() {
|
||||
const session = useShellStore((state) => state.session);
|
||||
const character = useShellStore(selectedCharacter);
|
||||
const phase = useShellStore((state) => state.phase);
|
||||
const snapshot = useOnlineGroupStore((state) => state.snapshot);
|
||||
const consumedActivityIds = useOnlineGroupStore((state) => state.consumedActivityIds);
|
||||
const refresh = useOnlineGroupStore((state) => state.refresh);
|
||||
const updateMember = useOnlineGroupStore((state) => state.updateMember);
|
||||
const consumeActivity = useOnlineGroupStore((state) => state.consumeActivity);
|
||||
const reset = useOnlineGroupStore((state) => state.reset);
|
||||
|
||||
useEffect(() => {
|
||||
if (!session || session.kind !== "account" || !session.accessToken) {
|
||||
reset();
|
||||
return undefined;
|
||||
}
|
||||
void refresh(session);
|
||||
const timer = window.setInterval(() => void refresh(session, true), GROUP_POLL_MS);
|
||||
const onVisibility = () => {
|
||||
if (document.visibilityState === "visible") void refresh(session, true);
|
||||
};
|
||||
document.addEventListener("visibilitychange", onVisibility);
|
||||
return () => {
|
||||
window.clearInterval(timer);
|
||||
document.removeEventListener("visibilitychange", onVisibility);
|
||||
};
|
||||
}, [refresh, reset, session?.accessToken, session?.ownerId]);
|
||||
|
||||
useEffect(() => {
|
||||
const group = snapshot.group;
|
||||
if (!session || !character || !group || group.activity) return;
|
||||
const member = group.members.find((candidate) => candidate.accountId === session.ownerId);
|
||||
if (!member) return;
|
||||
const role: PartyRole = member.role && classCanFillPartyRole(character.classId, member.role)
|
||||
? member.role
|
||||
: defaultPartyRoleForClass(character.classId);
|
||||
const characterChanged = member.character?.id !== character.id
|
||||
|| member.character?.level !== character.level
|
||||
|| member.character?.classId !== character.classId;
|
||||
if (!characterChanged && member.role === role) return;
|
||||
void updateMember(session, character, role);
|
||||
}, [character?.classId, character?.id, character?.level, session?.ownerId, snapshot.group?.id, snapshot.group?.updatedAt, updateMember]);
|
||||
|
||||
useEffect(() => {
|
||||
const activity = snapshot.group?.activity;
|
||||
if (!session || !activity || phase === "game" || consumedActivityIds.includes(activity.id)) return;
|
||||
if (Date.now() - activity.startedAt > ACTIVITY_JOIN_WINDOW_MS) return;
|
||||
const member = snapshot.group?.members.find((candidate) => candidate.accountId === session.ownerId);
|
||||
if (!member?.character || !member.role) return;
|
||||
consumeActivity(activity.id);
|
||||
void (async () => {
|
||||
if (activity.type === "dungeon") {
|
||||
const { installDungeonShellRuntime } = await import("./dungeonShellRuntime");
|
||||
installDungeonShellRuntime();
|
||||
useShellStore.getState().enterDungeon(activity.contentId, member.role ?? undefined);
|
||||
return;
|
||||
}
|
||||
const { installManastormShellRuntime } = await import("./manastormShellRuntime");
|
||||
installManastormShellRuntime();
|
||||
const partySize = isManastormPartySize(activity.partySize) ? activity.partySize : 5;
|
||||
useShellStore.getState().enterManastorm(
|
||||
member.role ?? undefined,
|
||||
partySize,
|
||||
activity.startingLevel ?? undefined,
|
||||
);
|
||||
})().catch((error) => {
|
||||
useShellStore.getState().setNotice(error instanceof Error ? error.message : "The group activity could not be opened.");
|
||||
});
|
||||
}, [consumeActivity, consumedActivityIds, phase, session?.ownerId, snapshot.group?.activity?.id]);
|
||||
|
||||
return null;
|
||||
}
|
||||
@@ -6,6 +6,8 @@ import {
|
||||
type AccountResult,
|
||||
} from "./accountRepository";
|
||||
import type { CharacterProfile, PlayerSession } from "./types";
|
||||
import type { OnlineGroupSnapshot, StartOnlineGroupActivityInput } from "./onlineGroupTypes";
|
||||
import type { OnlineSessionSnapshot, OnlineSessionSyncInput } from "./onlineSessionTypes";
|
||||
|
||||
const NATIVE_API_ORIGIN = "https://iwanttoheal.phenomrom.com";
|
||||
const SAVE_DEBOUNCE_MS = 500;
|
||||
@@ -159,3 +161,77 @@ export async function logoutOnlineSession(session: PlayerSession | null): Promis
|
||||
}
|
||||
if (activeSession?.ownerId === session?.ownerId) activeSession = null;
|
||||
}
|
||||
|
||||
function onlineGroupRequest<T>(session: PlayerSession, pathname: string, init: RequestInit = {}): Promise<T> {
|
||||
if (!session.accessToken) throw new OnlineApiError("Online grouping requires a signed-in account.", 401);
|
||||
return requestOnlineJson<T>(pathname, init, session.accessToken);
|
||||
}
|
||||
|
||||
export function fetchOnlineGroup(session: PlayerSession): Promise<OnlineGroupSnapshot> {
|
||||
return onlineGroupRequest(session, "/api/online-group");
|
||||
}
|
||||
|
||||
export async function inviteOnlineGroupPlayer(session: PlayerSession, username: string): Promise<OnlineGroupSnapshot> {
|
||||
const response = await onlineGroupRequest<{ state: OnlineGroupSnapshot }>(session, "/api/online-group/invite", {
|
||||
method: "POST",
|
||||
body: JSON.stringify({ username }),
|
||||
});
|
||||
return response.state;
|
||||
}
|
||||
|
||||
export function respondToOnlineGroupInvite(
|
||||
session: PlayerSession,
|
||||
inviteId: string,
|
||||
accept: boolean,
|
||||
): Promise<OnlineGroupSnapshot> {
|
||||
return onlineGroupRequest(session, "/api/online-group/invite/respond", {
|
||||
method: "POST",
|
||||
body: JSON.stringify({ inviteId, accept }),
|
||||
});
|
||||
}
|
||||
|
||||
export function leaveOnlineGroup(session: PlayerSession): Promise<OnlineGroupSnapshot> {
|
||||
return onlineGroupRequest(session, "/api/online-group/leave", { method: "POST" });
|
||||
}
|
||||
|
||||
export function removeOnlineGroupPlayer(session: PlayerSession, accountId: string): Promise<OnlineGroupSnapshot> {
|
||||
return onlineGroupRequest(session, "/api/online-group/remove", {
|
||||
method: "POST",
|
||||
body: JSON.stringify({ accountId }),
|
||||
});
|
||||
}
|
||||
|
||||
export function updateOnlineGroupMember(
|
||||
session: PlayerSession,
|
||||
character: CharacterProfile,
|
||||
role: string,
|
||||
): Promise<OnlineGroupSnapshot> {
|
||||
return onlineGroupRequest(session, "/api/online-group/member", {
|
||||
method: "PUT",
|
||||
body: JSON.stringify({ character, role }),
|
||||
});
|
||||
}
|
||||
|
||||
export function startOnlineGroupActivity(
|
||||
session: PlayerSession,
|
||||
activity: StartOnlineGroupActivityInput,
|
||||
): Promise<OnlineGroupSnapshot> {
|
||||
return onlineGroupRequest(session, "/api/online-group/activity", {
|
||||
method: "POST",
|
||||
body: JSON.stringify(activity),
|
||||
});
|
||||
}
|
||||
|
||||
export function clearOnlineGroupActivity(session: PlayerSession): Promise<OnlineGroupSnapshot> {
|
||||
return onlineGroupRequest(session, "/api/online-group/activity/clear", { method: "POST" });
|
||||
}
|
||||
|
||||
export function synchronizeOnlineSession(
|
||||
session: PlayerSession,
|
||||
input: OnlineSessionSyncInput,
|
||||
): Promise<OnlineSessionSnapshot> {
|
||||
return onlineGroupRequest(session, "/api/online-session/sync", {
|
||||
method: "POST",
|
||||
body: JSON.stringify(input),
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
import { beforeEach, describe, expect, it } from "vitest";
|
||||
import { onlineGroupAiPartySize, useOnlineGroupStore } from "./onlineGroupStore";
|
||||
|
||||
describe("online group AI fill sizing", () => {
|
||||
beforeEach(() => useOnlineGroupStore.getState().reset());
|
||||
|
||||
it("reserves one simulated slot for every grouped human other than the local player", () => {
|
||||
useOnlineGroupStore.setState({
|
||||
snapshot: {
|
||||
invitations: [],
|
||||
group: {
|
||||
id: "group-test",
|
||||
leaderAccountId: "one",
|
||||
activity: null,
|
||||
activityRevision: 0,
|
||||
createdAt: 1,
|
||||
updatedAt: 1,
|
||||
members: [
|
||||
{ accountId: "one", username: "One", character: null, role: null, joinedAt: 1 },
|
||||
{ accountId: "two", username: "Two", character: null, role: null, joinedAt: 2 },
|
||||
{ accountId: "three", username: "Three", character: null, role: null, joinedAt: 3 },
|
||||
],
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
expect(onlineGroupAiPartySize(5)).toBe(3);
|
||||
expect(onlineGroupAiPartySize(3)).toBe(1);
|
||||
});
|
||||
|
||||
it("keeps ordinary solo activity sizes unchanged", () => {
|
||||
expect(onlineGroupAiPartySize(5)).toBe(5);
|
||||
expect(onlineGroupAiPartySize(1)).toBe(1);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,177 @@
|
||||
import { create } from "zustand";
|
||||
import type { CharacterProfile, PlayerSession } from "./types";
|
||||
import type { PartyRole } from "../game/partyRoles";
|
||||
import type { PartySize } from "../game/partyRoles";
|
||||
import {
|
||||
clearOnlineGroupActivity,
|
||||
fetchOnlineGroup,
|
||||
inviteOnlineGroupPlayer,
|
||||
leaveOnlineGroup,
|
||||
removeOnlineGroupPlayer,
|
||||
respondToOnlineGroupInvite,
|
||||
startOnlineGroupActivity,
|
||||
updateOnlineGroupMember,
|
||||
} from "./onlineAccountClient";
|
||||
import type { OnlineGroupActivity, OnlineGroupSnapshot, StartOnlineGroupActivityInput } from "./onlineGroupTypes";
|
||||
|
||||
const EMPTY_SNAPSHOT: OnlineGroupSnapshot = Object.freeze({ group: null, invitations: Object.freeze([]) });
|
||||
|
||||
interface OnlineGroupState {
|
||||
snapshot: OnlineGroupSnapshot;
|
||||
loading: boolean;
|
||||
busy: boolean;
|
||||
error: string;
|
||||
fillWithAi: boolean;
|
||||
activeActivity: OnlineGroupActivity | null;
|
||||
consumedActivityIds: readonly string[];
|
||||
refresh: (session: PlayerSession, silent?: boolean) => Promise<void>;
|
||||
invite: (session: PlayerSession, username: string) => Promise<boolean>;
|
||||
respond: (session: PlayerSession, inviteId: string, accept: boolean) => Promise<boolean>;
|
||||
leave: (session: PlayerSession) => Promise<boolean>;
|
||||
remove: (session: PlayerSession, accountId: string) => Promise<boolean>;
|
||||
updateMember: (session: PlayerSession, character: CharacterProfile, role: PartyRole) => Promise<boolean>;
|
||||
startActivity: (session: PlayerSession, input: Omit<StartOnlineGroupActivityInput, "fillWithAi">) => Promise<boolean>;
|
||||
clearActivity: (session: PlayerSession) => Promise<boolean>;
|
||||
setFillWithAi: (fillWithAi: boolean) => void;
|
||||
consumeActivity: (activityId: string) => void;
|
||||
reset: () => void;
|
||||
}
|
||||
|
||||
function messageFor(error: unknown): string {
|
||||
return error instanceof Error ? error.message : "The online group could not be updated.";
|
||||
}
|
||||
|
||||
export const useOnlineGroupStore = create<OnlineGroupState>((set, get) => ({
|
||||
snapshot: EMPTY_SNAPSHOT,
|
||||
loading: false,
|
||||
busy: false,
|
||||
error: "",
|
||||
fillWithAi: true,
|
||||
activeActivity: null,
|
||||
consumedActivityIds: [],
|
||||
|
||||
refresh: async (session, silent = false) => {
|
||||
if (session.kind !== "account" || !session.accessToken) {
|
||||
set({ snapshot: EMPTY_SNAPSHOT, loading: false, busy: false, error: "" });
|
||||
return;
|
||||
}
|
||||
if (!silent) set({ loading: true });
|
||||
try {
|
||||
const snapshot = await fetchOnlineGroup(session);
|
||||
set({ snapshot, loading: false, error: "" });
|
||||
} catch (error) {
|
||||
set({ loading: false, error: messageFor(error) });
|
||||
}
|
||||
},
|
||||
|
||||
invite: async (session, username) => {
|
||||
set({ busy: true, error: "" });
|
||||
try {
|
||||
const snapshot = await inviteOnlineGroupPlayer(session, username);
|
||||
set({ snapshot, busy: false, activeActivity: null });
|
||||
return true;
|
||||
} catch (error) {
|
||||
set({ busy: false, error: messageFor(error) });
|
||||
return false;
|
||||
}
|
||||
},
|
||||
|
||||
respond: async (session, inviteId, accept) => {
|
||||
set({ busy: true, error: "" });
|
||||
try {
|
||||
const snapshot = await respondToOnlineGroupInvite(session, inviteId, accept);
|
||||
set({ snapshot, busy: false, activeActivity: null });
|
||||
return true;
|
||||
} catch (error) {
|
||||
set({ busy: false, error: messageFor(error) });
|
||||
return false;
|
||||
}
|
||||
},
|
||||
|
||||
leave: async (session) => {
|
||||
set({ busy: true, error: "" });
|
||||
try {
|
||||
const snapshot = await leaveOnlineGroup(session);
|
||||
set({ snapshot, busy: false, activeActivity: null });
|
||||
return true;
|
||||
} catch (error) {
|
||||
set({ busy: false, error: messageFor(error) });
|
||||
return false;
|
||||
}
|
||||
},
|
||||
|
||||
remove: async (session, accountId) => {
|
||||
set({ busy: true, error: "" });
|
||||
try {
|
||||
const snapshot = await removeOnlineGroupPlayer(session, accountId);
|
||||
set({ snapshot, busy: false, activeActivity: null });
|
||||
return true;
|
||||
} catch (error) {
|
||||
set({ busy: false, error: messageFor(error) });
|
||||
return false;
|
||||
}
|
||||
},
|
||||
|
||||
updateMember: async (session, character, role) => {
|
||||
set({ busy: true, error: "" });
|
||||
try {
|
||||
const snapshot = await updateOnlineGroupMember(session, character, role);
|
||||
set({ snapshot, busy: false, activeActivity: null });
|
||||
return true;
|
||||
} catch (error) {
|
||||
set({ busy: false, error: messageFor(error) });
|
||||
return false;
|
||||
}
|
||||
},
|
||||
|
||||
startActivity: async (session, input) => {
|
||||
set({ busy: true, error: "" });
|
||||
try {
|
||||
const snapshot = await startOnlineGroupActivity(session, { ...input, fillWithAi: get().fillWithAi });
|
||||
set({ snapshot, busy: false });
|
||||
return true;
|
||||
} catch (error) {
|
||||
set({ busy: false, error: messageFor(error) });
|
||||
return false;
|
||||
}
|
||||
},
|
||||
|
||||
clearActivity: async (session) => {
|
||||
set({ busy: true, error: "" });
|
||||
try {
|
||||
const snapshot = await clearOnlineGroupActivity(session);
|
||||
set({ snapshot, busy: false, activeActivity: null });
|
||||
return true;
|
||||
} catch (error) {
|
||||
set({ busy: false, error: messageFor(error) });
|
||||
return false;
|
||||
}
|
||||
},
|
||||
|
||||
setFillWithAi: (fillWithAi) => set({ fillWithAi }),
|
||||
consumeActivity: (activityId) => set((state) => {
|
||||
if (state.consumedActivityIds.includes(activityId)) return state;
|
||||
return {
|
||||
activeActivity: state.snapshot.group?.activity?.id === activityId
|
||||
? state.snapshot.group.activity
|
||||
: state.activeActivity,
|
||||
consumedActivityIds: [...state.consumedActivityIds.slice(-7), activityId],
|
||||
};
|
||||
}),
|
||||
reset: () => set({
|
||||
snapshot: EMPTY_SNAPSHOT,
|
||||
loading: false,
|
||||
busy: false,
|
||||
error: "",
|
||||
activeActivity: null,
|
||||
consumedActivityIds: [],
|
||||
}),
|
||||
}));
|
||||
|
||||
export function onlineGroupHumanCount(): number {
|
||||
return useOnlineGroupStore.getState().snapshot.group?.members.length ?? 1;
|
||||
}
|
||||
|
||||
export function onlineGroupAiPartySize(activityPartySize: number): PartySize {
|
||||
return Math.max(1, Math.min(5, activityPartySize - Math.max(0, onlineGroupHumanCount() - 1))) as PartySize;
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
import type { PartyRole } from "../game/partyRoles";
|
||||
import type { CharacterAppearance } from "./characterCatalog";
|
||||
|
||||
export interface OnlineGroupCharacter {
|
||||
readonly id: string;
|
||||
readonly name: string;
|
||||
readonly classId: string;
|
||||
readonly categoryId: string;
|
||||
readonly raceId: string;
|
||||
readonly gender: "female" | "male";
|
||||
readonly level: number;
|
||||
readonly appearance?: CharacterAppearance;
|
||||
}
|
||||
|
||||
export interface OnlineGroupMember {
|
||||
readonly accountId: string;
|
||||
readonly username: string;
|
||||
readonly character: OnlineGroupCharacter | null;
|
||||
readonly role: PartyRole | null;
|
||||
readonly joinedAt: number;
|
||||
}
|
||||
|
||||
export interface OnlineGroupActivity {
|
||||
readonly id: string;
|
||||
readonly type: "dungeon" | "manastorm";
|
||||
readonly contentId: string;
|
||||
readonly fillWithAi: boolean;
|
||||
readonly partySize: number;
|
||||
readonly startingLevel: number | null;
|
||||
readonly startedAt: number;
|
||||
}
|
||||
|
||||
export interface OnlineGroup {
|
||||
readonly id: string;
|
||||
readonly leaderAccountId: string;
|
||||
readonly members: readonly OnlineGroupMember[];
|
||||
readonly activity: OnlineGroupActivity | null;
|
||||
readonly activityRevision: number;
|
||||
readonly createdAt: number;
|
||||
readonly updatedAt: number;
|
||||
}
|
||||
|
||||
export interface OnlineGroupInvitation {
|
||||
readonly id: string;
|
||||
readonly groupId: string;
|
||||
readonly inviterAccountId: string;
|
||||
readonly inviterUsername: string;
|
||||
readonly createdAt: number;
|
||||
readonly expiresAt: number;
|
||||
}
|
||||
|
||||
export interface OnlineGroupSnapshot {
|
||||
readonly group: OnlineGroup | null;
|
||||
readonly invitations: readonly OnlineGroupInvitation[];
|
||||
}
|
||||
|
||||
export interface StartOnlineGroupActivityInput {
|
||||
readonly type: OnlineGroupActivity["type"];
|
||||
readonly contentId: string;
|
||||
readonly fillWithAi: boolean;
|
||||
readonly partySize: number;
|
||||
readonly startingLevel?: number;
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
import { create } from "zustand";
|
||||
import type { OnlineSessionSnapshot } from "./onlineSessionTypes";
|
||||
|
||||
interface OnlineSessionState {
|
||||
snapshot: OnlineSessionSnapshot | null;
|
||||
connected: boolean;
|
||||
synchronizing: boolean;
|
||||
error: string;
|
||||
setSynchronizing: (synchronizing: boolean) => void;
|
||||
accept: (snapshot: OnlineSessionSnapshot) => void;
|
||||
fail: (message: string) => void;
|
||||
reset: () => void;
|
||||
}
|
||||
|
||||
export const useOnlineSessionStore = create<OnlineSessionState>((set) => ({
|
||||
snapshot: null,
|
||||
connected: false,
|
||||
synchronizing: false,
|
||||
error: "",
|
||||
setSynchronizing: (synchronizing) => set({ synchronizing }),
|
||||
accept: (snapshot) => set({ snapshot, connected: true, synchronizing: false, error: "" }),
|
||||
fail: (error) => set({ connected: false, synchronizing: false, error }),
|
||||
reset: () => set({ snapshot: null, connected: false, synchronizing: false, error: "" }),
|
||||
}));
|
||||
@@ -0,0 +1,108 @@
|
||||
import type { CharacterCombatAnimationEvent } from "../game/combatAnimation";
|
||||
import type { ActiveCast, MobCombatState } from "../game/combatStore";
|
||||
import type { DamageSchool } from "../game/combatAuras";
|
||||
import type { InventoryItem } from "../game/lootTypes";
|
||||
import type { PartyMember } from "../game/partyStore";
|
||||
import type { OnlineGroupActivity, OnlineGroupCharacter } from "./onlineGroupTypes";
|
||||
import type { PartyRole } from "../game/partyRoles";
|
||||
|
||||
export type OnlineSessionVector = readonly [number, number, number];
|
||||
|
||||
export interface OnlineSessionPlayer {
|
||||
readonly accountId: string;
|
||||
readonly username: string;
|
||||
readonly character: OnlineGroupCharacter | null;
|
||||
readonly role: PartyRole | null;
|
||||
readonly position: OnlineSessionVector;
|
||||
readonly yaw: number;
|
||||
readonly grounded: boolean;
|
||||
readonly health: number;
|
||||
readonly maxHealth: number;
|
||||
readonly resource: number;
|
||||
readonly maxResource: number;
|
||||
readonly resourceName: string;
|
||||
readonly selectedTargetId: string | null;
|
||||
readonly activeCast: ActiveCast | null;
|
||||
readonly animationEvent: CharacterCombatAnimationEvent | null;
|
||||
readonly equipment: readonly InventoryItem[];
|
||||
readonly updatedAt: number;
|
||||
}
|
||||
|
||||
export interface OnlineMobTransform {
|
||||
readonly position: OnlineSessionVector;
|
||||
readonly yaw: number;
|
||||
}
|
||||
|
||||
export interface OnlineSharedWorld {
|
||||
readonly mobs: Readonly<Record<string, MobCombatState>>;
|
||||
readonly mobTransforms: Readonly<Record<string, OnlineMobTransform>>;
|
||||
readonly partyMembers: readonly PartyMember[];
|
||||
readonly partyPositions: Readonly<Record<string, OnlineSessionVector>>;
|
||||
readonly partyRuntime: {
|
||||
readonly command: string;
|
||||
readonly activeMobId: string | null;
|
||||
readonly objectiveBossId: string | null;
|
||||
readonly memberTargetIds: Readonly<Record<string, string>>;
|
||||
};
|
||||
readonly wailing: Readonly<Record<string, unknown>> | null;
|
||||
readonly manastorm: Readonly<Record<string, unknown>> | null;
|
||||
readonly publishedAt?: number;
|
||||
readonly publishedBy?: string;
|
||||
}
|
||||
|
||||
export type OnlineRelayEventKind = "damage" | "healing" | "resurrection" | "loot" | "portal";
|
||||
|
||||
export interface OnlineRelayEventInput {
|
||||
readonly clientEventId: string;
|
||||
readonly kind: OnlineRelayEventKind;
|
||||
readonly sourceActorId: string;
|
||||
readonly targetActorId: string;
|
||||
readonly abilityId?: string | null;
|
||||
readonly school?: DamageSchool | null;
|
||||
readonly rawAmount?: number;
|
||||
readonly effectiveAmount?: number;
|
||||
readonly critical?: boolean;
|
||||
readonly occurredAt?: number;
|
||||
}
|
||||
|
||||
export interface OnlineRelayEvent extends OnlineRelayEventInput {
|
||||
readonly id: number;
|
||||
readonly sourceAccountId: string;
|
||||
readonly createdAt: number;
|
||||
}
|
||||
|
||||
export interface OnlineSessionSnapshot {
|
||||
readonly activity: OnlineGroupActivity;
|
||||
readonly groupId: string;
|
||||
readonly leaderAccountId: string;
|
||||
readonly localAccountId: string;
|
||||
readonly authority: boolean;
|
||||
readonly revision: number;
|
||||
readonly serverTime: number;
|
||||
readonly players: readonly OnlineSessionPlayer[];
|
||||
readonly world: OnlineSharedWorld | null;
|
||||
readonly events: readonly OnlineRelayEvent[];
|
||||
readonly latestEventId: number;
|
||||
}
|
||||
|
||||
export interface OnlinePresenceInput {
|
||||
readonly position: OnlineSessionVector;
|
||||
readonly yaw: number;
|
||||
readonly grounded: boolean;
|
||||
readonly health: number;
|
||||
readonly maxHealth: number;
|
||||
readonly resource: number;
|
||||
readonly maxResource: number;
|
||||
readonly resourceName: string;
|
||||
readonly selectedTargetId: string | null;
|
||||
readonly activeCast: ActiveCast | null;
|
||||
readonly animationEvent: CharacterCombatAnimationEvent | null;
|
||||
readonly equipment: readonly InventoryItem[];
|
||||
}
|
||||
|
||||
export interface OnlineSessionSyncInput {
|
||||
readonly afterEventId: number;
|
||||
readonly presence: OnlinePresenceInput;
|
||||
readonly world?: OnlineSharedWorld;
|
||||
readonly events?: readonly OnlineRelayEventInput[];
|
||||
}
|
||||
Reference in new issue
Block a user