Android build v1.1.2
This commit is contained in:
@@ -0,0 +1,170 @@
|
||||
import type { PartyMember } from '../game'
|
||||
import {
|
||||
clamp,
|
||||
healAmount,
|
||||
memberHotEffects,
|
||||
tickHotEffects,
|
||||
} from './rules'
|
||||
|
||||
export type MemberTickInput = {
|
||||
member: PartyMember
|
||||
party: PartyMember[]
|
||||
damage: number
|
||||
hotHealing: number
|
||||
hotTicks: 'effects' | 'ticks'
|
||||
healingMultiplier?: number
|
||||
damageReductionMultiplier?: number
|
||||
damageReductionRounding?: 'round' | 'ceil'
|
||||
shieldedDamageMultiplier?: number
|
||||
applyDebuff?: {
|
||||
label: string
|
||||
ticks: number
|
||||
}
|
||||
applyPoisonStacks?: boolean
|
||||
poisonDamage?: (stacks: number) => number
|
||||
applyMaxHealthPenaltyTicks?: number
|
||||
applyHealingReductionTicks?: number
|
||||
decrementDamageReduction?: boolean
|
||||
useBounceHeals?: boolean
|
||||
jumpTarget?: () => PartyMember | undefined
|
||||
}
|
||||
|
||||
export type MemberTickResult = {
|
||||
member: PartyMember
|
||||
floatingHeal: number
|
||||
jumpedBounceHeals: Array<{
|
||||
targetId: string
|
||||
heal: NonNullable<PartyMember['bounceHeals']>[number]
|
||||
}>
|
||||
}
|
||||
|
||||
/**
|
||||
* Applies one combat tick to a party member. Screen-level engines still decide
|
||||
* who is targeted and when fights end; this helper owns repeated health math:
|
||||
* damage reduction, shields, HoT healing, debuff timers, poison, and bounce heals.
|
||||
*/
|
||||
export function advanceMemberTick({
|
||||
member,
|
||||
party,
|
||||
damage,
|
||||
hotHealing,
|
||||
hotTicks,
|
||||
healingMultiplier = 1,
|
||||
damageReductionMultiplier = 0.5,
|
||||
damageReductionRounding = 'round',
|
||||
shieldedDamageMultiplier,
|
||||
applyDebuff,
|
||||
applyPoisonStacks = false,
|
||||
poisonDamage = () => 0,
|
||||
applyMaxHealthPenaltyTicks,
|
||||
applyHealingReductionTicks,
|
||||
decrementDamageReduction = false,
|
||||
useBounceHeals = false,
|
||||
jumpTarget,
|
||||
}: MemberTickInput): MemberTickResult {
|
||||
if (member.health <= 0) {
|
||||
return { member, floatingHeal: 0, jumpedBounceHeals: [] }
|
||||
}
|
||||
|
||||
let nextDamage = damage
|
||||
const nextPoisonStacks = applyPoisonStacks
|
||||
? Math.max(1, (member.poisonStacks ?? 0) + 1)
|
||||
: member.poisonStacks ?? 0
|
||||
if (nextPoisonStacks > 0) nextDamage += poisonDamage(nextPoisonStacks)
|
||||
|
||||
if ((member.damageReductionTicks ?? 0) > 0) {
|
||||
const reducedDamage = nextDamage * damageReductionMultiplier
|
||||
nextDamage = damageReductionRounding === 'ceil'
|
||||
? Math.ceil(reducedDamage)
|
||||
: Math.round(reducedDamage)
|
||||
}
|
||||
if (member.shield > 0 && shieldedDamageMultiplier !== undefined) {
|
||||
nextDamage = Math.round(nextDamage * shieldedDamageMultiplier)
|
||||
}
|
||||
|
||||
const absorbed = Math.min(member.shield, nextDamage)
|
||||
const hotEffects = hotTicks === 'effects' ? memberHotEffects(member) : []
|
||||
let healing = hotTicks === 'effects'
|
||||
? hotEffects.reduce((total, effect) => total + healAmount(member, effect.power, healingMultiplier), 0)
|
||||
: member.hotTicks > 0 ? healAmount(member, hotHealing, healingMultiplier) : 0
|
||||
|
||||
let nextBounceHeals = [...(member.bounceHeals ?? [])]
|
||||
const jumpedBounceHeals: MemberTickResult['jumpedBounceHeals'] = []
|
||||
if (useBounceHeals && nextDamage > 0 && nextBounceHeals.length > 0) {
|
||||
nextBounceHeals = nextBounceHeals.flatMap((effect) => {
|
||||
healing += healAmount(member, effect.power, healingMultiplier)
|
||||
const nextCharges = effect.charges - 1
|
||||
if (nextCharges <= 0) return []
|
||||
const target = jumpTarget?.() ?? party.find((candidate) => candidate.health > 0 && candidate.id !== member.id) ?? member
|
||||
jumpedBounceHeals.push({
|
||||
targetId: target.id,
|
||||
heal: { ...effect, charges: nextCharges },
|
||||
})
|
||||
return []
|
||||
})
|
||||
}
|
||||
|
||||
const nextMaxHealthPenaltyTicks = applyMaxHealthPenaltyTicks !== undefined
|
||||
? applyMaxHealthPenaltyTicks
|
||||
: Math.max(0, (member.maxHealthPenaltyTicks ?? 0) - 1)
|
||||
const nextHealingReductionTicks = applyHealingReductionTicks !== undefined
|
||||
? applyHealingReductionTicks
|
||||
: Math.max(0, (member.healingReductionTicks ?? 0) - 1)
|
||||
const nextDebuffTicks = applyDebuff
|
||||
? applyDebuff.ticks
|
||||
: Math.max(0, (member.debuffTicks ?? 0) - 1)
|
||||
const nextEffectiveMaxHealth = Math.max(1, Math.round(
|
||||
member.maxHealth * (nextMaxHealthPenaltyTicks > 0 ? 0.75 : 1),
|
||||
))
|
||||
|
||||
return {
|
||||
member: {
|
||||
...member,
|
||||
health: clamp(
|
||||
clamp(member.health + healing, 0, nextEffectiveMaxHealth) - nextDamage + absorbed,
|
||||
0,
|
||||
nextEffectiveMaxHealth,
|
||||
),
|
||||
shield: Math.max(0, member.shield - nextDamage),
|
||||
hotTicks: hotTicks === 'effects' ? 0 : Math.max(0, member.hotTicks - 1),
|
||||
hotEffects: hotTicks === 'effects' ? tickHotEffects(hotEffects) : member.hotEffects,
|
||||
bounceHeals: useBounceHeals ? nextBounceHeals : member.bounceHeals,
|
||||
damageReductionTicks: decrementDamageReduction
|
||||
? Math.max(0, (member.damageReductionTicks ?? 0) - 1)
|
||||
: member.damageReductionTicks,
|
||||
debuff: nextDebuffTicks > 0
|
||||
? applyDebuff?.label ?? member.debuff
|
||||
: undefined,
|
||||
debuffTicks: nextDebuffTicks > 0 ? nextDebuffTicks : undefined,
|
||||
poisonStacks: nextPoisonStacks,
|
||||
maxHealthPenaltyTicks: nextMaxHealthPenaltyTicks,
|
||||
healingReductionTicks: nextHealingReductionTicks,
|
||||
},
|
||||
floatingHeal: healing,
|
||||
jumpedBounceHeals,
|
||||
}
|
||||
}
|
||||
|
||||
export function attachJumpedBounceHeals(
|
||||
party: PartyMember[],
|
||||
jumpedBounceHeals: MemberTickResult['jumpedBounceHeals'],
|
||||
) {
|
||||
if (jumpedBounceHeals.length === 0) return party
|
||||
const jumpedByTarget = new Map<string, NonNullable<PartyMember['bounceHeals']>>()
|
||||
for (const jump of jumpedBounceHeals) {
|
||||
const current = jumpedByTarget.get(jump.targetId)
|
||||
if (current) current.push(jump.heal)
|
||||
else jumpedByTarget.set(jump.targetId, [jump.heal])
|
||||
}
|
||||
return party.map((member) => {
|
||||
const jumped = jumpedByTarget.get(member.id)
|
||||
if (!jumped || jumped.length === 0) return member
|
||||
return {
|
||||
...member,
|
||||
bounceHeals: [
|
||||
...(member.bounceHeals ?? []),
|
||||
...jumped,
|
||||
],
|
||||
}
|
||||
})
|
||||
}
|
||||
Reference in New Issue
Block a user