import type { PartyMember } from '../game' export type NavigateAction = 'navigateLeft' | 'navigateRight' | 'navigateUp' | 'navigateDown' type TargetOptions = { livingOnly?: boolean } function canTarget(member: PartyMember, options: TargetOptions) { return !options.livingOnly || member.health > 0 } export function selectRelativePartyTarget( party: PartyMember[], selectedId: string, direction: -1 | 1, options: TargetOptions = {}, ) { const candidates = party.filter((member) => canTarget(member, options)) if (candidates.length === 0) return null const currentIndex = candidates.findIndex((member) => member.id === selectedId) const nextIndex = currentIndex < 0 ? 0 : (currentIndex + direction + candidates.length) % candidates.length return candidates[nextIndex]?.id ?? null } export function selectDirectionalPartyTarget( party: PartyMember[], selectedId: string, action: NavigateAction, columns: number, options: TargetOptions = {}, ) { const currentIndex = party.findIndex((member) => member.id === selectedId) if (currentIndex < 0) { return party.find((member) => canTarget(member, options))?.id ?? null } const currentRow = Math.floor(currentIndex / columns) const currentColumn = currentIndex % columns const horizontal = action === 'navigateLeft' || action === 'navigateRight' const candidate = party .map((member, index) => ({ member, index, row: Math.floor(index / columns), column: index % columns, })) .filter(({ member, index, row, column }) => { if (!canTarget(member, options) || index === currentIndex) return false if (action === 'navigateLeft') return row === currentRow && column < currentColumn if (action === 'navigateRight') return row === currentRow && column > currentColumn if (action === 'navigateUp') return row < currentRow return row > currentRow }) .sort((left, right) => { const leftPrimary = horizontal ? Math.abs(left.column - currentColumn) : Math.abs(left.row - currentRow) const rightPrimary = horizontal ? Math.abs(right.column - currentColumn) : Math.abs(right.row - currentRow) const leftSecondary = horizontal ? 0 : Math.abs(left.column - currentColumn) const rightSecondary = horizontal ? 0 : Math.abs(right.column - currentColumn) return leftPrimary - rightPrimary || leftSecondary - rightSecondary })[0] return candidate?.member.id ?? null } export function selectDirectPartyTarget( party: PartyMember[], slot: number, options: TargetOptions & { targetGroup?: number groupSize?: number } = {}, ) { const groupSize = options.groupSize ?? 6 const index = slot + (options.targetGroup ?? 0) * groupSize const member = party[index] return member && canTarget(member, options) ? member.id : null } export function nextTargetGroupSelection( party: PartyMember[], selectedId: string, currentGroup: number, groupSize = 6, ) { const groupCount = Math.max(1, Math.ceil(party.length / groupSize)) const nextGroup = (currentGroup + 1) % groupCount const selectedIndex = party.findIndex((member) => member.id === selectedId) const slot = selectedIndex < 0 ? 0 : selectedIndex % groupSize return { group: nextGroup, selectedId: party[slot + nextGroup * groupSize]?.id ?? null, } }