Files
i-want-to-heal/src/game.ts
T
2026-06-20 16:10:35 -04:00

210 lines
6.4 KiB
TypeScript

export type Role = 'Tank' | 'Healer' | 'Damage'
export type PartyMember = {
id: string
name: string
role: Role
health: number
maxHealth: number
shield: number
hotTicks: number
hotEffects?: Array<{
id: string
spellId: string
label: string
ticks: number
power: number
}>
bounceHeals?: Array<{
id: string
label: string
charges: number
power: number
}>
damageReductionTicks?: number
debuff?: string
debuffTicks?: number
poisonStacks?: number
maxHealthPenaltyTicks?: number
healingReductionTicks?: number
}
export type Spell = {
id: string
key: string
name: string
description: string
cost: number
cooldown: number
power: number
glyph: string
kind: 'direct' | 'hot' | 'group' | 'shield' | 'cleanse' | 'damage_reduction' | 'bounce_heal'
effectType?: string
}
export type Encounter = {
id: string
enemyName: string
description: string
maxHealth: number
damage: number
tankDamage: number
partyDamage: number
isBoss: boolean
}
export type CombatLogEntry = {
id: number
text: string
tone: 'system' | 'heal' | 'danger' | 'loot'
}
export const TANKLESS_DAMAGE_MULTIPLIER = 1.35
export const DEFAULT_GROUP_HEAL_TARGETS = 4
export const INITIAL_PARTY: PartyMember[] = [
{ id: 'brann', name: 'Brann', role: 'Tank', health: 150, maxHealth: 150, shield: 0, hotTicks: 0 },
{ id: 'mira', name: 'Mira', role: 'Healer', health: 100, maxHealth: 100, shield: 0, hotTicks: 0 },
{ id: 'kael', name: 'Kael', role: 'Damage', health: 105, maxHealth: 105, shield: 0, hotTicks: 0 },
{ id: 'ves', name: 'Ves', role: 'Damage', health: 95, maxHealth: 95, shield: 0, hotTicks: 0 },
{ id: 'orin', name: 'Orin', role: 'Damage', health: 110, maxHealth: 110, shield: 0, hotTicks: 0 },
{ id: 'lyra', name: 'Lyra', role: 'Damage', health: 98, maxHealth: 98, shield: 0, hotTicks: 0 },
]
export const RAID_PARTY: PartyMember[] = [
{ id: 'brann', name: 'Brann', role: 'Tank', health: 165, maxHealth: 165, shield: 0, hotTicks: 0 },
{ id: 'tala', name: 'Tala', role: 'Tank', health: 155, maxHealth: 155, shield: 0, hotTicks: 0 },
{ id: 'mira', name: 'Mira', role: 'Healer', health: 100, maxHealth: 100, shield: 0, hotTicks: 0 },
{ id: 'seren', name: 'Seren', role: 'Healer', health: 102, maxHealth: 102, shield: 0, hotTicks: 0 },
{ id: 'kael', name: 'Kael', role: 'Damage', health: 105, maxHealth: 105, shield: 0, hotTicks: 0 },
{ id: 'ves', name: 'Ves', role: 'Damage', health: 95, maxHealth: 95, shield: 0, hotTicks: 0 },
{ id: 'orin', name: 'Orin', role: 'Damage', health: 110, maxHealth: 110, shield: 0, hotTicks: 0 },
{ id: 'lyra', name: 'Lyra', role: 'Damage', health: 98, maxHealth: 98, shield: 0, hotTicks: 0 },
{ id: 'dax', name: 'Dax', role: 'Damage', health: 108, maxHealth: 108, shield: 0, hotTicks: 0 },
{ id: 'nyx', name: 'Nyx', role: 'Damage', health: 100, maxHealth: 100, shield: 0, hotTicks: 0 },
{ id: 'ren', name: 'Ren', role: 'Damage', health: 104, maxHealth: 104, shield: 0, hotTicks: 0 },
{ id: 'iona', name: 'Iona', role: 'Damage', health: 96, maxHealth: 96, shield: 0, hotTicks: 0 },
{ id: 'sol', name: 'Sol', role: 'Damage', health: 106, maxHealth: 106, shield: 0, hotTicks: 0 },
{ id: 'nari', name: 'Nari', role: 'Damage', health: 99, maxHealth: 99, shield: 0, hotTicks: 0 },
{ id: 'joss', name: 'Joss', role: 'Damage', health: 103, maxHealth: 103, shield: 0, hotTicks: 0 },
{ id: 'eira', name: 'Eira', role: 'Damage', health: 97, maxHealth: 97, shield: 0, hotTicks: 0 },
{ id: 'cato', name: 'Cato', role: 'Damage', health: 107, maxHealth: 107, shield: 0, hotTicks: 0 },
{ id: 'rhea', name: 'Rhea', role: 'Damage', health: 101, maxHealth: 101, shield: 0, hotTicks: 0 },
]
export const SPELLS: Spell[] = [
{
id: 'mend',
key: '1',
name: 'Mend',
description: 'A fast, efficient single-target heal.',
cost: 5,
cooldown: 0.5,
power: 30,
glyph: '+',
kind: 'direct',
},
{
id: 'renew',
key: '2',
name: 'Renew',
description: 'Heals now and continues healing over time.',
cost: 7,
cooldown: 0.5,
power: 12,
glyph: '~',
kind: 'hot',
},
{
id: 'radiance',
key: '3',
name: 'Radiance',
description: 'Restores health to up to 4 injured party members.',
cost: 12,
cooldown: 8,
power: 18,
glyph: '*',
kind: 'group',
},
{
id: 'ward',
key: '4',
name: 'Sun Ward',
description: 'Places a damage-absorbing shield on your target.',
cost: 8,
cooldown: 7,
power: 36,
glyph: 'O',
kind: 'shield',
},
{
id: 'purify',
key: '5',
name: 'Purify',
description: 'Removes a harmful effect and restores a little health.',
cost: 5,
cooldown: 5,
power: 10,
glyph: 'x',
kind: 'cleanse',
},
]
export const ENCOUNTERS: Encounter[] = [
{
id: 'ashfang-pack',
enemyName: 'Ashfang Pack',
description: 'Three beasts snap at random party members.',
maxHealth: 390,
damage: 13,
tankDamage: 7,
partyDamage: 24,
isBoss: false,
},
{
id: 'cinder-adepts',
enemyName: 'Cinder Adepts',
description: 'Cultists pressure the tank while throwing embers into the group.',
maxHealth: 470,
damage: 16,
tankDamage: 10,
partyDamage: 25,
isBoss: false,
},
{
id: 'warden-vhal',
enemyName: 'Warden Vhal',
description: 'Boss: cleanse Searing Mark and prepare group healing for Cinder Pulse.',
maxHealth: 820,
damage: 18,
tankDamage: 13,
partyDamage: 27,
isBoss: true,
},
]
export function partyDamageOutput(party: PartyMember[], baseDamage: number) {
const livingCount = party.filter((member) => member.health > 0).length
return Math.round(baseDamage * (livingCount / Math.max(1, party.length)))
}
export function tankPressureTargets(party: PartyMember[]) {
const living = party.filter((member) => member.health > 0)
const tanks = living.filter((member) => member.role === 'Tank')
if (tanks.length > 0) return { targets: tanks, multiplier: 1 }
const damageDealer = living
.filter((member) => member.role === 'Damage')
.sort((left, right) => right.health - left.health)[0]
return {
targets: damageDealer ? [damageDealer] : [],
multiplier: TANKLESS_DAMAGE_MULTIPLIER,
}
}
export function groupHealTargets(party: PartyMember[], targetCount = DEFAULT_GROUP_HEAL_TARGETS) {
return party
.filter((member) => member.health > 0)
.sort((left, right) => (left.health / left.maxHealth) - (right.health / right.maxHealth))
.slice(0, targetCount)
}