Task 2: Fix Combat UI Casting Bar progress animation
This commit is contained in:
+84
-1
@@ -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<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 ─────────────────────────────────────────────────
|
||||
let golemancy = state.golemancy;
|
||||
const fabricatorLevel = state.attunements.fabricator?.level || 0;
|
||||
@@ -1600,6 +1682,7 @@ export const useGameStore = create<GameStore>()(
|
||||
unlockedEffects,
|
||||
log,
|
||||
castProgress,
|
||||
equipmentSpellStates,
|
||||
golemancy,
|
||||
flowSurgeEndTime,
|
||||
comboHitCount,
|
||||
|
||||
@@ -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<string, string | null>,
|
||||
equipmentInstances: Record<string, EquipmentInstance>
|
||||
): 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;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user