Files
i-want-to-heal/src/combat/pvpRoguelikeMatchSetup.ts
T
2026-06-29 22:35:49 -04:00

141 lines
3.8 KiB
TypeScript

import type { PartyMember } from '../game'
import type { PvpMatchSnapshot, PvpMatchSide } from '../pvpRoguelike'
export type PvpRoguelikeSetupSide<TBuff extends string, TDebuff extends string> = {
party: PartyMember[]
resource: number
cooldowns: Record<string, number>
enemyHealth: number
buffs: TBuff[]
debuffs: TDebuff[]
castsTowardFree: number
freeCastReady: boolean
}
export type PvpRoguelikeLiveMatchSetup = {
id: string
side: PvpMatchSide
opponentSide: PvpMatchSide
opponentName: string
opponentClassName: string
}
export function createPvpRoguelikeStarterSide<TBuff extends string, TDebuff extends string>(
partyTemplate: readonly PartyMember[],
maxResource: number,
): PvpRoguelikeSetupSide<TBuff, TDebuff> {
return {
party: partyTemplate.map((member) => ({ ...member })),
resource: maxResource,
cooldowns: {},
enemyHealth: 0,
buffs: [],
debuffs: [],
castsTowardFree: 0,
freeCastReady: false,
}
}
export function createPvpRoguelikeMatchStart<
TBuff extends string,
TDebuff extends string,
TEncounter extends { maxHealth: number },
>({
startStage,
encounterPool,
buildSegment,
partyTemplate,
opponentPartyTemplate,
maxResource,
upgradeChoiceSeconds,
}: {
startStage: number
encounterPool: readonly TEncounter[]
buildSegment: (pool: readonly TEncounter[], stage: number) => TEncounter[]
partyTemplate: readonly PartyMember[]
opponentPartyTemplate: readonly PartyMember[]
maxResource: number
upgradeChoiceSeconds: number
}) {
const firstSegment = buildSegment(encounterPool, startStage)
const firstEncounter = firstSegment[0]
const playerSide = createPvpRoguelikeStarterSide<TBuff, TDebuff>(partyTemplate, maxResource)
const opponentSide = createPvpRoguelikeStarterSide<TBuff, TDebuff>(opponentPartyTemplate, maxResource)
playerSide.enemyHealth = firstEncounter.maxHealth
opponentSide.enemyHealth = firstEncounter.maxHealth
return {
startStage,
firstSegment,
firstEncounter,
playerSide,
opponentSide,
defaults: {
encounterIndex: 0,
elapsedTicks: 0,
encountersCleared: 0,
paused: false,
targetGroup: 0 as const,
upgradeTimeLeft: upgradeChoiceSeconds,
liveUpgradePending: false,
rewardError: '',
showEndLog: false,
},
}
}
export function createPvpRoguelikeLiveMatchStart<
TBuff extends string,
TDebuff extends string,
TEncounter extends { maxHealth: number },
>({
match,
side,
encounterPool,
buildSegment,
partyTemplate,
opponentPartyTemplate,
maxResource,
upgradeChoiceSeconds,
message,
}: {
match: PvpMatchSnapshot<PvpRoguelikeSetupSide<TBuff, TDebuff>>
side: PvpMatchSide
encounterPool: readonly TEncounter[]
buildSegment: (pool: readonly TEncounter[], stage: number) => TEncounter[]
partyTemplate: readonly PartyMember[]
opponentPartyTemplate: readonly PartyMember[]
maxResource: number
upgradeChoiceSeconds: number
message?: string
}) {
const opponentSideId: PvpMatchSide = side === 'a' ? 'b' : 'a'
const opponent = match.players[opponentSideId]
const opponentTemplate = opponentPartyTemplate.map((member) => ({
...member,
name: member.id === 'mira' ? opponent.characterName : member.name,
}))
const setup = createPvpRoguelikeMatchStart<TBuff, TDebuff, TEncounter>({
startStage: match.startStage,
encounterPool,
buildSegment,
partyTemplate,
opponentPartyTemplate: opponentTemplate,
maxResource,
upgradeChoiceSeconds,
})
const liveMatch: PvpRoguelikeLiveMatchSetup = {
id: match.id,
side,
opponentSide: opponentSideId,
opponentName: opponent.characterName,
opponentClassName: opponent.className,
}
return {
...setup,
liveMatch,
logText: message ?? `${opponent.characterName} found. Stage ${match.startStage} begins.`,
}
}