From 9bf6e911f4981a938a66a87daec87d5b2a6cf311 Mon Sep 17 00:00:00 2001 From: Refactoring Agent <[email protected]> Date: Sun, 26 Apr 2026 15:16:19 +0200 Subject: [PATCH] Task 2: Fix Combat UI Casting Bar progress animation --- src/lib/game/store.ts | 85 +++++++++++++++++++++++++++++- src/lib/game/utils/combat-utils.ts | 20 +++++-- 2 files changed, 99 insertions(+), 6 deletions(-) diff --git a/src/lib/game/store.ts b/src/lib/game/store.ts index e57db5e..9496246 100755 --- a/src/lib/game/store.ts +++ b/src/lib/game/store.ts @@ -2,7 +2,7 @@ import { create } from 'zustand'; import { persist } from 'zustand/middleware'; -import type { GameState, GameAction, StudyTarget, SpellCost, SkillUpgradeChoice, EquipmentSlot, EquipmentInstance, EnchantmentDesign, DesignEffect, AttunementState, FloorState, EnemyState, RoomType } from './types'; +import type { GameState, GameAction, StudyTarget, SpellCost, SkillUpgradeChoice, EquipmentSlot, EquipmentInstance, EnchantmentDesign, DesignEffect, AttunementState, FloorState, EnemyState, RoomType, EquipmentSpellState } from './types'; import { ELEMENTS, GUARDIANS, @@ -45,6 +45,7 @@ import { getSpellsFromEquipment, type CraftingActions } from './crafting-slice'; +import { getActiveEquipmentSpells, type ActiveEquipmentSpell } from './utils/combat-utils'; import { EQUIPMENT_TYPES } from './data/equipment'; import { ENCHANTMENT_EFFECTS, calculateEffectCapacityCost } from './data/enchantment-effects'; import { ATTUNEMENTS_DEF, getTotalAttunementRegen, getAttunementConversionRate, getAttunementXPForLevel, MAX_ATTUNEMENT_LEVEL } from './data/attunements'; @@ -1361,6 +1362,87 @@ export const useGameStore = create()( } } + // ─── Equipment Spell Processing ──────────────────────────────────────── + // Process casting for spells from equipped weapons + const activeEquipSpells = getActiveEquipmentSpells(state.equippedInstances, state.equipmentInstances); + + // Update equipmentSpellStates with current progress and process casts + const updatedSpellStates: EquipmentSpellState[] = []; + + for (const { spellId, equipmentId } of activeEquipSpells) { + const spellDef = SPELLS_DEF[spellId]; + if (!spellDef) continue; + + // Find or create spell state for this spell + equipment combo + let spellState = state.equipmentSpellStates.find( + s => s.spellId === spellId && s.sourceEquipment === equipmentId + ); + + if (!spellState) { + spellState = { + spellId, + sourceEquipment: equipmentId, + castProgress: 0, + }; + } + + // Only process if climbing + if (currentAction === 'climb') { + // Calculate progress per tick for this spell + const baseAttackSpeed = 1 + (state.skills.quickCast || 0) * 0.05; + const totalAttackSpeed = baseAttackSpeed * effects.attackSpeedMultiplier; + const spellCastSpeed = spellDef.castSpeed || 1; + const lightningBonus = spellDef.elem === 'lightning' ? 0.3 : 0; + const progressPerTick = HOURS_PER_TICK * spellCastSpeed * totalAttackSpeed * (1 + lightningBonus); + + // Accumulate progress + let equipCastProgress = (spellState.castProgress || 0) + progressPerTick; + + // Process complete casts + while (equipCastProgress >= 1 && canAffordSpellCost(spellDef.cost, rawMana, elements)) { + // Deduct cost + const afterCost = deductSpellCost(spellDef.cost, rawMana, elements); + rawMana = afterCost.rawMana; + elements = afterCost.elements; + totalManaGathered += spellDef.cost.amount; + + // Calculate damage + const floorElement = getFloorElement(currentFloor); + let baseDmg = calcDamage( + { ...state, skills: state.skills, signedPacts: state.signedPacts }, + spellId, + floorElement + ); + baseDmg = baseDmg * effects.baseDamageMultiplier + effects.baseDamageBonus; + + // Apply damage to enemies + const aliveEnemies = currentRoom.enemies.filter(e => e.hp > 0); + if (aliveEnemies.length > 0) { + const target = aliveEnemies[0]; // Simple: target first alive enemy + const armorPierceEffect = spellDef.effects?.find(e => e.type === 'armor_pierce'); + const armorPierce = armorPierceEffect?.value || 0; + const effectiveArmor = Math.max(0, target.armor - armorPierce); + const dmg = Math.floor(baseDmg * (1 - effectiveArmor)); + target.hp = Math.max(0, target.hp - dmg); + } + + equipCastProgress -= 1; + } + + // Update spell state with new progress + spellState = { ...spellState, castProgress: equipCastProgress }; + } + + updatedSpellStates.push(spellState); + } + + // Keep spell states for equipment that's no longer active (they'll be cleaned up elsewhere) + const inactiveStates = state.equipmentSpellStates.filter( + s => !activeEquipSpells.some(es => es.spellId === s.spellId && es.equipmentId === s.sourceEquipment) + ); + + const equipmentSpellStates = [...updatedSpellStates, ...inactiveStates]; + // ─── Golemancy Processing ───────────────────────────────────────────────── let golemancy = state.golemancy; const fabricatorLevel = state.attunements.fabricator?.level || 0; @@ -1600,6 +1682,7 @@ export const useGameStore = create()( unlockedEffects, log, castProgress, + equipmentSpellStates, golemancy, flowSurgeEndTime, comboHitCount, diff --git a/src/lib/game/utils/combat-utils.ts b/src/lib/game/utils/combat-utils.ts index d8d89a0..cfe561c 100644 --- a/src/lib/game/utils/combat-utils.ts +++ b/src/lib/game/utils/combat-utils.ts @@ -221,13 +221,19 @@ export function deductSpellCost( // ─── Equipment Spell Helpers ────────────────────────────────────────────────── +// Return type for active equipment spells with source equipment +export interface ActiveEquipmentSpell { + spellId: string; + equipmentId: string; +} + // Get active spells from equipped equipment export function getActiveEquipmentSpells( equippedInstances: Record, equipmentInstances: Record -): string[] { +): ActiveEquipmentSpell[] { const equippedIds = Object.values(equippedInstances).filter((id): id is string => id !== null); - const spells: string[] = []; + const spells: ActiveEquipmentSpell[] = []; for (const id of equippedIds) { const instance = equipmentInstances[id]; @@ -236,12 +242,16 @@ export function getActiveEquipmentSpells( for (const ench of instance.enchantments) { const effectDef = ENCHANTMENT_EFFECTS[ench.effectId]; if (effectDef?.effect.type === 'spell' && effectDef.effect.spellId) { - spells.push(effectDef.effect.spellId); + // Check if we already have this spell from this equipment + const exists = spells.some(s => s.spellId === effectDef.effect.spellId && s.equipmentId === id); + if (!exists) { + spells.push({ spellId: effectDef.effect.spellId, equipmentId: id }); + } } } } - return [...new Set(spells)]; + return spells; } // ─── DPS Calculation ────────────────────────────────────────────────────────── @@ -258,7 +268,7 @@ export function getTotalDPS( const activeSpells = getActiveEquipmentSpells(state.equippedInstances, state.equipmentInstances); // Calculate DPS for each active spell - for (const spellId of activeSpells) { + for (const { spellId } of activeSpells) { const spellDef = SPELLS_DEF[spellId]; if (!spellDef) continue;