Task 2: Fix Combat UI Casting Bar progress animation

This commit is contained in:
Refactoring Agent
2026-04-26 15:16:19 +02:00
parent 50ce70efdd
commit 9bf6e911f4
2 changed files with 99 additions and 6 deletions
+84 -1
View File
@@ -2,7 +2,7 @@
import { create } from 'zustand'; import { create } from 'zustand';
import { persist } from 'zustand/middleware'; 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 { import {
ELEMENTS, ELEMENTS,
GUARDIANS, GUARDIANS,
@@ -45,6 +45,7 @@ import {
getSpellsFromEquipment, getSpellsFromEquipment,
type CraftingActions type CraftingActions
} from './crafting-slice'; } from './crafting-slice';
import { getActiveEquipmentSpells, type ActiveEquipmentSpell } from './utils/combat-utils';
import { EQUIPMENT_TYPES } from './data/equipment'; import { EQUIPMENT_TYPES } from './data/equipment';
import { ENCHANTMENT_EFFECTS, calculateEffectCapacityCost } from './data/enchantment-effects'; import { ENCHANTMENT_EFFECTS, calculateEffectCapacityCost } from './data/enchantment-effects';
import { ATTUNEMENTS_DEF, getTotalAttunementRegen, getAttunementConversionRate, getAttunementXPForLevel, MAX_ATTUNEMENT_LEVEL } from './data/attunements'; import { ATTUNEMENTS_DEF, getTotalAttunementRegen, getAttunementConversionRate, getAttunementXPForLevel, MAX_ATTUNEMENT_LEVEL } from './data/attunements';
@@ -1361,6 +1362,87 @@ export const useGameStore = create<GameStore>()(
} }
} }
// ─── 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 ───────────────────────────────────────────────── // ─── Golemancy Processing ─────────────────────────────────────────────────
let golemancy = state.golemancy; let golemancy = state.golemancy;
const fabricatorLevel = state.attunements.fabricator?.level || 0; const fabricatorLevel = state.attunements.fabricator?.level || 0;
@@ -1600,6 +1682,7 @@ export const useGameStore = create<GameStore>()(
unlockedEffects, unlockedEffects,
log, log,
castProgress, castProgress,
equipmentSpellStates,
golemancy, golemancy,
flowSurgeEndTime, flowSurgeEndTime,
comboHitCount, comboHitCount,
+15 -5
View File
@@ -221,13 +221,19 @@ export function deductSpellCost(
// ─── Equipment Spell Helpers ────────────────────────────────────────────────── // ─── 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 // Get active spells from equipped equipment
export function getActiveEquipmentSpells( export function getActiveEquipmentSpells(
equippedInstances: Record<string, string | null>, equippedInstances: Record<string, string | null>,
equipmentInstances: Record<string, EquipmentInstance> equipmentInstances: Record<string, EquipmentInstance>
): string[] { ): ActiveEquipmentSpell[] {
const equippedIds = Object.values(equippedInstances).filter((id): id is string => id !== null); const equippedIds = Object.values(equippedInstances).filter((id): id is string => id !== null);
const spells: string[] = []; const spells: ActiveEquipmentSpell[] = [];
for (const id of equippedIds) { for (const id of equippedIds) {
const instance = equipmentInstances[id]; const instance = equipmentInstances[id];
@@ -236,12 +242,16 @@ export function getActiveEquipmentSpells(
for (const ench of instance.enchantments) { for (const ench of instance.enchantments) {
const effectDef = ENCHANTMENT_EFFECTS[ench.effectId]; const effectDef = ENCHANTMENT_EFFECTS[ench.effectId];
if (effectDef?.effect.type === 'spell' && effectDef.effect.spellId) { 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 ────────────────────────────────────────────────────────── // ─── DPS Calculation ──────────────────────────────────────────────────────────
@@ -258,7 +268,7 @@ export function getTotalDPS(
const activeSpells = getActiveEquipmentSpells(state.equippedInstances, state.equipmentInstances); const activeSpells = getActiveEquipmentSpells(state.equippedInstances, state.equipmentInstances);
// Calculate DPS for each active spell // Calculate DPS for each active spell
for (const spellId of activeSpells) { for (const { spellId } of activeSpells) {
const spellDef = SPELLS_DEF[spellId]; const spellDef = SPELLS_DEF[spellId];
if (!spellDef) continue; if (!spellDef) continue;