fix: migrate golemancy/activity/achievements, fix CraftingTab/SkillsTab/SpireTab/GolemancyTab/AchievementsTab store reads
Build and Publish Mana Loop Docker Image / build-and-publish (push) Successful in 2m1s
Build and Publish Mana Loop Docker Image / build-and-publish (push) Successful in 2m1s
This commit is contained in:
@@ -386,6 +386,7 @@ Mana-Loop/
|
|||||||
│ │ │ │ ├── store-methods.test.ts
|
│ │ │ │ ├── store-methods.test.ts
|
||||||
│ │ │ │ └── stores.test.ts
|
│ │ │ │ └── stores.test.ts
|
||||||
│ │ │ ├── attunementStore.ts
|
│ │ │ ├── attunementStore.ts
|
||||||
|
│ │ │ ├── combat-actions.ts
|
||||||
│ │ │ ├── combatStore.ts
|
│ │ │ ├── combatStore.ts
|
||||||
│ │ │ ├── craftingStore.ts
|
│ │ │ ├── craftingStore.ts
|
||||||
│ │ │ ├── gameActions.ts
|
│ │ │ ├── gameActions.ts
|
||||||
|
|||||||
@@ -11,15 +11,15 @@ import { CRAFTING_RECIPES, canCraftRecipe } from '@/lib/game/data/crafting-recip
|
|||||||
import { LOOT_DROPS, RARITY_COLORS } from '@/lib/game/data/loot-drops';
|
import { LOOT_DROPS, RARITY_COLORS } from '@/lib/game/data/loot-drops';
|
||||||
import type { EquipmentInstance, AppliedEnchantment, LootInventory, EquipmentCraftingProgress } from '@/lib/game/types';
|
import type { EquipmentInstance, AppliedEnchantment, LootInventory, EquipmentCraftingProgress } from '@/lib/game/types';
|
||||||
import { fmt } from '@/lib/game/stores';
|
import { fmt } from '@/lib/game/stores';
|
||||||
import { useGameStore, useCraftingStore } from '@/lib/game/stores';
|
import { useCraftingStore, useCombatStore, useManaStore } from '@/lib/game/stores';
|
||||||
|
|
||||||
export function EquipmentCrafter() {
|
export function EquipmentCrafter() {
|
||||||
const lootInventory = useCraftingStore((s) => s.lootInventory);
|
const lootInventory = useCraftingStore((s) => s.lootInventory);
|
||||||
const equipmentCraftingProgress = useGameStore((s) => s.equipmentCraftingProgress);
|
const equipmentCraftingProgress = useCraftingStore((s) => s.equipmentCraftingProgress);
|
||||||
const rawMana = useGameStore((s) => s.rawMana);
|
const rawMana = useManaStore((s) => s.rawMana);
|
||||||
const currentAction = useGameStore((s) => s.currentAction);
|
const currentAction = useCombatStore((s) => s.currentAction);
|
||||||
const startCraftingEquipment = useGameStore((s) => s.startCraftingEquipment);
|
const startCraftingEquipment = useCraftingStore((s) => s.startCraftingEquipment);
|
||||||
const cancelEquipmentCrafting = useGameStore((s) => s.cancelEquipmentCrafting);
|
const cancelEquipmentCrafting = useCraftingStore((s) => s.cancelEquipmentCrafting);
|
||||||
const deleteMaterial = useCraftingStore((s) => s.deleteMaterial);
|
const deleteMaterial = useCraftingStore((s) => s.deleteMaterial);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -1,16 +1,16 @@
|
|||||||
'use client';
|
'use client';
|
||||||
|
|
||||||
import { AchievementsDisplay } from '@/components/game/AchievementsDisplay';
|
import { AchievementsDisplay } from '@/components/game/AchievementsDisplay';
|
||||||
import { useGameStore } from '@/lib/game/stores';
|
import { useCombatStore, useManaStore, usePrestigeStore } from '@/lib/game/stores';
|
||||||
|
|
||||||
export function AchievementsTab() {
|
export function AchievementsTab() {
|
||||||
const achievements = useGameStore((s) => s.achievements);
|
const achievements = useCombatStore((s) => s.achievements);
|
||||||
const maxFloorReached = useGameStore((s) => s.maxFloorReached);
|
const maxFloorReached = useCombatStore((s) => s.maxFloorReached);
|
||||||
const totalManaGathered = useGameStore((s) => s.totalManaGathered);
|
const totalManaGathered = useManaStore((s) => s.totalManaGathered);
|
||||||
const signedPacts = useGameStore((s) => s.signedPacts);
|
const signedPacts = usePrestigeStore((s) => s.signedPacts);
|
||||||
const totalSpellsCast = useGameStore((s) => s.totalSpellsCast);
|
const totalSpellsCast = useCombatStore((s) => s.totalSpellsCast);
|
||||||
const totalDamageDealt = useGameStore((s) => s.totalDamageDealt);
|
const totalDamageDealt = useCombatStore((s) => s.totalDamageDealt);
|
||||||
const totalCraftsCompleted = useGameStore((s) => s.totalCraftsCompleted);
|
const totalCraftsCompleted = useCombatStore((s) => s.totalCraftsCompleted);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="space-y-4">
|
<div className="space-y-4">
|
||||||
|
|||||||
@@ -13,18 +13,18 @@ import {
|
|||||||
EnchantmentApplier,
|
EnchantmentApplier,
|
||||||
EquipmentCrafter,
|
EquipmentCrafter,
|
||||||
} from '@/components/game/crafting';
|
} from '@/components/game/crafting';
|
||||||
import { useGameStore } from '@/lib/game/stores';
|
import { useCombatStore, useCraftingStore } from '@/lib/game/stores';
|
||||||
import { useGameToast } from '@/components/game/GameToast';
|
import { useGameToast } from '@/components/game/GameToast';
|
||||||
|
|
||||||
export function CraftingTab() {
|
export function CraftingTab() {
|
||||||
const showToast = useGameToast();
|
const showToast = useGameToast();
|
||||||
const currentAction = useGameStore((s) => s.currentAction);
|
const currentAction = useCombatStore((s) => s.currentAction);
|
||||||
const designProgress = useGameStore((s) => s.designProgress);
|
const designProgress = useCraftingStore((s) => s.designProgress);
|
||||||
const preparationProgress = useGameStore((s) => s.preparationProgress);
|
const preparationProgress = useCraftingStore((s) => s.preparationProgress);
|
||||||
const applicationProgress = useGameStore((s) => s.applicationProgress);
|
const applicationProgress = useCraftingStore((s) => s.applicationProgress);
|
||||||
const equipmentCraftingProgress = useGameStore((s) => s.equipmentCraftingProgress);
|
const equipmentCraftingProgress = useCraftingStore((s) => s.equipmentCraftingProgress);
|
||||||
const pauseApplication = useGameStore((s) => s.pauseApplication);
|
const pauseApplication = useCraftingStore((s) => s.pauseApplication);
|
||||||
const resumeApplication = useGameStore((s) => s.resumeApplication);
|
const resumeApplication = useCraftingStore((s) => s.resumeApplication);
|
||||||
|
|
||||||
const [activeTab, setActiveTab] = useState<'fabricate' | 'enchant'>('fabricate');
|
const [activeTab, setActiveTab] = useState<'fabricate' | 'enchant'>('fabricate');
|
||||||
const [enchantStage, setEnchantStage] = useState<'design' | 'prepare' | 'apply'>('design');
|
const [enchantStage, setEnchantStage] = useState<'design' | 'prepare' | 'apply'>('design');
|
||||||
@@ -176,7 +176,7 @@ export function CraftingTab() {
|
|||||||
<SectionHeader
|
<SectionHeader
|
||||||
title="Designing Enchantment"
|
title="Designing Enchantment"
|
||||||
action={
|
action={
|
||||||
<ActionButton variant="ghost" size="sm" onClick={() => useGameStore.getState().cancelDesign()}>
|
<ActionButton variant="ghost" size="sm" onClick={() => useCraftingStore.getState().cancelDesign()}>
|
||||||
Cancel
|
Cancel
|
||||||
</ActionButton>
|
</ActionButton>
|
||||||
}
|
}
|
||||||
@@ -198,7 +198,7 @@ export function CraftingTab() {
|
|||||||
<SectionHeader
|
<SectionHeader
|
||||||
title="Preparing Equipment"
|
title="Preparing Equipment"
|
||||||
action={
|
action={
|
||||||
<ActionButton variant="ghost" size="sm" onClick={() => useGameStore.getState().cancelPreparation()}>
|
<ActionButton variant="ghost" size="sm" onClick={() => useCraftingStore.getState().cancelPreparation()}>
|
||||||
Cancel
|
Cancel
|
||||||
</ActionButton>
|
</ActionButton>
|
||||||
}
|
}
|
||||||
@@ -230,7 +230,7 @@ export function CraftingTab() {
|
|||||||
<>
|
<>
|
||||||
<ActionButton variant="secondary" size="sm" onClick={pauseApplication}>Pause</ActionButton>
|
<ActionButton variant="secondary" size="sm" onClick={pauseApplication}>Pause</ActionButton>
|
||||||
<ActionButton variant="ghost" size="sm" onClick={() => {
|
<ActionButton variant="ghost" size="sm" onClick={() => {
|
||||||
useGameStore.getState().cancelApplication();
|
useCraftingStore.getState().cancelApplication();
|
||||||
showToast('warning', 'Enchantment Cancelled', 'The enchantment application was cancelled.');
|
showToast('warning', 'Enchantment Cancelled', 'The enchantment application was cancelled.');
|
||||||
}}>Cancel</ActionButton>
|
}}>Cancel</ActionButton>
|
||||||
</>
|
</>
|
||||||
|
|||||||
@@ -9,16 +9,16 @@ import {
|
|||||||
} from 'lucide-react';
|
} from 'lucide-react';
|
||||||
import { GOLEMS_DEF, getGolemSlots, isGolemUnlocked, getGolemDamage, getGolemAttackSpeed, getGolemFloorDuration } from '@/lib/game/data/golems';
|
import { GOLEMS_DEF, getGolemSlots, isGolemUnlocked, getGolemDamage, getGolemAttackSpeed, getGolemFloorDuration } from '@/lib/game/data/golems';
|
||||||
import { ELEMENTS } from '@/lib/game/constants';
|
import { ELEMENTS } from '@/lib/game/constants';
|
||||||
import { useGameStore, useManaStore, useSkillStore, useCombatStore, useAttunementStore } from '@/lib/game/stores';
|
import { useManaStore, useSkillStore, useCombatStore, useAttunementStore } from '@/lib/game/stores';
|
||||||
|
|
||||||
export function GolemancyTab() {
|
export function GolemancyTab() {
|
||||||
const attunements = useAttunementStore((s) => s.attunements);
|
const attunements = useAttunementStore((s) => s.attunements);
|
||||||
const elements = useManaStore((s) => s.elements);
|
const elements = useManaStore((s) => s.elements);
|
||||||
const skills = useSkillStore((s) => s.skills);
|
const skills = useSkillStore((s) => s.skills);
|
||||||
const golemancy = useGameStore((s) => s.golemancy);
|
const golemancy = useCombatStore((s) => s.golemancy);
|
||||||
const currentFloor = useCombatStore((s) => s.currentFloor);
|
const currentFloor = useCombatStore((s) => s.currentFloor);
|
||||||
const currentRoom = useGameStore((s) => s.currentRoom);
|
const currentRoom = useCombatStore((s) => s.currentRoom);
|
||||||
const toggleGolem = useGameStore((s) => s.toggleGolem);
|
const toggleGolem = useCombatStore((s) => s.toggleGolem);
|
||||||
const rawMana = useManaStore((s) => s.rawMana);
|
const rawMana = useManaStore((s) => s.rawMana);
|
||||||
|
|
||||||
// Get Fabricator level and golem slots
|
// Get Fabricator level and golem slots
|
||||||
|
|||||||
@@ -39,7 +39,7 @@ import { ChevronDown, ChevronRight } from 'lucide-react';
|
|||||||
import { SkillRow } from './SkillRow';
|
import { SkillRow } from './SkillRow';
|
||||||
import { useSkillUpgradeSelection } from '@/lib/game/hooks/useSkillUpgradeSelection';
|
import { useSkillUpgradeSelection } from '@/lib/game/hooks/useSkillUpgradeSelection';
|
||||||
import { CategorySkillsList } from './CategorySkillsList';
|
import { CategorySkillsList } from './CategorySkillsList';
|
||||||
import { useGameStore, useSkillStore, usePrestigeStore } from '@/lib/game/stores';
|
import { useSkillStore, usePrestigeStore } from '@/lib/game/stores';
|
||||||
|
|
||||||
export function SkillsTab() {
|
export function SkillsTab() {
|
||||||
const showToast = useGameToast();
|
const showToast = useGameToast();
|
||||||
@@ -55,11 +55,11 @@ export function SkillsTab() {
|
|||||||
const skillUpgrades = useSkillStore((s) => s.skillUpgrades);
|
const skillUpgrades = useSkillStore((s) => s.skillUpgrades);
|
||||||
const skillTiers = useSkillStore((s) => s.skillTiers);
|
const skillTiers = useSkillStore((s) => s.skillTiers);
|
||||||
const prestigeUpgrades = usePrestigeStore((s) => s.prestigeUpgrades);
|
const prestigeUpgrades = usePrestigeStore((s) => s.prestigeUpgrades);
|
||||||
const currentStudyTarget = useGameStore((s) => s.currentStudyTarget);
|
const currentStudyTarget = useSkillStore((s) => s.currentStudyTarget);
|
||||||
const parallelStudyTarget = useGameStore((s) => s.parallelStudyTarget);
|
const parallelStudyTarget = useSkillStore((s) => s.parallelStudyTarget);
|
||||||
const startStudyingSkill = useSkillStore((s) => s.startStudyingSkill);
|
const startStudyingSkill = useSkillStore((s) => s.startStudyingSkill);
|
||||||
const startParallelStudySkill = useSkillStore((s) => s.startParallelStudySkill);
|
const startParallelStudySkill = useSkillStore((s) => s.startParallelStudySkill);
|
||||||
const cancelStudy = useGameStore((s) => s.cancelStudy);
|
const cancelStudy = useSkillStore((s) => s.cancelStudy);
|
||||||
const commitSkillUpgrades = useSkillStore((s) => s.commitSkillUpgrades);
|
const commitSkillUpgrades = useSkillStore((s) => s.commitSkillUpgrades);
|
||||||
const tierUpSkill = useSkillStore((s) => s.tierUpSkill);
|
const tierUpSkill = useSkillStore((s) => s.tierUpSkill);
|
||||||
|
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ import { getActiveEquipmentSpells, getTotalDPS } from '@/lib/game/computed-stats
|
|||||||
import { getUnifiedEffects } from '@/lib/game/effects';
|
import { getUnifiedEffects } from '@/lib/game/effects';
|
||||||
import { formatSpellCost, getSpellCostColor } from '@/lib/game/formatting';
|
import { formatSpellCost, getSpellCostColor } from '@/lib/game/formatting';
|
||||||
import { canAffordSpellCost, getFloorElement } from '@/lib/game/stores';
|
import { canAffordSpellCost, getFloorElement } from '@/lib/game/stores';
|
||||||
import { useGameStore, useManaStore, useSkillStore, useCombatStore, usePrestigeStore, useCraftingStore } from '@/lib/game/stores';
|
import { useManaStore, useSkillStore, useCombatStore, usePrestigeStore, useCraftingStore } from '@/lib/game/stores';
|
||||||
import { GOLEMS_DEF, getGolemDamage, getGolemAttackSpeed } from '@/lib/game/data/golems';
|
import { GOLEMS_DEF, getGolemDamage, getGolemAttackSpeed } from '@/lib/game/data/golems';
|
||||||
|
|
||||||
// Extracted components
|
// Extracted components
|
||||||
@@ -52,16 +52,16 @@ export function SpireTab({ simpleMode = false }: SpireTabProps) {
|
|||||||
const activeSpell = useCombatStore((s) => s.activeSpell);
|
const activeSpell = useCombatStore((s) => s.activeSpell);
|
||||||
const startClimbUp = useCombatStore((s) => s.startClimbUp);
|
const startClimbUp = useCombatStore((s) => s.startClimbUp);
|
||||||
const startClimbDown = useCombatStore((s) => s.startClimbDown);
|
const startClimbDown = useCombatStore((s) => s.startClimbDown);
|
||||||
const enterSpireMode = useGameStore((s) => s.enterSpireMode);
|
const enterSpireMode = useCombatStore((s) => s.enterSpireMode);
|
||||||
const spireMode = useGameStore((s) => s.spireMode);
|
const spireMode = useCombatStore((s) => s.spireMode);
|
||||||
const climbDirection = useGameStore((s) => s.climbDirection) || 'up';
|
const climbDirection = useCombatStore((s) => s.climbDirection) || 'up';
|
||||||
const clearedFloors = useGameStore((s) => s.clearedFloors || {});
|
const clearedFloors = useCombatStore((s) => s.clearedFloors || {});
|
||||||
const currentRoom = useGameStore((s) => s.currentRoom);
|
const currentRoom = useCombatStore((s) => s.currentRoom);
|
||||||
const equipmentSpellStates = useGameStore((s) => s.equipmentSpellStates);
|
const equipmentSpellStates = useCombatStore((s) => s.equipmentSpellStates);
|
||||||
const golemancy = useGameStore((s) => s.golemancy);
|
const golemancy = useCombatStore((s) => s.golemancy);
|
||||||
const activityLog = useGameStore((s) => s.activityLog);
|
const activityLog = useCombatStore((s) => s.activityLog);
|
||||||
const currentStudyTarget = useGameStore((s) => s.currentStudyTarget);
|
const currentStudyTarget = useSkillStore((s) => s.currentStudyTarget);
|
||||||
const parallelStudyTarget = useGameStore((s) => s.parallelStudyTarget);
|
const parallelStudyTarget = useSkillStore((s) => s.parallelStudyTarget);
|
||||||
const signedPacts = usePrestigeStore((s) => s.signedPacts);
|
const signedPacts = usePrestigeStore((s) => s.signedPacts);
|
||||||
const skills = useSkillStore((s) => s.skills);
|
const skills = useSkillStore((s) => s.skills);
|
||||||
const skillUpgrades = useSkillStore((s) => s.skillUpgrades);
|
const skillUpgrades = useSkillStore((s) => s.skillUpgrades);
|
||||||
@@ -73,8 +73,8 @@ export function SpireTab({ simpleMode = false }: SpireTabProps) {
|
|||||||
const designProgress = useCraftingStore((s) => s.designProgress);
|
const designProgress = useCraftingStore((s) => s.designProgress);
|
||||||
const designProgress2 = useCraftingStore((s) => s.designProgress2);
|
const designProgress2 = useCraftingStore((s) => s.designProgress2);
|
||||||
const preparationProgress = useCraftingStore((s) => s.preparationProgress);
|
const preparationProgress = useCraftingStore((s) => s.preparationProgress);
|
||||||
const applicationProgress = useGameStore((s) => s.applicationProgress);
|
const applicationProgress = useCraftingStore((s) => s.applicationProgress);
|
||||||
const equipmentCraftingProgress = useGameStore((s) => s.equipmentCraftingProgress);
|
const equipmentCraftingProgress = useCraftingStore((s) => s.equipmentCraftingProgress);
|
||||||
|
|
||||||
// Derived data
|
// Derived data
|
||||||
const floorElem = getFloorElement(currentFloor);
|
const floorElem = getFloorElement(currentFloor);
|
||||||
|
|||||||
@@ -0,0 +1,117 @@
|
|||||||
|
// ─── Combat Actions ─────────────────────────────────────────────────────────────
|
||||||
|
// Extracted combat logic from combatStore.ts
|
||||||
|
|
||||||
|
import { SPELLS_DEF, GUARDIANS, HOURS_PER_TICK } from '../constants';
|
||||||
|
import type { CombatState } from './combatStore';
|
||||||
|
import type { SpellState } from '../types';
|
||||||
|
import { getFloorMaxHP, getFloorElement, calcDamage, canAffordSpellCost, deductSpellCost } from '../utils';
|
||||||
|
import { usePrestigeStore } from './prestigeStore';
|
||||||
|
|
||||||
|
export function processCombatTick(
|
||||||
|
get: () => CombatState,
|
||||||
|
set: (state: Partial<CombatState>) => void,
|
||||||
|
skills: Record<string, number>,
|
||||||
|
rawMana: number,
|
||||||
|
elements: Record<string, { current: number; max: number; unlocked: boolean }>,
|
||||||
|
maxMana: number,
|
||||||
|
attackSpeedMult: number,
|
||||||
|
onFloorCleared: (floor: number, wasGuardian: boolean) => void,
|
||||||
|
onDamageDealt: (damage: number) => {
|
||||||
|
rawMana: number;
|
||||||
|
elements: Record<string, { current: number; max: number; unlocked: boolean }>;
|
||||||
|
modifiedDamage?: number;
|
||||||
|
},
|
||||||
|
) {
|
||||||
|
const state = get();
|
||||||
|
const logMessages: string[] = [];
|
||||||
|
let totalManaGathered = 0;
|
||||||
|
|
||||||
|
if (state.currentAction !== 'climb') {
|
||||||
|
return { rawMana, elements, logMessages, totalManaGathered };
|
||||||
|
}
|
||||||
|
|
||||||
|
const spellId = state.activeSpell;
|
||||||
|
const spellDef = SPELLS_DEF[spellId];
|
||||||
|
if (!spellDef) {
|
||||||
|
return { rawMana, elements, logMessages, totalManaGathered };
|
||||||
|
}
|
||||||
|
|
||||||
|
// Calculate cast speed
|
||||||
|
const baseAttackSpeed = 1 + (skills.quickCast || 0) * 0.05;
|
||||||
|
const totalAttackSpeed = baseAttackSpeed * attackSpeedMult;
|
||||||
|
const spellCastSpeed = spellDef.castSpeed || 1;
|
||||||
|
const progressPerTick = HOURS_PER_TICK * spellCastSpeed * totalAttackSpeed;
|
||||||
|
|
||||||
|
let castProgress = (state.castProgress || 0) + progressPerTick;
|
||||||
|
let floorHP = state.floorHP;
|
||||||
|
let currentFloor = state.currentFloor;
|
||||||
|
let floorMaxHP = state.floorMaxHP;
|
||||||
|
|
||||||
|
// Process complete casts
|
||||||
|
while (castProgress >= 1 && canAffordSpellCost(spellDef.cost, rawMana, elements)) {
|
||||||
|
// Deduct spell cost
|
||||||
|
const afterCost = deductSpellCost(spellDef.cost, rawMana, elements);
|
||||||
|
rawMana = afterCost.rawMana;
|
||||||
|
elements = afterCost.elements;
|
||||||
|
totalManaGathered += spellDef.cost.amount;
|
||||||
|
|
||||||
|
// Calculate base damage
|
||||||
|
const floorElement = getFloorElement(currentFloor);
|
||||||
|
const damage = calcDamage(
|
||||||
|
{ skills, signedPacts: usePrestigeStore.getState().signedPacts },
|
||||||
|
spellId,
|
||||||
|
floorElement,
|
||||||
|
);
|
||||||
|
|
||||||
|
// Let gameStore apply damage modifiers (executioner, berserker, spell echo)
|
||||||
|
const result = onDamageDealt(damage);
|
||||||
|
rawMana = result.rawMana;
|
||||||
|
elements = result.elements;
|
||||||
|
const finalDamage = result.modifiedDamage || damage;
|
||||||
|
|
||||||
|
// Apply damage
|
||||||
|
floorHP = Math.max(0, floorHP - finalDamage);
|
||||||
|
castProgress -= 1;
|
||||||
|
|
||||||
|
// Check if floor is cleared
|
||||||
|
if (floorHP <= 0) {
|
||||||
|
const wasGuardian = GUARDIANS[currentFloor];
|
||||||
|
onFloorCleared(currentFloor, !!wasGuardian);
|
||||||
|
|
||||||
|
currentFloor = Math.min(currentFloor + 1, 100);
|
||||||
|
floorMaxHP = getFloorMaxHP(currentFloor);
|
||||||
|
floorHP = floorMaxHP;
|
||||||
|
castProgress = 0;
|
||||||
|
|
||||||
|
if (wasGuardian) {
|
||||||
|
logMessages.push(`⚔️ ${wasGuardian.name} defeated!`);
|
||||||
|
} else if (currentFloor % 5 === 0) {
|
||||||
|
logMessages.push(`🏰 Floor ${currentFloor - 1} cleared!`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
set({
|
||||||
|
currentFloor,
|
||||||
|
floorHP,
|
||||||
|
floorMaxHP: getFloorMaxHP(currentFloor),
|
||||||
|
maxFloorReached: Math.max(state.maxFloorReached, currentFloor),
|
||||||
|
castProgress,
|
||||||
|
});
|
||||||
|
|
||||||
|
return { rawMana, elements, logMessages, totalManaGathered };
|
||||||
|
}
|
||||||
|
|
||||||
|
// Helper function to create initial spells
|
||||||
|
export function makeInitialSpells(spellsToKeep: string[] = []): Record<string, SpellState> {
|
||||||
|
const startSpells: Record<string, SpellState> = {
|
||||||
|
manaBolt: { learned: true, level: 1, studyProgress: 0 },
|
||||||
|
};
|
||||||
|
|
||||||
|
// Add kept spells
|
||||||
|
for (const spellId of spellsToKeep) {
|
||||||
|
startSpells[spellId] = { learned: true, level: 1, studyProgress: 0 };
|
||||||
|
}
|
||||||
|
|
||||||
|
return startSpells;
|
||||||
|
}
|
||||||
@@ -3,11 +3,13 @@
|
|||||||
|
|
||||||
import { create } from 'zustand';
|
import { create } from 'zustand';
|
||||||
import { persist } from 'zustand/middleware';
|
import { persist } from 'zustand/middleware';
|
||||||
import { SPELLS_DEF, GUARDIANS, HOURS_PER_TICK } from '../constants';
|
import type { GameAction, SpellState, FloorState, GolemancyState, ActivityLogEntry, AchievementState, EquipmentSpellState, ActivityEventType } from '../types';
|
||||||
import type { GameAction, SpellState } from '../types';
|
import { getFloorMaxHP } from '../utils';
|
||||||
import { getFloorMaxHP, getFloorElement, calcDamage, canAffordSpellCost, deductSpellCost } from '../utils';
|
|
||||||
import { usePrestigeStore } from './prestigeStore';
|
import { usePrestigeStore } from './prestigeStore';
|
||||||
import { useGameStore } from './gameStore';
|
import { useGameStore } from './gameStore';
|
||||||
|
import { generateFloorState } from '../store-modules/room-utils';
|
||||||
|
import { addActivityLogEntry } from '../store-modules/activity-log';
|
||||||
|
import { processCombatTick, makeInitialSpells } from './combat-actions';
|
||||||
|
|
||||||
export interface CombatState {
|
export interface CombatState {
|
||||||
// Floor state
|
// Floor state
|
||||||
@@ -24,9 +26,38 @@ export interface CombatState {
|
|||||||
// Spire mode
|
// Spire mode
|
||||||
spireMode: boolean;
|
spireMode: boolean;
|
||||||
|
|
||||||
|
// Room system for special floors
|
||||||
|
currentRoom: FloorState;
|
||||||
|
|
||||||
|
// Spire climbing state
|
||||||
|
clearedFloors: Record<number, boolean>;
|
||||||
|
climbDirection: 'up' | 'down' | null;
|
||||||
|
isDescending: boolean;
|
||||||
|
|
||||||
|
// Golemancy (summoned golems)
|
||||||
|
golemancy: GolemancyState;
|
||||||
|
|
||||||
|
// Equipment spell states for multi-casting
|
||||||
|
equipmentSpellStates: EquipmentSpellState[];
|
||||||
|
|
||||||
|
// Combat special effect tracking
|
||||||
|
comboHitCount: number;
|
||||||
|
floorHitCount: number;
|
||||||
|
|
||||||
// Spells
|
// Spells
|
||||||
spells: Record<string, SpellState>;
|
spells: Record<string, SpellState>;
|
||||||
|
|
||||||
|
// Activity Log (for Spire Mode UI)
|
||||||
|
activityLog: ActivityLogEntry[];
|
||||||
|
|
||||||
|
// Achievements
|
||||||
|
achievements: AchievementState;
|
||||||
|
|
||||||
|
// Stats tracking
|
||||||
|
totalSpellsCast: number;
|
||||||
|
totalDamageDealt: number;
|
||||||
|
totalCraftsCompleted: number;
|
||||||
|
|
||||||
// Actions
|
// Actions
|
||||||
setCurrentFloor: (floor: number) => void;
|
setCurrentFloor: (floor: number) => void;
|
||||||
advanceFloor: () => void;
|
advanceFloor: () => void;
|
||||||
@@ -37,10 +68,32 @@ export interface CombatState {
|
|||||||
setSpell: (spellId: string) => void;
|
setSpell: (spellId: string) => void;
|
||||||
setCastProgress: (progress: number) => void;
|
setCastProgress: (progress: number) => void;
|
||||||
|
|
||||||
|
// Room state
|
||||||
|
setCurrentRoom: (room: FloorState) => void;
|
||||||
|
|
||||||
|
// Spire climbing
|
||||||
|
setClimbDirection: (direction: 'up' | 'down' | null) => void;
|
||||||
|
setClearedFloor: (floor: number, cleared: boolean) => void;
|
||||||
|
setIsDescending: (descending: boolean) => void;
|
||||||
|
climbDownFloor: () => void;
|
||||||
|
exitSpireMode: () => void;
|
||||||
|
|
||||||
|
// Golemancy
|
||||||
|
toggleGolem: (golemId: string) => void;
|
||||||
|
setEnabledGolems: (golemIds: string[]) => void;
|
||||||
|
|
||||||
// Spells
|
// Spells
|
||||||
learnSpell: (spellId: string) => void;
|
learnSpell: (spellId: string) => void;
|
||||||
setSpellState: (spellId: string, state: Partial<SpellState>) => void;
|
setSpellState: (spellId: string, state: Partial<SpellState>) => void;
|
||||||
|
|
||||||
|
// Activity Log
|
||||||
|
addActivityLog: (eventType: ActivityEventType, message: string, details?: ActivityLogEntry['details']) => void;
|
||||||
|
|
||||||
|
// Stats
|
||||||
|
incrementSpellsCast: () => void;
|
||||||
|
addDamageDealt: (damage: number) => void;
|
||||||
|
incrementCraftsCompleted: () => void;
|
||||||
|
|
||||||
// Spire mode
|
// Spire mode
|
||||||
enterSpireMode: () => void;
|
enterSpireMode: () => void;
|
||||||
|
|
||||||
@@ -75,10 +128,48 @@ export const useCombatStore = create<CombatState>()(
|
|||||||
currentAction: 'meditate',
|
currentAction: 'meditate',
|
||||||
castProgress: 0,
|
castProgress: 0,
|
||||||
spireMode: false,
|
spireMode: false,
|
||||||
|
|
||||||
|
// Room system
|
||||||
|
currentRoom: generateFloorState(1),
|
||||||
|
|
||||||
|
// Spire climbing state
|
||||||
|
clearedFloors: {},
|
||||||
|
climbDirection: null,
|
||||||
|
isDescending: false,
|
||||||
|
|
||||||
|
// Golemancy
|
||||||
|
golemancy: {
|
||||||
|
enabledGolems: [],
|
||||||
|
summonedGolems: [],
|
||||||
|
lastSummonFloor: 0,
|
||||||
|
},
|
||||||
|
|
||||||
|
// Equipment spell states
|
||||||
|
equipmentSpellStates: [],
|
||||||
|
|
||||||
|
// Combat tracking
|
||||||
|
comboHitCount: 0,
|
||||||
|
floorHitCount: 0,
|
||||||
|
|
||||||
|
// Spells
|
||||||
spells: {
|
spells: {
|
||||||
manaBolt: { learned: true, level: 1, studyProgress: 0 },
|
manaBolt: { learned: true, level: 1, studyProgress: 0 },
|
||||||
},
|
},
|
||||||
|
|
||||||
|
// Activity Log
|
||||||
|
activityLog: [],
|
||||||
|
|
||||||
|
// Achievements
|
||||||
|
achievements: {
|
||||||
|
unlocked: [],
|
||||||
|
progress: {},
|
||||||
|
},
|
||||||
|
|
||||||
|
// Stats tracking
|
||||||
|
totalSpellsCast: 0,
|
||||||
|
totalDamageDealt: 0,
|
||||||
|
totalCraftsCompleted: 0,
|
||||||
|
|
||||||
setCurrentFloor: (floor: number) => {
|
setCurrentFloor: (floor: number) => {
|
||||||
set({
|
set({
|
||||||
currentFloor: floor,
|
currentFloor: floor,
|
||||||
@@ -125,6 +216,66 @@ export const useCombatStore = create<CombatState>()(
|
|||||||
set({ castProgress: progress });
|
set({ castProgress: progress });
|
||||||
},
|
},
|
||||||
|
|
||||||
|
// Room state
|
||||||
|
setCurrentRoom: (room: FloorState) => {
|
||||||
|
set({ currentRoom: room });
|
||||||
|
},
|
||||||
|
|
||||||
|
// Spire climbing
|
||||||
|
setClimbDirection: (direction: 'up' | 'down' | null) => {
|
||||||
|
set({ climbDirection: direction });
|
||||||
|
},
|
||||||
|
|
||||||
|
setClearedFloor: (floor: number, cleared: boolean) => {
|
||||||
|
set((state) => ({
|
||||||
|
clearedFloors: { ...state.clearedFloors, [floor]: cleared },
|
||||||
|
}));
|
||||||
|
},
|
||||||
|
|
||||||
|
setIsDescending: (descending: boolean) => {
|
||||||
|
set({ isDescending: descending });
|
||||||
|
},
|
||||||
|
|
||||||
|
climbDownFloor: () => {
|
||||||
|
set((s) => {
|
||||||
|
if (s.currentFloor <= 1) return s;
|
||||||
|
const newFloor = s.currentFloor - 1;
|
||||||
|
return {
|
||||||
|
currentFloor: newFloor,
|
||||||
|
currentRoom: generateFloorState(newFloor),
|
||||||
|
floorMaxHP: getFloorMaxHP(newFloor),
|
||||||
|
floorHP: getFloorMaxHP(newFloor),
|
||||||
|
castProgress: 0,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
exitSpireMode: () => {
|
||||||
|
set({ spireMode: false, climbDirection: null, isDescending: false });
|
||||||
|
},
|
||||||
|
|
||||||
|
// Golemancy
|
||||||
|
toggleGolem: (golemId: string) => {
|
||||||
|
set((s) => {
|
||||||
|
const enabledGolems = s.golemancy?.enabledGolems || [];
|
||||||
|
const isEnabled = enabledGolems.includes(golemId);
|
||||||
|
return {
|
||||||
|
golemancy: {
|
||||||
|
...s.golemancy,
|
||||||
|
enabledGolems: isEnabled
|
||||||
|
? enabledGolems.filter(id => id !== golemId)
|
||||||
|
: [...enabledGolems, golemId]
|
||||||
|
},
|
||||||
|
};
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
setEnabledGolems: (golemIds: string[]) => {
|
||||||
|
set((s) => ({
|
||||||
|
golemancy: { ...s.golemancy, enabledGolems: golemIds },
|
||||||
|
}));
|
||||||
|
},
|
||||||
|
|
||||||
enterSpireMode: () => {
|
enterSpireMode: () => {
|
||||||
set({ spireMode: true });
|
set({ spireMode: true });
|
||||||
},
|
},
|
||||||
@@ -147,6 +298,26 @@ export const useCombatStore = create<CombatState>()(
|
|||||||
}));
|
}));
|
||||||
},
|
},
|
||||||
|
|
||||||
|
// Activity Log
|
||||||
|
addActivityLog: (eventType: ActivityEventType, message: string, details?: ActivityLogEntry['details']) => {
|
||||||
|
set((state) => ({
|
||||||
|
activityLog: addActivityLogEntry(state, eventType, message, details),
|
||||||
|
}));
|
||||||
|
},
|
||||||
|
|
||||||
|
// Stats
|
||||||
|
incrementSpellsCast: () => {
|
||||||
|
set((state) => ({ totalSpellsCast: state.totalSpellsCast + 1 }));
|
||||||
|
},
|
||||||
|
|
||||||
|
addDamageDealt: (damage: number) => {
|
||||||
|
set((state) => ({ totalDamageDealt: state.totalDamageDealt + damage }));
|
||||||
|
},
|
||||||
|
|
||||||
|
incrementCraftsCompleted: () => {
|
||||||
|
set((state) => ({ totalCraftsCompleted: state.totalCraftsCompleted + 1 }));
|
||||||
|
},
|
||||||
|
|
||||||
processCombatTick: (
|
processCombatTick: (
|
||||||
skills: Record<string, number>,
|
skills: Record<string, number>,
|
||||||
rawMana: number,
|
rawMana: number,
|
||||||
@@ -156,84 +327,17 @@ export const useCombatStore = create<CombatState>()(
|
|||||||
onFloorCleared: (floor: number, wasGuardian: boolean) => void,
|
onFloorCleared: (floor: number, wasGuardian: boolean) => void,
|
||||||
onDamageDealt: (damage: number) => { rawMana: number; elements: Record<string, { current: number; max: number; unlocked: boolean }> },
|
onDamageDealt: (damage: number) => { rawMana: number; elements: Record<string, { current: number; max: number; unlocked: boolean }> },
|
||||||
) => {
|
) => {
|
||||||
const state = get();
|
return processCombatTick(
|
||||||
const logMessages: string[] = [];
|
get,
|
||||||
let totalManaGathered = 0;
|
set,
|
||||||
|
skills,
|
||||||
if (state.currentAction !== 'climb') {
|
rawMana,
|
||||||
return { rawMana, elements, logMessages, totalManaGathered };
|
elements,
|
||||||
}
|
maxMana,
|
||||||
|
attackSpeedMult,
|
||||||
const spellId = state.activeSpell;
|
onFloorCleared,
|
||||||
const spellDef = SPELLS_DEF[spellId];
|
onDamageDealt,
|
||||||
if (!spellDef) {
|
|
||||||
return { rawMana, elements, logMessages, totalManaGathered };
|
|
||||||
}
|
|
||||||
|
|
||||||
// Calculate cast speed
|
|
||||||
const baseAttackSpeed = 1 + (skills.quickCast || 0) * 0.05;
|
|
||||||
const totalAttackSpeed = baseAttackSpeed * attackSpeedMult;
|
|
||||||
const spellCastSpeed = spellDef.castSpeed || 1;
|
|
||||||
const progressPerTick = HOURS_PER_TICK * spellCastSpeed * totalAttackSpeed;
|
|
||||||
|
|
||||||
let castProgress = (state.castProgress || 0) + progressPerTick;
|
|
||||||
let floorHP = state.floorHP;
|
|
||||||
let currentFloor = state.currentFloor;
|
|
||||||
let floorMaxHP = state.floorMaxHP;
|
|
||||||
|
|
||||||
// Process complete casts
|
|
||||||
while (castProgress >= 1 && canAffordSpellCost(spellDef.cost, rawMana, elements)) {
|
|
||||||
// Deduct spell cost
|
|
||||||
const afterCost = deductSpellCost(spellDef.cost, rawMana, elements);
|
|
||||||
rawMana = afterCost.rawMana;
|
|
||||||
elements = afterCost.elements;
|
|
||||||
totalManaGathered += spellDef.cost.amount;
|
|
||||||
|
|
||||||
// Calculate base damage
|
|
||||||
const floorElement = getFloorElement(currentFloor);
|
|
||||||
const damage = calcDamage(
|
|
||||||
{ skills, signedPacts: usePrestigeStore.getState().signedPacts },
|
|
||||||
spellId,
|
|
||||||
floorElement
|
|
||||||
);
|
);
|
||||||
|
|
||||||
// Let gameStore apply damage modifiers (executioner, berserker, spell echo)
|
|
||||||
const result = onDamageDealt(damage);
|
|
||||||
rawMana = result.rawMana;
|
|
||||||
elements = result.elements;
|
|
||||||
const finalDamage = result.modifiedDamage || damage;
|
|
||||||
|
|
||||||
// Apply damage
|
|
||||||
floorHP = Math.max(0, floorHP - finalDamage);
|
|
||||||
castProgress -= 1;
|
|
||||||
|
|
||||||
// Check if floor is cleared
|
|
||||||
if (floorHP <= 0) {
|
|
||||||
const wasGuardian = GUARDIANS[currentFloor];
|
|
||||||
onFloorCleared(currentFloor, !!wasGuardian);
|
|
||||||
|
|
||||||
currentFloor = Math.min(currentFloor + 1, 100);
|
|
||||||
floorMaxHP = getFloorMaxHP(currentFloor);
|
|
||||||
floorHP = floorMaxHP;
|
|
||||||
castProgress = 0;
|
|
||||||
|
|
||||||
if (wasGuardian) {
|
|
||||||
logMessages.push(`⚔️ ${wasGuardian.name} defeated!`);
|
|
||||||
} else if (currentFloor % 5 === 0) {
|
|
||||||
logMessages.push(`🏰 Floor ${currentFloor - 1} cleared!`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
set({
|
|
||||||
currentFloor,
|
|
||||||
floorHP,
|
|
||||||
floorMaxHP: getFloorMaxHP(currentFloor),
|
|
||||||
maxFloorReached: Math.max(state.maxFloorReached, currentFloor),
|
|
||||||
castProgress,
|
|
||||||
});
|
|
||||||
|
|
||||||
return { rawMana, elements, logMessages, totalManaGathered };
|
|
||||||
},
|
},
|
||||||
|
|
||||||
resetCombat: (startFloor: number, spellsToKeep: string[] = []) => {
|
resetCombat: (startFloor: number, spellsToKeep: string[] = []) => {
|
||||||
@@ -282,16 +386,6 @@ export const useCombatStore = create<CombatState>()(
|
|||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
|
||||||
// Helper function to create initial spells
|
// makeInitialSpells is now in combat-actions.ts
|
||||||
export function makeInitialSpells(spellsToKeep: string[] = []): Record<string, SpellState> {
|
// Re-export for backward compatibility
|
||||||
const startSpells: Record<string, SpellState> = {
|
export { makeInitialSpells } from './combat-actions';
|
||||||
manaBolt: { learned: true, level: 1, studyProgress: 0 },
|
|
||||||
};
|
|
||||||
|
|
||||||
// Add kept spells
|
|
||||||
for (const spellId of spellsToKeep) {
|
|
||||||
startSpells[spellId] = { learned: true, level: 1, studyProgress: 0 };
|
|
||||||
}
|
|
||||||
|
|
||||||
return startSpells;
|
|
||||||
}
|
|
||||||
|
|||||||
Reference in New Issue
Block a user