87 lines
3.3 KiB
TypeScript
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)
|
|
}
|