Files
Genarrative/src/data/companionRoster.ts
kdletters cbc27bad4a
Some checks failed
CI / verify (push) Has been cancelled
init with react+axum+spacetimedb
2026-04-26 18:06:23 +08:00

129 lines
3.7 KiB
TypeScript

import { CompanionState, GameState } from '../types';
import { MAX_COMPANIONS } from './npcInteractions';
function upsertCompanion(list: CompanionState[], companion: CompanionState) {
const next = [...list];
const existingIndex = next.findIndex(item => item.npcId === companion.npcId);
if (existingIndex >= 0) {
next[existingIndex] = companion;
return next;
}
next.push(companion);
return next;
}
function removeCompanion(list: CompanionState[], npcId: string) {
return list.filter(item => item.npcId !== npcId);
}
export function getRecruitedNpcIds(state: Pick<GameState, 'companions' | 'roster'>) {
return new Set([
...state.companions.map(companion => companion.npcId),
...state.roster.map(companion => companion.npcId),
]);
}
export function normalizeRoster(roster: CompanionState[], activeCompanions: CompanionState[]) {
const activeIds = new Set(activeCompanions.map(companion => companion.npcId));
return roster
.filter(companion => !activeIds.has(companion.npcId))
.reduce<CompanionState[]>((next, companion) => upsertCompanion(next, companion), []);
}
export function benchActiveCompanion(state: GameState, npcId: string) {
const activeCompanion = state.companions.find(companion => companion.npcId === npcId);
if (!activeCompanion) return state;
return {
...state,
companions: state.companions.filter(companion => companion.npcId !== npcId),
roster: upsertCompanion(state.roster, activeCompanion),
};
}
export function activateRosterCompanion(state: GameState, npcId: string, swapNpcId?: string | null) {
const reserveCompanion = state.roster.find(companion => companion.npcId === npcId);
if (!reserveCompanion) return state;
if (state.companions.some(companion => companion.npcId === npcId)) {
return {
...state,
roster: removeCompanion(state.roster, npcId),
};
}
if (state.companions.length < MAX_COMPANIONS) {
return {
...state,
companions: [...state.companions, reserveCompanion],
roster: removeCompanion(state.roster, npcId),
};
}
if (!swapNpcId) return state;
const swapIndex = state.companions.findIndex(companion => companion.npcId === swapNpcId);
if (swapIndex < 0) return state;
const swappedOut = state.companions[swapIndex];
if (!swappedOut) {
return state;
}
const nextCompanions = [...state.companions];
nextCompanions[swapIndex] = reserveCompanion;
return {
...state,
companions: nextCompanions,
roster: upsertCompanion(removeCompanion(state.roster, npcId), swappedOut),
};
}
export function recruitCompanionToParty(
state: GameState,
companion: CompanionState,
replacedNpcId?: string | null,
) {
const nextReserve = removeCompanion(state.roster, companion.npcId);
if (!replacedNpcId && state.companions.length < MAX_COMPANIONS) {
return {
...state,
companions: [...state.companions, companion],
roster: nextReserve,
};
}
if (!replacedNpcId) {
return {
...state,
companions: state.companions.slice(0, MAX_COMPANIONS),
roster: normalizeRoster(nextReserve, state.companions),
};
}
const replaceIndex = state.companions.findIndex(item => item.npcId === replacedNpcId);
if (replaceIndex < 0) {
return {
...state,
companions: [...state.companions, companion].slice(0, MAX_COMPANIONS),
roster: nextReserve,
};
}
const replacedCompanion = state.companions[replaceIndex];
if (!replacedCompanion) {
return state;
}
const nextCompanions = [...state.companions];
nextCompanions[replaceIndex] = companion;
return {
...state,
companions: nextCompanions,
roster: upsertCompanion(nextReserve, replacedCompanion),
};
}