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

72 lines
2.5 KiB
TypeScript

import type { DungeonEncounter } from '../profile'
import { chooseRandom } from './rules'
export function encounterThreat(encounter: DungeonEncounter) {
return (
encounter.maxHealth
+ encounter.damage * 18
+ encounter.tankDamage * 10
+ encounter.partyDamage * 18
)
}
export function buildRoguelikeSegment<TMechanic extends string, TExtra extends object = object>({
pool,
stage,
mechanics,
trashCandidateCount,
bossCandidateCount,
healthScale,
damageScale,
partyDamageScale,
idBase,
bossDescription,
bossName,
fallbackBossFirst = false,
extraFields,
}: {
pool: DungeonEncounter[]
stage: number
mechanics: TMechanic[]
trashCandidateCount: (trashCount: number) => number
bossCandidateCount: (bossCount: number) => number
healthScale: number
damageScale: number
partyDamageScale: number
idBase: number
bossDescription: (mechanics: TMechanic[]) => string
bossName?: (encounter: DungeonEncounter) => string
fallbackBossFirst?: boolean
extraFields?: (encounter: DungeonEncounter, isBoss: boolean, mechanics: TMechanic[]) => TExtra
}) {
const trashPool = [...pool.filter((encounter) => !encounter.isBoss)]
.sort((left, right) => encounterThreat(left) - encounterThreat(right))
const bossPool = [...pool.filter((encounter) => encounter.isBoss)]
.sort((left, right) => encounterThreat(left) - encounterThreat(right))
const selectedTrash = chooseRandom(trashPool.slice(0, trashCandidateCount(trashPool.length)), 2)
const selectedBoss = chooseRandom(bossPool.slice(0, bossCandidateCount(bossPool.length)), 1)[0]
?? (fallbackBossFirst ? bossPool[0] : trashPool[0])
?? trashPool[0]
?? pool[0]
const selectedMechanics = mechanics
return [...selectedTrash, selectedBoss].map((encounter, index) => {
const isBoss = index === 2
return {
...encounter,
id: idBase + stage * 10 + index,
sequence: (stage - 1) * 3 + index + 1,
isBoss,
encounterType: isBoss ? 'boss' : 'trash',
enemyName: isBoss ? (bossName?.(encounter) ?? `${encounter.enemyName} ${stage}`) : encounter.enemyName,
description: isBoss ? bossDescription(selectedMechanics) : encounter.description,
maxHealth: Math.round(encounter.maxHealth * healthScale),
damage: Math.round(encounter.damage * damageScale),
tankDamage: Math.round(encounter.tankDamage * damageScale),
partyDamage: Math.round(encounter.partyDamage * partyDamageScale),
lootTables: [],
...(extraFields?.(encounter, isBoss, selectedMechanics) ?? {} as TExtra),
}
})
}