Source: mechanics.js

/**
 * 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;
}