Android build v1.1.2
This commit is contained in:
@@ -0,0 +1,112 @@
|
||||
import type { PartyMember, Spell } from '../game'
|
||||
import { effectiveMaxHealth } from './rules'
|
||||
|
||||
export type CpuHealBehavior = {
|
||||
directHealThreshold: number
|
||||
groupHealThreshold: number
|
||||
hotThreshold: number
|
||||
shieldThreshold: number
|
||||
}
|
||||
|
||||
export type CpuTurnBehavior = CpuHealBehavior & {
|
||||
actionEveryTicks: number
|
||||
mistakeChance: number
|
||||
}
|
||||
|
||||
export function shouldCpuAct({
|
||||
elapsedTicks,
|
||||
behavior,
|
||||
}: {
|
||||
elapsedTicks: number
|
||||
behavior: CpuTurnBehavior
|
||||
}) {
|
||||
return elapsedTicks % behavior.actionEveryTicks === 0 && Math.random() >= behavior.mistakeChance
|
||||
}
|
||||
|
||||
export function chooseCpuHealActions({
|
||||
party,
|
||||
spells,
|
||||
behavior,
|
||||
preferSlots = false,
|
||||
}: {
|
||||
party: PartyMember[]
|
||||
spells: Spell[]
|
||||
behavior: CpuHealBehavior
|
||||
preferSlots?: boolean
|
||||
}) {
|
||||
let livingCount = 0
|
||||
let healthRatioTotal = 0
|
||||
let lowest: PartyMember | null = null
|
||||
let tank: PartyMember | null = null
|
||||
let cleanseTarget: PartyMember | null = null
|
||||
let renewTarget: PartyMember | null = null
|
||||
let shieldTarget: PartyMember | null = null
|
||||
let woundedCount = 0
|
||||
|
||||
for (const member of party) {
|
||||
if (member.health <= 0) continue
|
||||
livingCount += 1
|
||||
const ratio = member.health / effectiveMaxHealth(member)
|
||||
healthRatioTotal += ratio
|
||||
if (!lowest || ratio < lowest.health / effectiveMaxHealth(lowest)) lowest = member
|
||||
if (!tank && member.role === 'Tank') tank = member
|
||||
if (!cleanseTarget && (member.debuff || (member.poisonStacks ?? 0) > 0)) cleanseTarget = member
|
||||
if (!renewTarget && member.hotTicks <= 1 && ratio < behavior.hotThreshold) renewTarget = member
|
||||
if (!shieldTarget && member.role === 'Tank' && member.shield <= 5 && ratio < behavior.shieldThreshold) {
|
||||
shieldTarget = member
|
||||
}
|
||||
if (ratio < behavior.directHealThreshold) woundedCount += 1
|
||||
}
|
||||
|
||||
if (!lowest || livingCount === 0) return []
|
||||
const averageHealth = healthRatioTotal / livingCount
|
||||
const spellByKind = (kind: Spell['kind']) => spells.find((candidate) => candidate.kind === kind)
|
||||
const spellBySlot = (slot: string) => spells.find((candidate) => candidate.key === slot)
|
||||
const direct = preferSlots ? spellBySlot('1') : spellByKind('direct')
|
||||
const hot = preferSlots ? spellBySlot('2') : spellByKind('hot')
|
||||
const group = preferSlots ? spellBySlot('3') : spellByKind('group')
|
||||
const shield = preferSlots ? spellBySlot('4') : spellByKind('shield')
|
||||
const cleanse = preferSlots ? spellBySlot('5') : spellByKind('cleanse')
|
||||
const ordered: Array<{ spell: Spell | undefined; targetId: string | null }> = [
|
||||
{ spell: cleanseTarget ? cleanse : undefined, targetId: cleanseTarget?.id ?? null },
|
||||
{ spell: averageHealth < behavior.groupHealThreshold ? group : undefined, targetId: lowest.id },
|
||||
{ spell: shieldTarget ? shield : undefined, targetId: shieldTarget?.id ?? null },
|
||||
{ spell: woundedCount > 0 ? direct : undefined, targetId: lowest.id },
|
||||
{ spell: renewTarget ? hot : undefined, targetId: renewTarget?.id ?? null },
|
||||
{ spell: tank ? direct : undefined, targetId: tank?.id ?? null },
|
||||
]
|
||||
|
||||
return ordered
|
||||
.filter((action): action is { spell: Spell; targetId: string } => Boolean(action.spell && action.targetId))
|
||||
}
|
||||
|
||||
export function chooseCpuHealAction(options: Parameters<typeof chooseCpuHealActions>[0]) {
|
||||
return chooseCpuHealActions(options)[0] ?? null
|
||||
}
|
||||
|
||||
export function runCpuHealTurn<TSide>({
|
||||
side,
|
||||
elapsedTicks,
|
||||
spells,
|
||||
behavior,
|
||||
preferSlots = false,
|
||||
applySpell,
|
||||
}: {
|
||||
side: TSide & { party: PartyMember[] }
|
||||
elapsedTicks: number
|
||||
spells: Spell[]
|
||||
behavior: CpuTurnBehavior
|
||||
preferSlots?: boolean
|
||||
applySpell: (side: TSide, spell: Spell, targetId: string) => void
|
||||
}) {
|
||||
if (!shouldCpuAct({ elapsedTicks, behavior })) return false
|
||||
const action = chooseCpuHealAction({
|
||||
party: side.party,
|
||||
spells,
|
||||
behavior,
|
||||
preferSlots,
|
||||
})
|
||||
if (!action) return false
|
||||
applySpell(side, action.spell, action.targetId)
|
||||
return true
|
||||
}
|
||||
Reference in New Issue
Block a user