Phase 4: Combat special effects

This commit is contained in:
Refactoring Agent
2026-04-24 17:02:42 +02:00
parent 75a43c7209
commit edfc6f11c0
4 changed files with 113 additions and 3 deletions
@@ -44,4 +44,34 @@ export const SPECIAL_EFFECTS: Record<string, EnchantmentEffectDef> = {
allowedEquipmentCategories: CASTER_AND_HANDS, allowedEquipmentCategories: CASTER_AND_HANDS,
effect: { type: 'special', specialId: 'overpower' } effect: { type: 'special', specialId: 'overpower' }
}, },
first_strike: {
id: 'first_strike',
name: 'First Strike',
description: '+15% damage on first attack each floor',
category: 'special',
baseCapacityCost: 45,
maxStacks: 1,
allowedEquipmentCategories: CASTER_AND_HANDS,
effect: { type: 'special', specialId: 'firstStrike' }
},
combo_master: {
id: 'combo_master',
name: 'Combo Master',
description: 'Every 5th attack deals 3x damage',
category: 'special',
baseCapacityCost: 65,
maxStacks: 1,
allowedEquipmentCategories: CASTER_AND_HANDS,
effect: { type: 'special', specialId: 'comboMaster' }
},
adrenaline_rush: {
id: 'adrenaline_rush',
name: 'Adrenaline Rush',
description: 'Defeating enemy restores 5% mana',
category: 'special',
baseCapacityCost: 50,
maxStacks: 1,
allowedEquipmentCategories: CASTER_AND_HANDS,
effect: { type: 'special', specialId: 'adrenalineRush' }
},
}; };
+48 -3
View File
@@ -684,6 +684,10 @@ function makeInitial(overrides: Partial<GameState> = {}): GameState {
totalDamageDealt: 0, totalDamageDealt: 0,
totalCraftsCompleted: 0, totalCraftsCompleted: 0,
// Combat special effect tracking
comboHitCount: 0, // Hit counter for COMBO_MASTER (every 5th attack)
floorHitCount: 0, // Hit counter for current floor (for FIRST_STRIKE)
// New equipment system // New equipment system
equippedInstances: startingEquipment.equippedInstances, equippedInstances: startingEquipment.equippedInstances,
equipmentInstances: startingEquipment.equipmentInstances, equipmentInstances: startingEquipment.equipmentInstances,
@@ -1013,7 +1017,9 @@ export const useGameStore = create<GameStore>()(
} }
// Combat - uses cast speed and spell casting // Combat - uses cast speed and spell casting
let { currentFloor, floorHP, floorMaxHP, maxFloorReached, signedPacts, castProgress, currentRoom } = state; let { currentFloor, floorHP, floorMaxHP, maxFloorReached, signedPacts, castProgress, currentRoom, comboHitCount, floorHitCount } = state;
comboHitCount = comboHitCount || 0;
floorHitCount = floorHitCount || 0;
const floorElement = getFloorElement(currentFloor); const floorElement = getFloorElement(currentFloor);
// Handle puzzle rooms separately // Handle puzzle rooms separately
@@ -1120,14 +1126,32 @@ export const useGameStore = create<GameStore>()(
const effectiveArmor = Math.max(0, enemy.armor - armorPierce); const effectiveArmor = Math.max(0, enemy.armor - armorPierce);
dmg *= (1 - effectiveArmor); dmg *= (1 - effectiveArmor);
// Increment hit counters
comboHitCount += 1;
floorHitCount += 1;
// First Strike: +15% damage on first attack each floor
if (hasSpecial(effects, SPECIAL_EFFECTS.FIRST_STRIKE) && floorHitCount === 1) {
dmg *= 1.15;
log = [`⚡ First Strike! +15% damage!`, ...log.slice(0, 49)];
}
// Combo Master: Every 5th attack deals 3x damage
if (hasSpecial(effects, SPECIAL_EFFECTS.COMBO_MASTER) && comboHitCount % 5 === 0) {
dmg *= 3;
log = [`🌀 Combo Master! Triple damage!`, ...log.slice(0, 49)];
}
// Executioner: +100% damage to enemies below 25% HP // Executioner: +100% damage to enemies below 25% HP
if (hasSpecial(effects, SPECIAL_EFFECTS.EXECUTIONER) && enemy.hp / enemy.maxHP < 0.25) { if (hasSpecial(effects, SPECIAL_EFFECTS.EXECUTIONER) && enemy.hp / enemy.maxHP < 0.25) {
dmg *= 2; dmg *= 2;
log = [`💀 Executioner! Double damage!`, ...log.slice(0, 49)];
} }
// Berserker: +50% damage when below 50% mana // Berserker: +50% damage when below 50% mana
if (hasSpecial(effects, SPECIAL_EFFECTS.BERSERKER) && rawMana < maxMana * 0.5) { if (hasSpecial(effects, SPECIAL_EFFECTS.BERSERKER) && rawMana < maxMana * 0.5) {
dmg *= 1.5; dmg *= 1.5;
log = [`🔥 Berserker! +50% damage!`, ...log.slice(0, 49)];
} }
// Spell echo - chance to cast again // Spell echo - chance to cast again
@@ -1153,6 +1177,14 @@ export const useGameStore = create<GameStore>()(
if (allDead) { if (allDead) {
// Floor cleared // Floor cleared
const wasGuardian = GUARDIANS[currentFloor]; const wasGuardian = GUARDIANS[currentFloor];
// Adrenaline Rush: Defeating enemy restores 5% mana
if (hasSpecial(effects, SPECIAL_EFFECTS.ADRENALINE_RUSH)) {
const manaRestore = Math.floor(maxMana * 0.05);
rawMana = Math.min(rawMana + manaRestore, maxMana);
log = [`💚 Adrenaline Rush! Restored ${manaRestore} mana!`, ...log.slice(0, 49)];
}
if (wasGuardian && !signedPacts.includes(currentFloor)) { if (wasGuardian && !signedPacts.includes(currentFloor)) {
signedPacts = [...signedPacts, currentFloor]; signedPacts = [...signedPacts, currentFloor];
log = [`⚔️ ${wasGuardian.name} defeated! Pact signed! (${wasGuardian.pact}x)`, ...log.slice(0, 49)]; log = [`⚔️ ${wasGuardian.name} defeated! Pact signed! (${wasGuardian.pact}x)`, ...log.slice(0, 49)];
@@ -1175,8 +1207,9 @@ export const useGameStore = create<GameStore>()(
floorHP = currentRoom.enemies[0]?.hp || floorMaxHP; floorHP = currentRoom.enemies[0]?.hp || floorMaxHP;
maxFloorReached = Math.max(maxFloorReached, currentFloor); maxFloorReached = Math.max(maxFloorReached, currentFloor);
// Reset cast progress on floor change // Reset cast progress and floor hit counter on floor change
castProgress = 0; castProgress = 0;
floorHitCount = 0;
} }
} }
} else { } else {
@@ -1445,6 +1478,8 @@ export const useGameStore = create<GameStore>()(
castProgress, castProgress,
golemancy, golemancy,
flowSurgeEndTime, flowSurgeEndTime,
comboHitCount,
floorHitCount,
...craftingUpdates, ...craftingUpdates,
}); });
}, },
@@ -2449,7 +2484,7 @@ export const useGameStore = create<GameStore>()(
}), }),
{ {
name: 'mana-loop-storage', name: 'mana-loop-storage',
version: 2, version: 3,
migrate: (persistedState: unknown, version: number) => { migrate: (persistedState: unknown, version: number) => {
const state = persistedState as Record<string, unknown>; const state = persistedState as Record<string, unknown>;
// Migration from version 0/1 to version 2 - add missing fields // Migration from version 0/1 to version 2 - add missing fields
@@ -2462,6 +2497,14 @@ export const useGameStore = create<GameStore>()(
parallelStudyTarget: state.parallelStudyTarget ?? null, parallelStudyTarget: state.parallelStudyTarget ?? null,
}; };
} }
// Migration to version 3 - add combo hit counters
if (version < 3) {
return {
...state,
comboHitCount: state.comboHitCount ?? 0,
floorHitCount: state.floorHitCount ?? 0,
};
}
return state; return state;
}, },
partialize: (state) => ({ partialize: (state) => ({
@@ -2493,6 +2536,8 @@ export const useGameStore = create<GameStore>()(
totalSpellsCast: state.totalSpellsCast, totalSpellsCast: state.totalSpellsCast,
totalDamageDealt: state.totalDamageDealt, totalDamageDealt: state.totalDamageDealt,
totalCraftsCompleted: state.totalCraftsCompleted, totalCraftsCompleted: state.totalCraftsCompleted,
comboHitCount: state.comboHitCount,
floorHitCount: state.floorHitCount,
insight: state.insight, insight: state.insight,
totalInsight: state.totalInsight, totalInsight: state.totalInsight,
prestigeUpgrades: state.prestigeUpgrades, prestigeUpgrades: state.prestigeUpgrades,
+31
View File
@@ -84,6 +84,8 @@ export const createCombatSlice = (
let pendingPactOffer = state.pendingPactOffer; let pendingPactOffer = state.pendingPactOffer;
const log = [...state.log]; const log = [...state.log];
const skills = state.skills; const skills = state.skills;
let comboHitCount = state.comboHitCount || 0;
let floorHitCount = state.floorHitCount || 0;
const floorElement = getFloorElement(currentFloor); const floorElement = getFloorElement(currentFloor);
@@ -98,15 +100,33 @@ export const createCombatSlice = (
let dmg = calcDamage(state, spellId, floorElement); let dmg = calcDamage(state, spellId, floorElement);
dmg = dmg * effects.baseDamageMultiplier + effects.baseDamageBonus; dmg = dmg * effects.baseDamageMultiplier + effects.baseDamageBonus;
// Increment hit counters
comboHitCount += 1;
floorHitCount += 1;
// First Strike: +15% damage on first attack each floor
if (hasSpecial(effects, SPECIAL_EFFECTS.FIRST_STRIKE) && floorHitCount === 1) {
dmg *= 1.15;
log.unshift('⚡ First Strike! +15% damage!');
}
// Combo Master: Every 5th attack deals 3x damage
if (hasSpecial(effects, SPECIAL_EFFECTS.COMBO_MASTER) && comboHitCount % 5 === 0) {
dmg *= 3;
log.unshift('🌀 Combo Master! Triple damage!');
}
// Executioner: +100% damage to enemies below 25% HP // Executioner: +100% damage to enemies below 25% HP
if (hasSpecial(effects, SPECIAL_EFFECTS.EXECUTIONER) && floorHP / floorMaxHP < 0.25) { if (hasSpecial(effects, SPECIAL_EFFECTS.EXECUTIONER) && floorHP / floorMaxHP < 0.25) {
dmg *= 2; dmg *= 2;
log.unshift('💀 Executioner! Double damage!');
} }
// Berserker: +50% damage when below 50% mana // Berserker: +50% damage when below 50% mana
const maxMana = 100; // Would need proper max mana calculation const maxMana = 100; // Would need proper max mana calculation
if (hasSpecial(effects, SPECIAL_EFFECTS.BERSERKER) && rawMana < maxMana * 0.5) { if (hasSpecial(effects, SPECIAL_EFFECTS.BERSERKER) && rawMana < maxMana * 0.5) {
dmg *= 1.5; dmg *= 1.5;
log.unshift('🔥 Berserker! +50% damage!');
} }
// Spell echo - chance to cast again // Spell echo - chance to cast again
@@ -122,6 +142,14 @@ export const createCombatSlice = (
if (floorHP <= 0) { if (floorHP <= 0) {
const wasGuardian = GUARDIANS[currentFloor]; const wasGuardian = GUARDIANS[currentFloor];
// Adrenaline Rush: Defeating enemy restores 5% mana
if (hasSpecial(effects, SPECIAL_EFFECTS.ADRENALINE_RUSH)) {
const manaRestore = Math.floor(maxMana * 0.05);
rawMana = Math.min(rawMana + manaRestore, maxMana);
log.unshift(`💚 Adrenaline Rush! Restored ${manaRestore} mana!`);
}
if (wasGuardian && !signedPacts.includes(currentFloor)) { if (wasGuardian && !signedPacts.includes(currentFloor)) {
pendingPactOffer = currentFloor; pendingPactOffer = currentFloor;
log.unshift(`⚔️ ${wasGuardian.name} defeated! They offer a pact...`); log.unshift(`⚔️ ${wasGuardian.name} defeated! They offer a pact...`);
@@ -137,6 +165,7 @@ export const createCombatSlice = (
floorHP = floorMaxHP; floorHP = floorMaxHP;
maxFloorReached = Math.max(maxFloorReached, currentFloor); maxFloorReached = Math.max(maxFloorReached, currentFloor);
castProgress = 0; castProgress = 0;
floorHitCount = 0; // Reset floor hit counter for new floor
} }
} }
@@ -152,6 +181,8 @@ export const createCombatSlice = (
pendingPactOffer, pendingPactOffer,
castProgress, castProgress,
log, log,
comboHitCount,
floorHitCount,
}; };
}, },
}); });
+4
View File
@@ -185,6 +185,10 @@ export interface GameState {
totalDamageDealt: number; totalDamageDealt: number;
totalCraftsCompleted: number; totalCraftsCompleted: number;
// Combat special effect tracking
comboHitCount: number; // Hit counter for COMBO_MASTER (every 5th attack)
floorHitCount: number; // Hit counter for current floor (for FIRST_STRIKE)
// Prestige // Prestige
insight: number; insight: number;
totalInsight: number; totalInsight: number;