'use strict'; // === COMBAT SYSTEM === function tryAttack() { const weapon = gameState.player.activeWeapon; if (!weapon) { // Try to use first available weapon if (gameState.player.weapons.length > 0) { gameState.player.activeWeapon = gameState.player.weapons[0]; } else { showMessage('You need a weapon!'); return; } } const active = gameState.player.activeWeapon; if (gameState.player.attackTimer > 0) return; // Check ammo if (active.ammoRequired) { const ammoType = active.ammoType || 'arrows'; const ammoCount = ammoType === 'arrows' ? gameState.player.arrows : gameState.player.bombs; if (ammoCount <= 0) { showMessage(`No ${ammoType} left!`); return; } if (ammoType === 'arrows') gameState.player.arrows--; else gameState.player.bombs--; } // Set attack timer gameState.player.attackTimer = active.cooldown || ATTACK_COOLDOWN; gameState.player.attackAnim = active.animDuration || ATTACK_ANIM; const dir = DIRS[gameState.player.dir]; const range = active.range || 1; // Melee attack if (active.type === 'melee') { for (let i = 1; i <= range; i++) { const ax = gameState.player.x + dir.x * i; const ay = gameState.player.y + dir.y * i; if (!inBounds(ax, ay)) break; // Hit enemies for (const enemy of gameState.enemies) { if (enemy.x === ax && enemy.y === ay) { dealDamage(enemy, active.damage, active); } } // Hit boss if (gameState.currentBoss && gameState.currentBoss.x === ax && gameState.currentBoss.y === ay) { dealDamage(gameState.currentBoss, active.damage, active); } } // AoE weapons hit in arc if (active.aoe) { for (const enemy of gameState.enemies) { if (manhattanDist(gameState.player.x, gameState.player.y, enemy.x, enemy.y) <= active.aoeRange) { dealDamage(enemy, Math.floor(active.damage * 0.5), active); } } } } // Ranged attack if (active.type === 'ranged') { spawnProjectile(gameState.player.x, gameState.player.y, dir, active); } // Special effects if (active.effect === 'stun') { for (const enemy of gameState.enemies) { if (enemy.x === gameState.player.x + dir.x && enemy.y === gameState.player.y + dir.y) { enemy.stunned = true; enemy.specialTimer = active.effectDuration || 2000; } } } if (active.effect === 'freeze') { for (const enemy of gameState.enemies) { if (enemy.x === gameState.player.x + dir.x && enemy.y === gameState.player.y + dir.y) { enemy.frozen = true; enemy.specialTimer = active.effectDuration || 3000; } } } if (active.effect === 'fire') { for (const enemy of gameState.enemies) { if (enemy.x === gameState.player.x + dir.x && enemy.y === gameState.player.y + dir.y) { dealDamage(enemy, active.damage, active); enemy.onFire = true; enemy.specialTimer = active.effectDuration || 2000; } } } // Clean up dead enemies const beforeCount = gameState.enemies.length; gameState.enemies = gameState.enemies.filter(e => e.hp > 0); const killed = beforeCount - gameState.enemies.length; if (killed > 0) { gameState.enemiesDefeated += killed; showMessage(`${killed} enemy${killed > 1 ? 'ies' : ''} defeated!`); } // Check boss death if (gameState.currentBoss && gameState.currentBoss.hp <= 0) { const boss = gameState.currentBoss; showMessage(`${boss.name} defeated!`); gameState.currentBoss = null; gameState.exitOpen = true; gameState.enemiesDefeated++; spawnParticles(boss.x * TILE + TILE / 2, boss.y * TILE + TILE / 2, '#FFD700', 20); } // If no enemies and no exit, room is clear if (gameState.enemies.length === 0 && !gameState.exit && !gameState.currentBoss) { gameState.exitOpen = true; showMessage('Room cleared!'); } } function dealDamage(target, damage, weapon) { if (target.invincible && target.invincibleTimer > 0) return; // Apply defense reduction const def = target.defense || 0; const actualDamage = Math.max(1, damage - def); target.hp -= actualDamage; target.hitTimer = HIT_FLASH; // Spawn hit particles spawnParticles(target.x * TILE + TILE / 2, target.y * TILE + TILE / 2, '#FF0000', 4); // Check for phase change (bosses) if (target.phases && target.phases[target.phase + 1] && target.hp <= target.phases[target.phase + 1].hpThreshold * target.maxHp / 100) { target.phase++; showMessage(`${target.name} enrage${target.phase > 0 ? 's' : ''}! (Phase ${target.phase + 1})`); target.attackTimer = 0; target.moveTimer = 0; } } function spawnProjectile(x, y, dir, weapon) { if (!gameState.projectiles) gameState.projectiles = []; gameState.projectiles.push({ x: x + dir.x, y: y + dir.y, dir: { ...dir }, speed: weapon.projSpeed || 4, damage: weapon.damage, range: weapon.projRange || 8, traveled: 0, type: weapon.type, effect: weapon.effect, effectDuration: weapon.effectDuration, color: weapon.projColor || '#FFFF00', moveTimer: 0, }); } function updateProjectiles(dt) { if (!gameState.projectiles) return; for (let i = gameState.projectiles.length - 1; i >= 0; i--) { const p = gameState.projectiles[i]; p.moveTimer -= dt; if (p.moveTimer > 0) continue; p.moveTimer = 100; const nx = p.x + p.dir.x; const ny = p.y + p.dir.y; p.traveled++; // Check bounds if (!inBounds(nx, ny) || p.traveled >= p.range) { gameState.projectiles.splice(i, 1); continue; } // Check walls if (isWall(gameState.layout, nx, ny)) { spawnParticles(nx * TILE + TILE / 2, ny * TILE + TILE / 2, p.color, 3); gameState.projectiles.splice(i, 1); continue; } // Hit enemies let hit = false; for (const enemy of gameState.enemies) { if (enemy.x === nx && enemy.y === ny) { dealDamage(enemy, p.damage, { name: 'projectile' }); if (p.effect === 'stun') { enemy.stunned = true; enemy.specialTimer = p.effectDuration || 2000; } if (p.effect === 'freeze') { enemy.frozen = true; enemy.specialTimer = p.effectDuration || 3000; } hit = true; break; } } // Hit boss if (!hit && gameState.currentBoss && gameState.currentBoss.x === nx && gameState.currentBoss.y === ny) { dealDamage(gameState.currentBoss, p.damage, { name: 'projectile' }); hit = true; } if (hit) { spawnParticles(nx * TILE + TILE / 2, ny * TILE + TILE / 2, '#FF0000', 5); gameState.projectiles.splice(i, 1); continue; } p.x = nx; p.y = ny; } } if (typeof module !== 'undefined' && module.exports) { module.exports = { tryAttack, dealDamage, spawnProjectile, updateProjectiles }; }