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; invite: (session: PlayerSession, username: string) => Promise; respond: (session: PlayerSession, inviteId: string, accept: boolean) => Promise; leave: (session: PlayerSession) => Promise; remove: (session: PlayerSession, accountId: string) => Promise; updateMember: (session: PlayerSession, character: CharacterProfile, role: PartyRole) => Promise; startActivity: (session: PlayerSession, input: Omit & { readonly fillWithAi?: boolean; }) => Promise; clearActivity: (session: PlayerSession) => Promise; 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((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; }