Phase 4: Combat special effects
This commit is contained in:
@@ -44,4 +44,34 @@ export const SPECIAL_EFFECTS: Record<string, EnchantmentEffectDef> = {
|
||||
allowedEquipmentCategories: CASTER_AND_HANDS,
|
||||
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
@@ -684,6 +684,10 @@ function makeInitial(overrides: Partial<GameState> = {}): GameState {
|
||||
totalDamageDealt: 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
|
||||
equippedInstances: startingEquipment.equippedInstances,
|
||||
equipmentInstances: startingEquipment.equipmentInstances,
|
||||
@@ -1013,7 +1017,9 @@ export const useGameStore = create<GameStore>()(
|
||||
}
|
||||
|
||||
// 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);
|
||||
|
||||
// Handle puzzle rooms separately
|
||||
@@ -1120,14 +1126,32 @@ export const useGameStore = create<GameStore>()(
|
||||
const effectiveArmor = Math.max(0, enemy.armor - armorPierce);
|
||||
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
|
||||
if (hasSpecial(effects, SPECIAL_EFFECTS.EXECUTIONER) && enemy.hp / enemy.maxHP < 0.25) {
|
||||
dmg *= 2;
|
||||
log = [`💀 Executioner! Double damage!`, ...log.slice(0, 49)];
|
||||
}
|
||||
|
||||
// Berserker: +50% damage when below 50% mana
|
||||
if (hasSpecial(effects, SPECIAL_EFFECTS.BERSERKER) && rawMana < maxMana * 0.5) {
|
||||
dmg *= 1.5;
|
||||
log = [`🔥 Berserker! +50% damage!`, ...log.slice(0, 49)];
|
||||
}
|
||||
|
||||
// Spell echo - chance to cast again
|
||||
@@ -1153,6 +1177,14 @@ export const useGameStore = create<GameStore>()(
|
||||
if (allDead) {
|
||||
// Floor cleared
|
||||
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)) {
|
||||
signedPacts = [...signedPacts, currentFloor];
|
||||
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;
|
||||
maxFloorReached = Math.max(maxFloorReached, currentFloor);
|
||||
|
||||
// Reset cast progress on floor change
|
||||
// Reset cast progress and floor hit counter on floor change
|
||||
castProgress = 0;
|
||||
floorHitCount = 0;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
@@ -1445,6 +1478,8 @@ export const useGameStore = create<GameStore>()(
|
||||
castProgress,
|
||||
golemancy,
|
||||
flowSurgeEndTime,
|
||||
comboHitCount,
|
||||
floorHitCount,
|
||||
...craftingUpdates,
|
||||
});
|
||||
},
|
||||
@@ -2449,7 +2484,7 @@ export const useGameStore = create<GameStore>()(
|
||||
}),
|
||||
{
|
||||
name: 'mana-loop-storage',
|
||||
version: 2,
|
||||
version: 3,
|
||||
migrate: (persistedState: unknown, version: number) => {
|
||||
const state = persistedState as Record<string, unknown>;
|
||||
// Migration from version 0/1 to version 2 - add missing fields
|
||||
@@ -2462,6 +2497,14 @@ export const useGameStore = create<GameStore>()(
|
||||
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;
|
||||
},
|
||||
partialize: (state) => ({
|
||||
@@ -2493,6 +2536,8 @@ export const useGameStore = create<GameStore>()(
|
||||
totalSpellsCast: state.totalSpellsCast,
|
||||
totalDamageDealt: state.totalDamageDealt,
|
||||
totalCraftsCompleted: state.totalCraftsCompleted,
|
||||
comboHitCount: state.comboHitCount,
|
||||
floorHitCount: state.floorHitCount,
|
||||
insight: state.insight,
|
||||
totalInsight: state.totalInsight,
|
||||
prestigeUpgrades: state.prestigeUpgrades,
|
||||
|
||||
@@ -84,6 +84,8 @@ export const createCombatSlice = (
|
||||
let pendingPactOffer = state.pendingPactOffer;
|
||||
const log = [...state.log];
|
||||
const skills = state.skills;
|
||||
let comboHitCount = state.comboHitCount || 0;
|
||||
let floorHitCount = state.floorHitCount || 0;
|
||||
|
||||
const floorElement = getFloorElement(currentFloor);
|
||||
|
||||
@@ -98,15 +100,33 @@ export const createCombatSlice = (
|
||||
let dmg = calcDamage(state, spellId, floorElement);
|
||||
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
|
||||
if (hasSpecial(effects, SPECIAL_EFFECTS.EXECUTIONER) && floorHP / floorMaxHP < 0.25) {
|
||||
dmg *= 2;
|
||||
log.unshift('💀 Executioner! Double damage!');
|
||||
}
|
||||
|
||||
// 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;
|
||||
log.unshift('🔥 Berserker! +50% damage!');
|
||||
}
|
||||
|
||||
// Spell echo - chance to cast again
|
||||
@@ -122,6 +142,14 @@ export const createCombatSlice = (
|
||||
|
||||
if (floorHP <= 0) {
|
||||
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)) {
|
||||
pendingPactOffer = currentFloor;
|
||||
log.unshift(`⚔️ ${wasGuardian.name} defeated! They offer a pact...`);
|
||||
@@ -137,6 +165,7 @@ export const createCombatSlice = (
|
||||
floorHP = floorMaxHP;
|
||||
maxFloorReached = Math.max(maxFloorReached, currentFloor);
|
||||
castProgress = 0;
|
||||
floorHitCount = 0; // Reset floor hit counter for new floor
|
||||
}
|
||||
}
|
||||
|
||||
@@ -152,6 +181,8 @@ export const createCombatSlice = (
|
||||
pendingPactOffer,
|
||||
castProgress,
|
||||
log,
|
||||
comboHitCount,
|
||||
floorHitCount,
|
||||
};
|
||||
},
|
||||
});
|
||||
|
||||
@@ -185,6 +185,10 @@ export interface GameState {
|
||||
totalDamageDealt: 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
|
||||
insight: number;
|
||||
totalInsight: number;
|
||||
|
||||
Reference in New Issue
Block a user