diff options
Diffstat (limited to 'html/tower/js/mechanics.js')
-rw-r--r-- | html/tower/js/mechanics.js | 430 |
1 files changed, 430 insertions, 0 deletions
diff --git a/html/tower/js/mechanics.js b/html/tower/js/mechanics.js new file mode 100644 index 0000000..bc73fff --- /dev/null +++ b/html/tower/js/mechanics.js @@ -0,0 +1,430 @@ +/** + * 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 + * + * @module mechanics + */ + +/** + * 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 => { + // Initialize progress tracking for new enemies + if (typeof enemy.progress === 'undefined') { + enemy.progress = 0; + } + + // Update movement progress + enemy.progress += enemy.speed * 0.001; + + // Handle path completion + if (enemy.progress >= 1) { + gameState.enemiesEscaped++; + // Deduct currency when enemy escapes + gameState.currency = Math.max(0, gameState.currency - 10); + // Check for game over + if (gameState.currency <= 0) { + handleGameOver(); + } + return false; + } + + // Projectile collision detection and damage application + const hitByProjectile = gameState.projectiles.some(projectile => { + // Calculate current projectile position based on lifetime + const age = performance.now() - projectile.createdAt; + const progress = Math.min(age / projectile.lifetime, 1); + + const currentX = projectile.startPos.x + (projectile.targetPos.x - projectile.startPos.x) * progress; + const currentY = projectile.startPos.y + (projectile.targetPos.y - projectile.startPos.y) * progress; + + const distance = Math.hypot( + enemy.position.x - currentX, + enemy.position.y - currentY + ); + + if (distance < 0.5) { + // Apply damage when projectile hits + enemy.currentHealth -= projectile.damage; + return true; + } + return false; + }); + + if (enemy.currentHealth <= 0) { + gameState.awardEnemyDestroyed(); + // Create death particles + gameState.particles.push(...createDeathParticles(enemy, cellSize)); + return false; + } + + // Update position based on path progress + const pathPosition = getPathPosition(enemy.progress, gameState.path); + enemy.position.x = pathPosition.x; + enemy.position.y = pathPosition.y; + + // Process slow effect expiration + if (enemy.slowed && performance.now() > enemy.slowExpiry) { + enemy.slowed = false; + enemy.slowStacks = 0; + enemy.currentSlowAmount = 0; + enemy.speed = enemy.originalSpeed; + } + + // Visual feedback for slowed status + if (enemy.slowed && Math.random() < 0.2 + (enemy.slowStacks * 0.05)) { + gameState.particles.push(createSlimeTrail(enemy, cellSize)); + } + + return true; + }); + + // Remove projectiles that hit enemies + gameState.projectiles = gameState.projectiles.filter(projectile => { + const age = performance.now() - projectile.createdAt; + if (age >= projectile.lifetime) return false; + + const progress = age / projectile.lifetime; + const currentX = projectile.startPos.x + (projectile.targetPos.x - projectile.startPos.x) * progress; + const currentY = projectile.startPos.y + (projectile.targetPos.y - projectile.startPos.y) * progress; + + const hitEnemy = gameState.enemies.some(enemy => { + const distance = Math.hypot( + enemy.position.x - currentX, + enemy.position.y - currentY + ); + return distance < 0.5; + }); + + return !hitEnemy; + }); +} + +/** + * 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; + + if (particle.velocity) { + particle.position.x += particle.velocity.x * deltaTime; + particle.position.y += particle.velocity.y * deltaTime; + } + + return true; + }); +} + +/** + * 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; + 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; + + particles.push(createParticle( + { + ...ParticleTypes.DEATH_PARTICLE, + speed: ParticleTypes.DEATH_PARTICLE.speed * speedMultiplier, + lifetime: ParticleTypes.DEATH_PARTICLE.lifetime * (0.8 + Math.random() * 0.4) + }, + { + 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 && tower.ammo > 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 createSlimeTrail(enemy, cellSize) { + return { + position: { + x: (enemy.position.x + 0.5) * cellSize, + y: (enemy.position.y + 0.5) * cellSize + }, + createdAt: performance.now(), + type: 'SLIME_TRAIL', + ...ParticleTypes.SLIME_TRAIL + }; +} + +/** + * 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) { + // Only attack if we have ammo + if (tower.ammo <= 0) return; + + // Decrease ammo (already checked it's > 0) + tower.ammo--; + + // Create projectile + projectiles.push({ + startPos: { ...tower.position }, + targetPos: { ...target.position }, + createdAt: timestamp, + lifetime: 300, + towerType: tower.type, + damage: tower.damage + }); + + // Process special abilities + if (tower.special === 'slow') { + handleSlowEffect(target, tower, timestamp, particles, cellSize); + } else if (tower.special === 'aoe') { + handleAOEEffect(target, tower, gameState.enemies, particles, cellSize); + } + + tower.lastAttackTime = timestamp; +} + +/** + * 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; + + 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); + } + } + }); +} + +/** + * 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; + const dy = tower.position.y - enemy.position.y; + return Math.sqrt(dx * dx + dy * dy) <= EnemyTypes[enemy.type].attackRange; + }); +} + +/** + * 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 projectile effect + particles.push(createParticle( + { + ...ParticleTypes.PROJECTILE, + color: '#8e44ad80', // Semi-transparent purple + 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 + ) + )); + + // Apply damage and update tower state + tower.currentHealth -= enemy.damage; + enemy.lastAttackTime = timestamp; + + // 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 = { + // ... existing enemy properties ... + slowStacks: 0, + currentSlowAmount: 0 + }; + enemy.originalSpeed = enemy.speed; // Store original speed + return enemy; +} \ No newline at end of file |