Files
i-want-to-heal/src/modes/iwt2/sim/equipmentStats.ts
T
2026-07-06 11:46:53 -04:00

87 lines
3.3 KiB
TypeScript

import type { Iwt2HealerAbility } from '../content/healerAbilities'
import {
IWT2_GEAR_SLOT_RECIPES,
IWT2_GEAR_SLOTS,
type Iwt2GearProgress,
} from '../content/gear'
import type { Iwt2ArenaState, Iwt2PartyEntityState } from './types'
import { applyIwt2InfusionLoadout } from './infusionLoadout'
const PER_LEVEL_OUTPUT = 0.04
const PER_LEVEL_HEALTH = 0.04
const PER_LEVEL_SPEED = 0.025
const PER_LEVEL_COOLDOWN = 0.03
const PER_LEVEL_PROJECTILE = 0.03
const PER_LEVEL_DAMAGE_TAKEN_REDUCTION = 0.03
const PER_LEVEL_STUN_REDUCTION = 0.08
export function applyIwt2PveGearStats<TState extends Pick<Iwt2ArenaState, 'party'>>(state: TState, gearProgress: Iwt2GearProgress): TState {
const party = state.party.map((member) => applyMemberGearStats(member, gearProgress))
return applyIwt2InfusionLoadout({
...state,
party,
}, gearProgress)
}
export function applyIwt2PveGearToHealerAbilities(
abilities: Iwt2HealerAbility[],
gearProgress: Iwt2GearProgress,
): Iwt2HealerAbility[] {
const healer = gearProgress.healer
const healingLevel = healer.slots.weapon.level
const cooldownLevel = healer.slots.helmet.level
if (healingLevel <= 0 && cooldownLevel <= 0) return abilities
return abilities.map((ability) => ({
...ability,
cooldownSeconds: roundOne(ability.cooldownSeconds * (1 - cooldownLevel * PER_LEVEL_COOLDOWN)),
power: Math.max(1, Math.round(ability.power * (1 + healingLevel * PER_LEVEL_OUTPUT))),
}))
}
function applyMemberGearStats(
member: Iwt2PartyEntityState,
gearProgress: Iwt2GearProgress,
): Iwt2PartyEntityState {
const classProgress = gearProgress[member.classId]
let next: Iwt2PartyEntityState = {
...member,
gearLevels: {
weapon: classProgress.slots.weapon.level,
helmet: classProgress.slots.helmet.level,
chest: classProgress.slots.chest.level,
legs: classProgress.slots.legs.level,
feet: classProgress.slots.feet.level,
},
}
for (const slotId of IWT2_GEAR_SLOTS) {
const level = classProgress.slots[slotId].level
if (level <= 0) continue
const statId = IWT2_GEAR_SLOT_RECIPES[member.classId][slotId].statId
if (statId === 'maxHealth') {
const maxHealth = Math.round(next.maxHealth * (1 + level * PER_LEVEL_HEALTH))
next = {
...next,
health: Math.min(maxHealth, Math.round(next.health * (maxHealth / next.maxHealth))),
maxHealth,
}
} else if (statId === 'moveSpeed') {
next = { ...next, moveSpeed: next.moveSpeed * (1 + level * PER_LEVEL_SPEED) }
} else if (statId === 'damage') {
next = { ...next, attackDamage: next.attackDamage * (1 + level * PER_LEVEL_OUTPUT) }
} else if (statId === 'attackCooldown') {
next = { ...next, attackCooldown: next.attackCooldown * (1 - level * PER_LEVEL_COOLDOWN) }
} else if (statId === 'projectileSpeed') {
next = { ...next, projectileSpeed: next.projectileSpeed * (1 + level * PER_LEVEL_PROJECTILE) }
} else if (statId === 'hazardDamageTaken') {
next = { ...next, damageTakenScale: next.damageTakenScale * (1 - level * PER_LEVEL_DAMAGE_TAKEN_REDUCTION) }
} else if (statId === 'stunResist') {
next = { ...next, stunTakenScale: Math.max(0, next.stunTakenScale * (1 - level * PER_LEVEL_STUN_REDUCTION)) }
}
}
return next
}
function roundOne(value: number): number {
return Math.max(0.1, Math.round(value * 10) / 10)
}