Update combat system with AOE, armor, dodge, and puzzle rooms
- Updated combat tick to handle multiple enemies (swarm rooms) - Added armor reduction calculation with armor pierce - Added dodge chance handling for speed rooms - Lightning spells have 30% faster cast and 50% reduced dodge chance - Frost Blade enchantment prevents dodge (freeze effect) - Puzzle rooms progress based on relevant attunement levels - Room state is now tracked in currentRoom field
This commit is contained in:
@@ -962,10 +962,35 @@ export const useGameStore = create<GameStore>()(
|
||||
}
|
||||
|
||||
// Combat - uses cast speed and spell casting
|
||||
let { currentFloor, floorHP, floorMaxHP, maxFloorReached, signedPacts, castProgress } = state;
|
||||
let { currentFloor, floorHP, floorMaxHP, maxFloorReached, signedPacts, castProgress, currentRoom } = state;
|
||||
const floorElement = getFloorElement(currentFloor);
|
||||
|
||||
if (state.currentAction === 'climb') {
|
||||
// Handle puzzle rooms separately
|
||||
if (state.currentAction === 'climb' && currentRoom.roomType === 'puzzle') {
|
||||
const progressSpeed = getPuzzleProgressSpeed(
|
||||
currentRoom.puzzleId || '',
|
||||
state.attunements
|
||||
);
|
||||
|
||||
currentRoom = {
|
||||
...currentRoom,
|
||||
puzzleProgress: (currentRoom.puzzleProgress || 0) + progressSpeed * HOURS_PER_TICK,
|
||||
};
|
||||
|
||||
// Check if puzzle is complete
|
||||
if (currentRoom.puzzleProgress >= (currentRoom.puzzleRequired || 1)) {
|
||||
const puzzle = PUZZLE_ROOMS[currentRoom.puzzleId || ''];
|
||||
log = [`🧩 ${puzzle?.name || 'Puzzle'} solved! Proceeding to floor ${currentFloor + 1}.`, ...log.slice(0, 49)];
|
||||
|
||||
currentFloor = currentFloor + 1;
|
||||
if (currentFloor > 100) currentFloor = 100;
|
||||
currentRoom = generateFloorState(currentFloor);
|
||||
floorMaxHP = getFloorMaxHP(currentFloor);
|
||||
floorHP = currentRoom.enemies[0]?.hp || floorMaxHP;
|
||||
maxFloorReached = Math.max(maxFloorReached, currentFloor);
|
||||
castProgress = 0;
|
||||
}
|
||||
} else if (state.currentAction === 'climb') {
|
||||
const spellId = state.activeSpell;
|
||||
const spellDef = SPELLS_DEF[spellId];
|
||||
|
||||
@@ -977,8 +1002,11 @@ export const useGameStore = create<GameStore>()(
|
||||
// Get spell cast speed (casts per hour, default 1)
|
||||
const spellCastSpeed = spellDef.castSpeed || 1;
|
||||
|
||||
// Lightning is faster and harder to dodge
|
||||
const lightningBonus = spellDef.elem === 'lightning' ? 0.3 : 0;
|
||||
|
||||
// Effective casts per tick = spellCastSpeed * totalAttackSpeed
|
||||
const progressPerTick = HOURS_PER_TICK * spellCastSpeed * totalAttackSpeed;
|
||||
const progressPerTick = HOURS_PER_TICK * spellCastSpeed * totalAttackSpeed * (1 + lightningBonus);
|
||||
|
||||
// Accumulate cast progress
|
||||
castProgress = (castProgress || 0) + progressPerTick;
|
||||
@@ -992,13 +1020,57 @@ export const useGameStore = create<GameStore>()(
|
||||
totalManaGathered += spellDef.cost.amount;
|
||||
|
||||
// Calculate damage
|
||||
let dmg = calcDamage(state, spellId, floorElement);
|
||||
let baseDmg = calcDamage(state, spellId, floorElement);
|
||||
|
||||
// Apply upgrade damage multipliers and bonuses
|
||||
dmg = dmg * effects.baseDamageMultiplier + effects.baseDamageBonus;
|
||||
baseDmg = baseDmg * effects.baseDamageMultiplier + effects.baseDamageBonus;
|
||||
|
||||
// Determine how many targets to hit
|
||||
const isAoe = spellDef.isAoe || spellDef.aoeTargets;
|
||||
const numTargets = Math.min(
|
||||
isAoe ? (spellDef.aoeTargets || 3) : 1,
|
||||
currentRoom.enemies.filter(e => e.hp > 0).length
|
||||
);
|
||||
|
||||
// Get armor pierce from spell effects
|
||||
const armorPierceEffect = spellDef.effects?.find(e => e.type === 'armor_pierce');
|
||||
const armorPierce = armorPierceEffect?.value || 0;
|
||||
|
||||
// Hit targets
|
||||
const aliveEnemies = currentRoom.enemies.filter(e => e.hp > 0);
|
||||
const targetsToHit = aliveEnemies.slice(0, numTargets);
|
||||
|
||||
for (const enemy of targetsToHit) {
|
||||
let dmg = baseDmg;
|
||||
|
||||
// AOE does less damage per target
|
||||
if (isAoe && numTargets > 1) {
|
||||
dmg *= (1 - 0.1 * (numTargets - 1)); // 10% less per additional target
|
||||
}
|
||||
|
||||
// Check for freeze (prevents dodge)
|
||||
const freezeEffect = spellDef.effects?.find(e => e.type === 'freeze');
|
||||
const isFrozen = freezeEffect && freezeEffect.chance === 1;
|
||||
|
||||
// Check for dodge (speed rooms)
|
||||
if (!isFrozen && enemy.dodgeChance > 0) {
|
||||
// Lightning has lower chance to be dodged
|
||||
const effectiveDodge = spellDef.elem === 'lightning'
|
||||
? enemy.dodgeChance * 0.5
|
||||
: enemy.dodgeChance;
|
||||
|
||||
if (Math.random() < effectiveDodge) {
|
||||
log = [`💨 Enemy dodged the attack!`, ...log.slice(0, 49)];
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
// Apply armor reduction
|
||||
const effectiveArmor = Math.max(0, enemy.armor - armorPierce);
|
||||
dmg *= (1 - effectiveArmor);
|
||||
|
||||
// Executioner: +100% damage to enemies below 25% HP
|
||||
if (hasSpecial(effects, SPECIAL_EFFECTS.EXECUTIONER) && floorHP / floorMaxHP < 0.25) {
|
||||
if (hasSpecial(effects, SPECIAL_EFFECTS.EXECUTIONER) && enemy.hp / enemy.maxHP < 0.25) {
|
||||
dmg *= 2;
|
||||
}
|
||||
|
||||
@@ -1021,21 +1093,32 @@ export const useGameStore = create<GameStore>()(
|
||||
rawMana = Math.min(rawMana + healAmount, maxMana);
|
||||
}
|
||||
|
||||
// Apply damage
|
||||
floorHP = Math.max(0, floorHP - dmg);
|
||||
// Apply damage to enemy
|
||||
enemy.hp = Math.max(0, enemy.hp - Math.floor(dmg));
|
||||
}
|
||||
|
||||
// Update currentRoom with damaged enemies
|
||||
currentRoom = { ...currentRoom, enemies: [...currentRoom.enemies] };
|
||||
|
||||
// Reduce cast progress by 1 (one cast completed)
|
||||
castProgress -= 1;
|
||||
|
||||
if (floorHP <= 0) {
|
||||
// Check if all enemies are dead
|
||||
const allDead = currentRoom.enemies.every(e => e.hp <= 0);
|
||||
|
||||
if (allDead) {
|
||||
// Floor cleared
|
||||
const wasGuardian = GUARDIANS[currentFloor];
|
||||
if (wasGuardian && !signedPacts.includes(currentFloor)) {
|
||||
signedPacts = [...signedPacts, currentFloor];
|
||||
log = [`⚔️ ${wasGuardian.name} defeated! Pact signed! (${wasGuardian.pact}x)`, ...log.slice(0, 49)];
|
||||
} else if (!wasGuardian) {
|
||||
if (currentFloor % 5 === 0) {
|
||||
log = [`🏰 Floor ${currentFloor} cleared!`, ...log.slice(0, 49)];
|
||||
const roomTypeName = currentRoom.roomType === 'swarm' ? 'Swarm'
|
||||
: currentRoom.roomType === 'speed' ? 'Speed floor'
|
||||
: currentRoom.roomType === 'puzzle' ? 'Puzzle'
|
||||
: 'Floor';
|
||||
if (currentFloor % 5 === 0 || currentRoom.roomType !== 'combat') {
|
||||
log = [`🏰 ${roomTypeName} ${currentFloor} cleared!`, ...log.slice(0, 49)];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1043,8 +1126,9 @@ export const useGameStore = create<GameStore>()(
|
||||
if (currentFloor > 100) {
|
||||
currentFloor = 100;
|
||||
}
|
||||
currentRoom = generateFloorState(currentFloor);
|
||||
floorMaxHP = getFloorMaxHP(currentFloor);
|
||||
floorHP = floorMaxHP;
|
||||
floorHP = currentRoom.enemies[0]?.hp || floorMaxHP;
|
||||
maxFloorReached = Math.max(maxFloorReached, currentFloor);
|
||||
|
||||
// Reset cast progress on floor change
|
||||
@@ -1096,6 +1180,7 @@ export const useGameStore = create<GameStore>()(
|
||||
floorMaxHP,
|
||||
maxFloorReached,
|
||||
signedPacts,
|
||||
currentRoom,
|
||||
incursionStrength,
|
||||
currentStudyTarget,
|
||||
skills,
|
||||
@@ -1119,6 +1204,7 @@ export const useGameStore = create<GameStore>()(
|
||||
floorMaxHP,
|
||||
maxFloorReached,
|
||||
signedPacts,
|
||||
currentRoom,
|
||||
incursionStrength,
|
||||
currentStudyTarget,
|
||||
skills,
|
||||
|
||||
Reference in New Issue
Block a user