Fix mana conversion visibility and UI improvements
All checks were successful
Build and Publish Mana Loop Docker Image / build-and-publish (push) Successful in 3m9s
All checks were successful
Build and Publish Mana Loop Docker Image / build-and-publish (push) Successful in 3m9s
- Increase attunement mana conversion rates (0.2 -> 2 for Enchanter) - Hide mana types with current < 1 in ManaDisplay and LabTab - Only show owned equipment types when designing enchantments
This commit is contained in:
164
src/lib/game/store/combatSlice.ts
Executable file
164
src/lib/game/store/combatSlice.ts
Executable file
@@ -0,0 +1,164 @@
|
||||
// ─── Combat Slice ─────────────────────────────────────────────────────────────
|
||||
// Manages spire climbing, combat, and floor progression
|
||||
|
||||
import type { StateCreator } from 'zustand';
|
||||
import type { GameState, GameAction, SpellCost } from '../types';
|
||||
import { GUARDIANS, SPELLS_DEF, ELEMENTS, ELEMENT_OPPOSITES } from '../constants';
|
||||
import { getFloorMaxHP, getFloorElement, calcDamage, computePactMultiplier, canAffordSpellCost, deductSpellCost } from './computed';
|
||||
import { computeEffects, hasSpecial, SPECIAL_EFFECTS } from '../upgrade-effects';
|
||||
|
||||
export interface CombatSlice {
|
||||
// State
|
||||
currentFloor: number;
|
||||
floorHP: number;
|
||||
floorMaxHP: number;
|
||||
maxFloorReached: number;
|
||||
activeSpell: string;
|
||||
currentAction: GameAction;
|
||||
castProgress: number;
|
||||
|
||||
// Actions
|
||||
setAction: (action: GameAction) => void;
|
||||
setSpell: (spellId: string) => void;
|
||||
getDamage: (spellId: string) => number;
|
||||
|
||||
// Internal combat processing
|
||||
processCombat: (deltaHours: number) => Partial<GameState>;
|
||||
}
|
||||
|
||||
export const createCombatSlice = (
|
||||
set: StateCreator<GameState>['set'],
|
||||
get: () => GameState
|
||||
): CombatSlice => ({
|
||||
currentFloor: 1,
|
||||
floorHP: getFloorMaxHP(1),
|
||||
floorMaxHP: getFloorMaxHP(1),
|
||||
maxFloorReached: 1,
|
||||
activeSpell: 'manaBolt',
|
||||
currentAction: 'meditate',
|
||||
castProgress: 0,
|
||||
|
||||
setAction: (action: GameAction) => {
|
||||
set((state) => ({
|
||||
currentAction: action,
|
||||
meditateTicks: action === 'meditate' ? state.meditateTicks : 0,
|
||||
}));
|
||||
},
|
||||
|
||||
setSpell: (spellId: string) => {
|
||||
const state = get();
|
||||
if (state.spells[spellId]?.learned) {
|
||||
set({ activeSpell: spellId });
|
||||
}
|
||||
},
|
||||
|
||||
getDamage: (spellId: string) => {
|
||||
const state = get();
|
||||
const floorElem = getFloorElement(state.currentFloor);
|
||||
return calcDamage(state, spellId, floorElem);
|
||||
},
|
||||
|
||||
processCombat: (deltaHours: number) => {
|
||||
const state = get();
|
||||
if (state.currentAction !== 'climb') return {};
|
||||
|
||||
const spellId = state.activeSpell;
|
||||
const spellDef = SPELLS_DEF[spellId];
|
||||
if (!spellDef) return {};
|
||||
|
||||
const effects = computeEffects(state.skillUpgrades || {}, state.skillTiers || {});
|
||||
const baseAttackSpeed = 1 + (state.skills.quickCast || 0) * 0.05;
|
||||
const totalAttackSpeed = baseAttackSpeed * effects.attackSpeedMultiplier;
|
||||
const spellCastSpeed = spellDef.castSpeed || 1;
|
||||
const progressPerTick = deltaHours * spellCastSpeed * totalAttackSpeed;
|
||||
|
||||
let castProgress = (state.castProgress || 0) + progressPerTick;
|
||||
let rawMana = state.rawMana;
|
||||
let elements = state.elements;
|
||||
let totalManaGathered = state.totalManaGathered;
|
||||
let currentFloor = state.currentFloor;
|
||||
let floorHP = state.floorHP;
|
||||
let floorMaxHP = state.floorMaxHP;
|
||||
let maxFloorReached = state.maxFloorReached;
|
||||
let signedPacts = state.signedPacts;
|
||||
let pendingPactOffer = state.pendingPactOffer;
|
||||
const log = [...state.log];
|
||||
const skills = state.skills;
|
||||
|
||||
const floorElement = getFloorElement(currentFloor);
|
||||
|
||||
while (castProgress >= 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
|
||||
let dmg = calcDamage(state, spellId, floorElement);
|
||||
dmg = dmg * effects.baseDamageMultiplier + effects.baseDamageBonus;
|
||||
|
||||
// Executioner: +100% damage to enemies below 25% HP
|
||||
if (hasSpecial(effects, SPECIAL_EFFECTS.EXECUTIONER) && floorHP / floorMaxHP < 0.25) {
|
||||
dmg *= 2;
|
||||
}
|
||||
|
||||
// Berserker: +50% damage when below 50% mana
|
||||
const maxMana = 100; // Would need proper max mana calculation
|
||||
if (hasSpecial(effects, SPECIAL_EFFECTS.BERSERKER) && rawMana < maxMana * 0.5) {
|
||||
dmg *= 1.5;
|
||||
}
|
||||
|
||||
// Spell echo - chance to cast again
|
||||
const echoChance = (skills.spellEcho || 0) * 0.1;
|
||||
if (Math.random() < echoChance) {
|
||||
dmg *= 2;
|
||||
log.unshift('✨ Spell Echo! Double damage!');
|
||||
}
|
||||
|
||||
// Lifesteal effect
|
||||
const lifestealEffect = spellDef.effects?.find(e => e.type === 'lifesteal');
|
||||
if (lifestealEffect) {
|
||||
const healAmount = dmg * lifestealEffect.value;
|
||||
rawMana = Math.min(rawMana + healAmount, maxMana);
|
||||
}
|
||||
|
||||
// Apply damage
|
||||
floorHP = Math.max(0, floorHP - dmg);
|
||||
castProgress -= 1;
|
||||
|
||||
if (floorHP <= 0) {
|
||||
const wasGuardian = GUARDIANS[currentFloor];
|
||||
if (wasGuardian && !signedPacts.includes(currentFloor)) {
|
||||
pendingPactOffer = currentFloor;
|
||||
log.unshift(`⚔️ ${wasGuardian.name} defeated! They offer a pact...`);
|
||||
} else if (!wasGuardian) {
|
||||
if (currentFloor % 5 === 0) {
|
||||
log.unshift(`🏰 Floor ${currentFloor} cleared!`);
|
||||
}
|
||||
}
|
||||
|
||||
currentFloor = currentFloor + 1;
|
||||
if (currentFloor > 100) currentFloor = 100;
|
||||
floorMaxHP = getFloorMaxHP(currentFloor);
|
||||
floorHP = floorMaxHP;
|
||||
maxFloorReached = Math.max(maxFloorReached, currentFloor);
|
||||
castProgress = 0;
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
rawMana,
|
||||
elements,
|
||||
totalManaGathered,
|
||||
currentFloor,
|
||||
floorHP,
|
||||
floorMaxHP,
|
||||
maxFloorReached,
|
||||
signedPacts,
|
||||
pendingPactOffer,
|
||||
castProgress,
|
||||
log,
|
||||
};
|
||||
},
|
||||
});
|
||||
Reference in New Issue
Block a user