183 lines
5.8 KiB
TypeScript
183 lines
5.8 KiB
TypeScript
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"> & {
|
|
readonly fillWithAi?: boolean;
|
|
}) => 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: 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;
|
|
}
|