154 lines
3.9 KiB
TypeScript
154 lines
3.9 KiB
TypeScript
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<TEncounter>({
|
|
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,
|
|
}
|
|
}
|