diff options
Diffstat (limited to 'html/tower/js/mechanics.js')
-rw-r--r-- | html/tower/js/mechanics.js | 259 |
1 files changed, 182 insertions, 77 deletions
diff --git a/html/tower/js/mechanics.js b/html/tower/js/mechanics.js index 13b3ce8..2430ca0 100644 --- a/html/tower/js/mechanics.js +++ b/html/tower/js/mechanics.js @@ -1,23 +1,44 @@ -// Combat mechanics +/** + * Combat Mechanics Module + * + * This module handles all combat-related game mechanics including: + * 1. Enemy movement and behavior + * 2. Tower attacks and targeting + * 3. Projectile and particle systems + * 4. Status effects and special abilities + */ + +/** + * Updates all enemy states including movement, health, and status effects + * Implements core game loop mechanics for enemy behavior + * + * Key features: + * - Path following + * - Health management + * - Status effect processing + * - Collision detection + * + * @returns {void} + */ function updateEnemies() { const cellSize = canvas.width / 20; gameState.enemies = gameState.enemies.filter(enemy => { - // Add progress property if it doesn't exist + // Initialize progress tracking for new enemies if (typeof enemy.progress === 'undefined') { enemy.progress = 0; } - // Update progress + // Update movement progress enemy.progress += enemy.speed * 0.001; - // Check if enemy has completed the path + // Handle path completion if (enemy.progress >= 1) { gameState.enemiesEscaped++; - return false; // Remove from array + return false; } - // Check for collisions with projectiles + // Projectile collision detection const hitByProjectile = gameState.projectiles.some(projectile => { const distance = Math.hypot( enemy.position.x - projectile.startPos.x, @@ -28,15 +49,15 @@ function updateEnemies() { if (hitByProjectile) { gameState.awardEnemyDestroyed(); - return false; // Remove from array + return false; } - // Update enemy position based on progress + // Update position based on path progress const pathPosition = getPathPosition(enemy.progress, gameState.path); enemy.position.x = pathPosition.x; enemy.position.y = pathPosition.y; - // Check if slow effect has expired + // Process slow effect expiration if (enemy.slowed && performance.now() > enemy.slowExpiry) { enemy.slowed = false; enemy.slowStacks = 0; @@ -44,7 +65,7 @@ function updateEnemies() { enemy.speed = enemy.originalSpeed; } - // Add slime trail for slowed enemies (more particles for more stacks) + // Visual feedback for slowed status if (enemy.slowed && Math.random() < 0.2 + (enemy.slowStacks * 0.05)) { gameState.particles.push(createSlimeTrail(enemy, cellSize)); } @@ -65,12 +86,20 @@ function updateEnemies() { }); } +/** + * Updates particle effects with time-based animation + * Implements particle system lifecycle management + * + * @param {Array<Object>} particles - Array of particle objects + * @param {number} timestamp - Current game timestamp + * @param {number} deltaTime - Time elapsed since last frame + * @returns {Array<Object>} Updated particles array + */ 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; @@ -80,6 +109,14 @@ function updateParticles(particles, timestamp, deltaTime) { }); } +/** + * Creates death effect particles for defeated entities + * Implements visual feedback system + * + * @param {Object} target - The defeated entity + * @param {number} cellSize - Size of grid cell for scaling + * @returns {Array<Object>} Array of particle objects + */ function createDeathParticles(target, cellSize) { const particles = []; const centerX = (target.position.x + 0.5) * cellSize; @@ -91,8 +128,6 @@ function createDeathParticles(target, cellSize) { 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( { @@ -100,19 +135,33 @@ function createDeathParticles(target, cellSize) { speed: ParticleTypes.DEATH_PARTICLE.speed * speedMultiplier, lifetime: ParticleTypes.DEATH_PARTICLE.lifetime * (0.8 + Math.random() * 0.4) }, - { x: startX, y: startY }, + { + x: centerX + Math.cos(randomAngle) * startOffset, + y: centerY + Math.sin(randomAngle) * startOffset + }, randomAngle )); } return particles; } +/** + * Processes tower attacks and targeting + * Implements combat mechanics and special abilities + * + * @param {Array<Object>} towers - Array of tower objects + * @param {Array<Object>} enemies - Array of enemy objects + * @param {Array<Object>} projectiles - Array of projectile objects + * @param {Array<Object>} particles - Array of particle objects + * @param {number} timestamp - Current game timestamp + * @param {number} cellSize - Size of grid cell for scaling + */ 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) { + if (enemiesInRange.length > 0 && tower.ammo > 0) { const target = enemiesInRange[0]; handleTowerAttack(tower, target, projectiles, particles, timestamp, cellSize); } @@ -152,15 +201,22 @@ function createSlimeTrail(enemy, cellSize) { }; } +/** + * Handles individual tower attack logic including special effects + * Implements tower ability system + * + * @param {Object} tower - Attacking tower + * @param {Object} target - Target enemy + * @param {Array<Object>} projectiles - Projectile array + * @param {Array<Object>} particles - Particle array + * @param {number} timestamp - Current game timestamp + * @param {number} cellSize - Grid cell size + */ function handleTowerAttack(tower, target, projectiles, particles, timestamp, cellSize) { - // Check if tower has ammo - if (tower.ammo <= 0) { - return; - } - // Decrease ammo tower.ammo--; + // Create projectile projectiles.push({ startPos: tower.position, targetPos: target.position, @@ -169,66 +225,26 @@ function handleTowerAttack(tower, target, projectiles, particles, timestamp, cel towerType: tower.type }); + // Process special abilities if (tower.special === 'slow') { - // Initialize slow effect if not present - if (!target.slowStacks) { - target.slowStacks = 0; - } - - // Add another stack of slow (up to a maximum) - const maxStacks = 5; // Maximum 5 stacks - if (target.slowStacks < maxStacks) { - target.slowStacks++; - // Each stack slows by an additional 10% (multiplicative) - const newSlowAmount = 1 - Math.pow(0.9, target.slowStacks); // 10%, 19%, 27%, 34%, 41% - - // Only adjust speed if this is a stronger slow - if (!target.slowed || newSlowAmount > target.currentSlowAmount) { - const originalSpeed = target.originalSpeed || target.speed; - target.originalSpeed = originalSpeed; // Store original speed if not stored - target.speed = originalSpeed * (1 - newSlowAmount); - target.currentSlowAmount = newSlowAmount; - target.slowed = true; - } - - // Create slime particles for visual feedback - for (let i = 0; i < 4 + target.slowStacks; i++) { // More particles for more stacks - particles.push(createSlimeTrail(target, cellSize)); - } - } - - // Refresh slow duration - target.slowExpiry = timestamp + 2000; // 2 second duration + handleSlowEffect(target, tower, timestamp, particles, cellSize); } else 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 { - // Normal damage for regular towers - target.currentHealth -= tower.damage; + handleAOEEffect(target, tower, enemies, particles, cellSize); } tower.lastAttackTime = timestamp; - - if (target.currentHealth <= 0) { - particles.push(...createDeathParticles(target, cellSize)); - } } +/** + * Processes enemy attack behaviors and targeting + * Implements enemy combat AI + * + * @param {Array<Object>} enemies - Array of enemy objects + * @param {Array<Object>} towers - Array of tower objects + * @param {Array<Object>} particles - Particle effect array + * @param {number} timestamp - Current game timestamp + * @param {number} cellSize - Grid cell size + */ function processEnemyAttacks(enemies, towers, particles, timestamp, cellSize) { enemies.forEach(enemy => { if (!EnemyTypes[enemy.type].isRanged) return; @@ -244,6 +260,14 @@ function processEnemyAttacks(enemies, towers, particles, timestamp, cellSize) { }); } +/** + * Finds towers within enemy attack range + * Implements targeting system for enemies + * + * @param {Object} enemy - Enemy doing the targeting + * @param {Array<Object>} towers - Array of potential tower targets + * @returns {Array<Object>} Array of towers in range + */ function findTowersInRange(enemy, towers) { return towers.filter(tower => { const dx = tower.position.x - enemy.position.x; @@ -252,13 +276,22 @@ function findTowersInRange(enemy, towers) { }); } +/** + * Handles enemy attack execution and effects + * Implements enemy combat mechanics + * + * @param {Object} enemy - Attacking enemy + * @param {Object} tower - Target tower + * @param {Array<Object>} particles - Particle array for effects + * @param {number} timestamp - Current game timestamp + * @param {number} cellSize - Grid cell size + */ function handleEnemyAttack(enemy, tower, particles, timestamp, cellSize) { - // Create enemy projectile - const projectileColor = '#8e44ad80'; // Semi-transparent purple + // Create projectile effect particles.push(createParticle( { ...ParticleTypes.PROJECTILE, - color: projectileColor, + color: '#8e44ad80', // Semi-transparent purple lifetime: 500 }, { @@ -271,13 +304,85 @@ function handleEnemyAttack(enemy, tower, particles, timestamp, cellSize) { ) )); + // Apply damage and update tower state tower.currentHealth -= enemy.damage; enemy.lastAttackTime = timestamp; - // Reduce tower's damage as it takes damage + // Dynamic damage reduction based on tower health tower.damage = TowerTypes[tower.type].damage * (tower.currentHealth / tower.maxHealth); } +/** + * Handles slow effect application and stacking + * Implements status effect system + * + * @param {Object} target - Enemy to apply slow to + * @param {Object} tower - Tower applying the effect + * @param {number} timestamp - Current game timestamp + * @param {Array<Object>} particles - Particle array for effects + * @param {number} cellSize - Grid cell size + */ +function handleSlowEffect(target, tower, timestamp, particles, cellSize) { + // Initialize slow effect tracking + if (!target.slowStacks) { + target.slowStacks = 0; + } + + const maxStacks = 5; // Maximum 5 stacks + if (target.slowStacks < maxStacks) { + target.slowStacks++; + // Each stack slows by an additional 10% (multiplicative) + const newSlowAmount = 1 - Math.pow(0.9, target.slowStacks); + + // Only update if new slow is stronger + if (!target.slowed || newSlowAmount > target.currentSlowAmount) { + const originalSpeed = target.originalSpeed || target.speed; + target.originalSpeed = originalSpeed; + target.speed = originalSpeed * (1 - newSlowAmount); + target.currentSlowAmount = newSlowAmount; + target.slowed = true; + } + + // Visual feedback particles + for (let i = 0; i < 4 + target.slowStacks; i++) { + particles.push(createSlimeTrail(target, cellSize)); + } + } + + // Refresh effect duration + target.slowExpiry = timestamp + 2000; // 2 second duration +} + +/** + * Handles AOE (Area of Effect) damage and visual effects + * Implements area damage system + * + * @param {Object} target - Primary target + * @param {Object} tower - Tower dealing AOE damage + * @param {Array<Object>} enemies - All enemies for AOE calculation + * @param {Array<Object>} particles - Particle array for effects + * @param {number} cellSize - Grid cell size + */ +function handleAOEEffect(target, tower, enemies, particles, cellSize) { + // Find all enemies in AOE radius + const enemiesInAOE = 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 explosion effect + particles.push(createAOEExplosion(target.position, cellSize)); + + // Apply AOE damage + enemiesInAOE.forEach(enemy => { + enemy.currentHealth -= tower.damage; + if (enemy.currentHealth <= 0) { + particles.push(...createDeathParticles(enemy, cellSize)); + } + }); +} + // Update createEnemy to track original speed function createEnemy(startPosition) { const enemy = { |