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
|
// 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);
|
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 spellId = state.activeSpell;
|
||||||
const spellDef = SPELLS_DEF[spellId];
|
const spellDef = SPELLS_DEF[spellId];
|
||||||
|
|
||||||
@@ -977,8 +1002,11 @@ export const useGameStore = create<GameStore>()(
|
|||||||
// Get spell cast speed (casts per hour, default 1)
|
// Get spell cast speed (casts per hour, default 1)
|
||||||
const spellCastSpeed = spellDef.castSpeed || 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
|
// 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
|
// Accumulate cast progress
|
||||||
castProgress = (castProgress || 0) + progressPerTick;
|
castProgress = (castProgress || 0) + progressPerTick;
|
||||||
@@ -992,13 +1020,57 @@ export const useGameStore = create<GameStore>()(
|
|||||||
totalManaGathered += spellDef.cost.amount;
|
totalManaGathered += spellDef.cost.amount;
|
||||||
|
|
||||||
// Calculate damage
|
// Calculate damage
|
||||||
let dmg = calcDamage(state, spellId, floorElement);
|
let baseDmg = calcDamage(state, spellId, floorElement);
|
||||||
|
|
||||||
// Apply upgrade damage multipliers and bonuses
|
// 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
|
// 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;
|
dmg *= 2;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1021,21 +1093,32 @@ export const useGameStore = create<GameStore>()(
|
|||||||
rawMana = Math.min(rawMana + healAmount, maxMana);
|
rawMana = Math.min(rawMana + healAmount, maxMana);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Apply damage
|
// Apply damage to enemy
|
||||||
floorHP = Math.max(0, floorHP - dmg);
|
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)
|
// Reduce cast progress by 1 (one cast completed)
|
||||||
castProgress -= 1;
|
castProgress -= 1;
|
||||||
|
|
||||||
if (floorHP <= 0) {
|
// Check if all enemies are dead
|
||||||
|
const allDead = currentRoom.enemies.every(e => e.hp <= 0);
|
||||||
|
|
||||||
|
if (allDead) {
|
||||||
// Floor cleared
|
// Floor cleared
|
||||||
const wasGuardian = GUARDIANS[currentFloor];
|
const wasGuardian = GUARDIANS[currentFloor];
|
||||||
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)];
|
||||||
} else if (!wasGuardian) {
|
} else if (!wasGuardian) {
|
||||||
if (currentFloor % 5 === 0) {
|
const roomTypeName = currentRoom.roomType === 'swarm' ? 'Swarm'
|
||||||
log = [`🏰 Floor ${currentFloor} cleared!`, ...log.slice(0, 49)];
|
: 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) {
|
if (currentFloor > 100) {
|
||||||
currentFloor = 100;
|
currentFloor = 100;
|
||||||
}
|
}
|
||||||
|
currentRoom = generateFloorState(currentFloor);
|
||||||
floorMaxHP = getFloorMaxHP(currentFloor);
|
floorMaxHP = getFloorMaxHP(currentFloor);
|
||||||
floorHP = 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 on floor change
|
||||||
@@ -1096,6 +1180,7 @@ export const useGameStore = create<GameStore>()(
|
|||||||
floorMaxHP,
|
floorMaxHP,
|
||||||
maxFloorReached,
|
maxFloorReached,
|
||||||
signedPacts,
|
signedPacts,
|
||||||
|
currentRoom,
|
||||||
incursionStrength,
|
incursionStrength,
|
||||||
currentStudyTarget,
|
currentStudyTarget,
|
||||||
skills,
|
skills,
|
||||||
@@ -1119,6 +1204,7 @@ export const useGameStore = create<GameStore>()(
|
|||||||
floorMaxHP,
|
floorMaxHP,
|
||||||
maxFloorReached,
|
maxFloorReached,
|
||||||
signedPacts,
|
signedPacts,
|
||||||
|
currentRoom,
|
||||||
incursionStrength,
|
incursionStrength,
|
||||||
currentStudyTarget,
|
currentStudyTarget,
|
||||||
skills,
|
skills,
|
||||||
|
|||||||
Reference in New Issue
Block a user