// Combat mechanics function updateEnemies() { gameState.enemies = gameState.enemies.filter(enemy => { // Add progress property if it doesn't exist if (typeof enemy.progress === 'undefined') { enemy.progress = 0; } // Update progress enemy.progress += enemy.speed * 0.001; // Check if enemy has completed the path if (enemy.progress >= 1) { gameState.enemiesEscaped++; return false; // Remove from array } // Check for collisions with projectiles const hitByProjectile = gameState.projectiles.some(projectile => { const distance = Math.hypot( enemy.position.x - projectile.startPos.x, enemy.position.y - projectile.startPos.y ); return distance < 0.5; }); if (hitByProjectile) { gameState.awardEnemyDestroyed(); return false; // Remove from array } // Update enemy position based on progress const pathPosition = getPathPosition(enemy.progress, gameState.path); enemy.position.x = pathPosition.x; enemy.position.y = pathPosition.y; return true; }); // Remove projectiles that hit enemies gameState.projectiles = gameState.projectiles.filter(projectile => { const hitEnemy = gameState.enemies.some(enemy => { const distance = Math.hypot( enemy.position.x - projectile.startPos.x, enemy.position.y - projectile.startPos.y ); return distance < 0.5; }); return !hitEnemy; }); } function updateParticles(particles, timestamp, deltaTime) { return particles.filter(particle => { const age = timestamp - particle.createdAt; if (age > particle.lifetime) return false; // Only update position for particles with velocity if (particle.velocity) { particle.position.x += particle.velocity.x * deltaTime; particle.position.y += particle.velocity.y * deltaTime; } return true; }); } function createDeathParticles(target, cellSize) { const particles = []; const centerX = (target.position.x + 0.5) * cellSize; const centerY = (target.position.y + 0.5) * cellSize; const particleCount = 8 + Math.floor(Math.random() * 8); for (let i = 0; i < particleCount; i++) { const baseAngle = (Math.PI * 2 * i) / particleCount; const randomAngle = baseAngle + (Math.random() - 0.5) * 1.5; const speedMultiplier = 0.7 + Math.random() * 0.6; const startOffset = Math.random() * 5; const startX = centerX + Math.cos(randomAngle) * startOffset; const startY = centerY + Math.sin(randomAngle) * startOffset; particles.push(createParticle( { ...ParticleTypes.DEATH_PARTICLE, speed: ParticleTypes.DEATH_PARTICLE.speed * speedMultiplier, lifetime: ParticleTypes.DEATH_PARTICLE.lifetime * (0.8 + Math.random() * 0.4) }, { x: startX, y: startY }, randomAngle )); } return particles; } function processTowerAttacks(towers, enemies, projectiles, particles, timestamp, cellSize) { towers.forEach(tower => { if (timestamp - tower.lastAttackTime > 1000 / tower.attackSpeed) { const enemiesInRange = findEnemiesInRange(tower, enemies); if (enemiesInRange.length > 0) { const target = enemiesInRange[0]; handleTowerAttack(tower, target, projectiles, particles, timestamp, cellSize); } } }); } function findEnemiesInRange(tower, enemies) { return enemies.filter(enemy => { const dx = enemy.position.x - tower.position.x; const dy = enemy.position.y - tower.position.y; return Math.sqrt(dx * dx + dy * dy) <= tower.range; }); } function createAOEExplosion(position, cellSize) { return { position: { x: (position.x + 0.5) * cellSize, y: (position.y + 0.5) * cellSize }, createdAt: performance.now(), type: 'AOE_EXPLOSION', ...ParticleTypes.AOE_EXPLOSION }; } function handleTowerAttack(tower, target, projectiles, particles, timestamp, cellSize) { // Create projectile projectiles.push({ startPos: tower.position, targetPos: target.position, createdAt: timestamp, lifetime: 300, towerType: tower.type }); if (tower.special === 'aoe') { // Find all enemies in AOE radius const enemiesInAOE = gameState.enemies.filter(enemy => { const dx = enemy.position.x - target.position.x; const dy = enemy.position.y - target.position.y; return Math.sqrt(dx * dx + dy * dy) <= tower.aoeRadius; }); // Create AOE explosion effect particles.push(createAOEExplosion(target.position, cellSize)); // Damage all enemies in range enemiesInAOE.forEach(enemy => { enemy.currentHealth -= tower.damage; if (enemy.currentHealth <= 0) { particles.push(...createDeathParticles(enemy, cellSize)); } }); } else if (tower.special === 'slow') { // Apply slow effect instead of damage if (!target.slowed) { target.speed *= (1 - tower.slowAmount); target.slowed = true; // Visual indicator for slowed enemies target.color = '#27ae60' + Math.floor(0.5 * 255).toString(16).padStart(2, '0'); } } else { // Normal damage for regular towers target.currentHealth -= tower.damage; } tower.lastAttackTime = timestamp; if (target.currentHealth <= 0) { particles.push(...createDeathParticles(target, cellSize)); } } function processEnemyAttacks(enemies, towers, particles, timestamp, cellSize) { enemies.forEach(enemy => { if (!EnemyTypes[enemy.type].isRanged) return; if (timestamp - enemy.lastAttackTime > 1000 / EnemyTypes[enemy.type].attackSpeed) { const towersInRange = findTowersInRange(enemy, towers); if (towersInRange.length > 0) { const target = towersInRange[0]; handleEnemyAttack(enemy, target, particles, timestamp, cellSize); } } }); } function findTowersInRange(enemy, towers) { return towers.filter(tower => { const dx = tower.position.x - enemy.position.x; const dy = tower.position.y - enemy.position.y; return Math.sqrt(dx * dx + dy * dy) <= EnemyTypes[enemy.type].attackRange; }); } function handleEnemyAttack(enemy, tower, particles, timestamp, cellSize) { // Create enemy projectile const projectileColor = '#8e44ad80'; // Semi-transparent purple particles.push(createParticle( { ...ParticleTypes.PROJECTILE, color: projectileColor, lifetime: 500 }, { x: (enemy.position.x + 0.5) * cellSize, y: (enemy.position.y + 0.5) * cellSize }, Math.atan2( tower.position.y - enemy.position.y, tower.position.x - enemy.position.x ) )); tower.currentHealth -= enemy.damage; enemy.lastAttackTime = timestamp; // Reduce tower's damage as it takes damage tower.damage = TowerTypes[tower.type].damage * (tower.currentHealth / tower.maxHealth); }