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:
Z User
2026-03-30 15:35:09 +00:00
parent dd9528a418
commit cd10918a3b

View File

@@ -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,