import { ARENA_PRIEST_SPELL_BAR, SPELLS, type SpellSlot, } from './actionBoss/actionCombatSimulation' import { SPELL_MANA_COSTS } from './actionCombatCore' export type ArenaTeamId = 'player' | 'opponent' export type ArenaPhase = 'setup' | 'fighting' | 'upgrade' | 'complete' export type ArenaUpgradeId = | 'all-target' | 'all-cost' | 'all-cooldown' | `slot-${SpellSlot}-cost` | `slot-${SpellSlot}-cooldown` | 'slot-1-renew' | 'slot-1-shield' | 'slot-2-shield' | 'slot-2-duration' | 'slot-3-half-shield' | 'slot-3-renew' | 'slot-4-renew' | 'slot-5-renew' | 'slot-5-shield' | 'stored-momentum' | 'wide-radiance' | 'dense-shields' | 'atonement' export type ArenaUpgrade = { id: ArenaUpgradeId label: string scope: string description: string cost: number } export type ArenaMatchState = { phase: ArenaPhase round: number playerName: string opponentName: string playerWins: number opponentWins: number playerCredits: number opponentCredits: number playerUpgrades: ArenaUpgradeId[] opponentUpgrades: ArenaUpgradeId[] history: string[] } const FALLBACK_SPELL_NAMES: Partial> = { 1: 'Smite', 2: 'Power Word: Fortitude', 3: 'Shadow Word: Pain', 4: 'Power Word: Shield', 5: 'Renew', 6: 'Mind Blast', 7: 'Heal', 8: 'Mind Flay', 9: 'Flash Heal', } function getSlotCostUpgradeId(slot: SpellSlot): ArenaUpgradeId { return `slot-${slot}-cost` } function getSlotCooldownUpgradeId(slot: SpellSlot): ArenaUpgradeId { return `slot-${slot}-cooldown` } function createSlotEconomyUpgrades(slot: SpellSlot): ArenaUpgrade[] { const name = getArenaSpellName(slot) return [ { id: getSlotCostUpgradeId(slot), label: `${name}: -25% Mana Cost`, scope: `Slot ${slot}`, description: `${name} costs 25% less mana.`, cost: 1, }, { id: getSlotCooldownUpgradeId(slot), label: `${name}: -25% Cooldown`, scope: `Slot ${slot}`, description: `${name} cooldown recovers 25% faster.`, cost: 1, }, ] } const ARENA_UPGRADES: ArenaUpgrade[] = [ { id: 'all-target', label: 'Every Slot: +1 Target', scope: 'Every Slot', description: 'Friendly ClaudeCraft priest spells reach one extra ally.', cost: 2, }, { id: 'all-cost', label: 'Every Slot: -25% Cost', scope: 'Every Slot', description: 'All spell energy costs are reduced by 25%.', cost: 1, }, { id: 'all-cooldown', label: 'Every Slot: -25% Cooldown', scope: 'Every Slot', description: 'All spell cooldowns recover faster.', cost: 1, }, { id: 'slot-1-renew', label: `${getArenaSpellName(1)} Applies Renew`, scope: 'Slot 1', description: 'Slot 1 also applies Renew to its targets.', cost: 2, }, { id: 'slot-1-shield', label: `${getArenaSpellName(1)} Applies Shield`, scope: 'Slot 1', description: 'Slot 1 also grants a shield.', cost: 2, }, ...createSlotEconomyUpgrades(1), { id: 'slot-2-shield', label: `${getArenaSpellName(2)} Applies Shield`, scope: 'Slot 2', description: 'Slot 2 also grants a shield.', cost: 2, }, { id: 'slot-2-duration', label: `${getArenaSpellName(2)} Double Duration`, scope: 'Slot 2', description: 'Slot 2 Renew lasts twice as long.', cost: 2, }, ...createSlotEconomyUpgrades(2), { id: 'slot-3-half-shield', label: `${getArenaSpellName(3)} Applies 50% Shield`, scope: 'Slot 3', description: 'Slot 3 also grants a half-strength shield.', cost: 2, }, { id: 'slot-3-renew', label: `${getArenaSpellName(3)} Applies Renew`, scope: 'Slot 3', description: 'Slot 3 also applies Renew to healed targets.', cost: 2, }, ...createSlotEconomyUpgrades(3), { id: 'slot-4-renew', label: `${getArenaSpellName(4)} Applies Renew`, scope: 'Slot 4', description: 'Slot 4 also applies Renew.', cost: 2, }, ...createSlotEconomyUpgrades(4), { id: 'slot-5-renew', label: `${getArenaSpellName(5)} Applies Renew`, scope: 'Slot 5', description: 'Slot 5 also applies Renew.', cost: 2, }, { id: 'slot-5-shield', label: `${getArenaSpellName(5)} Applies Shield`, scope: 'Slot 5', description: 'Slot 5 also grants a shield.', cost: 2, }, ...createSlotEconomyUpgrades(5), ...createSlotEconomyUpgrades(6), ...createSlotEconomyUpgrades(7), ...createSlotEconomyUpgrades(8), ...createSlotEconomyUpgrades(9), ...createSlotEconomyUpgrades(10), { id: 'stored-momentum', label: 'Stored Momentum', scope: 'Misc', description: 'After 5 spell casts, the next cast is free.', cost: 1, }, { id: 'atonement', label: 'Atonement', scope: 'Priest', description: 'Smite and Shadow Word: Pain heal the lowest-health ally for 100% of damage dealt.', cost: 3, }, { id: 'wide-radiance', label: 'Stronger Heal', scope: 'Misc', description: 'Heal is 25% stronger.', cost: 1, }, { id: 'dense-shields', label: 'Dense Shields', scope: 'Misc', description: 'Shields absorb 25% more damage.', cost: 1, }, ] export function getArenaUpgrades() { return ARENA_UPGRADES } export function getArenaSpellName(slot: SpellSlot) { return SPELLS[slot]?.name || FALLBACK_SPELL_NAMES[slot] || `Slot ${slot}` } export function getArenaSpellCostMultiplier(upgrades: ArenaUpgradeId[], spell: SpellSlot) { let multiplier = upgrades.includes('all-cost') ? 0.75 : 1 if (upgrades.includes(getSlotCostUpgradeId(spell))) multiplier *= 0.75 return multiplier } export function getArenaSpellCooldownMultiplier(upgrades: ArenaUpgradeId[], spell: SpellSlot) { let multiplier = upgrades.includes('all-cooldown') ? 0.75 : 1 if (upgrades.includes(getSlotCooldownUpgradeId(spell))) multiplier *= 0.75 return multiplier } export function getArenaSpellLoadout() { return ARENA_PRIEST_SPELL_BAR.map((slot) => ({ slot, name: getArenaSpellName(slot), cost: SPELL_MANA_COSTS[slot], cooldown: SPELLS[slot]?.cooldown ?? 0, })) } export function createArenaMatchState(playerName = 'Action Healer'): ArenaMatchState { return { phase: 'setup', round: 1, playerName, opponentName: 'Rival Five', playerWins: 0, opponentWins: 0, playerCredits: 0, opponentCredits: 0, playerUpgrades: [], opponentUpgrades: [], history: [], } } export function resolveArenaRoundWinner( match: ArenaMatchState, winner: 'player' | 'opponent', elapsedSeconds: number, ): ArenaMatchState { const playerWon = winner === 'player' const playerWins = match.playerWins + (playerWon ? 1 : 0) const opponentWins = match.opponentWins + (playerWon ? 0 : 1) const complete = playerWins >= 3 || opponentWins >= 3 const winnerName = playerWon ? match.playerName : match.opponentName const playerPointAward = playerWon ? 3 : 4 const opponentPointAward = playerWon ? 4 : 3 const opponentDraft = complete ? { upgrades: match.opponentUpgrades, credits: match.opponentCredits + opponentPointAward, picked: null } : pickOpponentUpgrade(match.opponentUpgrades, match.opponentCredits + opponentPointAward) return { ...match, phase: complete ? 'complete' : 'upgrade', round: complete ? match.round : match.round + 1, playerWins, opponentWins, playerCredits: match.playerCredits + playerPointAward, opponentCredits: opponentDraft.credits, opponentUpgrades: opponentDraft.upgrades, history: [ `${winnerName} won round ${match.round} in ${elapsedSeconds.toFixed(1)}s.`, ...(opponentDraft.picked ? [`${match.opponentName} drafted ${getUpgradeLabel(opponentDraft.picked)}.`] : []), ...match.history, ].slice(0, 6), } } export function buyArenaUpgrade(match: ArenaMatchState, upgradeId: ArenaUpgradeId): ArenaMatchState { const upgrade = ARENA_UPGRADES.find((item) => item.id === upgradeId) if (!upgrade) return match if (match.playerUpgrades.includes(upgradeId)) { return { ...match, playerCredits: match.playerCredits + upgrade.cost, playerUpgrades: match.playerUpgrades.filter((id) => id !== upgradeId), history: [`${match.playerName} removed ${upgrade.label}.`, ...match.history].slice(0, 6), } } if (match.playerCredits < upgrade.cost) return match return { ...match, playerCredits: match.playerCredits - upgrade.cost, playerUpgrades: [...match.playerUpgrades, upgradeId], history: [`${match.playerName} drafted ${upgrade.label}.`, ...match.history].slice(0, 6), } } export function startNextArenaRound(match: ArenaMatchState): ArenaMatchState { return { ...match, phase: 'fighting', } } export function getArenaAvailableUpgrades(match: ArenaMatchState) { return ARENA_UPGRADES.map((upgrade) => ({ ...upgrade, owned: match.playerUpgrades.includes(upgrade.id), affordable: match.playerCredits >= upgrade.cost, })) } function pickOpponentUpgrade(upgrades: ArenaUpgradeId[], credits: number) { const priority: ArenaUpgradeId[] = [ 'all-cooldown', 'wide-radiance', 'all-cost', 'slot-1-cost', 'slot-1-cooldown', 'slot-3-cost', 'slot-3-cooldown', 'slot-4-renew', 'dense-shields', 'all-target', 'slot-3-half-shield', 'stored-momentum', ] const picked = priority.find((id) => { const upgrade = ARENA_UPGRADES.find((item) => item.id === id) return upgrade && !upgrades.includes(id) && credits >= upgrade.cost }) ?? null if (!picked) return { upgrades, credits, picked } const cost = ARENA_UPGRADES.find((upgrade) => upgrade.id === picked)?.cost ?? 0 return { upgrades: [...upgrades, picked], credits: credits - cost, picked, } } function getUpgradeLabel(id: ArenaUpgradeId) { return ARENA_UPGRADES.find((upgrade) => upgrade.id === id)?.label ?? id }