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 { 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,
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user