133 lines
4.0 KiB
TypeScript
133 lines
4.0 KiB
TypeScript
import type { PartyMember, Spell } from '../game'
|
|
import type { Ability } from '../profile'
|
|
import { createStackCounts, stackCount } from './stackCounts'
|
|
|
|
export const DEFAULT_TICK_MS = 700
|
|
|
|
export function clamp(value: number, min: number, max: number) {
|
|
return Math.min(max, Math.max(min, value))
|
|
}
|
|
|
|
export function chooseRandom<T>(items: T[], count: number) {
|
|
const pool = [...items]
|
|
const result: T[] = []
|
|
while (pool.length > 0 && result.length < count) {
|
|
const index = Math.floor(Math.random() * pool.length)
|
|
result.push(pool.splice(index, 1)[0])
|
|
}
|
|
return result
|
|
}
|
|
|
|
export function effectiveMaxHealth(member: PartyMember) {
|
|
return Math.max(
|
|
1,
|
|
Math.round(member.maxHealth * (member.maxHealthPenaltyTicks && member.maxHealthPenaltyTicks > 0 ? 0.75 : 1)),
|
|
)
|
|
}
|
|
|
|
export function healAmount(member: PartyMember, amount: number, multiplier = 1) {
|
|
return Math.round(amount * (member.healingReductionTicks && member.healingReductionTicks > 0 ? 0.75 : 1) * multiplier)
|
|
}
|
|
|
|
export function healMember(member: PartyMember, amount: number, multiplier = 1) {
|
|
return clamp(member.health + healAmount(member, amount, multiplier), 0, effectiveMaxHealth(member))
|
|
}
|
|
|
|
export function memberHotEffects(member: PartyMember) {
|
|
if (member.hotEffects?.length) return member.hotEffects
|
|
return member.hotTicks > 0
|
|
? [{ id: 'legacy-renew', spellId: 'legacy-renew', label: 'Renew', ticks: member.hotTicks, power: 6 }]
|
|
: []
|
|
}
|
|
|
|
export function effectId(prefix: string) {
|
|
return `${prefix}-${globalThis.crypto?.randomUUID?.() ?? `${Date.now()}-${Math.random()}`}`
|
|
}
|
|
|
|
export function addHotEffect(member: PartyMember, spell: Spell, ticks = 5) {
|
|
const nextEffect = {
|
|
id: effectId(spell.id),
|
|
spellId: spell.id,
|
|
label: spell.name,
|
|
ticks,
|
|
power: Math.max(1, Math.round(spell.power / 2)),
|
|
}
|
|
const currentEffects = memberHotEffects(member).filter((effect) => effect.spellId !== spell.id)
|
|
return [...currentEffects, nextEffect]
|
|
}
|
|
|
|
export function addBounceHeal(member: PartyMember, spell: Spell) {
|
|
return [
|
|
...(member.bounceHeals ?? []),
|
|
{
|
|
id: effectId(spell.id),
|
|
label: spell.name,
|
|
charges: 4,
|
|
power: spell.power,
|
|
},
|
|
]
|
|
}
|
|
|
|
export function tickHotEffects(effects: PartyMember['hotEffects']) {
|
|
return (effects ?? [])
|
|
.map((effect) => ({ ...effect, ticks: effect.ticks - 1 }))
|
|
.filter((effect) => effect.ticks > 0)
|
|
}
|
|
|
|
export function formatEffectTime(ticks: number, tickMs = DEFAULT_TICK_MS) {
|
|
const seconds = (ticks * tickMs) / 1000
|
|
return Number.isInteger(seconds) ? `${seconds}s` : `${seconds.toFixed(1)}s`
|
|
}
|
|
|
|
export function buffStacks<T extends string>(items: readonly T[], id: T) {
|
|
return stackCount(createStackCounts(items), id)
|
|
}
|
|
|
|
export function createLogEntry(nextLogId: { current: number }, text: string, tone: 'system' | 'heal' | 'danger' | 'loot') {
|
|
return { id: nextLogId.current++, text, tone }
|
|
}
|
|
|
|
export function toCombatSpell(ability: Ability, key: string, healingPower = 0): Spell {
|
|
const kinds: Record<string, Spell['kind']> = {
|
|
direct_heal: 'direct',
|
|
direct_hot: 'direct',
|
|
heal_over_time: 'hot',
|
|
party_heal: 'group',
|
|
party_hot: 'group',
|
|
party_absorb: 'group',
|
|
absorb: 'shield',
|
|
damage_reduction: 'damage_reduction',
|
|
bounce_heal: 'bounce_heal',
|
|
cleanse: 'cleanse',
|
|
}
|
|
return {
|
|
id: String(ability.id),
|
|
key,
|
|
name: ability.name,
|
|
description: ability.description,
|
|
cost: ability.cost,
|
|
cooldown: ability.cooldown,
|
|
power: ability.power + healingPower,
|
|
glyph: ability.glyph,
|
|
kind: kinds[ability.spellType] ?? 'direct',
|
|
effectType: ability.spellType,
|
|
}
|
|
}
|
|
|
|
export function resetParty(partyTemplate: PartyMember[]) {
|
|
return partyTemplate.map((member) => ({
|
|
...member,
|
|
health: member.maxHealth,
|
|
shield: 0,
|
|
hotTicks: 0,
|
|
hotEffects: undefined,
|
|
debuff: undefined,
|
|
debuffTicks: undefined,
|
|
poisonStacks: undefined,
|
|
maxHealthPenaltyTicks: undefined,
|
|
healingReductionTicks: undefined,
|
|
damageReductionTicks: undefined,
|
|
bounceHeals: undefined,
|
|
}))
|
|
}
|