export type PvpRoguelikeStatus = 'queueing' | 'round-countdown' | 'playing' | 'upgrade-choice' | 'won' | 'lost' export type PvpRoguelikeLogTone = 'system' | 'heal' | 'danger' | 'loot' export type PvpRoguelikeCombatOutcome = | { type: 'none' } | { type: 'lost' status: 'lost' log: { text: string; tone: PvpRoguelikeLogTone } } | { type: 'won' status: 'won' markOpponentDefeated: boolean log: { text: string; tone: PvpRoguelikeLogTone } } | { type: 'upgrade-choice' status: 'upgrade-choice' log: { text: string; tone: PvpRoguelikeLogTone } } export function resolvePvpRoguelikeCombatOutcome({ playerAlive, opponentAlive, playerCleared, encounterIsBoss, encounterName, opponentDefeated, liveMatchActive, opponentLabel, }: { playerAlive: boolean opponentAlive: boolean playerCleared: boolean encounterIsBoss: boolean encounterName: string opponentDefeated: boolean liveMatchActive: boolean opponentLabel: string }): PvpRoguelikeCombatOutcome { if (!playerAlive) { return { type: 'lost', status: 'lost', log: { text: 'Your party fell first.', tone: 'danger' }, } } if (!liveMatchActive && !opponentAlive && !opponentDefeated) { return { type: 'won', status: 'won', markOpponentDefeated: true, log: { text: `${opponentLabel} fell. Match complete.`, tone: 'loot' }, } } if (!playerCleared) return { type: 'none' } if (encounterIsBoss && opponentDefeated) { return { type: 'won', status: 'won', markOpponentDefeated: false, log: { text: `${opponentLabel} defeated. Match complete.`, tone: 'loot' }, } } return { type: 'upgrade-choice', status: 'upgrade-choice', log: { text: `${encounterName} cleared. Choose your next edge.`, tone: 'loot' }, } } export function resolvePvpRoguelikeLiveSnapshot({ currentStatus, opponentStatus, opponentAlive, alreadyLoggedOpponentDone, opponentName, }: { currentStatus: PvpRoguelikeStatus opponentStatus?: string opponentAlive?: boolean alreadyLoggedOpponentDone: boolean opponentName: string }) { if (currentStatus === 'won' || currentStatus === 'lost') return { type: 'none' as const } if ((opponentStatus === 'lost' || opponentAlive === false) && !alreadyLoggedOpponentDone) { return { type: 'won' as const, status: 'won' as const, markOpponentDefeated: true, log: { text: `${opponentName} fell. Match complete.`, tone: 'loot' as const }, } } if (opponentStatus === 'won') { return { type: 'lost' as const, status: 'lost' as const, log: { text: `${opponentName} finished first.`, tone: 'danger' as const }, } } return { type: 'none' as const } } export function resolvePvpRoguelikeUpgradeCompletion({ clearedBoss, opponentDefeated, opponentLabel, }: { clearedBoss: boolean opponentDefeated: boolean opponentLabel: string }) { if (!clearedBoss || !opponentDefeated) return { type: 'none' as const } return { type: 'won' as const, status: 'won' as const, log: { text: `${opponentLabel} defeated. Match complete.`, tone: 'loot' as const }, } } export function nextPvpRoguelikeStage({ clearedBoss, stage, encounterIndex, encounters, nextSegment, }: { clearedBoss: boolean stage: number encounterIndex: number encounters: readonly TEncounter[] nextSegment: readonly TEncounter[] }) { const nextStage = clearedBoss ? stage + 1 : stage const nextEncounter = clearedBoss ? nextSegment[0] : encounters[encounterIndex + 1] if (!nextEncounter) { return { type: 'complete' as const, status: 'won' as const, log: { text: 'No further encounters remain.', tone: 'loot' as const }, } } return { type: 'next' as const, nextStage, nextEncounter, nextEncounterIndex: encounterIndex + 1, appendSegment: clearedBoss, } }