User prompt
Haz que el máximo sea 14
User prompt
Haz que el mínimo de autos sea 5 y el máximo 8
User prompt
Incluye en ese círculo a playera y hazlo un poco más grande
User prompt
Haz que los enemigos no aparezcan en posiciones aleatorios del mapa. Haz que aparezcan como si hicieran un circulo con espacio similar entre cada auto
User prompt
Mejora la lógica de agresivo, huida. Haz que al tener mas vida sean más agresivos, al tener menos huyan más. También agrega un campero, este espera a que la vida de los demás disminuya para atacar
User prompt
Arregla el joystick, no se puede emoveral jugador
User prompt
Mejora la lógica de agresivo, huida. Haz que al tener mas vida sean más agresivos, al tener menos huyan más. También agrega un campero, este espera a que la vida de los demás disminuya para atacar
User prompt
Haz que los autos se vean influenciados (poco) en atacar a los autos con más vida
User prompt
Agrega una lógica para hacer que los 3 primeros autos (con mis vidas) jueguen agresivos
User prompt
Mejora la lógica de Uida para que el auto al tener menos vida tienda a uir o alejarse de los grupos grandes de autos. También haz que calculé si el golpe tendrá consecuencias para el
User prompt
Mejora la IA agregando un nuevo método de objetivo (velocidad, vida y distancia)
User prompt
Haz que la barra de vida de los enemigos Sena iguales a la del jugador
User prompt
Haz que la barra de vida de los enemigos Sena iguales a la del jugador ↪💡 Consider importing and using the following plugins: @upit/tween.v1
User prompt
Agrega debajo de la barra de vida del jugador las barras de vida de los demás autos con su respectivo color ↪💡 Consider importing and using the following plugins: @upit/tween.v1
User prompt
Agrega debajo de la barra de vida del jugador las barras de vida de los demás autos con su respectivo color ↪💡 Consider importing and using the following plugins: @upit/tween.v1
User prompt
Cuando se destruya un auto que ya no se pueda mover
User prompt
Agrega un sistema de daño que dependa de la velocidad, si se va muy rápido el daño será mucho, si se va muy lento no habrá daño.
User prompt
Agrega el sistema de daño. mientras más velocidad choque un auto más daño hará pero también recibirá un daño menor para el chocador. Cuando llegue a 0 el auto deja de funcionar y queda como obstaculo ↪💡 Consider importing and using the following plugins: @upit/tween.v1
User prompt
Agrega a cada auto su vida al 100
User prompt
Please fix the bug: 'Cannot set properties of undefined (setting 'carColor')' in or related to this line: 'enemyCar.carColor = selectedColor;' Line Number: 52
User prompt
El valor de las barras parece no mostrarse correctamente, probablemente salen como N/A y no como del valo de vida del auto que rpovienen
User prompt
Arregla el error de que no sale la barra de colores del auto que provienen, probablemente el valor se muestre como N/A
User prompt
Agrega a cada auto su vida y barra, que se muestre por debajo de la barra de vida de jugador ↪💡 Consider importing and using the following plugins: @upit/tween.v1
User prompt
Please fix the bug: 'enemyCarGraphics is not defined' in or related to this line: 'enemyCar.healthBar.tint = enemyCarGraphics.tint;' Line Number: 174
User prompt
Agrega a cada auto su vida y barra ↪💡 Consider importing and using the following plugins: @upit/tween.v1
/**** * Plugins ****/ var tween = LK.import("@upit/tween.v1"); /**** * Classes ****/ var EnemyCar = Container.expand(function () { var self = Container.call(this); var enemyCarGraphics = self.attachAsset('Cars', { anchorX: 0.5, anchorY: 0.5 }); // Assign random color (excluding red) var enemyColors = [0x0066ff, // Blue 0x00ff66, // Green 0xffff00, // Yellow 0xff8800, // Orange 0x8800ff, // Purple 0x00ffff, // Cyan 0xff00ff, // Magenta 0x888888, // Gray 0xffffff // White ]; var randomColorIndex = Math.floor(Math.random() * enemyColors.length); enemyCarGraphics.tint = enemyColors[randomColorIndex]; // Basic properties for enemy car with weight self.velocityX = 0; self.velocityY = 0; self.rotation = 0; self.weight = 1.0 + Math.random() * 1.5; // Random weight between 1.0 and 2.5 return self; }); var Particle = Container.expand(function () { var self = Container.call(this); // Random particle size between 10.8-25.2 pixels (20% smaller than original) var particleSize = 10.8 + Math.random() * 14.4; var particleGraphics = self.attachAsset('ParticulasVel', { anchorX: 0.5, anchorY: 0.5, scaleX: particleSize / 30, scaleY: particleSize / 30 }); // Random initial properties (20% smaller velocities) self.velocityX = (Math.random() - 0.5) * 3.2; self.velocityY = Math.random() * 2.4 + 0.8; self.lifespan = 20 + Math.random() * 20; // Reduced lifespan: 0.33-0.67 seconds at 60fps self.age = 0; self.update = function () { // Update position self.x += self.velocityX; self.y += self.velocityY; // Age particle self.age++; // Fade out over time var fadeProgress = self.age / self.lifespan; particleGraphics.alpha = 1 - fadeProgress; // Scale down over time var scaleProgress = 1 - fadeProgress * 0.5; particleGraphics.scaleX = particleSize / 30 * scaleProgress; particleGraphics.scaleY = particleSize / 30 * scaleProgress; // Apply gravity and air resistance (20% reduced for smaller scale) self.velocityY += 0.08; // Reduced gravity self.velocityX *= 0.984; // Slightly less air resistance self.velocityY *= 0.984; }; return self; }); /**** * Initialize Game ****/ var game = new LK.Game({ backgroundColor: 0x000000 }); /**** * Game Code ****/ // Create gameplay background - 4/5 of screen height (top portion) 6; var gameplayBackground = game.attachAsset('gameplayBg', { x: 0, y: 0, anchorX: 0, anchorY: 0 }); // Create carPlayer character on top of gameplayBackground var carPlayer = gameplayBackground.attachAsset('CarPlayer', { x: 1024, // Center horizontally y: 1800, // Position in lower portion of gameplay area anchorX: 0.5, anchorY: 0.5 }); // Create array to store multiple enemy cars var enemyCars = []; var numEnemyCars = 3 + Math.floor(Math.random() * 3); // Random between 3-5 cars // Create multiple enemy cars in gameplay area at random positions for (var carIndex = 0; carIndex < numEnemyCars; carIndex++) { var enemyCar = new EnemyCar(); // Generate random position around the edges or inside the gameplay area var spawnSide = Math.floor(Math.random() * 4); // 0=top, 1=right, 2=bottom, 3=left var centerX = 1024; // Center of gameplay area var centerY = 1093; // Center of gameplay area (2186/2) switch (spawnSide) { case 0: case 0: // Top edge enemyCar.x = Math.random() * 1800 + 124; // Random X between 124-1924 enemyCar.y = Math.random() * 300 + 50; // Random Y between 50-350 break; case 1: // Right edge enemyCar.x = Math.random() * 300 + 1700; // Random X between 1700-2000 enemyCar.y = Math.random() * 1800 + 193; // Random Y between 193-1993 break; case 2: // Bottom edge enemyCar.x = Math.random() * 1800 + 124; // Random X between 124-1924 enemyCar.y = Math.random() * 300 + 1836; // Random Y between 1836-2136 break; case 3: // Left edge enemyCar.x = Math.random() * 300 + 50; // Random X between 50-350 enemyCar.y = Math.random() * 1800 + 193; // Random Y between 193-1993 break; } // Calculate rotation to face the center var deltaX = centerX - enemyCar.x; var deltaY = centerY - enemyCar.y; enemyCar.rotation = Math.atan2(deltaX, -deltaY); // Rotation to face center // Initialize enemy car health enemyCar.health = maxHealth; // Enemy starts with full health // Create health bar background for this enemy car enemyCar.healthBarBg = gameplayBackground.attachAsset('BarBg', { anchorX: 0.5, anchorY: 0.5, scaleX: 1.5, scaleY: 0.2 }); // Create health bar for this enemy car enemyCar.healthBar = gameplayBackground.attachAsset('Bar', { anchorX: 0.5, anchorY: 0.5, scaleX: 1.5, scaleY: 0.2 }); // Tint health bar with enemy car color enemyCar.healthBar.tint = enemyCarGraphics.tint; // Give each AI car individual tactical preferences and personality enemyCar.tacticalPersonality = { // Primary strategy preference probabilities (0-1) directPreference: 0.2 + Math.random() * 0.6, // 0.2-0.8 preference for direct pursuit ambushPreference: 0.1 + Math.random() * 0.7, // 0.1-0.8 preference for ambush tactics intimidationPreference: 0.1 + Math.random() * 0.5, // 0.1-0.6 preference for intimidation // Speed-based tactical preferences slowTargetStrategy: Math.floor(Math.random() * 3), // 0=direct, 1=intimidate, 2=ambush for slow targets fastTargetStrategy: Math.floor(Math.random() * 3), // 0=direct, 1=intimidate, 2=ambush for fast targets mediumTargetStrategy: Math.floor(Math.random() * 3), // 0=direct, 1=intimidate, 2=ambush for medium targets // Individual behavioral traits aggressiveness: 0.3 + Math.random() * 0.7, // 0.3-1.0 how aggressive this car is patience: 0.2 + Math.random() * 0.8, // 0.2-1.0 how long car sticks to one strategy adaptability: 0.1 + Math.random() * 0.9, // 0.1-1.0 how quickly car changes strategies // Individual strategy duration preferences (in frames) minStrategyDuration: 30 + Math.floor(Math.random() * 60), // 0.5-1.5 seconds minimum maxStrategyDuration: 90 + Math.floor(Math.random() * 180), // 1.5-4.5 seconds maximum // Personal distance preferences preferredAttackDistance: 100 + Math.random() * 200, // 100-300 preferred distance for attacks preferredIntimidationDistance: 120 + Math.random() * 100, // 120-220 preferred intimidation distance personalSpaceRadius: 80 + Math.random() * 120, // 80-200 personal space when maneuvering // Individual flight and evasion preferences flightTendency: 0.2 + Math.random() * 0.6, // 0.2-0.8 how likely to flee when pursued bravery: 0.1 + Math.random() * 0.8, // 0.1-0.9 how brave this car is (opposes flight tendency) panicThreshold: 0.3 + Math.random() * 0.5, // 0.3-0.8 pursuit intensity needed to trigger panic // Preferred evasion tactics (0-1 probability for each) preferSpeedEscape: Math.random(), // 0-1 preference for straight-line speed escapes preferZigzagEscape: Math.random(), // 0-1 preference for zigzag evasion patterns preferCircularEscape: Math.random(), // 0-1 preference for circular evasion patterns // Flight decision factors flightDistance: 150 + Math.random() * 200, // 150-350 distance at which flight is considered maxFlightDuration: 120 + Math.random() * 240, // 2-6 seconds maximum flight time before reconsidering counterAttackChance: 0.2 + Math.random() * 0.5, // 0.2-0.7 base chance to counter-attack instead of fleeing riskTolerance: 0.1 + Math.random() * 0.7 // 0.1-0.8 tolerance for risky situations }; // Add to array and scene enemyCars.push(enemyCar); gameplayBackground.addChild(enemyCar); } // For backward compatibility, keep reference to first enemy car var enemyCar = enemyCars[0]; // Create UI background - 1/5 of screen height (bottom portion) var uiBackground = game.attachAsset('uiBg', { x: 0, y: 2186, anchorX: 0, anchorY: 0 }); // Create player health bar at the top right of UI with 20px margin var healthBarBg = uiBackground.attachAsset('BarBg', { x: 1800, y: 40, anchorX: 0.5, anchorY: 0.5, scaleX: 4, scaleY: 0.3 }); var healthBar = uiBackground.attachAsset('Bar', { x: 1800, y: 40, anchorX: 0.5, anchorY: 0.5, scaleX: 4, scaleY: 0.3 }); // Tint health bar with player car color (red) healthBar.tint = 0xff0000; // Create speed display text var speedText = new Text2('Speed: 0', { size: 60, fill: 0x000000 }); speedText.anchor.set(0, 0.5); speedText.x = 50; speedText.y = 2459; // Center vertically in UI area game.addChild(speedText); // Create joystickBG centered in UI background var joystickBG = uiBackground.attachAsset('JoystickBG', { x: 1024, // Center horizontally in UI y: 273, // Center vertically in UI (546/2 = 273) anchorX: 0.5, anchorY: 0.5 }); // Create point object that will follow touch position var point = null; // Create JoystickPoinr that will follow point position smoothly var joystickPoinr = game.attachAsset('JoystickPoinr', { x: 1024, y: 2459, anchorX: 0.5, anchorY: 0.5 }); // Variables for smooth movement var targetX = 1024; var targetY = 2459; var smoothSpeed = 0.2; // Variables for smooth rotation var targetRotation = 0; var baseRotationSpeed = 0.052; // Variables for realistic car physics var currentVelocity = 0; var acceleration = 0.16; var deceleration = 0.44; var maxSpeed = 15.36; // Variables for drift physics var velocityX = 0; var velocityY = 0; var driftFactor = 0.85; // How much momentum is retained (lower = more drift) var gripFactor = 0.3; // How quickly car aligns with direction (lower = more drift) // Player car weight var playerCarWeight = 1.2; // Player car is moderately heavy // Health system variables var playerHealth = 100; // Player starts with full health var maxHealth = 100; // Maximum health for all cars // Handle touch down - create and show point game.down = function (x, y, obj) { // Create point at touch position point = game.attachAsset('Puntero', { x: x, y: y, anchorX: 0.5, anchorY: 0.5 }); }; // Handle touch move - update point position game.move = function (x, y, obj) { if (point) { point.x = x; point.y = y; // Calculate joystickBG world position var joystickWorldX = joystickBG.x + uiBackground.x; var joystickWorldY = joystickBG.y + uiBackground.y; // Calculate distance from joystick center var deltaX = x - joystickWorldX; var deltaY = y - joystickWorldY; var distance = Math.sqrt(deltaX * deltaX + deltaY * deltaY); var maxRadius = joystickBG.width / 2; // Limit movement to joystick radius if (distance > maxRadius) { var angle = Math.atan2(deltaY, deltaX); deltaX = Math.cos(angle) * maxRadius; deltaY = Math.sin(angle) * maxRadius; } // Update target position for smooth movement (constrained) targetX = joystickWorldX + deltaX; targetY = joystickWorldY + deltaY; } }; // Handle touch up - remove point game.up = function (x, y, obj) { if (point) { point.destroy(); point = null; // Reset target position to joystick center var joystickWorldX = joystickBG.x + uiBackground.x; var joystickWorldY = joystickBG.y + uiBackground.y; targetX = joystickWorldX; targetY = joystickWorldY; } }; // Update function for smooth movement game.update = function () { // Smoothly move JoystickPoinr towards target position var deltaX = targetX - joystickPoinr.x; var deltaY = targetY - joystickPoinr.y; joystickPoinr.x += deltaX * smoothSpeed; joystickPoinr.y += deltaY * smoothSpeed; // Double-check that joystickPoinr stays within bounds var joystickWorldX = joystickBG.x + uiBackground.x; var joystickWorldY = joystickBG.y + uiBackground.y; var currentDeltaX = joystickPoinr.x - joystickWorldX; var currentDeltaY = joystickPoinr.y - joystickWorldY; var currentDistance = Math.sqrt(currentDeltaX * currentDeltaX + currentDeltaY * currentDeltaY); var maxRadius = joystickBG.width / 2; if (currentDistance > maxRadius) { var angle = Math.atan2(currentDeltaY, currentDeltaX); joystickPoinr.x = joystickWorldX + Math.cos(angle) * maxRadius; joystickPoinr.y = joystickWorldY + Math.sin(angle) * maxRadius; } // Update car rotation based on joystick position var joystickOffsetX = joystickPoinr.x - joystickWorldX; var joystickOffsetY = joystickPoinr.y - joystickWorldY; var joystickDistance = Math.sqrt(joystickOffsetX * joystickOffsetX + joystickOffsetY * joystickOffsetY); // Calculate power based on distance from center (0 to 1) var power = Math.min(joystickDistance / maxRadius, 1); // Only rotate if joystick is moved significantly from center if (joystickDistance > 10) { var joystickAngle = Math.atan2(joystickOffsetX, -joystickOffsetY); targetRotation = joystickAngle; } // Smoothly interpolate car rotation towards target var rotationDelta = targetRotation - carPlayer.rotation; // Handle angle wrapping for shortest rotation path while (rotationDelta > Math.PI) { rotationDelta -= 2 * Math.PI; } while (rotationDelta < -Math.PI) { rotationDelta += 2 * Math.PI; } // Calculate rotation speed based on current velocity (slower speed = much slower turning) var speedRatio = Math.sqrt(velocityX * velocityX + velocityY * velocityY) / maxSpeed; var dynamicRotationSpeed = baseRotationSpeed * Math.max(0.1, speedRatio); // Minimum 10% rotation speed carPlayer.rotation += rotationDelta * dynamicRotationSpeed; // Calculate target velocity based on joystick power var targetVelocity = maxSpeed * power; // Apply smooth velocity transitions with exponential interpolation if (power > 0.1) { // Accelerating - smooth exponential approach to target velocity var velocityDiff = targetVelocity - currentVelocity; var accelerationRate = 0.004; // Smooth acceleration rate (20% slower for smaller car) currentVelocity += velocityDiff * accelerationRate; } else { // Decelerating when joystick is near center - smooth exponential decay var decelerationRate = 0.048; // Smooth deceleration rate (20% slower for smaller car) currentVelocity *= 1 - decelerationRate; if (Math.abs(currentVelocity) < 0.1) { currentVelocity = 0; } } // Limit velocity to max speed currentVelocity = Math.min(currentVelocity, maxSpeed); // Calculate intended movement direction based on car rotation and current velocity var intendedMoveX = Math.sin(carPlayer.rotation) * currentVelocity; var intendedMoveY = -Math.cos(carPlayer.rotation) * currentVelocity; // Calculate turning friction based on dynamic rotation speed var rotationFriction = Math.abs(rotationDelta * dynamicRotationSpeed) * 0.8; // Reduced friction intensity for smoother feel var frictionMultiplier = Math.max(0.85, 1 - rotationFriction); // Less velocity reduction when turning (min 0.85 for more natural feel) // Apply drift physics - blend current momentum with intended direction velocityX = velocityX * driftFactor + intendedMoveX * gripFactor; velocityY = velocityY * driftFactor + intendedMoveY * gripFactor; // Apply turning friction to reduce velocity when steering velocityX *= frictionMultiplier; velocityY *= frictionMultiplier; // Apply some base deceleration to drift momentum velocityX *= 0.98; velocityY *= 0.98; // Update car position using drift momentum carPlayer.x += velocityX; carPlayer.y += velocityY; // Keep car within gameplay area bounds with realistic collision physics var halfCarWidth = 16; // CarPlayer width is 32, so half is 16 var halfCarHeight = 24; // CarPlayer height is 47.36, so half is ~24 // Calculate current speed for impact calculations var currentSpeed = Math.sqrt(velocityX * velocityX + velocityY * velocityY); var impactThreshold = 2; // Minimum speed to trigger impact effects // Realistic collision physics with energy loss and proper angles if (carPlayer.x < halfCarWidth) { carPlayer.x = halfCarWidth; // Calculate impact intensity based on perpendicular velocity component var impactVelocity = Math.abs(velocityX); var energyLoss = 0.4 + impactVelocity / maxSpeed * 0.3; // More energy loss at higher speeds // Realistic bounce with energy conservation velocityX = -velocityX * (1 - energyLoss); velocityY *= 0.8; // Friction reduces parallel velocity component // Visual feedback for significant impacts if (impactVelocity > impactThreshold) { LK.effects.flashObject(carPlayer, 0xff4444, 200); } } if (carPlayer.x > 2048 - halfCarWidth) { carPlayer.x = 2048 - halfCarWidth; // Calculate impact intensity based on perpendicular velocity component var impactVelocity = Math.abs(velocityX); var energyLoss = 0.4 + impactVelocity / maxSpeed * 0.3; // More energy loss at higher speeds // Realistic bounce with energy conservation velocityX = -velocityX * (1 - energyLoss); velocityY *= 0.8; // Friction reduces parallel velocity component // Visual feedback for significant impacts if (impactVelocity > impactThreshold) { LK.effects.flashObject(carPlayer, 0xff4444, 200); } } if (carPlayer.y < halfCarHeight) { carPlayer.y = halfCarHeight; // Calculate impact intensity based on perpendicular velocity component var impactVelocity = Math.abs(velocityY); var energyLoss = 0.4 + impactVelocity / maxSpeed * 0.3; // More energy loss at higher speeds // Realistic bounce with energy conservation velocityY = -velocityY * (1 - energyLoss); velocityX *= 0.8; // Friction reduces parallel velocity component // Visual feedback for significant impacts if (impactVelocity > impactThreshold) { LK.effects.flashObject(carPlayer, 0xff4444, 200); } } if (carPlayer.y > 2186 - halfCarHeight) { carPlayer.y = 2186 - halfCarHeight; // Calculate impact intensity based on perpendicular velocity component var impactVelocity = Math.abs(velocityY); var energyLoss = 0.4 + impactVelocity / maxSpeed * 0.3; // More energy loss at higher speeds // Realistic bounce with energy conservation velocityY = -velocityY * (1 - energyLoss); velocityX *= 0.8; // Friction reduces parallel velocity component // Visual feedback for significant impacts if (impactVelocity > impactThreshold) { LK.effects.flashObject(carPlayer, 0xff4444, 200); } } // Apply smooth braking to player car after wall collision for more natural deceleration if (!carPlayer.smoothBraking) carPlayer.smoothBraking = false; if (!carPlayer.brakeFrames) carPlayer.brakeFrames = 0; var currentPlayerSpeed = Math.sqrt(velocityX * velocityX + velocityY * velocityY); // If player car was just launched (high velocity), enable smooth braking if (currentPlayerSpeed > maxSpeed * 0.6 && !carPlayer.smoothBraking) { carPlayer.smoothBraking = true; carPlayer.brakeFrames = 0; } // Smooth braking logic: apply a gentle, progressive friction for a short period after wall collision if (carPlayer.smoothBraking) { // Braking lasts for 18 frames (~0.3s at 60fps) var brakeDuration = 18; var brakeProgress = Math.min(1, carPlayer.brakeFrames / brakeDuration); // Start with gentle friction, increase to normal friction var minFriction = 0.96; var maxFriction = 0.92 - currentPlayerSpeed / maxSpeed * 0.05; var frictionRate = minFriction + (maxFriction - minFriction) * brakeProgress; velocityX *= frictionRate; velocityY *= frictionRate; carPlayer.brakeFrames++; if (carPlayer.brakeFrames >= brakeDuration) { carPlayer.smoothBraking = false; } } else if (currentPlayerSpeed > 0.1) { // Normal progressive friction when not actively controlling if (power <= 0.1) { // Only apply extra friction when not accelerating var frictionRate = 0.92 - currentPlayerSpeed / maxSpeed * 0.05; // More friction at higher speeds velocityX *= frictionRate; velocityY *= frictionRate; } } else { // Stop very slow movement to prevent endless drift if (power <= 0.1) { velocityX = 0; velocityY = 0; } } // Particle system for car exhaust (global, supports all cars) if (!game.particles) { game.particles = []; } // Helper function to emit particles for any car function emitCarParticles(car, vx, vy, rotation, maxSpeed, gameplayBackground) { var totalSpeed = Math.sqrt(vx * vx + vy * vy); var speedRatio = totalSpeed / maxSpeed; var particleFrequency = Math.max(1, Math.floor(8 - speedRatio * 6)); if (speedRatio > 0.05 && LK.ticks % particleFrequency === 0) { // Calculate particle spawn position behind the car var particleSpawnX = car.x - Math.sin(rotation) * 55; var particleSpawnY = car.y + Math.cos(rotation) * 55; var particleCount = Math.max(1, Math.floor(speedRatio * 3)); for (var p = 0; p < particleCount; p++) { var newParticle = new Particle(); newParticle.x = particleSpawnX + (Math.random() - 0.5) * 19.2; newParticle.y = particleSpawnY + (Math.random() - 0.5) * 19.2; newParticle.velocityX += -vx * 0.12 + (Math.random() - 0.5) * 2.4; newParticle.velocityY += -vy * 0.12 + (Math.random() - 0.5) * 2.4; game.particles.push(newParticle); gameplayBackground.addChild(newParticle); // Tween particle color for variety var colors = [0xffffff, 0xcccccc, 0x999999, 0x666666]; var randomColor = colors[Math.floor(Math.random() * colors.length)]; tween(newParticle.children[0], { tint: randomColor }, { duration: 100 }); } } } // Optimize particle emissions - reduce frequency if (LK.ticks % 2 === 0) { // Only emit particles every other frame // Emit particles for player car emitCarParticles(carPlayer, velocityX, velocityY, carPlayer.rotation, maxSpeed, gameplayBackground); // Emit particles for all enemy cars for (var carIdx = 0; carIdx < enemyCars.length; carIdx++) { var currentEnemyCar = enemyCars[carIdx]; var enemyTotalVX = (currentEnemyCar.aiVelocityX || 0) + (currentEnemyCar.velocityX || 0); var enemyTotalVY = (currentEnemyCar.aiVelocityY || 0) + (currentEnemyCar.velocityY || 0); emitCarParticles(currentEnemyCar, enemyTotalVX, enemyTotalVY, currentEnemyCar.rotation, maxSpeed, gameplayBackground); } } // Update and clean up particles (batch process) var particlesToRemove = []; for (var i = 0; i < game.particles.length; i++) { var particle = game.particles[i]; if (particle.age >= particle.lifespan) { particlesToRemove.push(i); } } // Remove particles in reverse order to maintain indices for (var r = particlesToRemove.length - 1; r >= 0; r--) { var idx = particlesToRemove[r]; game.particles[idx].destroy(); game.particles.splice(idx, 1); } // Check collision between player car and all enemy cars using smaller collision boxes (optimized) if (!carPlayer.lastColliding) carPlayer.lastColliding = []; // Initialize collision tracking array for all cars while (carPlayer.lastColliding.length < enemyCars.length) { carPlayer.lastColliding.push(false); } // Define smaller collision boxes (approximately 60% of actual asset size for more precise collision) - cache constants var playerCollisionWidth = 38; // Reduced from 64px width var playerCollisionHeight = 57; // Reduced from ~95px height var enemyCollisionWidth = 38; // Reduced from 64px width var enemyCollisionHeight = 57; // Reduced from ~94px height var halfPlayerWidth = playerCollisionWidth / 2; var halfPlayerHeight = playerCollisionHeight / 2; var halfEnemyWidth = enemyCollisionWidth / 2; var halfEnemyHeight = enemyCollisionHeight / 2; var playerLeft = carPlayer.x - halfPlayerWidth; var playerRight = carPlayer.x + halfPlayerWidth; var playerTop = carPlayer.y - halfPlayerHeight; var playerBottom = carPlayer.y + halfPlayerHeight; // Check collision with each enemy car for (var enemyIdx = 0; enemyIdx < enemyCars.length; enemyIdx++) { var currentEnemyCar = enemyCars[enemyIdx]; var enemyLeft = currentEnemyCar.x - halfEnemyWidth; var enemyRight = currentEnemyCar.x + halfEnemyWidth; var enemyTop = currentEnemyCar.y - halfEnemyHeight; var enemyBottom = currentEnemyCar.y + halfEnemyHeight; // More precise collision detection using smaller bounding boxes var currentColliding = !(playerRight < enemyLeft || playerLeft > enemyRight || playerBottom < enemyTop || playerTop > enemyBottom); if (!carPlayer.lastColliding[enemyIdx] && currentColliding) { // Collision just started - determine collision responsibility var playerSpeed = Math.sqrt(velocityX * velocityX + velocityY * velocityY); var enemySpeed = Math.sqrt(currentEnemyCar.velocityX * currentEnemyCar.velocityX + currentEnemyCar.velocityY * currentEnemyCar.velocityY); // Calculate collision direction (from player car to enemy car) var collisionDeltaX = currentEnemyCar.x - carPlayer.x; var collisionDeltaY = currentEnemyCar.y - carPlayer.y; var collisionDistance = Math.sqrt(collisionDeltaX * collisionDeltaX + collisionDeltaY * collisionDeltaY); // Normalize collision direction if (collisionDistance > 0) { collisionDeltaX /= collisionDistance; collisionDeltaY /= collisionDistance; } // Determine collision responsibility based on velocity vectors and approach angles var playerCrasher = false; var enemyCrasher = false; var bothCrashed = false; // Calculate approach vectors (movement direction toward collision point) var playerApproachX = 0, playerApproachY = 0; var enemyApproachX = 0, enemyApproachY = 0; if (playerSpeed > 0.1) { // Normalize player velocity to get approach direction playerApproachX = velocityX / playerSpeed; playerApproachY = velocityY / playerSpeed; } if (enemySpeed > 0.1) { // Normalize enemy velocity to get approach direction var enemyTotalVelX = (currentEnemyCar.aiVelocityX || 0) + currentEnemyCar.velocityX; var enemyTotalVelY = (currentEnemyCar.aiVelocityY || 0) + currentEnemyCar.velocityY; var enemyTotalSpeed = Math.sqrt(enemyTotalVelX * enemyTotalVelX + enemyTotalVelY * enemyTotalVelY); if (enemyTotalSpeed > 0.1) { enemyApproachX = enemyTotalVelX / enemyTotalSpeed; enemyApproachY = enemyTotalVelY / enemyTotalSpeed; } } // Calculate how much each car is moving toward the collision var playerTowardCollision = 0; var enemyTowardCollision = 0; if (playerSpeed > 0.1) { // Dot product of player movement with collision direction playerTowardCollision = playerApproachX * collisionDeltaX + playerApproachY * collisionDeltaY; } if (enemySpeed > 0.1) { // Dot product of enemy movement with opposite collision direction (toward player) enemyTowardCollision = enemyApproachX * -collisionDeltaX + enemyApproachY * -collisionDeltaY; } // Speed thresholds for determining crasher responsibility var minCrasherSpeed = maxSpeed * 0.15; // 15% of max speed minimum to be considered crasher var dominantCrasherThreshold = 0.3; // How much more one car must be approaching to be sole crasher // Determine responsibility based on approach analysis if (playerSpeed > minCrasherSpeed && enemySpeed > minCrasherSpeed) { // Both cars moving significantly if (playerTowardCollision > dominantCrasherThreshold && enemyTowardCollision > dominantCrasherThreshold) { // Both approaching collision point - head-on or mutual collision bothCrashed = true; } else if (playerTowardCollision > enemyTowardCollision + dominantCrasherThreshold) { // Player moving much more toward collision playerCrasher = true; } else if (enemyTowardCollision > playerTowardCollision + dominantCrasherThreshold) { // Enemy moving much more toward collision enemyCrasher = true; } else { // Similar approach - treat as mutual collision bothCrashed = true; } } else if (playerSpeed > minCrasherSpeed && enemySpeed <= minCrasherSpeed) { // Only player moving significantly - player is crasher playerCrasher = true; } else if (enemySpeed > minCrasherSpeed && playerSpeed <= minCrasherSpeed) { // Only enemy moving significantly - enemy is crasher enemyCrasher = true; } else { // Both moving very slowly - treat as mutual collision bothCrashed = true; } // Calculate momentum transfer using conservation of momentum var totalMass = playerCarWeight + currentEnemyCar.weight; var massRatio1 = (playerCarWeight - currentEnemyCar.weight) / totalMass; var massRatio2 = 2 * currentEnemyCar.weight / totalMass; var massRatio3 = 2 * playerCarWeight / totalMass; var massRatio4 = (currentEnemyCar.weight - playerCarWeight) / totalMass; // Apply different energy loss based on collision responsibility var relativeSpeed = Math.max(playerSpeed, enemySpeed); var minLoss, maxLoss; var speedNorm = Math.min(1, relativeSpeed / maxSpeed); // 0 to 1 if (bothCrashed) { // Mutual collision - both get significant energy loss minLoss = 0.22; // Higher base energy loss for mutual crashes maxLoss = 0.70; // Higher max energy loss for mutual crashes } else { // Single crasher scenario - crasher gets less penalty minLoss = 0.15; // Lower base energy loss maxLoss = 0.60; // Lower max energy loss } var energyLoss = minLoss + (maxLoss - minLoss) * speedNorm; var restitution = 1 - energyLoss; // Apply different restitution based on responsibility var playerRestitution = restitution; var enemyRestitution = restitution; if (playerCrasher) { // Player is crasher - gets less penalty (keeps more momentum) playerRestitution = restitution * 1.15; // 15% less energy loss enemyRestitution = restitution * 0.85; // 15% more energy loss for crashed car } else if (enemyCrasher) { // Enemy is crasher - gets less penalty enemyRestitution = restitution * 1.15; // 15% less energy loss playerRestitution = restitution * 0.85; // 15% more energy loss for crashed car } // For bothCrashed, both use same restitution // Separate cars to prevent overlap var separationDistance = 70; carPlayer.x = currentEnemyCar.x - collisionDeltaX * separationDistance; carPlayer.y = currentEnemyCar.y - collisionDeltaY * separationDistance; // Calculate new velocities based on mass and current motion var playerImpactX = velocityX * massRatio1 + currentEnemyCar.velocityX * massRatio2; var playerImpactY = velocityY * massRatio1 + currentEnemyCar.velocityY * massRatio2; velocityX = playerImpactX * playerRestitution; velocityY = playerImpactY * playerRestitution; // Enemy car velocity change var enemyImpactX = velocityX * massRatio3 + currentEnemyCar.velocityX * massRatio4; var enemyImpactY = velocityY * massRatio3 + currentEnemyCar.velocityY * massRatio4; currentEnemyCar.velocityX = enemyImpactX * enemyRestitution; currentEnemyCar.velocityY = enemyImpactY * enemyRestitution; // Launch enemy car away from collision var launchSpeed = Math.max(playerSpeed, enemySpeed); var minPush = 0.4; var maxPush = 1.5; var pushMultiplier = minPush + (maxPush - minPush) * speedNorm; var launchDirX = currentEnemyCar.x - carPlayer.x; var launchDirY = currentEnemyCar.y - carPlayer.y; var launchDist = Math.sqrt(launchDirX * launchDirX + launchDirY * launchDirY); if (launchDist < 0.01) { var playerMoveNorm = Math.sqrt(velocityX * velocityX + velocityY * velocityY); if (playerMoveNorm > 0.01) { launchDirX = velocityX / playerMoveNorm; launchDirY = velocityY / playerMoveNorm; } else { launchDirX = 0; launchDirY = -1; } } else { launchDirX /= launchDist; launchDirY /= launchDist; } currentEnemyCar.velocityX += launchDirX * launchSpeed * pushMultiplier; currentEnemyCar.velocityY += launchDirY * launchSpeed * pushMultiplier; // Add directional push based on collision angle and relative mass var pushIntensity = (playerSpeed + enemySpeed) * (0.5 + 0.5 * speedNorm); var playerPushRatio = currentEnemyCar.weight / totalMass; var enemyPushRatio = playerCarWeight / totalMass; velocityX += -collisionDeltaX * pushIntensity * playerPushRatio * 0.7; velocityY += -collisionDeltaY * pushIntensity * playerPushRatio * 0.7; currentEnemyCar.velocityX += collisionDeltaX * pushIntensity * enemyPushRatio * 0.7; currentEnemyCar.velocityY += collisionDeltaY * pushIntensity * enemyPushRatio * 0.7; // Reset acceleration and velocity state based on responsibility if (playerCrasher) { // Player is crasher - smaller penalty to acceleration currentVelocity *= 0.3; // Keep 30% of acceleration state } else { // Player crashed into or mutual - full reset currentVelocity = 0; // Reset player's acceleration state to restart from zero } if (enemyCrasher) { // Enemy is crasher - smaller penalty to acceleration if (typeof currentEnemyCar.aiCurrentVelocity !== "undefined") { currentEnemyCar.aiCurrentVelocity *= 0.3; // Keep 30% of AI acceleration state } } else { // Enemy crashed into or mutual - full reset if (typeof currentEnemyCar.aiCurrentVelocity !== "undefined") { currentEnemyCar.aiCurrentVelocity = 0; // Reset enemy's AI acceleration state } } // Visual feedback based on collision responsibility if (bothCrashed) { // Both crashed - flash both in orange LK.effects.flashObject(carPlayer, 0xff8800, 500); LK.effects.flashObject(currentEnemyCar, 0xff8800, 500); } else if (playerCrasher) { // Player crashed into enemy - flash player red, enemy yellow LK.effects.flashObject(carPlayer, 0xff4400, 400); LK.effects.flashObject(currentEnemyCar, 0xffff00, 600); } else if (enemyCrasher) { // Enemy crashed into player - flash enemy red, player yellow LK.effects.flashObject(currentEnemyCar, 0xff4400, 400); LK.effects.flashObject(carPlayer, 0xffff00, 600); } else { // Default fallback LK.effects.flashObject(carPlayer, 0xff0000, 500); } } // Update last collision state for this enemy carPlayer.lastColliding[enemyIdx] = currentColliding; } // --- Enemy Cars AI Movement Logic (Optimized) --- // Process AI for each enemy car - stagger AI updates to reduce load var aiUpdateOffset = LK.ticks % enemyCars.length; // Stagger AI updates for (var aiCarIdx = 0; aiCarIdx < enemyCars.length; aiCarIdx++) { var enemyCar = enemyCars[aiCarIdx]; // Only update AI for one car per frame (staggered) var shouldUpdateAI = aiCarIdx === aiUpdateOffset; // Dynamic target selection system - choose best target based on position and velocity if (typeof enemyCar.currentTarget === "undefined") enemyCar.currentTarget = null; if (typeof enemyCar.targetSelectionTimer === "undefined") enemyCar.targetSelectionTimer = 0; if (typeof enemyCar.targetSelectionInterval === "undefined") enemyCar.targetSelectionInterval = 60; // Re-evaluate every second // Pursuit detection system - track who is following this AI car if (typeof enemyCar.pursuitDetection === "undefined") { enemyCar.pursuitDetection = { pursuers: [], // List of potential pursuers with tracking data beingPursued: false, pursuitIntensity: 0, // 0-1 scale of how intensely being pursued pursuitDuration: 0, // How long being pursued in frames lastPursuitCheck: 0, evasionMode: false, // Whether currently evading evasionStrategy: 0, // 0=speed, 1=zigzag, 2=circles evasionTimer: 0, evasionDuration: 0, counterAttackMode: false, // Whether to turn and fight counterAttackTarget: null }; } // Re-evaluate target selection periodically (only for the current AI car being updated) if (shouldUpdateAI) { enemyCar.targetSelectionTimer++; // Pursuit detection - analyze if other cars are targeting this one if (LK.ticks % 10 === 0) { // Check every 10 frames for performance enemyCar.pursuitDetection.pursuers = []; // Check all potential pursuers (player + other AI cars) var potentialPursuers = []; // Add player as potential pursuer potentialPursuers.push({ object: carPlayer, velocityX: velocityX, velocityY: velocityY, isPlayer: true }); // Add other enemy cars as potential pursuers for (var pursuerId = 0; pursuerId < enemyCars.length; pursuerId++) { if (pursuerId !== aiCarIdx) { var otherCar = enemyCars[pursuerId]; potentialPursuers.push({ object: otherCar, velocityX: (otherCar.aiVelocityX || 0) + (otherCar.velocityX || 0), velocityY: (otherCar.aiVelocityY || 0) + (otherCar.velocityY || 0), isPlayer: false }); } } // Analyze each potential pursuer for (var pIdx = 0; pIdx < potentialPursuers.length; pIdx++) { var pursuer = potentialPursuers[pIdx]; var pursuerId = pIdx; // Calculate distance and relative positioning var pursuerDist = Math.sqrt((pursuer.object.x - enemyCar.x) * (pursuer.object.x - enemyCar.x) + (pursuer.object.y - enemyCar.y) * (pursuer.object.y - enemyCar.y)); // Only consider pursuers within reasonable range if (pursuerDist < 500 && pursuerDist > 50) { // Calculate if pursuer is moving toward this car var toThisCar = { x: enemyCar.x - pursuer.object.x, y: enemyCar.y - pursuer.object.y }; var toThisCarMag = Math.sqrt(toThisCar.x * toThisCar.x + toThisCar.y * toThisCar.y); if (toThisCarMag > 0) { toThisCar.x /= toThisCarMag; toThisCar.y /= toThisCarMag; // Calculate pursuer's velocity direction var pursuerSpeed = Math.sqrt(pursuer.velocityX * pursuer.velocityX + pursuer.velocityY * pursuer.velocityY); if (pursuerSpeed > 1) { var pursuerDir = { x: pursuer.velocityX / pursuerSpeed, y: pursuer.velocityY / pursuerSpeed }; // Dot product to see if pursuer is moving toward this car var alignment = toThisCar.x * pursuerDir.x + toThisCar.y * pursuerDir.y; // Consider as pursuer if moving toward this car and close enough if (alignment > 0.3) { // 30 degree cone var pursuitScore = alignment * (1 - pursuerDist / 500) * (pursuerSpeed / maxSpeed); // Additional checks for AI pursuers - see if they're targeting this car if (!pursuer.isPlayer && pursuer.object.currentTarget) { if (pursuer.object.currentTarget.object === enemyCar) { pursuitScore += 0.4; // Bonus if we're their target } } if (pursuitScore > 0.3) { enemyCar.pursuitDetection.pursuers.push({ object: pursuer.object, distance: pursuerDist, score: pursuitScore, isPlayer: pursuer.isPlayer }); } } } } } } // Determine if being pursued based on pursuer analysis var totalPursuitScore = 0; var closestPursuerDist = Infinity; var strongestPursuer = null; for (var p = 0; p < enemyCar.pursuitDetection.pursuers.length; p++) { var pursuerData = enemyCar.pursuitDetection.pursuers[p]; totalPursuitScore += pursuerData.score; if (pursuerData.distance < closestPursuerDist) { closestPursuerDist = pursuerData.distance; strongestPursuer = pursuerData; } } // Update pursuit state var wasPursued = enemyCar.pursuitDetection.beingPursued; enemyCar.pursuitDetection.beingPursued = totalPursuitScore > 0.5 && enemyCar.pursuitDetection.pursuers.length > 0; enemyCar.pursuitDetection.pursuitIntensity = Math.min(1, totalPursuitScore); if (enemyCar.pursuitDetection.beingPursued) { enemyCar.pursuitDetection.pursuitDuration++; // Decide on response strategy when first detected or strategy expired if (!wasPursued || enemyCar.pursuitDetection.evasionTimer > enemyCar.pursuitDetection.evasionDuration) { var personality = enemyCar.tacticalPersonality; // Decision factors var currentSpeed = Math.sqrt((enemyCar.aiVelocityX || 0) * (enemyCar.aiVelocityX || 0) + (enemyCar.aiVelocityY || 0) * (enemyCar.aiVelocityY || 0)); var speedRatio = currentSpeed / maxSpeed; var pursuerThreat = enemyCar.pursuitDetection.pursuitIntensity; // Personality-based decision making var fleeChance = 0.6 + personality.patience * 0.2 - personality.aggressiveness * 0.3; var counterAttackChance = 0.4 + personality.aggressiveness * 0.4 - personality.patience * 0.2; // Situational modifiers if (speedRatio > 0.7) fleeChance += 0.2; // Easier to flee when fast if (closestPursuerDist < 150) counterAttackChance += 0.3; // More likely to fight when cornered if (pursuerThreat > 0.8) counterAttackChance += 0.2; // Fight when heavily pursued // Individual flight decision based on personality traits var flightPrefs = enemyCar.tacticalPersonality; var shouldFlee = false; var shouldCounterAttack = false; // Calculate individual flight decision factors var baseFlightChance = flightPrefs.flightTendency; var baseCounterChance = flightPrefs.counterAttackChance; // Modify chances based on individual traits var braveryBonus = flightPrefs.bravery * 0.3; // Brave cars more likely to fight var panicPenalty = pursuerThreat > flightPrefs.panicThreshold ? 0.2 : 0; // Panic reduces counter-attack var riskFactor = (1 - flightPrefs.riskTolerance) * 0.25; // Risk-averse cars flee more // Apply individual modifiers var adjustedFlightChance = baseFlightChance + riskFactor + panicPenalty - braveryBonus * 0.5; var adjustedCounterChance = baseCounterChance + braveryBonus - panicPenalty - riskFactor; // Situational modifiers based on individual thresholds if (speedRatio > 0.7 && speedRatio > flightPrefs.riskTolerance) { adjustedFlightChance += 0.2; // Fast cars with low risk tolerance flee more } if (closestPursuerDist < flightPrefs.flightDistance) { if (flightPrefs.bravery > 0.6) { adjustedCounterChance += 0.3; // Brave cars fight when cornered } else { adjustedFlightChance += 0.2; // Others flee when too close } } if (pursuerThreat > 0.8 && flightPrefs.panicThreshold < 0.5) { adjustedFlightChance += 0.3; // Easy-to-panic cars flee under heavy pursuit } // Make individual decision if (Math.random() < Math.max(0.1, Math.min(0.9, adjustedCounterChance))) { // Turn and fight enemyCar.pursuitDetection.counterAttackMode = true; enemyCar.pursuitDetection.counterAttackTarget = strongestPursuer ? strongestPursuer.object : null; enemyCar.pursuitDetection.evasionMode = false; enemyCar.pursuitDetection.evasionDuration = 120 + Math.floor(Math.random() * 180); // 2-5 seconds } else { // Flee - choose evasion strategy based on individual preferences enemyCar.pursuitDetection.evasionMode = true; enemyCar.pursuitDetection.counterAttackMode = false; // Individual evasion strategy selection based on preferences and situation var strategyScores = [flightPrefs.preferSpeedEscape, flightPrefs.preferZigzagEscape, flightPrefs.preferCircularEscape]; // Situational bonuses based on individual traits if (speedRatio < 0.4) { strategyScores[0] += 0.3; // Speed strategy bonus when slow } else if (speedRatio > 0.7) { strategyScores[1] += 0.2; // Zigzag bonus when fast (harder to predict) strategyScores[2] += 0.15; } if (closestPursuerDist < flightPrefs.flightDistance * 0.7) { strategyScores[1] += 0.25; // Zigzag when close strategyScores[2] += 0.3; // Circles when close } else if (closestPursuerDist > flightPrefs.flightDistance * 1.5) { strategyScores[0] += 0.4; // Speed when far } // Individual trait bonuses if (flightPrefs.adaptability > 0.7) { strategyScores[1] += 0.2; // Adaptive cars prefer zigzag } if (flightPrefs.patience > 0.6) { strategyScores[2] += 0.2; // Patient cars can handle circles } if (flightPrefs.aggressiveness > 0.6) { strategyScores[0] += 0.15; // Aggressive cars prefer speed } // Select strategy with highest score var maxScore = Math.max(strategyScores[0], strategyScores[1], strategyScores[2]); var bestStrategies = []; for (var si = 0; si < 3; si++) { if (strategyScores[si] >= maxScore - 0.1) { bestStrategies.push(si); } } enemyCar.pursuitDetection.evasionStrategy = bestStrategies[Math.floor(Math.random() * bestStrategies.length)]; // Individual evasion duration based on personality var baseDuration = Math.floor(flightPrefs.maxFlightDuration * 0.6); // 60% of max var personalityDuration = Math.floor(flightPrefs.maxFlightDuration * 0.4 * flightPrefs.patience); // Patience affects duration enemyCar.pursuitDetection.evasionDuration = baseDuration + personalityDuration; } enemyCar.pursuitDetection.evasionTimer = 0; } } else { enemyCar.pursuitDetection.pursuitDuration = 0; enemyCar.pursuitDetection.evasionMode = false; enemyCar.pursuitDetection.counterAttackMode = false; } } enemyCar.pursuitDetection.evasionTimer++; } if ((enemyCar.targetSelectionTimer >= enemyCar.targetSelectionInterval || enemyCar.currentTarget === null) && shouldUpdateAI) { enemyCar.targetSelectionTimer = 0; // Create list of all potential targets (player + other enemy cars) var potentialTargets = []; // Add player as potential target potentialTargets.push({ object: carPlayer, velocityX: velocityX, velocityY: velocityY, weight: playerCarWeight, isPlayer: true }); // Add other enemy cars as potential targets for (var targetIdx = 0; targetIdx < enemyCars.length; targetIdx++) { if (targetIdx !== aiCarIdx) { // Don't target self var otherCar = enemyCars[targetIdx]; potentialTargets.push({ object: otherCar, velocityX: (otherCar.aiVelocityX || 0) + (otherCar.velocityX || 0), velocityY: (otherCar.aiVelocityY || 0) + (otherCar.velocityY || 0), weight: otherCar.weight, isPlayer: false }); } } // Evaluate each target and assign scores var bestTarget = null; var bestScore = -1; for (var evalIdx = 0; evalIdx < potentialTargets.length; evalIdx++) { var target = potentialTargets[evalIdx]; var targetSpeed = Math.sqrt(target.velocityX * target.velocityX + target.velocityY * target.velocityY); var targetDistance = Math.sqrt((target.object.x - enemyCar.x) * (target.object.x - enemyCar.x) + (target.object.y - enemyCar.y) * (target.object.y - enemyCar.y)); // Calculate target score based on multiple factors var score = 0; // Distance factor - prefer closer targets (0-40 points) var distanceScore = Math.max(0, 40 - targetDistance / 1000 * 40); score += distanceScore; // Speed factor - prefer faster targets for more exciting gameplay (0-25 points) var speedRatio = targetSpeed / maxSpeed; var speedScore = speedRatio * 25; score += speedScore; // Weight factor - heavier targets are more satisfying to hit (0-15 points) var weightScore = Math.min(15, target.weight * 6); score += weightScore; // Player bonus - slight preference for player to maintain engagement (0-10 points) if (target.isPlayer) { score += 10; } // Velocity alignment factor - prefer targets moving in interesting directions (0-10 points) if (targetSpeed > 1) { var enemySpeed = Math.sqrt((enemyCar.aiVelocityX || 0) * (enemyCar.aiVelocityX || 0) + (enemyCar.aiVelocityY || 0) * (enemyCar.aiVelocityY || 0)); if (enemySpeed > 1) { // Calculate if target and enemy are moving in similar or opposite directions var enemyVelX = enemyCar.aiVelocityX || 0; var enemyVelY = enemyCar.aiVelocityY || 0; var dotProduct = (target.velocityX * enemyVelX + target.velocityY * enemyVelY) / (targetSpeed * enemySpeed); // Prefer head-on collisions (opposite directions) - more exciting var alignmentScore = (1 - dotProduct) * 5; // 0-10 points score += alignmentScore; } } // Avoid recently targeted objects to prevent fixation (penalty) if (enemyCar.currentTarget && target.object === enemyCar.currentTarget.object) { // Apply small penalty to current target to encourage switching score *= 0.9; } // Select best target if (score > bestScore) { bestScore = score; bestTarget = target; } } // Update current target enemyCar.currentTarget = bestTarget; // Vary target selection interval for more dynamic behavior enemyCar.targetSelectionInterval = 45 + Math.floor(Math.random() * 90); // 0.75-2.25 seconds } // Enemy car can switch between 'follow target' and 'free roam' AI modes if (typeof enemyCar.aiMode === "undefined") enemyCar.aiMode = "follow"; if (typeof enemyCar.aiModeTimer === "undefined") enemyCar.aiModeTimer = 0; if (typeof enemyCar.aiModeDuration === "undefined") enemyCar.aiModeDuration = 0; // AI mode switching logic: change mode at intervals with speed-based preferences enemyCar.aiModeTimer++; if (enemyCar.aiModeTimer > enemyCar.aiModeDuration) { // Check if being pursued - this overrides normal mode selection if (enemyCar.pursuitDetection.beingPursued && (enemyCar.pursuitDetection.evasionMode || enemyCar.pursuitDetection.counterAttackMode)) { // Being pursued - use special pursuit response mode if (enemyCar.pursuitDetection.counterAttackMode && enemyCar.pursuitDetection.counterAttackTarget) { // Turn and fight - target the pursuer enemyCar.aiMode = "follow"; // Override current target to pursuer enemyCar.currentTarget = { object: enemyCar.pursuitDetection.counterAttackTarget, velocityX: enemyCar.pursuitDetection.counterAttackTarget === carPlayer ? velocityX : (enemyCar.pursuitDetection.counterAttackTarget.aiVelocityX || 0) + (enemyCar.pursuitDetection.counterAttackTarget.velocityX || 0), velocityY: enemyCar.pursuitDetection.counterAttackTarget === carPlayer ? velocityY : (enemyCar.pursuitDetection.counterAttackTarget.aiVelocityY || 0) + (enemyCar.pursuitDetection.counterAttackTarget.velocityY || 0), weight: enemyCar.pursuitDetection.counterAttackTarget === carPlayer ? playerCarWeight : enemyCar.pursuitDetection.counterAttackTarget.weight, isPlayer: enemyCar.pursuitDetection.counterAttackTarget === carPlayer }; } else if (enemyCar.pursuitDetection.evasionMode) { // Fleeing - use special evasion mode enemyCar.aiMode = "evade"; } } else { // Normal mode selection // Calculate target speed for mode preference based on current target var targetSpeed = 0; if (enemyCar.currentTarget) { targetSpeed = Math.sqrt(enemyCar.currentTarget.velocityX * enemyCar.currentTarget.velocityX + enemyCar.currentTarget.velocityY * enemyCar.currentTarget.velocityY); } var targetSpeedRatio = targetSpeed / maxSpeed; // 0 to 1 var followChance; // Speed-based mode preferences: // Very fast targets (>70% max speed) - 90% follow (for ambush tactics) // Fast targets (40-70% max speed) - 80% follow // Medium targets (20-40% max speed) - 70% follow // Slow targets (<20% max speed) - 85% follow (for direct pursuit) if (targetSpeedRatio > 0.7) { // Very fast - prefer follow mode for ambush tactics followChance = 0.9; } else if (targetSpeedRatio > 0.4) { // Fast to medium - moderate follow preference followChance = 0.8; } else if (targetSpeedRatio > 0.2) { // Medium - balanced but still prefer follow followChance = 0.7; } else { // Slow - prefer follow mode for direct pursuit followChance = 0.85; } // Pick mode based on calculated preference if (Math.random() < followChance) { enemyCar.aiMode = "follow"; } else { enemyCar.aiMode = "free"; } } // Duration: 1.2s to 3.5s (72 to 210 frames) enemyCar.aiModeDuration = 72 + Math.floor(Math.random() * 138); enemyCar.aiModeTimer = 0; } // --- AI movement logic depending on mode --- var aiDeltaX, aiDeltaY, aiDistance, aiTargetRotation, aiPower, aiTargetVelocity; // Edge avoidance logic - calculate repulsion from boundaries var edgeAvoidanceX = 0; var edgeAvoidanceY = 0; var edgeBuffer = 200; // Distance from edge to start avoiding var avoidanceStrength = 0.3; // How strongly to avoid edges // Check distance from each edge and calculate avoidance force if (enemyCar.x < edgeBuffer) { // Too close to left edge, push right var leftForce = (edgeBuffer - enemyCar.x) / edgeBuffer; edgeAvoidanceX += leftForce * avoidanceStrength; } if (enemyCar.x > 2048 - edgeBuffer) { // Too close to right edge, push left var rightForce = (enemyCar.x - (2048 - edgeBuffer)) / edgeBuffer; edgeAvoidanceX -= rightForce * avoidanceStrength; } if (enemyCar.y < edgeBuffer) { // Too close to top edge, push down var topForce = (edgeBuffer - enemyCar.y) / edgeBuffer; edgeAvoidanceY += topForce * avoidanceStrength; } if (enemyCar.y > 2186 - edgeBuffer) { // Too close to bottom edge, push up var bottomForce = (enemyCar.y - (2186 - edgeBuffer)) / edgeBuffer; edgeAvoidanceY -= bottomForce * avoidanceStrength; } if (enemyCar.aiMode === "follow") { // Initialize follow strategy if not set if (typeof enemyCar.followStrategy === "undefined") { enemyCar.followStrategy = 0; enemyCar.followStrategyTimer = 0; enemyCar.followStrategyDuration = 60 + Math.floor(Math.random() * 60); // 1-2 seconds for adaptive strategy } // Individual strategy selection based on each car's unique personality enemyCar.followStrategyTimer++; if (enemyCar.followStrategyTimer > enemyCar.followStrategyDuration) { // Calculate target speed for strategy selection var targetSpeed = 0; if (enemyCar.currentTarget) { targetSpeed = Math.sqrt(enemyCar.currentTarget.velocityX * enemyCar.currentTarget.velocityX + enemyCar.currentTarget.velocityY * enemyCar.currentTarget.velocityY); } var targetSpeedRatio = targetSpeed / maxSpeed; // 0 to 1 var relativeDistance = Math.sqrt(aiDeltaX * aiDeltaX + aiDeltaY * aiDeltaY); var relativeAngle = Math.atan2(aiDeltaX, -aiDeltaY); var enemyAngle = enemyCar.rotation; var angleDifference = Math.abs(relativeAngle - enemyAngle); while (angleDifference > Math.PI) angleDifference = 2 * Math.PI - angleDifference; var isTargetAhead = angleDifference < Math.PI / 3; // Within 60 degrees ahead var isTargetBehind = angleDifference > 2 * Math.PI / 3; // More than 120 degrees behind // Individual strategy selection based on car's tactical personality var personality = enemyCar.tacticalPersonality; var strategyScores = [0, 0, 0]; // [direct, intimidation, ambush] // Base preference scores from personality strategyScores[0] = personality.directPreference; strategyScores[1] = personality.intimidationPreference; strategyScores[2] = personality.ambushPreference; // Modify scores based on target speed and individual preferences if (targetSpeedRatio < 0.3) { // Slow target - use individual slow target strategy preference var preferredStrategy = personality.slowTargetStrategy; strategyScores[preferredStrategy] += 0.4; } else if (targetSpeedRatio > 0.6) { // Fast target - use individual fast target strategy preference var preferredStrategy = personality.fastTargetStrategy; strategyScores[preferredStrategy] += 0.4; } else { // Medium speed target - use individual medium target strategy preference var preferredStrategy = personality.mediumTargetStrategy; strategyScores[preferredStrategy] += 0.3; } // Situational modifiers based on individual traits if (targetSpeedRatio < 0.05) { // Target is nearly stationary - intimidation gets bonus for patient cars if (personality.patience > 0.6) { strategyScores[1] += 0.3; // Patient cars prefer intimidation // Initialize intimidation counter if not set if (typeof enemyCar.intimidationCount === "undefined") { enemyCar.intimidationCount = 0; enemyCar.maxIntimidations = Math.floor(personality.patience * 4) + 1; // 1-4 based on patience } // Check if we've completed enough intimidations if (enemyCar.intimidationCount >= enemyCar.maxIntimidations) { strategyScores[0] += 0.5; // Switch to direct after enough intimidation strategyScores[1] -= 0.3; } } else { strategyScores[0] += 0.4; // Impatient cars go direct } } // Distance-based individual preferences if (relativeDistance < personality.preferredAttackDistance) { // Within preferred attack range - aggressive cars prefer direct strategyScores[0] += personality.aggressiveness * 0.3; } else if (relativeDistance > personality.preferredAttackDistance * 2) { // Far away - adaptive cars prefer ambush strategyScores[2] += personality.adaptability * 0.3; } // Positional modifiers with individual traits if (isTargetBehind || relativeDistance < personality.personalSpaceRadius) { // Target behind or in personal space - use intimidation for positioning strategyScores[1] += 0.2; } else if (isTargetAhead && relativeDistance < personality.preferredAttackDistance * 1.5) { // Target ahead and in preferred range if (personality.aggressiveness > 0.6) { strategyScores[0] += 0.3; // Aggressive cars go direct } else { strategyScores[1] += 0.2; // Less aggressive cars intimidate first } } // Select strategy with highest score (with some randomness based on adaptability) var maxScore = Math.max(strategyScores[0], strategyScores[1], strategyScores[2]); var candidateStrategies = []; for (var s = 0; s < 3; s++) { if (strategyScores[s] >= maxScore - personality.adaptability * 0.2) { candidateStrategies.push(s); } } enemyCar.followStrategy = candidateStrategies[Math.floor(Math.random() * candidateStrategies.length)]; // Individual strategy duration based on personality var baseDuration = personality.minStrategyDuration + (personality.maxStrategyDuration - personality.minStrategyDuration) * personality.patience; var strategyMultiplier = 1.0; switch (enemyCar.followStrategy) { case 0: // Direct - duration affected by aggressiveness (aggressive = shorter bursts) strategyMultiplier = 1.2 - personality.aggressiveness * 0.4; break; case 1: // Intimidation - duration affected by patience (patient = longer intimidation) strategyMultiplier = 0.8 + personality.patience * 0.6; break; case 2: // Ambush - duration affected by adaptability (adaptive = longer planning) strategyMultiplier = 1.0 + personality.adaptability * 0.5; break; } enemyCar.followStrategyDuration = Math.floor(baseDuration * strategyMultiplier); enemyCar.followStrategyTimer = 0; } // Calculate base values using current target instead of always player (optimized) if (enemyCar.currentTarget) { aiDeltaX = enemyCar.currentTarget.object.x - enemyCar.x; aiDeltaY = enemyCar.currentTarget.object.y - enemyCar.y; // Cache distance calculation var aiDeltaXSq = aiDeltaX * aiDeltaX; var aiDeltaYSq = aiDeltaY * aiDeltaY; aiDistance = Math.sqrt(aiDeltaXSq + aiDeltaYSq); // Calculate relative velocity and approach angle for smarter pursuit var aiVelX = enemyCar.aiVelocityX || 0; var aiVelY = enemyCar.aiVelocityY || 0; var relativeVelX = enemyCar.currentTarget.velocityX - aiVelX; var relativeVelY = enemyCar.currentTarget.velocityY - aiVelY; } else { // Fallback to player if no target selected aiDeltaX = carPlayer.x - enemyCar.x; aiDeltaY = carPlayer.y - enemyCar.y; // Cache distance calculation var aiDeltaXSq = aiDeltaX * aiDeltaX; var aiDeltaYSq = aiDeltaY * aiDeltaY; aiDistance = Math.sqrt(aiDeltaXSq + aiDeltaYSq); var aiVelX = enemyCar.aiVelocityX || 0; var aiVelY = enemyCar.aiVelocityY || 0; var relativeVelX = velocityX - aiVelX; var relativeVelY = velocityY - aiVelY; } var approachAngle = Math.atan2(aiDeltaX, -aiDeltaY); var enemyCurrentAngle = enemyCar.rotation; var angleDiff = approachAngle - enemyCurrentAngle; while (angleDiff > Math.PI) angleDiff -= 2 * Math.PI; while (angleDiff < -Math.PI) angleDiff += 2 * Math.PI; // Anti-circling detection: check if we're in a circular pattern if (typeof enemyCar.lastPositions === "undefined") { enemyCar.lastPositions = []; enemyCar.circlingDetected = false; enemyCar.circlingCooldown = 0; } // Store position history for circling detection enemyCar.lastPositions.push({ x: enemyCar.x, y: enemyCar.y, frame: LK.ticks }); if (enemyCar.lastPositions.length > 60) { // Keep 1 second of history enemyCar.lastPositions.shift(); } // Detect circling by checking if we've been in similar positions recently var circlingThreshold = 80; // Distance threshold for considering positions "similar" var circlingCount = 0; for (var i = 0; i < enemyCar.lastPositions.length - 20; i++) { var oldPos = enemyCar.lastPositions[i]; var dist = Math.sqrt((enemyCar.x - oldPos.x) * (enemyCar.x - oldPos.x) + (enemyCar.y - oldPos.y) * (enemyCar.y - oldPos.y)); if (dist < circlingThreshold) { circlingCount++; } } enemyCar.circlingDetected = circlingCount > 3 && aiDistance < 200; // Circling if close to player and repeating positions // Reduce circling cooldown if (enemyCar.circlingCooldown > 0) enemyCar.circlingCooldown--; // Initialize failure detection system for direct pursuit if (typeof enemyCar.directPursuitTimer === "undefined") { enemyCar.directPursuitTimer = 0; enemyCar.lastPlayerDistance = aiDistance; enemyCar.nearMissCount = 0; enemyCar.totalPursuitTime = 0; } // Apply different follow strategies with distance awareness switch (enemyCar.followStrategy) { case 0: // Direct pursuit - straight chase with distance management // Track pursuit effectiveness enemyCar.directPursuitTimer++; enemyCar.totalPursuitTime++; // Detect near misses - when AI gets very close but doesn't hit if (aiDistance < 80 && enemyCar.lastPlayerDistance > 80) { // Just entered close range enemyCar.directPursuitTimer = 0; // Reset timer on new approach } if (enemyCar.lastPlayerDistance < 80 && aiDistance > 120) { // Just left close range without collision - potential near miss enemyCar.nearMissCount++; } // Check for failure conditions in direct pursuit var pursuitFailed = false; if (enemyCar.nearMissCount >= 2) { // Failed after 2 near misses pursuitFailed = true; } else if (enemyCar.totalPursuitTime > 300 && !currentColliding) { // Failed after 5 seconds without collision pursuitFailed = true; } else if (enemyCar.directPursuitTimer > 120 && aiDistance > 200) { // Failed if stuck at medium distance for 2 seconds pursuitFailed = true; } // Switch strategy if direct pursuit failed if (pursuitFailed) { // Reset failure tracking enemyCar.nearMissCount = 0; enemyCar.totalPursuitTime = 0; enemyCar.directPursuitTimer = 0; // Calculate target speed for strategy selection var currentTargetSpeed = 0; if (enemyCar.currentTarget) { currentTargetSpeed = Math.sqrt(enemyCar.currentTarget.velocityX * enemyCar.currentTarget.velocityX + enemyCar.currentTarget.velocityY * enemyCar.currentTarget.velocityY); } var targetSpeedRatio = currentTargetSpeed / maxSpeed; // Choose new strategy based on current situation if (targetSpeedRatio > 0.5 || aiDistance > 300) { // Fast target or far away - try ambush enemyCar.followStrategy = 2; enemyCar.followStrategyDuration = 90 + Math.floor(Math.random() * 60); } else { // Slow target or close - try varied approach enemyCar.followStrategy = 1; enemyCar.followStrategyDuration = 60 + Math.floor(Math.random() * 60); } enemyCar.followStrategyTimer = 0; } // Check current AI speed to prioritize acceleration building var currentAISpeed = Math.sqrt((enemyCar.aiVelocityX || 0) * (enemyCar.aiVelocityX || 0) + (enemyCar.aiVelocityY || 0) * (enemyCar.aiVelocityY || 0)); var aiSpeedRatio = currentAISpeed / maxSpeed; var needsAcceleration = aiSpeedRatio < 0.4; // Less than 40% of max speed // Check if AI can go straight to target for speed optimization var currentEnemyAngle = enemyCar.rotation; var straightLineAngle = Math.atan2(aiDeltaX, -aiDeltaY); var angleToTarget = straightLineAngle - currentEnemyAngle; while (angleToTarget > Math.PI) angleToTarget -= 2 * Math.PI; while (angleToTarget < -Math.PI) angleToTarget += 2 * Math.PI; // AI is considered "aligned" if facing within 15 degrees of target var isAlignedWithTarget = Math.abs(angleToTarget) < Math.PI / 12; // 15 degrees // Prioritize acceleration building when speed is low if (needsAcceleration && aiDistance > 150) { // Focus on building speed when far enough from target if (isAlignedWithTarget) { // Perfect alignment - accelerate in straight line toward target aiTargetRotation = straightLineAngle; aiPower = 1.0; // Maximum acceleration aiTargetVelocity = maxSpeed * aiPower * 1.0; } else if (Math.abs(angleToTarget) < Math.PI / 6) { // Close to aligned (within 30 degrees) - minor correction while accelerating var correctionFactor = Math.abs(angleToTarget) / (Math.PI / 6); // 0 to 1 aiTargetRotation = enemyCar.rotation + angleToTarget * 0.3; // Gentle steering aiPower = 0.9 + correctionFactor * 0.1; // Maintain high acceleration aiTargetVelocity = maxSpeed * aiPower * (0.95 + correctionFactor * 0.05); } else { // Need significant turn - prioritize getting aligned for acceleration aiTargetRotation = straightLineAngle; aiPower = 0.7; // Moderate acceleration while turning aiTargetVelocity = maxSpeed * aiPower * 0.8; } } else if (aiDistance < enemyCar.tacticalPersonality.preferredAttackDistance * 0.6 && !enemyCar.circlingDetected) { // Too close for this car's preferred attack style - back off based on aggressiveness var backoffIntensity = 0.3 + (1 - enemyCar.tacticalPersonality.aggressiveness) * 0.4; // Less aggressive = more backoff var backoffAngle = approachAngle + Math.PI + (Math.random() - 0.5) * Math.PI * backoffIntensity; aiTargetRotation = backoffAngle; aiPower = 0.2 + enemyCar.tacticalPersonality.aggressiveness * 0.2; aiTargetVelocity = maxSpeed * aiPower * 0.6; } else if (aiDistance > enemyCar.tacticalPersonality.preferredAttackDistance * 2) { // Far away - check for straight line opportunity if (isAlignedWithTarget) { // Can go straight - use maximum speed for efficiency aiTargetRotation = straightLineAngle; aiPower = Math.min(1, aiDistance / 600); aiTargetVelocity = maxSpeed * aiPower * 1.0; // Full speed when aligned } else { // Need to turn first - direct approach at normal speed aiTargetRotation = approachAngle; aiPower = Math.min(1, aiDistance / 600); aiTargetVelocity = maxSpeed * aiPower * 0.95; } } else { // Medium distance - check alignment for speed boost, but prioritize acceleration if needed if (needsAcceleration) { // Still building speed - maintain acceleration focus if (isAlignedWithTarget) { aiTargetRotation = straightLineAngle; aiPower = 0.9; aiTargetVelocity = maxSpeed * aiPower * 0.95; } else { // Get aligned while building speed aiTargetRotation = approachAngle; aiPower = 0.8; aiTargetVelocity = maxSpeed * aiPower * 0.9; } } else if (isAlignedWithTarget && aiDistance > 200) { // Aligned and far enough - go straight at higher speed aiTargetRotation = straightLineAngle; aiPower = Math.min(1, aiDistance / 500); aiTargetVelocity = maxSpeed * aiPower * 0.98; // Near full speed when aligned } else { // Not aligned or too close - slight offset to avoid head-on collision var offsetAngle = (Math.random() - 0.5) * Math.PI * 0.2; // ±18 degrees aiTargetRotation = approachAngle + offsetAngle; aiPower = Math.min(1, aiDistance / 500); aiTargetVelocity = maxSpeed * aiPower * 0.92; } } // Update last distance for next frame enemyCar.lastPlayerDistance = aiDistance; break; case 1: // Varied pursuit - intimidation mode: follows like direct but maintains safe distance to scare // Check current AI speed for acceleration prioritization var currentAISpeedIntimidation = Math.sqrt((enemyCar.aiVelocityX || 0) * (enemyCar.aiVelocityX || 0) + (enemyCar.aiVelocityY || 0) * (enemyCar.aiVelocityY || 0)); var aiSpeedRatioIntimidation = currentAISpeedIntimidation / maxSpeed; var needsAccelerationIntimidation = aiSpeedRatioIntimidation < 0.35; // Less than 35% of max speed if (enemyCar.circlingDetected && enemyCar.circlingCooldown <= 0) { // Break out of circling pattern var escapeAngle = approachAngle + Math.PI * 0.75 * (Math.random() < 0.5 ? 1 : -1); aiTargetRotation = escapeAngle; aiPower = 0.8; aiTargetVelocity = maxSpeed * aiPower * 0.85; enemyCar.circlingCooldown = 120; // 2 second cooldown } else if (needsAccelerationIntimidation && aiDistance > 200) { // Build speed before attempting intimidation tactics var intimidationAngle = Math.atan2(aiDeltaX, -aiDeltaY); var currentAngle = enemyCar.rotation; var angleToIntimidationTarget = intimidationAngle - currentAngle; while (angleToIntimidationTarget > Math.PI) angleToIntimidationTarget -= 2 * Math.PI; while (angleToIntimidationTarget < -Math.PI) angleToIntimidationTarget += 2 * Math.PI; if (Math.abs(angleToIntimidationTarget) < Math.PI / 8) { // Well aligned - accelerate directly toward intimidation position aiTargetRotation = intimidationAngle; aiPower = 0.9; aiTargetVelocity = maxSpeed * aiPower * 0.95; } else { // Need to align - gentle steering while building speed aiTargetRotation = currentAngle + angleToIntimidationTarget * 0.4; aiPower = 0.8; aiTargetVelocity = maxSpeed * aiPower * 0.9; } } else if (aiDistance < enemyCar.tacticalPersonality.preferredIntimidationDistance * 1.2) { // Individual intimidation range based on car's personality var intimidationDistance = enemyCar.tacticalPersonality.preferredIntimidationDistance; var distanceError = aiDistance - intimidationDistance; var toleranceRange = 20 + enemyCar.tacticalPersonality.patience * 20; // 20-40 pixel tolerance based on patience if (Math.abs(distanceError) < toleranceRange) { // Perfect intimidation distance - behavior based on individual aggressiveness and speed var aggressiveness = enemyCar.tacticalPersonality.aggressiveness; var weavingIntensity = Math.PI * 0.1 + aggressiveness * Math.PI * 0.1; // More aggressive = more weaving var intimidationOffset = Math.sin(LK.ticks * (0.03 + aggressiveness * 0.04)) * weavingIntensity; aiTargetRotation = approachAngle + intimidationOffset; // Speed-dependent intimidation - more aggressive when faster var speedBonus = Math.min(0.2, aiSpeedRatioIntimidation * 0.3); aiPower = 0.5 + aggressiveness * 0.3 + speedBonus; aiTargetVelocity = maxSpeed * aiPower * (0.8 + aggressiveness * 0.1 + speedBonus); // Check if target is stationary and we're intimidating var currentTargetSpeed = 0; if (enemyCar.currentTarget) { currentTargetSpeed = Math.sqrt(enemyCar.currentTarget.velocityX * enemyCar.currentTarget.velocityX + enemyCar.currentTarget.velocityY * enemyCar.currentTarget.velocityY); } if (currentTargetSpeed < maxSpeed * 0.05 && typeof enemyCar.intimidationCount !== "undefined") { // Mark intimidation as successful - duration based on patience if (typeof enemyCar.intimidationTimer === "undefined") enemyCar.intimidationTimer = 0; enemyCar.intimidationTimer++; // Patient cars intimidate longer, impatient cars intimidate briefly var intimidationDuration = 60 + enemyCar.tacticalPersonality.patience * 120; // 1-3 seconds based on patience if (enemyCar.intimidationTimer > intimidationDuration) { enemyCar.intimidationCount++; enemyCar.intimidationTimer = 0; // Force strategy recalculation enemyCar.followStrategyTimer = enemyCar.followStrategyDuration + 1; } } } else if (distanceError < 0) { // Too close - back off behavior based on aggressiveness var backoffIntensity = 0.2 + (1 - enemyCar.tacticalPersonality.aggressiveness) * 0.2; // Less aggressive = back off more aiTargetRotation = approachAngle + Math.PI + (Math.random() - 0.5) * Math.PI * backoffIntensity; aiPower = 0.3 + enemyCar.tacticalPersonality.aggressiveness * 0.2; aiTargetVelocity = maxSpeed * aiPower * 0.6; } else { // Too far - approach for intimidation with individual aggressiveness aiTargetRotation = approachAngle; aiPower = 0.6 + enemyCar.tacticalPersonality.aggressiveness * 0.3; aiTargetVelocity = maxSpeed * aiPower * (0.85 + enemyCar.tacticalPersonality.aggressiveness * 0.1); } } else { // Long range - approach based on individual preferred attack distance var approachIntensity = Math.min(1, aiDistance / (enemyCar.tacticalPersonality.preferredAttackDistance * 2)); aiTargetRotation = approachAngle; aiPower = approachIntensity * (0.7 + enemyCar.tacticalPersonality.aggressiveness * 0.3); aiTargetVelocity = maxSpeed * aiPower * (0.9 + enemyCar.tacticalPersonality.aggressiveness * 0.05); } break; case 2: // Ambush - predict target's future position with smarter interception var targetVelX = 0; var targetVelY = 0; var targetPosX = enemyCar.x; // fallback position var targetPosY = enemyCar.y; if (enemyCar.currentTarget) { targetVelX = enemyCar.currentTarget.velocityX; targetVelY = enemyCar.currentTarget.velocityY; targetPosX = enemyCar.currentTarget.object.x; targetPosY = enemyCar.currentTarget.object.y; } var targetSpeed = Math.sqrt(targetVelX * targetVelX + targetVelY * targetVelY); // Check current AI speed for acceleration prioritization in ambush var currentAISpeedAmbush = Math.sqrt((enemyCar.aiVelocityX || 0) * (enemyCar.aiVelocityX || 0) + (enemyCar.aiVelocityY || 0) * (enemyCar.aiVelocityY || 0)); var aiSpeedRatioAmbush = currentAISpeedAmbush / maxSpeed; var needsAccelerationAmbush = aiSpeedRatioAmbush < 0.5; // Less than 50% of max speed for ambush if (enemyCar.circlingDetected && enemyCar.circlingCooldown <= 0) { // Break circling with wide flanking maneuver var flankAngle = approachAngle + Math.PI * 0.8 * (Math.random() < 0.5 ? 1 : -1); aiTargetRotation = flankAngle; aiPower = 0.9; aiTargetVelocity = maxSpeed * aiPower * 0.9; enemyCar.circlingCooldown = 180; // 3 second cooldown } else if (needsAccelerationAmbush && aiDistance > 250) { // Build speed before attempting interception - find clear acceleration path var accelerationAngle; // Try to find a path that doesn't lead directly to target (for ambush positioning) if (aiDistance > 400) { // Far from target - accelerate in a flanking direction var flankingOffset = Math.PI * 0.3 * (Math.random() < 0.5 ? 1 : -1); // ±54 degrees accelerationAngle = approachAngle + flankingOffset; } else { // Medium distance - position for future interception while building speed var leadAngle = Math.atan2(targetVelX, -targetVelY); // Target's movement direction var interceptAngle = leadAngle + Math.PI * 0.6 * (Math.random() < 0.5 ? 1 : -1); // Offset for ambush accelerationAngle = interceptAngle; } aiTargetRotation = accelerationAngle; aiPower = 0.95; // High acceleration priority aiTargetVelocity = maxSpeed * aiPower * 0.98; } else if (aiDistance < 150) { // Close range - position for optimal angle of attack, but only if we have good speed if (aiSpeedRatioAmbush > 0.4) { // Have enough speed for attack positioning var attackAngle = approachAngle + Math.PI * 0.4 * (angleDiff > 0 ? 1 : -1); aiTargetRotation = attackAngle; aiPower = 0.7 + aiSpeedRatioAmbush * 0.2; // More power when faster aiTargetVelocity = maxSpeed * aiPower * (0.8 + aiSpeedRatioAmbush * 0.15); } else { // Need more speed - create space while accelerating var retreatAngle = approachAngle + Math.PI + (Math.random() - 0.5) * Math.PI * 0.4; aiTargetRotation = retreatAngle; aiPower = 0.8; aiTargetVelocity = maxSpeed * aiPower * 0.85; } } else { // Long range interception var predictionTime = Math.min(2.5, aiDistance / maxSpeed * 0.9); var interceptX = targetPosX + targetVelX * predictionTime; var interceptY = targetPosY + targetVelY * predictionTime; // Keep predicted position within bounds interceptX = Math.max(80, Math.min(1968, interceptX)); interceptY = Math.max(80, Math.min(2106, interceptY)); // Calculate optimal interception approach var interceptDeltaX = interceptX - enemyCar.x; var interceptDeltaY = interceptY - enemyCar.y; var interceptDistance = Math.sqrt(interceptDeltaX * interceptDeltaX + interceptDeltaY * interceptDeltaY); // Adjust interception based on current speed capabilities var speedRatio = (enemyCar.aiCurrentVelocity || 0) / Math.max(0.1, targetSpeed); var leadAdjustment = (1 - speedRatio) * 0.3; // Speed-dependent interception strategy if (needsAccelerationAmbush) { // Focus on acceleration toward interception point aiTargetRotation = Math.atan2(interceptDeltaX, -interceptDeltaY); aiPower = 0.9; // High acceleration var speedBoost = Math.min(0.25, targetSpeed / maxSpeed * 0.25); aiTargetVelocity = maxSpeed * aiPower * (0.9 + speedBoost); } else { // Have good speed - execute precise interception aiTargetRotation = Math.atan2(interceptDeltaX, -interceptDeltaY); aiPower = Math.min(1, interceptDistance / 700); var speedBoost = Math.min(0.2, targetSpeed / maxSpeed * 0.2); aiTargetVelocity = maxSpeed * aiPower * (0.85 + speedBoost + leadAdjustment); } } break; } // Blend in edge avoidance with follow behavior if (edgeAvoidanceX !== 0 || edgeAvoidanceY !== 0) { // Calculate edge avoidance angle var avoidanceAngle = Math.atan2(edgeAvoidanceX, -edgeAvoidanceY); // Blend the target rotation with avoidance (stronger when closer to edges) var avoidanceWeight = Math.min(0.7, Math.abs(edgeAvoidanceX) + Math.abs(edgeAvoidanceY)); var followWeight = 1 - avoidanceWeight; // Convert angles to vectors for blending var followVecX = Math.sin(aiTargetRotation) * followWeight; var followVecY = -Math.cos(aiTargetRotation) * followWeight; var avoidVecX = Math.sin(avoidanceAngle) * avoidanceWeight; var avoidVecY = -Math.cos(avoidanceAngle) * avoidanceWeight; // Combine and convert back to angle var blendedVecX = followVecX + avoidVecX; var blendedVecY = followVecY + avoidVecY; aiTargetRotation = Math.atan2(blendedVecX, -blendedVecY); } } else if (enemyCar.aiMode === "evade") { // Evasion mode - flee from pursuers with different patterns if (typeof enemyCar.evasionState === "undefined" || enemyCar.aiModeTimer === 0) { // Initialize evasion variables enemyCar.evasionState = { baseAngle: Math.random() * Math.PI * 2, escapeTimer: 0, zigzagDirection: Math.random() < 0.5 ? 1 : -1, circleCenter: { x: enemyCar.x, y: enemyCar.y }, circleRadius: 150 + Math.random() * 100, circleAngle: 0, lastPursuerAngle: 0 }; } enemyCar.evasionState.escapeTimer++; // Calculate average pursuer position for evasion reference var avgPursuerX = 0, avgPursuerY = 0; var pursuerCount = enemyCar.pursuitDetection.pursuers.length; if (pursuerCount > 0) { for (var ep = 0; ep < pursuerCount; ep++) { avgPursuerX += enemyCar.pursuitDetection.pursuers[ep].object.x; avgPursuerY += enemyCar.pursuitDetection.pursuers[ep].object.y; } avgPursuerX /= pursuerCount; avgPursuerY /= pursuerCount; } else { // Fallback if no pursuers detected avgPursuerX = enemyCar.x - 100; avgPursuerY = enemyCar.y - 100; } // Calculate escape direction (away from average pursuer position) var escapeFromX = avgPursuerX - enemyCar.x; var escapeFromY = avgPursuerY - enemyCar.y; var escapeBaseAngle = Math.atan2(-escapeFromX, escapeFromY); // Opposite direction // Apply different evasion strategies with individual preferences var flightPrefs = enemyCar.tacticalPersonality; switch (enemyCar.pursuitDetection.evasionStrategy) { case 0: // Speed focus - straight line escape with individual variations var escapeEfficiency = 0.8 + flightPrefs.aggressiveness * 0.2; // Aggressive cars escape more efficiently aiTargetRotation = escapeBaseAngle; // Look for clear path ahead with individual distance preferences var lookAheadDist = 150 + flightPrefs.riskTolerance * 100; // Risk-tolerant cars look further var pathClearX = enemyCar.x + Math.sin(escapeBaseAngle) * lookAheadDist; var pathClearY = enemyCar.y - Math.cos(escapeBaseAngle) * lookAheadDist; // Individual wall avoidance based on risk tolerance var wallBuffer = 80 + (1 - flightPrefs.riskTolerance) * 60; // Risk-averse cars avoid walls more if (pathClearX < wallBuffer || pathClearX > 2048 - wallBuffer || pathClearY < wallBuffer || pathClearY > 2186 - wallBuffer) { // Escape route selection based on individual preferences if (flightPrefs.adaptability > 0.5) { // Adaptive cars find better escape routes var toCenterX = 1024 - enemyCar.x; var toCenterY = 1093 - enemyCar.y; var toCenterAngle = Math.atan2(toCenterX, -toCenterY); // Blend escape with center direction based on adaptability var blendFactor = flightPrefs.adaptability; var escapeVecX = Math.sin(escapeBaseAngle) * (1 - blendFactor); var escapeVecY = -Math.cos(escapeBaseAngle) * (1 - blendFactor); var centerVecX = Math.sin(toCenterAngle) * blendFactor; var centerVecY = -Math.cos(toCenterAngle) * blendFactor; aiTargetRotation = Math.atan2(escapeVecX + centerVecX, -(escapeVecY + centerVecY)); } else { // Less adaptive cars just turn toward center var toCenterX = 1024 - enemyCar.x; var toCenterY = 1093 - enemyCar.y; aiTargetRotation = Math.atan2(toCenterX, -toCenterY); } } // Individual speed based on aggressiveness and bravery aiPower = 0.9 + flightPrefs.aggressiveness * 0.1; // More aggressive = faster escape var speedMultiplier = escapeEfficiency * (0.95 + flightPrefs.bravery * 0.05); // Brave cars maintain higher speed aiTargetVelocity = maxSpeed * aiPower * speedMultiplier; break; case 1: // Zigzag pattern with individual variations var baseFrequency = 0.08 + flightPrefs.adaptability * 0.08; // Adaptive cars zigzag more frequently var pursuitFrequency = enemyCar.pursuitDetection.pursuitIntensity * 0.06; var zigzagFrequency = baseFrequency + pursuitFrequency; // Individual zigzag amplitude based on risk tolerance var baseAmplitude = Math.PI * (0.3 + flightPrefs.riskTolerance * 0.2); // Risk-tolerant cars make wider turns var panicAmplitude = flightPrefs.panicThreshold < 0.5 ? Math.PI * 0.1 : 0; // Panicky cars add erratic movement var zigzagAmplitude = baseAmplitude + panicAmplitude; var zigzagOffset = Math.sin(enemyCar.evasionState.escapeTimer * zigzagFrequency) * zigzagAmplitude; // Add individual unpredictability if (flightPrefs.adaptability > 0.7 && Math.random() < 0.05) { // Highly adaptive cars occasionally reverse zigzag direction enemyCar.evasionState.zigzagDirection *= -1; } aiTargetRotation = escapeBaseAngle + zigzagOffset * (enemyCar.evasionState.zigzagDirection || 1); // Individual power and speed based on personality var basePower = 0.75 + flightPrefs.aggressiveness * 0.15; var pursuitBonus = enemyCar.pursuitDetection.pursuitIntensity * 0.15; aiPower = basePower + pursuitBonus; var speedMultiplier = 0.85 + flightPrefs.bravery * 0.1; // Brave cars maintain higher speed while evading aiTargetVelocity = maxSpeed * aiPower * speedMultiplier; break; case 2: // Circular escape pattern with individual preferences // Individual circle updating frequency based on adaptability var updateFrequency = flightPrefs.adaptability > 0.6 ? 45 : 75; // Adaptive cars update circle more often if (enemyCar.evasionState.escapeTimer % updateFrequency === 0) { enemyCar.evasionState.circleCenter.x = enemyCar.x; enemyCar.evasionState.circleCenter.y = enemyCar.y; // Individual circle radius based on personality var baseRadius = 120 + flightPrefs.riskTolerance * 80; // Risk-tolerant cars use larger circles var panicRadius = flightPrefs.panicThreshold < 0.4 ? 40 : 0; // Panicky cars use smaller circles enemyCar.evasionState.circleRadius = baseRadius + panicRadius; } // Individual circle speed based on aggressiveness and pursuit intensity var baseCircleSpeed = 0.06 + flightPrefs.aggressiveness * 0.04; var pursuitSpeedBonus = enemyCar.pursuitDetection.pursuitIntensity * 0.04; var circleSpeed = baseCircleSpeed + pursuitSpeedBonus; // Add individual direction changes for unpredictability if (flightPrefs.adaptability > 0.8 && Math.random() < 0.02) { // Highly adaptive cars occasionally reverse circle direction circleSpeed *= -1; } enemyCar.evasionState.circleAngle += circleSpeed; var targetCircleX = enemyCar.evasionState.circleCenter.x + Math.cos(enemyCar.evasionState.circleAngle) * enemyCar.evasionState.circleRadius; var targetCircleY = enemyCar.evasionState.circleCenter.y + Math.sin(enemyCar.evasionState.circleAngle) * enemyCar.evasionState.circleRadius; // Individual boundary handling based on risk tolerance var boundary = 80 + (1 - flightPrefs.riskTolerance) * 40; // Risk-averse cars stay further from edges targetCircleX = Math.max(boundary, Math.min(2048 - boundary, targetCircleX)); targetCircleY = Math.max(boundary, Math.min(2186 - boundary, targetCircleY)); var toCircleX = targetCircleX - enemyCar.x; var toCircleY = targetCircleY - enemyCar.y; aiTargetRotation = Math.atan2(toCircleX, -toCircleY); // Individual power and speed var basePower = 0.65 + flightPrefs.patience * 0.15; // Patient cars better at sustained circular movement var pursuitBonus = enemyCar.pursuitDetection.pursuitIntensity * 0.15; aiPower = basePower + pursuitBonus; var speedMultiplier = 0.8 + flightPrefs.bravery * 0.1; // Brave cars circle at higher speeds aiTargetVelocity = maxSpeed * aiPower * speedMultiplier; break; } // Apply strong edge avoidance during evasion if (edgeAvoidanceX !== 0 || edgeAvoidanceY !== 0) { var avoidanceAngle = Math.atan2(edgeAvoidanceX, -edgeAvoidanceY); var avoidanceWeight = Math.min(0.8, (Math.abs(edgeAvoidanceX) + Math.abs(edgeAvoidanceY)) * 2); var evasionWeight = 1 - avoidanceWeight; var evasionVecX = Math.sin(aiTargetRotation) * evasionWeight; var evasionVecY = -Math.cos(aiTargetRotation) * evasionWeight; var avoidVecX = Math.sin(avoidanceAngle) * avoidanceWeight; var avoidVecY = -Math.cos(avoidanceAngle) * avoidanceWeight; var blendedVecX = evasionVecX + avoidVecX; var blendedVecY = evasionVecY + avoidVecY; aiTargetRotation = Math.atan2(blendedVecX, -blendedVecY); } } else { // Enhanced Free roam logic with dynamic behavior patterns if (typeof enemyCar.freeRoamAngle === "undefined" || enemyCar.aiModeTimer === 0) { // Initialize free roam variables enemyCar.freeRoamAngle = Math.random() * Math.PI * 2; enemyCar.freeRoamSpeed = maxSpeed * (0.4 + Math.random() * 0.4); // 40%-80% of max speed enemyCar.freeRoamTimer = 0; enemyCar.freeRoamDirectionTimer = 0; enemyCar.freeRoamPattern = Math.floor(Math.random() * 3); // 0=wander, 1=circular, 2=aggressive } // Dynamic free roam behavior - change direction periodically for more interesting movement enemyCar.freeRoamTimer++; enemyCar.freeRoamDirectionTimer++; // Change direction every 1-3 seconds based on pattern var directionChangeInterval; switch (enemyCar.freeRoamPattern) { case 0: // Wander pattern - gentle direction changes directionChangeInterval = 90 + Math.floor(Math.random() * 60); // 1.5-2.5 seconds break; case 1: // Circular pattern - more frequent turns directionChangeInterval = 45 + Math.floor(Math.random() * 30); // 0.75-1.25 seconds break; case 2: // Aggressive pattern - quick direction changes directionChangeInterval = 30 + Math.floor(Math.random() * 40); // 0.5-1.17 seconds break; } if (enemyCar.freeRoamDirectionTimer > directionChangeInterval) { enemyCar.freeRoamDirectionTimer = 0; // Different behavior patterns for more variety switch (enemyCar.freeRoamPattern) { case 0: // Wander - small random direction changes var angleChange = (Math.random() - 0.5) * Math.PI * 0.6; // ±54 degrees enemyCar.freeRoamAngle += angleChange; enemyCar.freeRoamSpeed = maxSpeed * (0.3 + Math.random() * 0.4); break; case 1: // Circular - tends to turn in one direction if (typeof enemyCar.circularDirection === "undefined") { enemyCar.circularDirection = Math.random() < 0.5 ? -1 : 1; } var circularTurn = enemyCar.circularDirection * (Math.PI * 0.3 + Math.random() * Math.PI * 0.4); // 54-126 degrees enemyCar.freeRoamAngle += circularTurn; enemyCar.freeRoamSpeed = maxSpeed * (0.5 + Math.random() * 0.3); // Occasionally reverse direction if (Math.random() < 0.15) enemyCar.circularDirection *= -1; break; case 2: // Aggressive - sharp turns and speed changes var aggressiveTurn = (Math.random() - 0.5) * Math.PI * 1.2; // ±108 degrees enemyCar.freeRoamAngle += aggressiveTurn; enemyCar.freeRoamSpeed = maxSpeed * (0.6 + Math.random() * 0.3); break; } } // Add some target awareness even in free roam - occasionally look towards closest target var closestTarget = null; var closestDistance = Infinity; // Check distance to player var playerDistance = Math.sqrt((carPlayer.x - enemyCar.x) * (carPlayer.x - enemyCar.x) + (carPlayer.y - enemyCar.y) * (carPlayer.y - enemyCar.y)); if (playerDistance < closestDistance) { closestDistance = playerDistance; closestTarget = carPlayer; } // Check distance to other enemy cars for (var nearIdx = 0; nearIdx < enemyCars.length; nearIdx++) { if (nearIdx !== aiCarIdx) { var otherCar = enemyCars[nearIdx]; var otherDistance = Math.sqrt((otherCar.x - enemyCar.x) * (otherCar.x - enemyCar.x) + (otherCar.y - enemyCar.y) * (otherCar.y - enemyCar.y)); if (otherDistance < closestDistance) { closestDistance = otherDistance; closestTarget = otherCar; } } } var targetInfluence = 0; if (closestTarget && closestDistance < 400) { // Within 400 pixels // Closer target = more influence on direction targetInfluence = Math.max(0, (400 - closestDistance) / 400 * 0.3); // Up to 30% influence if (Math.random() < 0.02) { // 2% chance per frame to look at closest target var targetAngle = Math.atan2(closestTarget.x - enemyCar.x, -(closestTarget.y - enemyCar.y)); // Blend current angle with target angle var currentVecX = Math.sin(enemyCar.freeRoamAngle); var currentVecY = -Math.cos(enemyCar.freeRoamAngle); var targetVecX = Math.sin(targetAngle) * targetInfluence; var targetVecY = -Math.cos(targetAngle) * targetInfluence; var blendedVecX = currentVecX * (1 - targetInfluence) + targetVecX; var blendedVecY = currentVecY * (1 - targetInfluence) + targetVecY; enemyCar.freeRoamAngle = Math.atan2(blendedVecX, -blendedVecY); } } aiTargetRotation = enemyCar.freeRoamAngle; aiPower = 1; aiTargetVelocity = enemyCar.freeRoamSpeed; // Apply edge avoidance to free roam mode with stronger influence if (edgeAvoidanceX !== 0 || edgeAvoidanceY !== 0) { // Calculate edge avoidance angle var avoidanceAngle = Math.atan2(edgeAvoidanceX, -edgeAvoidanceY); // Much stronger avoidance in free roam mode var avoidanceWeight = Math.min(0.85, (Math.abs(edgeAvoidanceX) + Math.abs(edgeAvoidanceY)) * 1.5); var roamWeight = 1 - avoidanceWeight; // Convert angles to vectors for blending var roamVecX = Math.sin(aiTargetRotation) * roamWeight; var roamVecY = -Math.cos(aiTargetRotation) * roamWeight; var avoidVecX = Math.sin(avoidanceAngle) * avoidanceWeight; var avoidVecY = -Math.cos(avoidanceAngle) * avoidanceWeight; // Combine and convert back to angle var blendedVecX = roamVecX + avoidVecX; var blendedVecY = roamVecY + avoidVecY; aiTargetRotation = Math.atan2(blendedVecX, -blendedVecY); // Update the free roam angle to new direction after avoidance enemyCar.freeRoamAngle = aiTargetRotation; } } // AI: Smoothly rotate enemy car towards target if (typeof enemyCar.aiRotation === "undefined") enemyCar.aiRotation = enemyCar.rotation; var aiRotationDelta = aiTargetRotation - enemyCar.aiRotation; while (aiRotationDelta > Math.PI) aiRotationDelta -= 2 * Math.PI; while (aiRotationDelta < -Math.PI) aiRotationDelta += 2 * Math.PI; var aiRotationSpeed = baseRotationSpeed * 0.7; // Slightly slower turning for AI enemyCar.aiRotation += aiRotationDelta * aiRotationSpeed; enemyCar.rotation = enemyCar.aiRotation; // AI: Velocity logic if (typeof enemyCar.aiCurrentVelocity === "undefined") enemyCar.aiCurrentVelocity = 0; if (aiPower > 0.1) { // Accelerate var aiVelocityDiff = aiTargetVelocity - enemyCar.aiCurrentVelocity; var aiAccelerationRate = 0.003; enemyCar.aiCurrentVelocity += aiVelocityDiff * aiAccelerationRate; } else { // Decelerate var aiDecelerationRate = 0.045; enemyCar.aiCurrentVelocity *= 1 - aiDecelerationRate; if (Math.abs(enemyCar.aiCurrentVelocity) < 0.1) enemyCar.aiCurrentVelocity = 0; } enemyCar.aiCurrentVelocity = Math.min(enemyCar.aiCurrentVelocity, maxSpeed * 0.92); // AI: Intended movement direction var aiIntendedMoveX = Math.sin(enemyCar.aiRotation) * enemyCar.aiCurrentVelocity; var aiIntendedMoveY = -Math.cos(enemyCar.aiRotation) * enemyCar.aiCurrentVelocity; // AI: Drift physics for enemy car if (typeof enemyCar.aiVelocityX === "undefined") enemyCar.aiVelocityX = 0; if (typeof enemyCar.aiVelocityY === "undefined") enemyCar.aiVelocityY = 0; enemyCar.aiVelocityX = enemyCar.aiVelocityX * driftFactor + aiIntendedMoveX * gripFactor; enemyCar.aiVelocityY = enemyCar.aiVelocityY * driftFactor + aiIntendedMoveY * gripFactor; // AI: Apply friction and update position enemyCar.aiVelocityX *= 0.98; enemyCar.aiVelocityY *= 0.98; enemyCar.x += enemyCar.aiVelocityX + enemyCar.velocityX; enemyCar.y += enemyCar.aiVelocityY + enemyCar.velocityY; // --- End Enemy Car AI Movement Logic --- // Apply smooth braking to enemy car after collision for more natural deceleration if (!enemyCar.smoothBraking) enemyCar.smoothBraking = false; if (!enemyCar.brakeFrames) enemyCar.brakeFrames = 0; var currentEnemySpeed = Math.sqrt(enemyCar.velocityX * enemyCar.velocityX + enemyCar.velocityY * enemyCar.velocityY); // If enemy car was just launched (high velocity), enable smooth braking if (currentEnemySpeed > maxSpeed * 0.6 && !enemyCar.smoothBraking) { enemyCar.smoothBraking = true; enemyCar.brakeFrames = 0; } // Smooth braking logic: apply a gentle, progressive friction for a short period after being launched if (enemyCar.smoothBraking) { // Braking lasts for 18 frames (~0.3s at 60fps) var brakeDuration = 18; var brakeProgress = Math.min(1, enemyCar.brakeFrames / brakeDuration); // Start with gentle friction, increase to normal friction var minFriction = 0.96; var maxFriction = 0.92 - currentEnemySpeed / maxSpeed * 0.05; var frictionRate = minFriction + (maxFriction - minFriction) * brakeProgress; enemyCar.velocityX *= frictionRate; enemyCar.velocityY *= frictionRate; enemyCar.brakeFrames++; if (enemyCar.brakeFrames >= brakeDuration) { enemyCar.smoothBraking = false; } } else if (currentEnemySpeed > 0.1) { // Normal progressive friction var frictionRate = 0.92 - currentEnemySpeed / maxSpeed * 0.05; // More friction at higher speeds enemyCar.velocityX *= frictionRate; enemyCar.velocityY *= frictionRate; } else { // Stop very slow movement to prevent endless drift enemyCar.velocityX = 0; enemyCar.velocityY = 0; } // Keep enemy car within bounds var enemyHalfWidth = 32; var enemyHalfHeight = 47; if (enemyCar.x < enemyHalfWidth) { enemyCar.x = enemyHalfWidth; enemyCar.velocityX = -enemyCar.velocityX * 0.6; } if (enemyCar.x > 2048 - enemyHalfWidth) { enemyCar.x = 2048 - enemyHalfWidth; enemyCar.velocityX = -enemyCar.velocityX * 0.6; } if (enemyCar.y < enemyHalfHeight) { enemyCar.y = enemyHalfHeight; enemyCar.velocityY = -enemyCar.velocityY * 0.6; } if (enemyCar.y > 2186 - enemyHalfHeight) { enemyCar.y = 2186 - enemyHalfHeight; enemyCar.velocityY = -enemyCar.velocityY * 0.6; } } // --- End Enemy Cars AI Movement Logic --- // Add collision detection between enemy cars (optimized - only check every 3rd frame) if (LK.ticks % 3 === 0) { for (var carA = 0; carA < enemyCars.length; carA++) { for (var carB = carA + 1; carB < enemyCars.length; carB++) { var enemyCarA = enemyCars[carA]; var enemyCarB = enemyCars[carB]; // Initialize collision tracking if not exists if (!enemyCarA.enemyColliding) enemyCarA.enemyColliding = []; if (!enemyCarB.enemyColliding) enemyCarB.enemyColliding = []; while (enemyCarA.enemyColliding.length <= carB) enemyCarA.enemyColliding.push(false); while (enemyCarB.enemyColliding.length <= carA) enemyCarB.enemyColliding.push(false); // Calculate collision boxes for both cars var carALeft = enemyCarA.x - enemyCollisionWidth / 2; var carARight = enemyCarA.x + enemyCollisionWidth / 2; var carATop = enemyCarA.y - enemyCollisionHeight / 2; var carABottom = enemyCarA.y + enemyCollisionHeight / 2; var carBLeft = enemyCarB.x - enemyCollisionWidth / 2; var carBRight = enemyCarB.x + enemyCollisionWidth / 2; var carBTop = enemyCarB.y - enemyCollisionHeight / 2; var carBBottom = enemyCarB.y + enemyCollisionHeight / 2; // Check for collision var currentColliding = !(carARight < carBLeft || carALeft > carBRight || carABottom < carBTop || carATop > carBBottom); if (!enemyCarA.enemyColliding[carB] && currentColliding) { // Collision just started - determine collision responsibility for enemy cars var carASpeedX = (enemyCarA.aiVelocityX || 0) + (enemyCarA.velocityX || 0); var carASpeedY = (enemyCarA.aiVelocityY || 0) + (enemyCarA.velocityY || 0); var carBSpeedX = (enemyCarB.aiVelocityX || 0) + (enemyCarB.velocityX || 0); var carBSpeedY = (enemyCarB.aiVelocityY || 0) + (enemyCarB.velocityY || 0); var carASpeed = Math.sqrt(carASpeedX * carASpeedX + carASpeedY * carASpeedY); var carBSpeed = Math.sqrt(carBSpeedX * carBSpeedX + carBSpeedY * carBSpeedY); // Calculate collision direction var collisionDeltaX = enemyCarB.x - enemyCarA.x; var collisionDeltaY = enemyCarB.y - enemyCarA.y; var collisionDistance = Math.sqrt(collisionDeltaX * collisionDeltaX + collisionDeltaY * collisionDeltaY); // Normalize collision direction if (collisionDistance > 0) { collisionDeltaX /= collisionDistance; collisionDeltaY /= collisionDistance; } // Determine collision responsibility for enemy cars var carACrasher = false; var carBCrasher = false; var bothEnemiesCrashed = false; // Calculate approach vectors var carAApproachX = 0, carAApproachY = 0; var carBApproachX = 0, carBApproachY = 0; if (carASpeed > 0.1) { carAApproachX = carASpeedX / carASpeed; carAApproachY = carASpeedY / carASpeed; } if (carBSpeed > 0.1) { carBApproachX = carBSpeedX / carBSpeed; carBApproachY = carBSpeedY / carBSpeed; } // Calculate how much each car is moving toward the collision var carATowardCollision = 0; var carBTowardCollision = 0; if (carASpeed > 0.1) { carATowardCollision = carAApproachX * collisionDeltaX + carAApproachY * collisionDeltaY; } if (carBSpeed > 0.1) { carBTowardCollision = carBApproachX * -collisionDeltaX + carBApproachY * -collisionDeltaY; } // Speed thresholds for determining crasher responsibility var minCrasherSpeed = maxSpeed * 0.15; var dominantCrasherThreshold = 0.3; // Determine responsibility if (carASpeed > minCrasherSpeed && carBSpeed > minCrasherSpeed) { if (carATowardCollision > dominantCrasherThreshold && carBTowardCollision > dominantCrasherThreshold) { bothEnemiesCrashed = true; } else if (carATowardCollision > carBTowardCollision + dominantCrasherThreshold) { carACrasher = true; } else if (carBTowardCollision > carATowardCollision + dominantCrasherThreshold) { carBCrasher = true; } else { bothEnemiesCrashed = true; } } else if (carASpeed > minCrasherSpeed && carBSpeed <= minCrasherSpeed) { carACrasher = true; } else if (carBSpeed > minCrasherSpeed && carASpeed <= minCrasherSpeed) { carBCrasher = true; } else { bothEnemiesCrashed = true; } // Calculate momentum transfer using conservation of momentum var totalMass = enemyCarA.weight + enemyCarB.weight; var massRatioA1 = (enemyCarA.weight - enemyCarB.weight) / totalMass; var massRatioA2 = 2 * enemyCarB.weight / totalMass; var massRatioB1 = 2 * enemyCarA.weight / totalMass; var massRatioB2 = (enemyCarB.weight - enemyCarA.weight) / totalMass; // Apply different energy loss based on collision responsibility var relativeSpeed = Math.max(carASpeed, carBSpeed); var minLoss, maxLoss; var speedNorm = Math.min(1, relativeSpeed / maxSpeed); if (bothEnemiesCrashed) { minLoss = 0.18; maxLoss = 0.60; } else { minLoss = 0.12; maxLoss = 0.50; } var energyLoss = minLoss + (maxLoss - minLoss) * speedNorm; var restitution = 1 - energyLoss; // Apply different restitution based on responsibility var carARestitution = restitution; var carBRestitution = restitution; if (carACrasher) { carARestitution = restitution * 1.15; // Car A is crasher - less penalty carBRestitution = restitution * 0.85; // Car B crashed into - more penalty } else if (carBCrasher) { carBRestitution = restitution * 1.15; // Car B is crasher - less penalty carARestitution = restitution * 0.85; // Car A crashed into - more penalty } // Separate cars to prevent overlap var separationDistance = 75; var halfSeparation = separationDistance / 2; enemyCarA.x = enemyCarA.x - collisionDeltaX * halfSeparation; enemyCarA.y = enemyCarA.y - collisionDeltaY * halfSeparation; enemyCarB.x = enemyCarB.x + collisionDeltaX * halfSeparation; enemyCarB.y = enemyCarB.y + collisionDeltaY * halfSeparation; // Calculate new velocities for car A var carAImpactX = carASpeedX * massRatioA1 + carBSpeedX * massRatioA2; var carAImpactY = carASpeedY * massRatioA1 + carBSpeedY * massRatioA2; enemyCarA.velocityX = carAImpactX * carARestitution; enemyCarA.velocityY = carAImpactY * carARestitution; // Calculate new velocities for car B var carBImpactX = carASpeedX * massRatioB1 + carBSpeedX * massRatioB2; var carBImpactY = carASpeedY * massRatioB1 + carBSpeedY * massRatioB2; enemyCarB.velocityX = carBImpactX * carBRestitution; enemyCarB.velocityY = carBImpactY * carBRestitution; // Add directional push based on collision var pushIntensity = (carASpeed + carBSpeed) * (0.4 + 0.4 * speedNorm); var carAPushRatio = enemyCarB.weight / totalMass; var carBPushRatio = enemyCarA.weight / totalMass; enemyCarA.velocityX += -collisionDeltaX * pushIntensity * carAPushRatio * 0.6; enemyCarA.velocityY += -collisionDeltaY * pushIntensity * carAPushRatio * 0.6; enemyCarB.velocityX += collisionDeltaX * pushIntensity * carBPushRatio * 0.6; enemyCarB.velocityY += collisionDeltaY * pushIntensity * carBPushRatio * 0.6; // Reset acceleration state based on responsibility if (carACrasher) { if (typeof enemyCarA.aiCurrentVelocity !== "undefined") { enemyCarA.aiCurrentVelocity *= 0.3; // Car A is crasher - smaller penalty } } else { if (typeof enemyCarA.aiCurrentVelocity !== "undefined") { enemyCarA.aiCurrentVelocity = 0; // Car A crashed into or mutual - full reset } } if (carBCrasher) { if (typeof enemyCarB.aiCurrentVelocity !== "undefined") { enemyCarB.aiCurrentVelocity *= 0.3; // Car B is crasher - smaller penalty } } else { if (typeof enemyCarB.aiCurrentVelocity !== "undefined") { enemyCarB.aiCurrentVelocity = 0; // Car B crashed into or mutual - full reset } } // Visual feedback based on collision responsibility if (bothEnemiesCrashed) { // Both crashed - flash both in orange LK.effects.flashObject(enemyCarA, 0xff8800, 300); LK.effects.flashObject(enemyCarB, 0xff8800, 300); } else if (carACrasher) { // Car A crashed into car B - flash A red, B yellow LK.effects.flashObject(enemyCarA, 0xff6600, 250); LK.effects.flashObject(enemyCarB, 0xffdd00, 350); } else if (carBCrasher) { // Car B crashed into car A - flash B red, A yellow LK.effects.flashObject(enemyCarB, 0xff6600, 250); LK.effects.flashObject(enemyCarA, 0xffdd00, 350); } else { // Default fallback LK.effects.flashObject(enemyCarA, 0xffaa00, 300); LK.effects.flashObject(enemyCarB, 0xffaa00, 300); } } // Update collision tracking enemyCarA.enemyColliding[carB] = currentColliding; enemyCarB.enemyColliding[carA] = currentColliding; } } } // Update player health bar scale var healthPercentage = playerHealth / maxHealth; healthBar.scaleX = 4 * healthPercentage; // Update enemy car health bars position and scale for (var healthIdx = 0; healthIdx < enemyCars.length; healthIdx++) { var currentCar = enemyCars[healthIdx]; // Update health bar background position (above car) currentCar.healthBarBg.x = currentCar.x; currentCar.healthBarBg.y = currentCar.y - 60; // 60 pixels above car // Update health bar position (above car) currentCar.healthBar.x = currentCar.x; currentCar.healthBar.y = currentCar.y - 60; // 60 pixels above car // Update health bar scale based on car's health var carHealthPercentage = currentCar.health / maxHealth; currentCar.healthBar.scaleX = 1.5 * carHealthPercentage; } // Update speed display var totalSpeed = Math.sqrt(velocityX * velocityX + velocityY * velocityY); speedText.setText('Speed: ' + Math.round(totalSpeed)); };
/****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
/****
* Classes
****/
var EnemyCar = Container.expand(function () {
var self = Container.call(this);
var enemyCarGraphics = self.attachAsset('Cars', {
anchorX: 0.5,
anchorY: 0.5
});
// Assign random color (excluding red)
var enemyColors = [0x0066ff,
// Blue
0x00ff66,
// Green
0xffff00,
// Yellow
0xff8800,
// Orange
0x8800ff,
// Purple
0x00ffff,
// Cyan
0xff00ff,
// Magenta
0x888888,
// Gray
0xffffff // White
];
var randomColorIndex = Math.floor(Math.random() * enemyColors.length);
enemyCarGraphics.tint = enemyColors[randomColorIndex];
// Basic properties for enemy car with weight
self.velocityX = 0;
self.velocityY = 0;
self.rotation = 0;
self.weight = 1.0 + Math.random() * 1.5; // Random weight between 1.0 and 2.5
return self;
});
var Particle = Container.expand(function () {
var self = Container.call(this);
// Random particle size between 10.8-25.2 pixels (20% smaller than original)
var particleSize = 10.8 + Math.random() * 14.4;
var particleGraphics = self.attachAsset('ParticulasVel', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: particleSize / 30,
scaleY: particleSize / 30
});
// Random initial properties (20% smaller velocities)
self.velocityX = (Math.random() - 0.5) * 3.2;
self.velocityY = Math.random() * 2.4 + 0.8;
self.lifespan = 20 + Math.random() * 20; // Reduced lifespan: 0.33-0.67 seconds at 60fps
self.age = 0;
self.update = function () {
// Update position
self.x += self.velocityX;
self.y += self.velocityY;
// Age particle
self.age++;
// Fade out over time
var fadeProgress = self.age / self.lifespan;
particleGraphics.alpha = 1 - fadeProgress;
// Scale down over time
var scaleProgress = 1 - fadeProgress * 0.5;
particleGraphics.scaleX = particleSize / 30 * scaleProgress;
particleGraphics.scaleY = particleSize / 30 * scaleProgress;
// Apply gravity and air resistance (20% reduced for smaller scale)
self.velocityY += 0.08; // Reduced gravity
self.velocityX *= 0.984; // Slightly less air resistance
self.velocityY *= 0.984;
};
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0x000000
});
/****
* Game Code
****/
// Create gameplay background - 4/5 of screen height (top portion)
6;
var gameplayBackground = game.attachAsset('gameplayBg', {
x: 0,
y: 0,
anchorX: 0,
anchorY: 0
});
// Create carPlayer character on top of gameplayBackground
var carPlayer = gameplayBackground.attachAsset('CarPlayer', {
x: 1024,
// Center horizontally
y: 1800,
// Position in lower portion of gameplay area
anchorX: 0.5,
anchorY: 0.5
});
// Create array to store multiple enemy cars
var enemyCars = [];
var numEnemyCars = 3 + Math.floor(Math.random() * 3); // Random between 3-5 cars
// Create multiple enemy cars in gameplay area at random positions
for (var carIndex = 0; carIndex < numEnemyCars; carIndex++) {
var enemyCar = new EnemyCar();
// Generate random position around the edges or inside the gameplay area
var spawnSide = Math.floor(Math.random() * 4); // 0=top, 1=right, 2=bottom, 3=left
var centerX = 1024; // Center of gameplay area
var centerY = 1093; // Center of gameplay area (2186/2)
switch (spawnSide) {
case 0:
case 0:
// Top edge
enemyCar.x = Math.random() * 1800 + 124; // Random X between 124-1924
enemyCar.y = Math.random() * 300 + 50; // Random Y between 50-350
break;
case 1:
// Right edge
enemyCar.x = Math.random() * 300 + 1700; // Random X between 1700-2000
enemyCar.y = Math.random() * 1800 + 193; // Random Y between 193-1993
break;
case 2:
// Bottom edge
enemyCar.x = Math.random() * 1800 + 124; // Random X between 124-1924
enemyCar.y = Math.random() * 300 + 1836; // Random Y between 1836-2136
break;
case 3:
// Left edge
enemyCar.x = Math.random() * 300 + 50; // Random X between 50-350
enemyCar.y = Math.random() * 1800 + 193; // Random Y between 193-1993
break;
}
// Calculate rotation to face the center
var deltaX = centerX - enemyCar.x;
var deltaY = centerY - enemyCar.y;
enemyCar.rotation = Math.atan2(deltaX, -deltaY); // Rotation to face center
// Initialize enemy car health
enemyCar.health = maxHealth; // Enemy starts with full health
// Create health bar background for this enemy car
enemyCar.healthBarBg = gameplayBackground.attachAsset('BarBg', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 1.5,
scaleY: 0.2
});
// Create health bar for this enemy car
enemyCar.healthBar = gameplayBackground.attachAsset('Bar', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 1.5,
scaleY: 0.2
});
// Tint health bar with enemy car color
enemyCar.healthBar.tint = enemyCarGraphics.tint;
// Give each AI car individual tactical preferences and personality
enemyCar.tacticalPersonality = {
// Primary strategy preference probabilities (0-1)
directPreference: 0.2 + Math.random() * 0.6,
// 0.2-0.8 preference for direct pursuit
ambushPreference: 0.1 + Math.random() * 0.7,
// 0.1-0.8 preference for ambush tactics
intimidationPreference: 0.1 + Math.random() * 0.5,
// 0.1-0.6 preference for intimidation
// Speed-based tactical preferences
slowTargetStrategy: Math.floor(Math.random() * 3),
// 0=direct, 1=intimidate, 2=ambush for slow targets
fastTargetStrategy: Math.floor(Math.random() * 3),
// 0=direct, 1=intimidate, 2=ambush for fast targets
mediumTargetStrategy: Math.floor(Math.random() * 3),
// 0=direct, 1=intimidate, 2=ambush for medium targets
// Individual behavioral traits
aggressiveness: 0.3 + Math.random() * 0.7,
// 0.3-1.0 how aggressive this car is
patience: 0.2 + Math.random() * 0.8,
// 0.2-1.0 how long car sticks to one strategy
adaptability: 0.1 + Math.random() * 0.9,
// 0.1-1.0 how quickly car changes strategies
// Individual strategy duration preferences (in frames)
minStrategyDuration: 30 + Math.floor(Math.random() * 60),
// 0.5-1.5 seconds minimum
maxStrategyDuration: 90 + Math.floor(Math.random() * 180),
// 1.5-4.5 seconds maximum
// Personal distance preferences
preferredAttackDistance: 100 + Math.random() * 200,
// 100-300 preferred distance for attacks
preferredIntimidationDistance: 120 + Math.random() * 100,
// 120-220 preferred intimidation distance
personalSpaceRadius: 80 + Math.random() * 120,
// 80-200 personal space when maneuvering
// Individual flight and evasion preferences
flightTendency: 0.2 + Math.random() * 0.6,
// 0.2-0.8 how likely to flee when pursued
bravery: 0.1 + Math.random() * 0.8,
// 0.1-0.9 how brave this car is (opposes flight tendency)
panicThreshold: 0.3 + Math.random() * 0.5,
// 0.3-0.8 pursuit intensity needed to trigger panic
// Preferred evasion tactics (0-1 probability for each)
preferSpeedEscape: Math.random(),
// 0-1 preference for straight-line speed escapes
preferZigzagEscape: Math.random(),
// 0-1 preference for zigzag evasion patterns
preferCircularEscape: Math.random(),
// 0-1 preference for circular evasion patterns
// Flight decision factors
flightDistance: 150 + Math.random() * 200,
// 150-350 distance at which flight is considered
maxFlightDuration: 120 + Math.random() * 240,
// 2-6 seconds maximum flight time before reconsidering
counterAttackChance: 0.2 + Math.random() * 0.5,
// 0.2-0.7 base chance to counter-attack instead of fleeing
riskTolerance: 0.1 + Math.random() * 0.7 // 0.1-0.8 tolerance for risky situations
};
// Add to array and scene
enemyCars.push(enemyCar);
gameplayBackground.addChild(enemyCar);
}
// For backward compatibility, keep reference to first enemy car
var enemyCar = enemyCars[0];
// Create UI background - 1/5 of screen height (bottom portion)
var uiBackground = game.attachAsset('uiBg', {
x: 0,
y: 2186,
anchorX: 0,
anchorY: 0
});
// Create player health bar at the top right of UI with 20px margin
var healthBarBg = uiBackground.attachAsset('BarBg', {
x: 1800,
y: 40,
anchorX: 0.5,
anchorY: 0.5,
scaleX: 4,
scaleY: 0.3
});
var healthBar = uiBackground.attachAsset('Bar', {
x: 1800,
y: 40,
anchorX: 0.5,
anchorY: 0.5,
scaleX: 4,
scaleY: 0.3
});
// Tint health bar with player car color (red)
healthBar.tint = 0xff0000;
// Create speed display text
var speedText = new Text2('Speed: 0', {
size: 60,
fill: 0x000000
});
speedText.anchor.set(0, 0.5);
speedText.x = 50;
speedText.y = 2459; // Center vertically in UI area
game.addChild(speedText);
// Create joystickBG centered in UI background
var joystickBG = uiBackground.attachAsset('JoystickBG', {
x: 1024,
// Center horizontally in UI
y: 273,
// Center vertically in UI (546/2 = 273)
anchorX: 0.5,
anchorY: 0.5
});
// Create point object that will follow touch position
var point = null;
// Create JoystickPoinr that will follow point position smoothly
var joystickPoinr = game.attachAsset('JoystickPoinr', {
x: 1024,
y: 2459,
anchorX: 0.5,
anchorY: 0.5
});
// Variables for smooth movement
var targetX = 1024;
var targetY = 2459;
var smoothSpeed = 0.2;
// Variables for smooth rotation
var targetRotation = 0;
var baseRotationSpeed = 0.052;
// Variables for realistic car physics
var currentVelocity = 0;
var acceleration = 0.16;
var deceleration = 0.44;
var maxSpeed = 15.36;
// Variables for drift physics
var velocityX = 0;
var velocityY = 0;
var driftFactor = 0.85; // How much momentum is retained (lower = more drift)
var gripFactor = 0.3; // How quickly car aligns with direction (lower = more drift)
// Player car weight
var playerCarWeight = 1.2; // Player car is moderately heavy
// Health system variables
var playerHealth = 100; // Player starts with full health
var maxHealth = 100; // Maximum health for all cars
// Handle touch down - create and show point
game.down = function (x, y, obj) {
// Create point at touch position
point = game.attachAsset('Puntero', {
x: x,
y: y,
anchorX: 0.5,
anchorY: 0.5
});
};
// Handle touch move - update point position
game.move = function (x, y, obj) {
if (point) {
point.x = x;
point.y = y;
// Calculate joystickBG world position
var joystickWorldX = joystickBG.x + uiBackground.x;
var joystickWorldY = joystickBG.y + uiBackground.y;
// Calculate distance from joystick center
var deltaX = x - joystickWorldX;
var deltaY = y - joystickWorldY;
var distance = Math.sqrt(deltaX * deltaX + deltaY * deltaY);
var maxRadius = joystickBG.width / 2;
// Limit movement to joystick radius
if (distance > maxRadius) {
var angle = Math.atan2(deltaY, deltaX);
deltaX = Math.cos(angle) * maxRadius;
deltaY = Math.sin(angle) * maxRadius;
}
// Update target position for smooth movement (constrained)
targetX = joystickWorldX + deltaX;
targetY = joystickWorldY + deltaY;
}
};
// Handle touch up - remove point
game.up = function (x, y, obj) {
if (point) {
point.destroy();
point = null;
// Reset target position to joystick center
var joystickWorldX = joystickBG.x + uiBackground.x;
var joystickWorldY = joystickBG.y + uiBackground.y;
targetX = joystickWorldX;
targetY = joystickWorldY;
}
};
// Update function for smooth movement
game.update = function () {
// Smoothly move JoystickPoinr towards target position
var deltaX = targetX - joystickPoinr.x;
var deltaY = targetY - joystickPoinr.y;
joystickPoinr.x += deltaX * smoothSpeed;
joystickPoinr.y += deltaY * smoothSpeed;
// Double-check that joystickPoinr stays within bounds
var joystickWorldX = joystickBG.x + uiBackground.x;
var joystickWorldY = joystickBG.y + uiBackground.y;
var currentDeltaX = joystickPoinr.x - joystickWorldX;
var currentDeltaY = joystickPoinr.y - joystickWorldY;
var currentDistance = Math.sqrt(currentDeltaX * currentDeltaX + currentDeltaY * currentDeltaY);
var maxRadius = joystickBG.width / 2;
if (currentDistance > maxRadius) {
var angle = Math.atan2(currentDeltaY, currentDeltaX);
joystickPoinr.x = joystickWorldX + Math.cos(angle) * maxRadius;
joystickPoinr.y = joystickWorldY + Math.sin(angle) * maxRadius;
}
// Update car rotation based on joystick position
var joystickOffsetX = joystickPoinr.x - joystickWorldX;
var joystickOffsetY = joystickPoinr.y - joystickWorldY;
var joystickDistance = Math.sqrt(joystickOffsetX * joystickOffsetX + joystickOffsetY * joystickOffsetY);
// Calculate power based on distance from center (0 to 1)
var power = Math.min(joystickDistance / maxRadius, 1);
// Only rotate if joystick is moved significantly from center
if (joystickDistance > 10) {
var joystickAngle = Math.atan2(joystickOffsetX, -joystickOffsetY);
targetRotation = joystickAngle;
}
// Smoothly interpolate car rotation towards target
var rotationDelta = targetRotation - carPlayer.rotation;
// Handle angle wrapping for shortest rotation path
while (rotationDelta > Math.PI) {
rotationDelta -= 2 * Math.PI;
}
while (rotationDelta < -Math.PI) {
rotationDelta += 2 * Math.PI;
}
// Calculate rotation speed based on current velocity (slower speed = much slower turning)
var speedRatio = Math.sqrt(velocityX * velocityX + velocityY * velocityY) / maxSpeed;
var dynamicRotationSpeed = baseRotationSpeed * Math.max(0.1, speedRatio); // Minimum 10% rotation speed
carPlayer.rotation += rotationDelta * dynamicRotationSpeed;
// Calculate target velocity based on joystick power
var targetVelocity = maxSpeed * power;
// Apply smooth velocity transitions with exponential interpolation
if (power > 0.1) {
// Accelerating - smooth exponential approach to target velocity
var velocityDiff = targetVelocity - currentVelocity;
var accelerationRate = 0.004; // Smooth acceleration rate (20% slower for smaller car)
currentVelocity += velocityDiff * accelerationRate;
} else {
// Decelerating when joystick is near center - smooth exponential decay
var decelerationRate = 0.048; // Smooth deceleration rate (20% slower for smaller car)
currentVelocity *= 1 - decelerationRate;
if (Math.abs(currentVelocity) < 0.1) {
currentVelocity = 0;
}
}
// Limit velocity to max speed
currentVelocity = Math.min(currentVelocity, maxSpeed);
// Calculate intended movement direction based on car rotation and current velocity
var intendedMoveX = Math.sin(carPlayer.rotation) * currentVelocity;
var intendedMoveY = -Math.cos(carPlayer.rotation) * currentVelocity;
// Calculate turning friction based on dynamic rotation speed
var rotationFriction = Math.abs(rotationDelta * dynamicRotationSpeed) * 0.8; // Reduced friction intensity for smoother feel
var frictionMultiplier = Math.max(0.85, 1 - rotationFriction); // Less velocity reduction when turning (min 0.85 for more natural feel)
// Apply drift physics - blend current momentum with intended direction
velocityX = velocityX * driftFactor + intendedMoveX * gripFactor;
velocityY = velocityY * driftFactor + intendedMoveY * gripFactor;
// Apply turning friction to reduce velocity when steering
velocityX *= frictionMultiplier;
velocityY *= frictionMultiplier;
// Apply some base deceleration to drift momentum
velocityX *= 0.98;
velocityY *= 0.98;
// Update car position using drift momentum
carPlayer.x += velocityX;
carPlayer.y += velocityY;
// Keep car within gameplay area bounds with realistic collision physics
var halfCarWidth = 16; // CarPlayer width is 32, so half is 16
var halfCarHeight = 24; // CarPlayer height is 47.36, so half is ~24
// Calculate current speed for impact calculations
var currentSpeed = Math.sqrt(velocityX * velocityX + velocityY * velocityY);
var impactThreshold = 2; // Minimum speed to trigger impact effects
// Realistic collision physics with energy loss and proper angles
if (carPlayer.x < halfCarWidth) {
carPlayer.x = halfCarWidth;
// Calculate impact intensity based on perpendicular velocity component
var impactVelocity = Math.abs(velocityX);
var energyLoss = 0.4 + impactVelocity / maxSpeed * 0.3; // More energy loss at higher speeds
// Realistic bounce with energy conservation
velocityX = -velocityX * (1 - energyLoss);
velocityY *= 0.8; // Friction reduces parallel velocity component
// Visual feedback for significant impacts
if (impactVelocity > impactThreshold) {
LK.effects.flashObject(carPlayer, 0xff4444, 200);
}
}
if (carPlayer.x > 2048 - halfCarWidth) {
carPlayer.x = 2048 - halfCarWidth;
// Calculate impact intensity based on perpendicular velocity component
var impactVelocity = Math.abs(velocityX);
var energyLoss = 0.4 + impactVelocity / maxSpeed * 0.3; // More energy loss at higher speeds
// Realistic bounce with energy conservation
velocityX = -velocityX * (1 - energyLoss);
velocityY *= 0.8; // Friction reduces parallel velocity component
// Visual feedback for significant impacts
if (impactVelocity > impactThreshold) {
LK.effects.flashObject(carPlayer, 0xff4444, 200);
}
}
if (carPlayer.y < halfCarHeight) {
carPlayer.y = halfCarHeight;
// Calculate impact intensity based on perpendicular velocity component
var impactVelocity = Math.abs(velocityY);
var energyLoss = 0.4 + impactVelocity / maxSpeed * 0.3; // More energy loss at higher speeds
// Realistic bounce with energy conservation
velocityY = -velocityY * (1 - energyLoss);
velocityX *= 0.8; // Friction reduces parallel velocity component
// Visual feedback for significant impacts
if (impactVelocity > impactThreshold) {
LK.effects.flashObject(carPlayer, 0xff4444, 200);
}
}
if (carPlayer.y > 2186 - halfCarHeight) {
carPlayer.y = 2186 - halfCarHeight;
// Calculate impact intensity based on perpendicular velocity component
var impactVelocity = Math.abs(velocityY);
var energyLoss = 0.4 + impactVelocity / maxSpeed * 0.3; // More energy loss at higher speeds
// Realistic bounce with energy conservation
velocityY = -velocityY * (1 - energyLoss);
velocityX *= 0.8; // Friction reduces parallel velocity component
// Visual feedback for significant impacts
if (impactVelocity > impactThreshold) {
LK.effects.flashObject(carPlayer, 0xff4444, 200);
}
}
// Apply smooth braking to player car after wall collision for more natural deceleration
if (!carPlayer.smoothBraking) carPlayer.smoothBraking = false;
if (!carPlayer.brakeFrames) carPlayer.brakeFrames = 0;
var currentPlayerSpeed = Math.sqrt(velocityX * velocityX + velocityY * velocityY);
// If player car was just launched (high velocity), enable smooth braking
if (currentPlayerSpeed > maxSpeed * 0.6 && !carPlayer.smoothBraking) {
carPlayer.smoothBraking = true;
carPlayer.brakeFrames = 0;
}
// Smooth braking logic: apply a gentle, progressive friction for a short period after wall collision
if (carPlayer.smoothBraking) {
// Braking lasts for 18 frames (~0.3s at 60fps)
var brakeDuration = 18;
var brakeProgress = Math.min(1, carPlayer.brakeFrames / brakeDuration);
// Start with gentle friction, increase to normal friction
var minFriction = 0.96;
var maxFriction = 0.92 - currentPlayerSpeed / maxSpeed * 0.05;
var frictionRate = minFriction + (maxFriction - minFriction) * brakeProgress;
velocityX *= frictionRate;
velocityY *= frictionRate;
carPlayer.brakeFrames++;
if (carPlayer.brakeFrames >= brakeDuration) {
carPlayer.smoothBraking = false;
}
} else if (currentPlayerSpeed > 0.1) {
// Normal progressive friction when not actively controlling
if (power <= 0.1) {
// Only apply extra friction when not accelerating
var frictionRate = 0.92 - currentPlayerSpeed / maxSpeed * 0.05; // More friction at higher speeds
velocityX *= frictionRate;
velocityY *= frictionRate;
}
} else {
// Stop very slow movement to prevent endless drift
if (power <= 0.1) {
velocityX = 0;
velocityY = 0;
}
}
// Particle system for car exhaust (global, supports all cars)
if (!game.particles) {
game.particles = [];
}
// Helper function to emit particles for any car
function emitCarParticles(car, vx, vy, rotation, maxSpeed, gameplayBackground) {
var totalSpeed = Math.sqrt(vx * vx + vy * vy);
var speedRatio = totalSpeed / maxSpeed;
var particleFrequency = Math.max(1, Math.floor(8 - speedRatio * 6));
if (speedRatio > 0.05 && LK.ticks % particleFrequency === 0) {
// Calculate particle spawn position behind the car
var particleSpawnX = car.x - Math.sin(rotation) * 55;
var particleSpawnY = car.y + Math.cos(rotation) * 55;
var particleCount = Math.max(1, Math.floor(speedRatio * 3));
for (var p = 0; p < particleCount; p++) {
var newParticle = new Particle();
newParticle.x = particleSpawnX + (Math.random() - 0.5) * 19.2;
newParticle.y = particleSpawnY + (Math.random() - 0.5) * 19.2;
newParticle.velocityX += -vx * 0.12 + (Math.random() - 0.5) * 2.4;
newParticle.velocityY += -vy * 0.12 + (Math.random() - 0.5) * 2.4;
game.particles.push(newParticle);
gameplayBackground.addChild(newParticle);
// Tween particle color for variety
var colors = [0xffffff, 0xcccccc, 0x999999, 0x666666];
var randomColor = colors[Math.floor(Math.random() * colors.length)];
tween(newParticle.children[0], {
tint: randomColor
}, {
duration: 100
});
}
}
}
// Optimize particle emissions - reduce frequency
if (LK.ticks % 2 === 0) {
// Only emit particles every other frame
// Emit particles for player car
emitCarParticles(carPlayer, velocityX, velocityY, carPlayer.rotation, maxSpeed, gameplayBackground);
// Emit particles for all enemy cars
for (var carIdx = 0; carIdx < enemyCars.length; carIdx++) {
var currentEnemyCar = enemyCars[carIdx];
var enemyTotalVX = (currentEnemyCar.aiVelocityX || 0) + (currentEnemyCar.velocityX || 0);
var enemyTotalVY = (currentEnemyCar.aiVelocityY || 0) + (currentEnemyCar.velocityY || 0);
emitCarParticles(currentEnemyCar, enemyTotalVX, enemyTotalVY, currentEnemyCar.rotation, maxSpeed, gameplayBackground);
}
}
// Update and clean up particles (batch process)
var particlesToRemove = [];
for (var i = 0; i < game.particles.length; i++) {
var particle = game.particles[i];
if (particle.age >= particle.lifespan) {
particlesToRemove.push(i);
}
}
// Remove particles in reverse order to maintain indices
for (var r = particlesToRemove.length - 1; r >= 0; r--) {
var idx = particlesToRemove[r];
game.particles[idx].destroy();
game.particles.splice(idx, 1);
}
// Check collision between player car and all enemy cars using smaller collision boxes (optimized)
if (!carPlayer.lastColliding) carPlayer.lastColliding = [];
// Initialize collision tracking array for all cars
while (carPlayer.lastColliding.length < enemyCars.length) {
carPlayer.lastColliding.push(false);
}
// Define smaller collision boxes (approximately 60% of actual asset size for more precise collision) - cache constants
var playerCollisionWidth = 38; // Reduced from 64px width
var playerCollisionHeight = 57; // Reduced from ~95px height
var enemyCollisionWidth = 38; // Reduced from 64px width
var enemyCollisionHeight = 57; // Reduced from ~94px height
var halfPlayerWidth = playerCollisionWidth / 2;
var halfPlayerHeight = playerCollisionHeight / 2;
var halfEnemyWidth = enemyCollisionWidth / 2;
var halfEnemyHeight = enemyCollisionHeight / 2;
var playerLeft = carPlayer.x - halfPlayerWidth;
var playerRight = carPlayer.x + halfPlayerWidth;
var playerTop = carPlayer.y - halfPlayerHeight;
var playerBottom = carPlayer.y + halfPlayerHeight;
// Check collision with each enemy car
for (var enemyIdx = 0; enemyIdx < enemyCars.length; enemyIdx++) {
var currentEnemyCar = enemyCars[enemyIdx];
var enemyLeft = currentEnemyCar.x - halfEnemyWidth;
var enemyRight = currentEnemyCar.x + halfEnemyWidth;
var enemyTop = currentEnemyCar.y - halfEnemyHeight;
var enemyBottom = currentEnemyCar.y + halfEnemyHeight;
// More precise collision detection using smaller bounding boxes
var currentColliding = !(playerRight < enemyLeft || playerLeft > enemyRight || playerBottom < enemyTop || playerTop > enemyBottom);
if (!carPlayer.lastColliding[enemyIdx] && currentColliding) {
// Collision just started - determine collision responsibility
var playerSpeed = Math.sqrt(velocityX * velocityX + velocityY * velocityY);
var enemySpeed = Math.sqrt(currentEnemyCar.velocityX * currentEnemyCar.velocityX + currentEnemyCar.velocityY * currentEnemyCar.velocityY);
// Calculate collision direction (from player car to enemy car)
var collisionDeltaX = currentEnemyCar.x - carPlayer.x;
var collisionDeltaY = currentEnemyCar.y - carPlayer.y;
var collisionDistance = Math.sqrt(collisionDeltaX * collisionDeltaX + collisionDeltaY * collisionDeltaY);
// Normalize collision direction
if (collisionDistance > 0) {
collisionDeltaX /= collisionDistance;
collisionDeltaY /= collisionDistance;
}
// Determine collision responsibility based on velocity vectors and approach angles
var playerCrasher = false;
var enemyCrasher = false;
var bothCrashed = false;
// Calculate approach vectors (movement direction toward collision point)
var playerApproachX = 0,
playerApproachY = 0;
var enemyApproachX = 0,
enemyApproachY = 0;
if (playerSpeed > 0.1) {
// Normalize player velocity to get approach direction
playerApproachX = velocityX / playerSpeed;
playerApproachY = velocityY / playerSpeed;
}
if (enemySpeed > 0.1) {
// Normalize enemy velocity to get approach direction
var enemyTotalVelX = (currentEnemyCar.aiVelocityX || 0) + currentEnemyCar.velocityX;
var enemyTotalVelY = (currentEnemyCar.aiVelocityY || 0) + currentEnemyCar.velocityY;
var enemyTotalSpeed = Math.sqrt(enemyTotalVelX * enemyTotalVelX + enemyTotalVelY * enemyTotalVelY);
if (enemyTotalSpeed > 0.1) {
enemyApproachX = enemyTotalVelX / enemyTotalSpeed;
enemyApproachY = enemyTotalVelY / enemyTotalSpeed;
}
}
// Calculate how much each car is moving toward the collision
var playerTowardCollision = 0;
var enemyTowardCollision = 0;
if (playerSpeed > 0.1) {
// Dot product of player movement with collision direction
playerTowardCollision = playerApproachX * collisionDeltaX + playerApproachY * collisionDeltaY;
}
if (enemySpeed > 0.1) {
// Dot product of enemy movement with opposite collision direction (toward player)
enemyTowardCollision = enemyApproachX * -collisionDeltaX + enemyApproachY * -collisionDeltaY;
}
// Speed thresholds for determining crasher responsibility
var minCrasherSpeed = maxSpeed * 0.15; // 15% of max speed minimum to be considered crasher
var dominantCrasherThreshold = 0.3; // How much more one car must be approaching to be sole crasher
// Determine responsibility based on approach analysis
if (playerSpeed > minCrasherSpeed && enemySpeed > minCrasherSpeed) {
// Both cars moving significantly
if (playerTowardCollision > dominantCrasherThreshold && enemyTowardCollision > dominantCrasherThreshold) {
// Both approaching collision point - head-on or mutual collision
bothCrashed = true;
} else if (playerTowardCollision > enemyTowardCollision + dominantCrasherThreshold) {
// Player moving much more toward collision
playerCrasher = true;
} else if (enemyTowardCollision > playerTowardCollision + dominantCrasherThreshold) {
// Enemy moving much more toward collision
enemyCrasher = true;
} else {
// Similar approach - treat as mutual collision
bothCrashed = true;
}
} else if (playerSpeed > minCrasherSpeed && enemySpeed <= minCrasherSpeed) {
// Only player moving significantly - player is crasher
playerCrasher = true;
} else if (enemySpeed > minCrasherSpeed && playerSpeed <= minCrasherSpeed) {
// Only enemy moving significantly - enemy is crasher
enemyCrasher = true;
} else {
// Both moving very slowly - treat as mutual collision
bothCrashed = true;
}
// Calculate momentum transfer using conservation of momentum
var totalMass = playerCarWeight + currentEnemyCar.weight;
var massRatio1 = (playerCarWeight - currentEnemyCar.weight) / totalMass;
var massRatio2 = 2 * currentEnemyCar.weight / totalMass;
var massRatio3 = 2 * playerCarWeight / totalMass;
var massRatio4 = (currentEnemyCar.weight - playerCarWeight) / totalMass;
// Apply different energy loss based on collision responsibility
var relativeSpeed = Math.max(playerSpeed, enemySpeed);
var minLoss, maxLoss;
var speedNorm = Math.min(1, relativeSpeed / maxSpeed); // 0 to 1
if (bothCrashed) {
// Mutual collision - both get significant energy loss
minLoss = 0.22; // Higher base energy loss for mutual crashes
maxLoss = 0.70; // Higher max energy loss for mutual crashes
} else {
// Single crasher scenario - crasher gets less penalty
minLoss = 0.15; // Lower base energy loss
maxLoss = 0.60; // Lower max energy loss
}
var energyLoss = minLoss + (maxLoss - minLoss) * speedNorm;
var restitution = 1 - energyLoss;
// Apply different restitution based on responsibility
var playerRestitution = restitution;
var enemyRestitution = restitution;
if (playerCrasher) {
// Player is crasher - gets less penalty (keeps more momentum)
playerRestitution = restitution * 1.15; // 15% less energy loss
enemyRestitution = restitution * 0.85; // 15% more energy loss for crashed car
} else if (enemyCrasher) {
// Enemy is crasher - gets less penalty
enemyRestitution = restitution * 1.15; // 15% less energy loss
playerRestitution = restitution * 0.85; // 15% more energy loss for crashed car
}
// For bothCrashed, both use same restitution
// Separate cars to prevent overlap
var separationDistance = 70;
carPlayer.x = currentEnemyCar.x - collisionDeltaX * separationDistance;
carPlayer.y = currentEnemyCar.y - collisionDeltaY * separationDistance;
// Calculate new velocities based on mass and current motion
var playerImpactX = velocityX * massRatio1 + currentEnemyCar.velocityX * massRatio2;
var playerImpactY = velocityY * massRatio1 + currentEnemyCar.velocityY * massRatio2;
velocityX = playerImpactX * playerRestitution;
velocityY = playerImpactY * playerRestitution;
// Enemy car velocity change
var enemyImpactX = velocityX * massRatio3 + currentEnemyCar.velocityX * massRatio4;
var enemyImpactY = velocityY * massRatio3 + currentEnemyCar.velocityY * massRatio4;
currentEnemyCar.velocityX = enemyImpactX * enemyRestitution;
currentEnemyCar.velocityY = enemyImpactY * enemyRestitution;
// Launch enemy car away from collision
var launchSpeed = Math.max(playerSpeed, enemySpeed);
var minPush = 0.4;
var maxPush = 1.5;
var pushMultiplier = minPush + (maxPush - minPush) * speedNorm;
var launchDirX = currentEnemyCar.x - carPlayer.x;
var launchDirY = currentEnemyCar.y - carPlayer.y;
var launchDist = Math.sqrt(launchDirX * launchDirX + launchDirY * launchDirY);
if (launchDist < 0.01) {
var playerMoveNorm = Math.sqrt(velocityX * velocityX + velocityY * velocityY);
if (playerMoveNorm > 0.01) {
launchDirX = velocityX / playerMoveNorm;
launchDirY = velocityY / playerMoveNorm;
} else {
launchDirX = 0;
launchDirY = -1;
}
} else {
launchDirX /= launchDist;
launchDirY /= launchDist;
}
currentEnemyCar.velocityX += launchDirX * launchSpeed * pushMultiplier;
currentEnemyCar.velocityY += launchDirY * launchSpeed * pushMultiplier;
// Add directional push based on collision angle and relative mass
var pushIntensity = (playerSpeed + enemySpeed) * (0.5 + 0.5 * speedNorm);
var playerPushRatio = currentEnemyCar.weight / totalMass;
var enemyPushRatio = playerCarWeight / totalMass;
velocityX += -collisionDeltaX * pushIntensity * playerPushRatio * 0.7;
velocityY += -collisionDeltaY * pushIntensity * playerPushRatio * 0.7;
currentEnemyCar.velocityX += collisionDeltaX * pushIntensity * enemyPushRatio * 0.7;
currentEnemyCar.velocityY += collisionDeltaY * pushIntensity * enemyPushRatio * 0.7;
// Reset acceleration and velocity state based on responsibility
if (playerCrasher) {
// Player is crasher - smaller penalty to acceleration
currentVelocity *= 0.3; // Keep 30% of acceleration state
} else {
// Player crashed into or mutual - full reset
currentVelocity = 0; // Reset player's acceleration state to restart from zero
}
if (enemyCrasher) {
// Enemy is crasher - smaller penalty to acceleration
if (typeof currentEnemyCar.aiCurrentVelocity !== "undefined") {
currentEnemyCar.aiCurrentVelocity *= 0.3; // Keep 30% of AI acceleration state
}
} else {
// Enemy crashed into or mutual - full reset
if (typeof currentEnemyCar.aiCurrentVelocity !== "undefined") {
currentEnemyCar.aiCurrentVelocity = 0; // Reset enemy's AI acceleration state
}
}
// Visual feedback based on collision responsibility
if (bothCrashed) {
// Both crashed - flash both in orange
LK.effects.flashObject(carPlayer, 0xff8800, 500);
LK.effects.flashObject(currentEnemyCar, 0xff8800, 500);
} else if (playerCrasher) {
// Player crashed into enemy - flash player red, enemy yellow
LK.effects.flashObject(carPlayer, 0xff4400, 400);
LK.effects.flashObject(currentEnemyCar, 0xffff00, 600);
} else if (enemyCrasher) {
// Enemy crashed into player - flash enemy red, player yellow
LK.effects.flashObject(currentEnemyCar, 0xff4400, 400);
LK.effects.flashObject(carPlayer, 0xffff00, 600);
} else {
// Default fallback
LK.effects.flashObject(carPlayer, 0xff0000, 500);
}
}
// Update last collision state for this enemy
carPlayer.lastColliding[enemyIdx] = currentColliding;
}
// --- Enemy Cars AI Movement Logic (Optimized) ---
// Process AI for each enemy car - stagger AI updates to reduce load
var aiUpdateOffset = LK.ticks % enemyCars.length; // Stagger AI updates
for (var aiCarIdx = 0; aiCarIdx < enemyCars.length; aiCarIdx++) {
var enemyCar = enemyCars[aiCarIdx];
// Only update AI for one car per frame (staggered)
var shouldUpdateAI = aiCarIdx === aiUpdateOffset;
// Dynamic target selection system - choose best target based on position and velocity
if (typeof enemyCar.currentTarget === "undefined") enemyCar.currentTarget = null;
if (typeof enemyCar.targetSelectionTimer === "undefined") enemyCar.targetSelectionTimer = 0;
if (typeof enemyCar.targetSelectionInterval === "undefined") enemyCar.targetSelectionInterval = 60; // Re-evaluate every second
// Pursuit detection system - track who is following this AI car
if (typeof enemyCar.pursuitDetection === "undefined") {
enemyCar.pursuitDetection = {
pursuers: [],
// List of potential pursuers with tracking data
beingPursued: false,
pursuitIntensity: 0,
// 0-1 scale of how intensely being pursued
pursuitDuration: 0,
// How long being pursued in frames
lastPursuitCheck: 0,
evasionMode: false,
// Whether currently evading
evasionStrategy: 0,
// 0=speed, 1=zigzag, 2=circles
evasionTimer: 0,
evasionDuration: 0,
counterAttackMode: false,
// Whether to turn and fight
counterAttackTarget: null
};
}
// Re-evaluate target selection periodically (only for the current AI car being updated)
if (shouldUpdateAI) {
enemyCar.targetSelectionTimer++;
// Pursuit detection - analyze if other cars are targeting this one
if (LK.ticks % 10 === 0) {
// Check every 10 frames for performance
enemyCar.pursuitDetection.pursuers = [];
// Check all potential pursuers (player + other AI cars)
var potentialPursuers = [];
// Add player as potential pursuer
potentialPursuers.push({
object: carPlayer,
velocityX: velocityX,
velocityY: velocityY,
isPlayer: true
});
// Add other enemy cars as potential pursuers
for (var pursuerId = 0; pursuerId < enemyCars.length; pursuerId++) {
if (pursuerId !== aiCarIdx) {
var otherCar = enemyCars[pursuerId];
potentialPursuers.push({
object: otherCar,
velocityX: (otherCar.aiVelocityX || 0) + (otherCar.velocityX || 0),
velocityY: (otherCar.aiVelocityY || 0) + (otherCar.velocityY || 0),
isPlayer: false
});
}
}
// Analyze each potential pursuer
for (var pIdx = 0; pIdx < potentialPursuers.length; pIdx++) {
var pursuer = potentialPursuers[pIdx];
var pursuerId = pIdx;
// Calculate distance and relative positioning
var pursuerDist = Math.sqrt((pursuer.object.x - enemyCar.x) * (pursuer.object.x - enemyCar.x) + (pursuer.object.y - enemyCar.y) * (pursuer.object.y - enemyCar.y));
// Only consider pursuers within reasonable range
if (pursuerDist < 500 && pursuerDist > 50) {
// Calculate if pursuer is moving toward this car
var toThisCar = {
x: enemyCar.x - pursuer.object.x,
y: enemyCar.y - pursuer.object.y
};
var toThisCarMag = Math.sqrt(toThisCar.x * toThisCar.x + toThisCar.y * toThisCar.y);
if (toThisCarMag > 0) {
toThisCar.x /= toThisCarMag;
toThisCar.y /= toThisCarMag;
// Calculate pursuer's velocity direction
var pursuerSpeed = Math.sqrt(pursuer.velocityX * pursuer.velocityX + pursuer.velocityY * pursuer.velocityY);
if (pursuerSpeed > 1) {
var pursuerDir = {
x: pursuer.velocityX / pursuerSpeed,
y: pursuer.velocityY / pursuerSpeed
};
// Dot product to see if pursuer is moving toward this car
var alignment = toThisCar.x * pursuerDir.x + toThisCar.y * pursuerDir.y;
// Consider as pursuer if moving toward this car and close enough
if (alignment > 0.3) {
// 30 degree cone
var pursuitScore = alignment * (1 - pursuerDist / 500) * (pursuerSpeed / maxSpeed);
// Additional checks for AI pursuers - see if they're targeting this car
if (!pursuer.isPlayer && pursuer.object.currentTarget) {
if (pursuer.object.currentTarget.object === enemyCar) {
pursuitScore += 0.4; // Bonus if we're their target
}
}
if (pursuitScore > 0.3) {
enemyCar.pursuitDetection.pursuers.push({
object: pursuer.object,
distance: pursuerDist,
score: pursuitScore,
isPlayer: pursuer.isPlayer
});
}
}
}
}
}
}
// Determine if being pursued based on pursuer analysis
var totalPursuitScore = 0;
var closestPursuerDist = Infinity;
var strongestPursuer = null;
for (var p = 0; p < enemyCar.pursuitDetection.pursuers.length; p++) {
var pursuerData = enemyCar.pursuitDetection.pursuers[p];
totalPursuitScore += pursuerData.score;
if (pursuerData.distance < closestPursuerDist) {
closestPursuerDist = pursuerData.distance;
strongestPursuer = pursuerData;
}
}
// Update pursuit state
var wasPursued = enemyCar.pursuitDetection.beingPursued;
enemyCar.pursuitDetection.beingPursued = totalPursuitScore > 0.5 && enemyCar.pursuitDetection.pursuers.length > 0;
enemyCar.pursuitDetection.pursuitIntensity = Math.min(1, totalPursuitScore);
if (enemyCar.pursuitDetection.beingPursued) {
enemyCar.pursuitDetection.pursuitDuration++;
// Decide on response strategy when first detected or strategy expired
if (!wasPursued || enemyCar.pursuitDetection.evasionTimer > enemyCar.pursuitDetection.evasionDuration) {
var personality = enemyCar.tacticalPersonality;
// Decision factors
var currentSpeed = Math.sqrt((enemyCar.aiVelocityX || 0) * (enemyCar.aiVelocityX || 0) + (enemyCar.aiVelocityY || 0) * (enemyCar.aiVelocityY || 0));
var speedRatio = currentSpeed / maxSpeed;
var pursuerThreat = enemyCar.pursuitDetection.pursuitIntensity;
// Personality-based decision making
var fleeChance = 0.6 + personality.patience * 0.2 - personality.aggressiveness * 0.3;
var counterAttackChance = 0.4 + personality.aggressiveness * 0.4 - personality.patience * 0.2;
// Situational modifiers
if (speedRatio > 0.7) fleeChance += 0.2; // Easier to flee when fast
if (closestPursuerDist < 150) counterAttackChance += 0.3; // More likely to fight when cornered
if (pursuerThreat > 0.8) counterAttackChance += 0.2; // Fight when heavily pursued
// Individual flight decision based on personality traits
var flightPrefs = enemyCar.tacticalPersonality;
var shouldFlee = false;
var shouldCounterAttack = false;
// Calculate individual flight decision factors
var baseFlightChance = flightPrefs.flightTendency;
var baseCounterChance = flightPrefs.counterAttackChance;
// Modify chances based on individual traits
var braveryBonus = flightPrefs.bravery * 0.3; // Brave cars more likely to fight
var panicPenalty = pursuerThreat > flightPrefs.panicThreshold ? 0.2 : 0; // Panic reduces counter-attack
var riskFactor = (1 - flightPrefs.riskTolerance) * 0.25; // Risk-averse cars flee more
// Apply individual modifiers
var adjustedFlightChance = baseFlightChance + riskFactor + panicPenalty - braveryBonus * 0.5;
var adjustedCounterChance = baseCounterChance + braveryBonus - panicPenalty - riskFactor;
// Situational modifiers based on individual thresholds
if (speedRatio > 0.7 && speedRatio > flightPrefs.riskTolerance) {
adjustedFlightChance += 0.2; // Fast cars with low risk tolerance flee more
}
if (closestPursuerDist < flightPrefs.flightDistance) {
if (flightPrefs.bravery > 0.6) {
adjustedCounterChance += 0.3; // Brave cars fight when cornered
} else {
adjustedFlightChance += 0.2; // Others flee when too close
}
}
if (pursuerThreat > 0.8 && flightPrefs.panicThreshold < 0.5) {
adjustedFlightChance += 0.3; // Easy-to-panic cars flee under heavy pursuit
}
// Make individual decision
if (Math.random() < Math.max(0.1, Math.min(0.9, adjustedCounterChance))) {
// Turn and fight
enemyCar.pursuitDetection.counterAttackMode = true;
enemyCar.pursuitDetection.counterAttackTarget = strongestPursuer ? strongestPursuer.object : null;
enemyCar.pursuitDetection.evasionMode = false;
enemyCar.pursuitDetection.evasionDuration = 120 + Math.floor(Math.random() * 180); // 2-5 seconds
} else {
// Flee - choose evasion strategy based on individual preferences
enemyCar.pursuitDetection.evasionMode = true;
enemyCar.pursuitDetection.counterAttackMode = false;
// Individual evasion strategy selection based on preferences and situation
var strategyScores = [flightPrefs.preferSpeedEscape, flightPrefs.preferZigzagEscape, flightPrefs.preferCircularEscape];
// Situational bonuses based on individual traits
if (speedRatio < 0.4) {
strategyScores[0] += 0.3; // Speed strategy bonus when slow
} else if (speedRatio > 0.7) {
strategyScores[1] += 0.2; // Zigzag bonus when fast (harder to predict)
strategyScores[2] += 0.15;
}
if (closestPursuerDist < flightPrefs.flightDistance * 0.7) {
strategyScores[1] += 0.25; // Zigzag when close
strategyScores[2] += 0.3; // Circles when close
} else if (closestPursuerDist > flightPrefs.flightDistance * 1.5) {
strategyScores[0] += 0.4; // Speed when far
}
// Individual trait bonuses
if (flightPrefs.adaptability > 0.7) {
strategyScores[1] += 0.2; // Adaptive cars prefer zigzag
}
if (flightPrefs.patience > 0.6) {
strategyScores[2] += 0.2; // Patient cars can handle circles
}
if (flightPrefs.aggressiveness > 0.6) {
strategyScores[0] += 0.15; // Aggressive cars prefer speed
}
// Select strategy with highest score
var maxScore = Math.max(strategyScores[0], strategyScores[1], strategyScores[2]);
var bestStrategies = [];
for (var si = 0; si < 3; si++) {
if (strategyScores[si] >= maxScore - 0.1) {
bestStrategies.push(si);
}
}
enemyCar.pursuitDetection.evasionStrategy = bestStrategies[Math.floor(Math.random() * bestStrategies.length)];
// Individual evasion duration based on personality
var baseDuration = Math.floor(flightPrefs.maxFlightDuration * 0.6); // 60% of max
var personalityDuration = Math.floor(flightPrefs.maxFlightDuration * 0.4 * flightPrefs.patience); // Patience affects duration
enemyCar.pursuitDetection.evasionDuration = baseDuration + personalityDuration;
}
enemyCar.pursuitDetection.evasionTimer = 0;
}
} else {
enemyCar.pursuitDetection.pursuitDuration = 0;
enemyCar.pursuitDetection.evasionMode = false;
enemyCar.pursuitDetection.counterAttackMode = false;
}
}
enemyCar.pursuitDetection.evasionTimer++;
}
if ((enemyCar.targetSelectionTimer >= enemyCar.targetSelectionInterval || enemyCar.currentTarget === null) && shouldUpdateAI) {
enemyCar.targetSelectionTimer = 0;
// Create list of all potential targets (player + other enemy cars)
var potentialTargets = [];
// Add player as potential target
potentialTargets.push({
object: carPlayer,
velocityX: velocityX,
velocityY: velocityY,
weight: playerCarWeight,
isPlayer: true
});
// Add other enemy cars as potential targets
for (var targetIdx = 0; targetIdx < enemyCars.length; targetIdx++) {
if (targetIdx !== aiCarIdx) {
// Don't target self
var otherCar = enemyCars[targetIdx];
potentialTargets.push({
object: otherCar,
velocityX: (otherCar.aiVelocityX || 0) + (otherCar.velocityX || 0),
velocityY: (otherCar.aiVelocityY || 0) + (otherCar.velocityY || 0),
weight: otherCar.weight,
isPlayer: false
});
}
}
// Evaluate each target and assign scores
var bestTarget = null;
var bestScore = -1;
for (var evalIdx = 0; evalIdx < potentialTargets.length; evalIdx++) {
var target = potentialTargets[evalIdx];
var targetSpeed = Math.sqrt(target.velocityX * target.velocityX + target.velocityY * target.velocityY);
var targetDistance = Math.sqrt((target.object.x - enemyCar.x) * (target.object.x - enemyCar.x) + (target.object.y - enemyCar.y) * (target.object.y - enemyCar.y));
// Calculate target score based on multiple factors
var score = 0;
// Distance factor - prefer closer targets (0-40 points)
var distanceScore = Math.max(0, 40 - targetDistance / 1000 * 40);
score += distanceScore;
// Speed factor - prefer faster targets for more exciting gameplay (0-25 points)
var speedRatio = targetSpeed / maxSpeed;
var speedScore = speedRatio * 25;
score += speedScore;
// Weight factor - heavier targets are more satisfying to hit (0-15 points)
var weightScore = Math.min(15, target.weight * 6);
score += weightScore;
// Player bonus - slight preference for player to maintain engagement (0-10 points)
if (target.isPlayer) {
score += 10;
}
// Velocity alignment factor - prefer targets moving in interesting directions (0-10 points)
if (targetSpeed > 1) {
var enemySpeed = Math.sqrt((enemyCar.aiVelocityX || 0) * (enemyCar.aiVelocityX || 0) + (enemyCar.aiVelocityY || 0) * (enemyCar.aiVelocityY || 0));
if (enemySpeed > 1) {
// Calculate if target and enemy are moving in similar or opposite directions
var enemyVelX = enemyCar.aiVelocityX || 0;
var enemyVelY = enemyCar.aiVelocityY || 0;
var dotProduct = (target.velocityX * enemyVelX + target.velocityY * enemyVelY) / (targetSpeed * enemySpeed);
// Prefer head-on collisions (opposite directions) - more exciting
var alignmentScore = (1 - dotProduct) * 5; // 0-10 points
score += alignmentScore;
}
}
// Avoid recently targeted objects to prevent fixation (penalty)
if (enemyCar.currentTarget && target.object === enemyCar.currentTarget.object) {
// Apply small penalty to current target to encourage switching
score *= 0.9;
}
// Select best target
if (score > bestScore) {
bestScore = score;
bestTarget = target;
}
}
// Update current target
enemyCar.currentTarget = bestTarget;
// Vary target selection interval for more dynamic behavior
enemyCar.targetSelectionInterval = 45 + Math.floor(Math.random() * 90); // 0.75-2.25 seconds
}
// Enemy car can switch between 'follow target' and 'free roam' AI modes
if (typeof enemyCar.aiMode === "undefined") enemyCar.aiMode = "follow";
if (typeof enemyCar.aiModeTimer === "undefined") enemyCar.aiModeTimer = 0;
if (typeof enemyCar.aiModeDuration === "undefined") enemyCar.aiModeDuration = 0;
// AI mode switching logic: change mode at intervals with speed-based preferences
enemyCar.aiModeTimer++;
if (enemyCar.aiModeTimer > enemyCar.aiModeDuration) {
// Check if being pursued - this overrides normal mode selection
if (enemyCar.pursuitDetection.beingPursued && (enemyCar.pursuitDetection.evasionMode || enemyCar.pursuitDetection.counterAttackMode)) {
// Being pursued - use special pursuit response mode
if (enemyCar.pursuitDetection.counterAttackMode && enemyCar.pursuitDetection.counterAttackTarget) {
// Turn and fight - target the pursuer
enemyCar.aiMode = "follow";
// Override current target to pursuer
enemyCar.currentTarget = {
object: enemyCar.pursuitDetection.counterAttackTarget,
velocityX: enemyCar.pursuitDetection.counterAttackTarget === carPlayer ? velocityX : (enemyCar.pursuitDetection.counterAttackTarget.aiVelocityX || 0) + (enemyCar.pursuitDetection.counterAttackTarget.velocityX || 0),
velocityY: enemyCar.pursuitDetection.counterAttackTarget === carPlayer ? velocityY : (enemyCar.pursuitDetection.counterAttackTarget.aiVelocityY || 0) + (enemyCar.pursuitDetection.counterAttackTarget.velocityY || 0),
weight: enemyCar.pursuitDetection.counterAttackTarget === carPlayer ? playerCarWeight : enemyCar.pursuitDetection.counterAttackTarget.weight,
isPlayer: enemyCar.pursuitDetection.counterAttackTarget === carPlayer
};
} else if (enemyCar.pursuitDetection.evasionMode) {
// Fleeing - use special evasion mode
enemyCar.aiMode = "evade";
}
} else {
// Normal mode selection
// Calculate target speed for mode preference based on current target
var targetSpeed = 0;
if (enemyCar.currentTarget) {
targetSpeed = Math.sqrt(enemyCar.currentTarget.velocityX * enemyCar.currentTarget.velocityX + enemyCar.currentTarget.velocityY * enemyCar.currentTarget.velocityY);
}
var targetSpeedRatio = targetSpeed / maxSpeed; // 0 to 1
var followChance;
// Speed-based mode preferences:
// Very fast targets (>70% max speed) - 90% follow (for ambush tactics)
// Fast targets (40-70% max speed) - 80% follow
// Medium targets (20-40% max speed) - 70% follow
// Slow targets (<20% max speed) - 85% follow (for direct pursuit)
if (targetSpeedRatio > 0.7) {
// Very fast - prefer follow mode for ambush tactics
followChance = 0.9;
} else if (targetSpeedRatio > 0.4) {
// Fast to medium - moderate follow preference
followChance = 0.8;
} else if (targetSpeedRatio > 0.2) {
// Medium - balanced but still prefer follow
followChance = 0.7;
} else {
// Slow - prefer follow mode for direct pursuit
followChance = 0.85;
}
// Pick mode based on calculated preference
if (Math.random() < followChance) {
enemyCar.aiMode = "follow";
} else {
enemyCar.aiMode = "free";
}
}
// Duration: 1.2s to 3.5s (72 to 210 frames)
enemyCar.aiModeDuration = 72 + Math.floor(Math.random() * 138);
enemyCar.aiModeTimer = 0;
}
// --- AI movement logic depending on mode ---
var aiDeltaX, aiDeltaY, aiDistance, aiTargetRotation, aiPower, aiTargetVelocity;
// Edge avoidance logic - calculate repulsion from boundaries
var edgeAvoidanceX = 0;
var edgeAvoidanceY = 0;
var edgeBuffer = 200; // Distance from edge to start avoiding
var avoidanceStrength = 0.3; // How strongly to avoid edges
// Check distance from each edge and calculate avoidance force
if (enemyCar.x < edgeBuffer) {
// Too close to left edge, push right
var leftForce = (edgeBuffer - enemyCar.x) / edgeBuffer;
edgeAvoidanceX += leftForce * avoidanceStrength;
}
if (enemyCar.x > 2048 - edgeBuffer) {
// Too close to right edge, push left
var rightForce = (enemyCar.x - (2048 - edgeBuffer)) / edgeBuffer;
edgeAvoidanceX -= rightForce * avoidanceStrength;
}
if (enemyCar.y < edgeBuffer) {
// Too close to top edge, push down
var topForce = (edgeBuffer - enemyCar.y) / edgeBuffer;
edgeAvoidanceY += topForce * avoidanceStrength;
}
if (enemyCar.y > 2186 - edgeBuffer) {
// Too close to bottom edge, push up
var bottomForce = (enemyCar.y - (2186 - edgeBuffer)) / edgeBuffer;
edgeAvoidanceY -= bottomForce * avoidanceStrength;
}
if (enemyCar.aiMode === "follow") {
// Initialize follow strategy if not set
if (typeof enemyCar.followStrategy === "undefined") {
enemyCar.followStrategy = 0;
enemyCar.followStrategyTimer = 0;
enemyCar.followStrategyDuration = 60 + Math.floor(Math.random() * 60); // 1-2 seconds for adaptive strategy
}
// Individual strategy selection based on each car's unique personality
enemyCar.followStrategyTimer++;
if (enemyCar.followStrategyTimer > enemyCar.followStrategyDuration) {
// Calculate target speed for strategy selection
var targetSpeed = 0;
if (enemyCar.currentTarget) {
targetSpeed = Math.sqrt(enemyCar.currentTarget.velocityX * enemyCar.currentTarget.velocityX + enemyCar.currentTarget.velocityY * enemyCar.currentTarget.velocityY);
}
var targetSpeedRatio = targetSpeed / maxSpeed; // 0 to 1
var relativeDistance = Math.sqrt(aiDeltaX * aiDeltaX + aiDeltaY * aiDeltaY);
var relativeAngle = Math.atan2(aiDeltaX, -aiDeltaY);
var enemyAngle = enemyCar.rotation;
var angleDifference = Math.abs(relativeAngle - enemyAngle);
while (angleDifference > Math.PI) angleDifference = 2 * Math.PI - angleDifference;
var isTargetAhead = angleDifference < Math.PI / 3; // Within 60 degrees ahead
var isTargetBehind = angleDifference > 2 * Math.PI / 3; // More than 120 degrees behind
// Individual strategy selection based on car's tactical personality
var personality = enemyCar.tacticalPersonality;
var strategyScores = [0, 0, 0]; // [direct, intimidation, ambush]
// Base preference scores from personality
strategyScores[0] = personality.directPreference;
strategyScores[1] = personality.intimidationPreference;
strategyScores[2] = personality.ambushPreference;
// Modify scores based on target speed and individual preferences
if (targetSpeedRatio < 0.3) {
// Slow target - use individual slow target strategy preference
var preferredStrategy = personality.slowTargetStrategy;
strategyScores[preferredStrategy] += 0.4;
} else if (targetSpeedRatio > 0.6) {
// Fast target - use individual fast target strategy preference
var preferredStrategy = personality.fastTargetStrategy;
strategyScores[preferredStrategy] += 0.4;
} else {
// Medium speed target - use individual medium target strategy preference
var preferredStrategy = personality.mediumTargetStrategy;
strategyScores[preferredStrategy] += 0.3;
}
// Situational modifiers based on individual traits
if (targetSpeedRatio < 0.05) {
// Target is nearly stationary - intimidation gets bonus for patient cars
if (personality.patience > 0.6) {
strategyScores[1] += 0.3; // Patient cars prefer intimidation
// Initialize intimidation counter if not set
if (typeof enemyCar.intimidationCount === "undefined") {
enemyCar.intimidationCount = 0;
enemyCar.maxIntimidations = Math.floor(personality.patience * 4) + 1; // 1-4 based on patience
}
// Check if we've completed enough intimidations
if (enemyCar.intimidationCount >= enemyCar.maxIntimidations) {
strategyScores[0] += 0.5; // Switch to direct after enough intimidation
strategyScores[1] -= 0.3;
}
} else {
strategyScores[0] += 0.4; // Impatient cars go direct
}
}
// Distance-based individual preferences
if (relativeDistance < personality.preferredAttackDistance) {
// Within preferred attack range - aggressive cars prefer direct
strategyScores[0] += personality.aggressiveness * 0.3;
} else if (relativeDistance > personality.preferredAttackDistance * 2) {
// Far away - adaptive cars prefer ambush
strategyScores[2] += personality.adaptability * 0.3;
}
// Positional modifiers with individual traits
if (isTargetBehind || relativeDistance < personality.personalSpaceRadius) {
// Target behind or in personal space - use intimidation for positioning
strategyScores[1] += 0.2;
} else if (isTargetAhead && relativeDistance < personality.preferredAttackDistance * 1.5) {
// Target ahead and in preferred range
if (personality.aggressiveness > 0.6) {
strategyScores[0] += 0.3; // Aggressive cars go direct
} else {
strategyScores[1] += 0.2; // Less aggressive cars intimidate first
}
}
// Select strategy with highest score (with some randomness based on adaptability)
var maxScore = Math.max(strategyScores[0], strategyScores[1], strategyScores[2]);
var candidateStrategies = [];
for (var s = 0; s < 3; s++) {
if (strategyScores[s] >= maxScore - personality.adaptability * 0.2) {
candidateStrategies.push(s);
}
}
enemyCar.followStrategy = candidateStrategies[Math.floor(Math.random() * candidateStrategies.length)];
// Individual strategy duration based on personality
var baseDuration = personality.minStrategyDuration + (personality.maxStrategyDuration - personality.minStrategyDuration) * personality.patience;
var strategyMultiplier = 1.0;
switch (enemyCar.followStrategy) {
case 0:
// Direct - duration affected by aggressiveness (aggressive = shorter bursts)
strategyMultiplier = 1.2 - personality.aggressiveness * 0.4;
break;
case 1:
// Intimidation - duration affected by patience (patient = longer intimidation)
strategyMultiplier = 0.8 + personality.patience * 0.6;
break;
case 2:
// Ambush - duration affected by adaptability (adaptive = longer planning)
strategyMultiplier = 1.0 + personality.adaptability * 0.5;
break;
}
enemyCar.followStrategyDuration = Math.floor(baseDuration * strategyMultiplier);
enemyCar.followStrategyTimer = 0;
}
// Calculate base values using current target instead of always player (optimized)
if (enemyCar.currentTarget) {
aiDeltaX = enemyCar.currentTarget.object.x - enemyCar.x;
aiDeltaY = enemyCar.currentTarget.object.y - enemyCar.y;
// Cache distance calculation
var aiDeltaXSq = aiDeltaX * aiDeltaX;
var aiDeltaYSq = aiDeltaY * aiDeltaY;
aiDistance = Math.sqrt(aiDeltaXSq + aiDeltaYSq);
// Calculate relative velocity and approach angle for smarter pursuit
var aiVelX = enemyCar.aiVelocityX || 0;
var aiVelY = enemyCar.aiVelocityY || 0;
var relativeVelX = enemyCar.currentTarget.velocityX - aiVelX;
var relativeVelY = enemyCar.currentTarget.velocityY - aiVelY;
} else {
// Fallback to player if no target selected
aiDeltaX = carPlayer.x - enemyCar.x;
aiDeltaY = carPlayer.y - enemyCar.y;
// Cache distance calculation
var aiDeltaXSq = aiDeltaX * aiDeltaX;
var aiDeltaYSq = aiDeltaY * aiDeltaY;
aiDistance = Math.sqrt(aiDeltaXSq + aiDeltaYSq);
var aiVelX = enemyCar.aiVelocityX || 0;
var aiVelY = enemyCar.aiVelocityY || 0;
var relativeVelX = velocityX - aiVelX;
var relativeVelY = velocityY - aiVelY;
}
var approachAngle = Math.atan2(aiDeltaX, -aiDeltaY);
var enemyCurrentAngle = enemyCar.rotation;
var angleDiff = approachAngle - enemyCurrentAngle;
while (angleDiff > Math.PI) angleDiff -= 2 * Math.PI;
while (angleDiff < -Math.PI) angleDiff += 2 * Math.PI;
// Anti-circling detection: check if we're in a circular pattern
if (typeof enemyCar.lastPositions === "undefined") {
enemyCar.lastPositions = [];
enemyCar.circlingDetected = false;
enemyCar.circlingCooldown = 0;
}
// Store position history for circling detection
enemyCar.lastPositions.push({
x: enemyCar.x,
y: enemyCar.y,
frame: LK.ticks
});
if (enemyCar.lastPositions.length > 60) {
// Keep 1 second of history
enemyCar.lastPositions.shift();
}
// Detect circling by checking if we've been in similar positions recently
var circlingThreshold = 80; // Distance threshold for considering positions "similar"
var circlingCount = 0;
for (var i = 0; i < enemyCar.lastPositions.length - 20; i++) {
var oldPos = enemyCar.lastPositions[i];
var dist = Math.sqrt((enemyCar.x - oldPos.x) * (enemyCar.x - oldPos.x) + (enemyCar.y - oldPos.y) * (enemyCar.y - oldPos.y));
if (dist < circlingThreshold) {
circlingCount++;
}
}
enemyCar.circlingDetected = circlingCount > 3 && aiDistance < 200; // Circling if close to player and repeating positions
// Reduce circling cooldown
if (enemyCar.circlingCooldown > 0) enemyCar.circlingCooldown--;
// Initialize failure detection system for direct pursuit
if (typeof enemyCar.directPursuitTimer === "undefined") {
enemyCar.directPursuitTimer = 0;
enemyCar.lastPlayerDistance = aiDistance;
enemyCar.nearMissCount = 0;
enemyCar.totalPursuitTime = 0;
}
// Apply different follow strategies with distance awareness
switch (enemyCar.followStrategy) {
case 0:
// Direct pursuit - straight chase with distance management
// Track pursuit effectiveness
enemyCar.directPursuitTimer++;
enemyCar.totalPursuitTime++;
// Detect near misses - when AI gets very close but doesn't hit
if (aiDistance < 80 && enemyCar.lastPlayerDistance > 80) {
// Just entered close range
enemyCar.directPursuitTimer = 0; // Reset timer on new approach
}
if (enemyCar.lastPlayerDistance < 80 && aiDistance > 120) {
// Just left close range without collision - potential near miss
enemyCar.nearMissCount++;
}
// Check for failure conditions in direct pursuit
var pursuitFailed = false;
if (enemyCar.nearMissCount >= 2) {
// Failed after 2 near misses
pursuitFailed = true;
} else if (enemyCar.totalPursuitTime > 300 && !currentColliding) {
// Failed after 5 seconds without collision
pursuitFailed = true;
} else if (enemyCar.directPursuitTimer > 120 && aiDistance > 200) {
// Failed if stuck at medium distance for 2 seconds
pursuitFailed = true;
}
// Switch strategy if direct pursuit failed
if (pursuitFailed) {
// Reset failure tracking
enemyCar.nearMissCount = 0;
enemyCar.totalPursuitTime = 0;
enemyCar.directPursuitTimer = 0;
// Calculate target speed for strategy selection
var currentTargetSpeed = 0;
if (enemyCar.currentTarget) {
currentTargetSpeed = Math.sqrt(enemyCar.currentTarget.velocityX * enemyCar.currentTarget.velocityX + enemyCar.currentTarget.velocityY * enemyCar.currentTarget.velocityY);
}
var targetSpeedRatio = currentTargetSpeed / maxSpeed;
// Choose new strategy based on current situation
if (targetSpeedRatio > 0.5 || aiDistance > 300) {
// Fast target or far away - try ambush
enemyCar.followStrategy = 2;
enemyCar.followStrategyDuration = 90 + Math.floor(Math.random() * 60);
} else {
// Slow target or close - try varied approach
enemyCar.followStrategy = 1;
enemyCar.followStrategyDuration = 60 + Math.floor(Math.random() * 60);
}
enemyCar.followStrategyTimer = 0;
}
// Check current AI speed to prioritize acceleration building
var currentAISpeed = Math.sqrt((enemyCar.aiVelocityX || 0) * (enemyCar.aiVelocityX || 0) + (enemyCar.aiVelocityY || 0) * (enemyCar.aiVelocityY || 0));
var aiSpeedRatio = currentAISpeed / maxSpeed;
var needsAcceleration = aiSpeedRatio < 0.4; // Less than 40% of max speed
// Check if AI can go straight to target for speed optimization
var currentEnemyAngle = enemyCar.rotation;
var straightLineAngle = Math.atan2(aiDeltaX, -aiDeltaY);
var angleToTarget = straightLineAngle - currentEnemyAngle;
while (angleToTarget > Math.PI) angleToTarget -= 2 * Math.PI;
while (angleToTarget < -Math.PI) angleToTarget += 2 * Math.PI;
// AI is considered "aligned" if facing within 15 degrees of target
var isAlignedWithTarget = Math.abs(angleToTarget) < Math.PI / 12; // 15 degrees
// Prioritize acceleration building when speed is low
if (needsAcceleration && aiDistance > 150) {
// Focus on building speed when far enough from target
if (isAlignedWithTarget) {
// Perfect alignment - accelerate in straight line toward target
aiTargetRotation = straightLineAngle;
aiPower = 1.0; // Maximum acceleration
aiTargetVelocity = maxSpeed * aiPower * 1.0;
} else if (Math.abs(angleToTarget) < Math.PI / 6) {
// Close to aligned (within 30 degrees) - minor correction while accelerating
var correctionFactor = Math.abs(angleToTarget) / (Math.PI / 6); // 0 to 1
aiTargetRotation = enemyCar.rotation + angleToTarget * 0.3; // Gentle steering
aiPower = 0.9 + correctionFactor * 0.1; // Maintain high acceleration
aiTargetVelocity = maxSpeed * aiPower * (0.95 + correctionFactor * 0.05);
} else {
// Need significant turn - prioritize getting aligned for acceleration
aiTargetRotation = straightLineAngle;
aiPower = 0.7; // Moderate acceleration while turning
aiTargetVelocity = maxSpeed * aiPower * 0.8;
}
} else if (aiDistance < enemyCar.tacticalPersonality.preferredAttackDistance * 0.6 && !enemyCar.circlingDetected) {
// Too close for this car's preferred attack style - back off based on aggressiveness
var backoffIntensity = 0.3 + (1 - enemyCar.tacticalPersonality.aggressiveness) * 0.4; // Less aggressive = more backoff
var backoffAngle = approachAngle + Math.PI + (Math.random() - 0.5) * Math.PI * backoffIntensity;
aiTargetRotation = backoffAngle;
aiPower = 0.2 + enemyCar.tacticalPersonality.aggressiveness * 0.2;
aiTargetVelocity = maxSpeed * aiPower * 0.6;
} else if (aiDistance > enemyCar.tacticalPersonality.preferredAttackDistance * 2) {
// Far away - check for straight line opportunity
if (isAlignedWithTarget) {
// Can go straight - use maximum speed for efficiency
aiTargetRotation = straightLineAngle;
aiPower = Math.min(1, aiDistance / 600);
aiTargetVelocity = maxSpeed * aiPower * 1.0; // Full speed when aligned
} else {
// Need to turn first - direct approach at normal speed
aiTargetRotation = approachAngle;
aiPower = Math.min(1, aiDistance / 600);
aiTargetVelocity = maxSpeed * aiPower * 0.95;
}
} else {
// Medium distance - check alignment for speed boost, but prioritize acceleration if needed
if (needsAcceleration) {
// Still building speed - maintain acceleration focus
if (isAlignedWithTarget) {
aiTargetRotation = straightLineAngle;
aiPower = 0.9;
aiTargetVelocity = maxSpeed * aiPower * 0.95;
} else {
// Get aligned while building speed
aiTargetRotation = approachAngle;
aiPower = 0.8;
aiTargetVelocity = maxSpeed * aiPower * 0.9;
}
} else if (isAlignedWithTarget && aiDistance > 200) {
// Aligned and far enough - go straight at higher speed
aiTargetRotation = straightLineAngle;
aiPower = Math.min(1, aiDistance / 500);
aiTargetVelocity = maxSpeed * aiPower * 0.98; // Near full speed when aligned
} else {
// Not aligned or too close - slight offset to avoid head-on collision
var offsetAngle = (Math.random() - 0.5) * Math.PI * 0.2; // ±18 degrees
aiTargetRotation = approachAngle + offsetAngle;
aiPower = Math.min(1, aiDistance / 500);
aiTargetVelocity = maxSpeed * aiPower * 0.92;
}
}
// Update last distance for next frame
enemyCar.lastPlayerDistance = aiDistance;
break;
case 1:
// Varied pursuit - intimidation mode: follows like direct but maintains safe distance to scare
// Check current AI speed for acceleration prioritization
var currentAISpeedIntimidation = Math.sqrt((enemyCar.aiVelocityX || 0) * (enemyCar.aiVelocityX || 0) + (enemyCar.aiVelocityY || 0) * (enemyCar.aiVelocityY || 0));
var aiSpeedRatioIntimidation = currentAISpeedIntimidation / maxSpeed;
var needsAccelerationIntimidation = aiSpeedRatioIntimidation < 0.35; // Less than 35% of max speed
if (enemyCar.circlingDetected && enemyCar.circlingCooldown <= 0) {
// Break out of circling pattern
var escapeAngle = approachAngle + Math.PI * 0.75 * (Math.random() < 0.5 ? 1 : -1);
aiTargetRotation = escapeAngle;
aiPower = 0.8;
aiTargetVelocity = maxSpeed * aiPower * 0.85;
enemyCar.circlingCooldown = 120; // 2 second cooldown
} else if (needsAccelerationIntimidation && aiDistance > 200) {
// Build speed before attempting intimidation tactics
var intimidationAngle = Math.atan2(aiDeltaX, -aiDeltaY);
var currentAngle = enemyCar.rotation;
var angleToIntimidationTarget = intimidationAngle - currentAngle;
while (angleToIntimidationTarget > Math.PI) angleToIntimidationTarget -= 2 * Math.PI;
while (angleToIntimidationTarget < -Math.PI) angleToIntimidationTarget += 2 * Math.PI;
if (Math.abs(angleToIntimidationTarget) < Math.PI / 8) {
// Well aligned - accelerate directly toward intimidation position
aiTargetRotation = intimidationAngle;
aiPower = 0.9;
aiTargetVelocity = maxSpeed * aiPower * 0.95;
} else {
// Need to align - gentle steering while building speed
aiTargetRotation = currentAngle + angleToIntimidationTarget * 0.4;
aiPower = 0.8;
aiTargetVelocity = maxSpeed * aiPower * 0.9;
}
} else if (aiDistance < enemyCar.tacticalPersonality.preferredIntimidationDistance * 1.2) {
// Individual intimidation range based on car's personality
var intimidationDistance = enemyCar.tacticalPersonality.preferredIntimidationDistance;
var distanceError = aiDistance - intimidationDistance;
var toleranceRange = 20 + enemyCar.tacticalPersonality.patience * 20; // 20-40 pixel tolerance based on patience
if (Math.abs(distanceError) < toleranceRange) {
// Perfect intimidation distance - behavior based on individual aggressiveness and speed
var aggressiveness = enemyCar.tacticalPersonality.aggressiveness;
var weavingIntensity = Math.PI * 0.1 + aggressiveness * Math.PI * 0.1; // More aggressive = more weaving
var intimidationOffset = Math.sin(LK.ticks * (0.03 + aggressiveness * 0.04)) * weavingIntensity;
aiTargetRotation = approachAngle + intimidationOffset;
// Speed-dependent intimidation - more aggressive when faster
var speedBonus = Math.min(0.2, aiSpeedRatioIntimidation * 0.3);
aiPower = 0.5 + aggressiveness * 0.3 + speedBonus;
aiTargetVelocity = maxSpeed * aiPower * (0.8 + aggressiveness * 0.1 + speedBonus);
// Check if target is stationary and we're intimidating
var currentTargetSpeed = 0;
if (enemyCar.currentTarget) {
currentTargetSpeed = Math.sqrt(enemyCar.currentTarget.velocityX * enemyCar.currentTarget.velocityX + enemyCar.currentTarget.velocityY * enemyCar.currentTarget.velocityY);
}
if (currentTargetSpeed < maxSpeed * 0.05 && typeof enemyCar.intimidationCount !== "undefined") {
// Mark intimidation as successful - duration based on patience
if (typeof enemyCar.intimidationTimer === "undefined") enemyCar.intimidationTimer = 0;
enemyCar.intimidationTimer++;
// Patient cars intimidate longer, impatient cars intimidate briefly
var intimidationDuration = 60 + enemyCar.tacticalPersonality.patience * 120; // 1-3 seconds based on patience
if (enemyCar.intimidationTimer > intimidationDuration) {
enemyCar.intimidationCount++;
enemyCar.intimidationTimer = 0;
// Force strategy recalculation
enemyCar.followStrategyTimer = enemyCar.followStrategyDuration + 1;
}
}
} else if (distanceError < 0) {
// Too close - back off behavior based on aggressiveness
var backoffIntensity = 0.2 + (1 - enemyCar.tacticalPersonality.aggressiveness) * 0.2; // Less aggressive = back off more
aiTargetRotation = approachAngle + Math.PI + (Math.random() - 0.5) * Math.PI * backoffIntensity;
aiPower = 0.3 + enemyCar.tacticalPersonality.aggressiveness * 0.2;
aiTargetVelocity = maxSpeed * aiPower * 0.6;
} else {
// Too far - approach for intimidation with individual aggressiveness
aiTargetRotation = approachAngle;
aiPower = 0.6 + enemyCar.tacticalPersonality.aggressiveness * 0.3;
aiTargetVelocity = maxSpeed * aiPower * (0.85 + enemyCar.tacticalPersonality.aggressiveness * 0.1);
}
} else {
// Long range - approach based on individual preferred attack distance
var approachIntensity = Math.min(1, aiDistance / (enemyCar.tacticalPersonality.preferredAttackDistance * 2));
aiTargetRotation = approachAngle;
aiPower = approachIntensity * (0.7 + enemyCar.tacticalPersonality.aggressiveness * 0.3);
aiTargetVelocity = maxSpeed * aiPower * (0.9 + enemyCar.tacticalPersonality.aggressiveness * 0.05);
}
break;
case 2:
// Ambush - predict target's future position with smarter interception
var targetVelX = 0;
var targetVelY = 0;
var targetPosX = enemyCar.x; // fallback position
var targetPosY = enemyCar.y;
if (enemyCar.currentTarget) {
targetVelX = enemyCar.currentTarget.velocityX;
targetVelY = enemyCar.currentTarget.velocityY;
targetPosX = enemyCar.currentTarget.object.x;
targetPosY = enemyCar.currentTarget.object.y;
}
var targetSpeed = Math.sqrt(targetVelX * targetVelX + targetVelY * targetVelY);
// Check current AI speed for acceleration prioritization in ambush
var currentAISpeedAmbush = Math.sqrt((enemyCar.aiVelocityX || 0) * (enemyCar.aiVelocityX || 0) + (enemyCar.aiVelocityY || 0) * (enemyCar.aiVelocityY || 0));
var aiSpeedRatioAmbush = currentAISpeedAmbush / maxSpeed;
var needsAccelerationAmbush = aiSpeedRatioAmbush < 0.5; // Less than 50% of max speed for ambush
if (enemyCar.circlingDetected && enemyCar.circlingCooldown <= 0) {
// Break circling with wide flanking maneuver
var flankAngle = approachAngle + Math.PI * 0.8 * (Math.random() < 0.5 ? 1 : -1);
aiTargetRotation = flankAngle;
aiPower = 0.9;
aiTargetVelocity = maxSpeed * aiPower * 0.9;
enemyCar.circlingCooldown = 180; // 3 second cooldown
} else if (needsAccelerationAmbush && aiDistance > 250) {
// Build speed before attempting interception - find clear acceleration path
var accelerationAngle;
// Try to find a path that doesn't lead directly to target (for ambush positioning)
if (aiDistance > 400) {
// Far from target - accelerate in a flanking direction
var flankingOffset = Math.PI * 0.3 * (Math.random() < 0.5 ? 1 : -1); // ±54 degrees
accelerationAngle = approachAngle + flankingOffset;
} else {
// Medium distance - position for future interception while building speed
var leadAngle = Math.atan2(targetVelX, -targetVelY); // Target's movement direction
var interceptAngle = leadAngle + Math.PI * 0.6 * (Math.random() < 0.5 ? 1 : -1); // Offset for ambush
accelerationAngle = interceptAngle;
}
aiTargetRotation = accelerationAngle;
aiPower = 0.95; // High acceleration priority
aiTargetVelocity = maxSpeed * aiPower * 0.98;
} else if (aiDistance < 150) {
// Close range - position for optimal angle of attack, but only if we have good speed
if (aiSpeedRatioAmbush > 0.4) {
// Have enough speed for attack positioning
var attackAngle = approachAngle + Math.PI * 0.4 * (angleDiff > 0 ? 1 : -1);
aiTargetRotation = attackAngle;
aiPower = 0.7 + aiSpeedRatioAmbush * 0.2; // More power when faster
aiTargetVelocity = maxSpeed * aiPower * (0.8 + aiSpeedRatioAmbush * 0.15);
} else {
// Need more speed - create space while accelerating
var retreatAngle = approachAngle + Math.PI + (Math.random() - 0.5) * Math.PI * 0.4;
aiTargetRotation = retreatAngle;
aiPower = 0.8;
aiTargetVelocity = maxSpeed * aiPower * 0.85;
}
} else {
// Long range interception
var predictionTime = Math.min(2.5, aiDistance / maxSpeed * 0.9);
var interceptX = targetPosX + targetVelX * predictionTime;
var interceptY = targetPosY + targetVelY * predictionTime;
// Keep predicted position within bounds
interceptX = Math.max(80, Math.min(1968, interceptX));
interceptY = Math.max(80, Math.min(2106, interceptY));
// Calculate optimal interception approach
var interceptDeltaX = interceptX - enemyCar.x;
var interceptDeltaY = interceptY - enemyCar.y;
var interceptDistance = Math.sqrt(interceptDeltaX * interceptDeltaX + interceptDeltaY * interceptDeltaY);
// Adjust interception based on current speed capabilities
var speedRatio = (enemyCar.aiCurrentVelocity || 0) / Math.max(0.1, targetSpeed);
var leadAdjustment = (1 - speedRatio) * 0.3;
// Speed-dependent interception strategy
if (needsAccelerationAmbush) {
// Focus on acceleration toward interception point
aiTargetRotation = Math.atan2(interceptDeltaX, -interceptDeltaY);
aiPower = 0.9; // High acceleration
var speedBoost = Math.min(0.25, targetSpeed / maxSpeed * 0.25);
aiTargetVelocity = maxSpeed * aiPower * (0.9 + speedBoost);
} else {
// Have good speed - execute precise interception
aiTargetRotation = Math.atan2(interceptDeltaX, -interceptDeltaY);
aiPower = Math.min(1, interceptDistance / 700);
var speedBoost = Math.min(0.2, targetSpeed / maxSpeed * 0.2);
aiTargetVelocity = maxSpeed * aiPower * (0.85 + speedBoost + leadAdjustment);
}
}
break;
}
// Blend in edge avoidance with follow behavior
if (edgeAvoidanceX !== 0 || edgeAvoidanceY !== 0) {
// Calculate edge avoidance angle
var avoidanceAngle = Math.atan2(edgeAvoidanceX, -edgeAvoidanceY);
// Blend the target rotation with avoidance (stronger when closer to edges)
var avoidanceWeight = Math.min(0.7, Math.abs(edgeAvoidanceX) + Math.abs(edgeAvoidanceY));
var followWeight = 1 - avoidanceWeight;
// Convert angles to vectors for blending
var followVecX = Math.sin(aiTargetRotation) * followWeight;
var followVecY = -Math.cos(aiTargetRotation) * followWeight;
var avoidVecX = Math.sin(avoidanceAngle) * avoidanceWeight;
var avoidVecY = -Math.cos(avoidanceAngle) * avoidanceWeight;
// Combine and convert back to angle
var blendedVecX = followVecX + avoidVecX;
var blendedVecY = followVecY + avoidVecY;
aiTargetRotation = Math.atan2(blendedVecX, -blendedVecY);
}
} else if (enemyCar.aiMode === "evade") {
// Evasion mode - flee from pursuers with different patterns
if (typeof enemyCar.evasionState === "undefined" || enemyCar.aiModeTimer === 0) {
// Initialize evasion variables
enemyCar.evasionState = {
baseAngle: Math.random() * Math.PI * 2,
escapeTimer: 0,
zigzagDirection: Math.random() < 0.5 ? 1 : -1,
circleCenter: {
x: enemyCar.x,
y: enemyCar.y
},
circleRadius: 150 + Math.random() * 100,
circleAngle: 0,
lastPursuerAngle: 0
};
}
enemyCar.evasionState.escapeTimer++;
// Calculate average pursuer position for evasion reference
var avgPursuerX = 0,
avgPursuerY = 0;
var pursuerCount = enemyCar.pursuitDetection.pursuers.length;
if (pursuerCount > 0) {
for (var ep = 0; ep < pursuerCount; ep++) {
avgPursuerX += enemyCar.pursuitDetection.pursuers[ep].object.x;
avgPursuerY += enemyCar.pursuitDetection.pursuers[ep].object.y;
}
avgPursuerX /= pursuerCount;
avgPursuerY /= pursuerCount;
} else {
// Fallback if no pursuers detected
avgPursuerX = enemyCar.x - 100;
avgPursuerY = enemyCar.y - 100;
}
// Calculate escape direction (away from average pursuer position)
var escapeFromX = avgPursuerX - enemyCar.x;
var escapeFromY = avgPursuerY - enemyCar.y;
var escapeBaseAngle = Math.atan2(-escapeFromX, escapeFromY); // Opposite direction
// Apply different evasion strategies with individual preferences
var flightPrefs = enemyCar.tacticalPersonality;
switch (enemyCar.pursuitDetection.evasionStrategy) {
case 0:
// Speed focus - straight line escape with individual variations
var escapeEfficiency = 0.8 + flightPrefs.aggressiveness * 0.2; // Aggressive cars escape more efficiently
aiTargetRotation = escapeBaseAngle;
// Look for clear path ahead with individual distance preferences
var lookAheadDist = 150 + flightPrefs.riskTolerance * 100; // Risk-tolerant cars look further
var pathClearX = enemyCar.x + Math.sin(escapeBaseAngle) * lookAheadDist;
var pathClearY = enemyCar.y - Math.cos(escapeBaseAngle) * lookAheadDist;
// Individual wall avoidance based on risk tolerance
var wallBuffer = 80 + (1 - flightPrefs.riskTolerance) * 60; // Risk-averse cars avoid walls more
if (pathClearX < wallBuffer || pathClearX > 2048 - wallBuffer || pathClearY < wallBuffer || pathClearY > 2186 - wallBuffer) {
// Escape route selection based on individual preferences
if (flightPrefs.adaptability > 0.5) {
// Adaptive cars find better escape routes
var toCenterX = 1024 - enemyCar.x;
var toCenterY = 1093 - enemyCar.y;
var toCenterAngle = Math.atan2(toCenterX, -toCenterY);
// Blend escape with center direction based on adaptability
var blendFactor = flightPrefs.adaptability;
var escapeVecX = Math.sin(escapeBaseAngle) * (1 - blendFactor);
var escapeVecY = -Math.cos(escapeBaseAngle) * (1 - blendFactor);
var centerVecX = Math.sin(toCenterAngle) * blendFactor;
var centerVecY = -Math.cos(toCenterAngle) * blendFactor;
aiTargetRotation = Math.atan2(escapeVecX + centerVecX, -(escapeVecY + centerVecY));
} else {
// Less adaptive cars just turn toward center
var toCenterX = 1024 - enemyCar.x;
var toCenterY = 1093 - enemyCar.y;
aiTargetRotation = Math.atan2(toCenterX, -toCenterY);
}
}
// Individual speed based on aggressiveness and bravery
aiPower = 0.9 + flightPrefs.aggressiveness * 0.1; // More aggressive = faster escape
var speedMultiplier = escapeEfficiency * (0.95 + flightPrefs.bravery * 0.05); // Brave cars maintain higher speed
aiTargetVelocity = maxSpeed * aiPower * speedMultiplier;
break;
case 1:
// Zigzag pattern with individual variations
var baseFrequency = 0.08 + flightPrefs.adaptability * 0.08; // Adaptive cars zigzag more frequently
var pursuitFrequency = enemyCar.pursuitDetection.pursuitIntensity * 0.06;
var zigzagFrequency = baseFrequency + pursuitFrequency;
// Individual zigzag amplitude based on risk tolerance
var baseAmplitude = Math.PI * (0.3 + flightPrefs.riskTolerance * 0.2); // Risk-tolerant cars make wider turns
var panicAmplitude = flightPrefs.panicThreshold < 0.5 ? Math.PI * 0.1 : 0; // Panicky cars add erratic movement
var zigzagAmplitude = baseAmplitude + panicAmplitude;
var zigzagOffset = Math.sin(enemyCar.evasionState.escapeTimer * zigzagFrequency) * zigzagAmplitude;
// Add individual unpredictability
if (flightPrefs.adaptability > 0.7 && Math.random() < 0.05) {
// Highly adaptive cars occasionally reverse zigzag direction
enemyCar.evasionState.zigzagDirection *= -1;
}
aiTargetRotation = escapeBaseAngle + zigzagOffset * (enemyCar.evasionState.zigzagDirection || 1);
// Individual power and speed based on personality
var basePower = 0.75 + flightPrefs.aggressiveness * 0.15;
var pursuitBonus = enemyCar.pursuitDetection.pursuitIntensity * 0.15;
aiPower = basePower + pursuitBonus;
var speedMultiplier = 0.85 + flightPrefs.bravery * 0.1; // Brave cars maintain higher speed while evading
aiTargetVelocity = maxSpeed * aiPower * speedMultiplier;
break;
case 2:
// Circular escape pattern with individual preferences
// Individual circle updating frequency based on adaptability
var updateFrequency = flightPrefs.adaptability > 0.6 ? 45 : 75; // Adaptive cars update circle more often
if (enemyCar.evasionState.escapeTimer % updateFrequency === 0) {
enemyCar.evasionState.circleCenter.x = enemyCar.x;
enemyCar.evasionState.circleCenter.y = enemyCar.y;
// Individual circle radius based on personality
var baseRadius = 120 + flightPrefs.riskTolerance * 80; // Risk-tolerant cars use larger circles
var panicRadius = flightPrefs.panicThreshold < 0.4 ? 40 : 0; // Panicky cars use smaller circles
enemyCar.evasionState.circleRadius = baseRadius + panicRadius;
}
// Individual circle speed based on aggressiveness and pursuit intensity
var baseCircleSpeed = 0.06 + flightPrefs.aggressiveness * 0.04;
var pursuitSpeedBonus = enemyCar.pursuitDetection.pursuitIntensity * 0.04;
var circleSpeed = baseCircleSpeed + pursuitSpeedBonus;
// Add individual direction changes for unpredictability
if (flightPrefs.adaptability > 0.8 && Math.random() < 0.02) {
// Highly adaptive cars occasionally reverse circle direction
circleSpeed *= -1;
}
enemyCar.evasionState.circleAngle += circleSpeed;
var targetCircleX = enemyCar.evasionState.circleCenter.x + Math.cos(enemyCar.evasionState.circleAngle) * enemyCar.evasionState.circleRadius;
var targetCircleY = enemyCar.evasionState.circleCenter.y + Math.sin(enemyCar.evasionState.circleAngle) * enemyCar.evasionState.circleRadius;
// Individual boundary handling based on risk tolerance
var boundary = 80 + (1 - flightPrefs.riskTolerance) * 40; // Risk-averse cars stay further from edges
targetCircleX = Math.max(boundary, Math.min(2048 - boundary, targetCircleX));
targetCircleY = Math.max(boundary, Math.min(2186 - boundary, targetCircleY));
var toCircleX = targetCircleX - enemyCar.x;
var toCircleY = targetCircleY - enemyCar.y;
aiTargetRotation = Math.atan2(toCircleX, -toCircleY);
// Individual power and speed
var basePower = 0.65 + flightPrefs.patience * 0.15; // Patient cars better at sustained circular movement
var pursuitBonus = enemyCar.pursuitDetection.pursuitIntensity * 0.15;
aiPower = basePower + pursuitBonus;
var speedMultiplier = 0.8 + flightPrefs.bravery * 0.1; // Brave cars circle at higher speeds
aiTargetVelocity = maxSpeed * aiPower * speedMultiplier;
break;
}
// Apply strong edge avoidance during evasion
if (edgeAvoidanceX !== 0 || edgeAvoidanceY !== 0) {
var avoidanceAngle = Math.atan2(edgeAvoidanceX, -edgeAvoidanceY);
var avoidanceWeight = Math.min(0.8, (Math.abs(edgeAvoidanceX) + Math.abs(edgeAvoidanceY)) * 2);
var evasionWeight = 1 - avoidanceWeight;
var evasionVecX = Math.sin(aiTargetRotation) * evasionWeight;
var evasionVecY = -Math.cos(aiTargetRotation) * evasionWeight;
var avoidVecX = Math.sin(avoidanceAngle) * avoidanceWeight;
var avoidVecY = -Math.cos(avoidanceAngle) * avoidanceWeight;
var blendedVecX = evasionVecX + avoidVecX;
var blendedVecY = evasionVecY + avoidVecY;
aiTargetRotation = Math.atan2(blendedVecX, -blendedVecY);
}
} else {
// Enhanced Free roam logic with dynamic behavior patterns
if (typeof enemyCar.freeRoamAngle === "undefined" || enemyCar.aiModeTimer === 0) {
// Initialize free roam variables
enemyCar.freeRoamAngle = Math.random() * Math.PI * 2;
enemyCar.freeRoamSpeed = maxSpeed * (0.4 + Math.random() * 0.4); // 40%-80% of max speed
enemyCar.freeRoamTimer = 0;
enemyCar.freeRoamDirectionTimer = 0;
enemyCar.freeRoamPattern = Math.floor(Math.random() * 3); // 0=wander, 1=circular, 2=aggressive
}
// Dynamic free roam behavior - change direction periodically for more interesting movement
enemyCar.freeRoamTimer++;
enemyCar.freeRoamDirectionTimer++;
// Change direction every 1-3 seconds based on pattern
var directionChangeInterval;
switch (enemyCar.freeRoamPattern) {
case 0:
// Wander pattern - gentle direction changes
directionChangeInterval = 90 + Math.floor(Math.random() * 60); // 1.5-2.5 seconds
break;
case 1:
// Circular pattern - more frequent turns
directionChangeInterval = 45 + Math.floor(Math.random() * 30); // 0.75-1.25 seconds
break;
case 2:
// Aggressive pattern - quick direction changes
directionChangeInterval = 30 + Math.floor(Math.random() * 40); // 0.5-1.17 seconds
break;
}
if (enemyCar.freeRoamDirectionTimer > directionChangeInterval) {
enemyCar.freeRoamDirectionTimer = 0;
// Different behavior patterns for more variety
switch (enemyCar.freeRoamPattern) {
case 0:
// Wander - small random direction changes
var angleChange = (Math.random() - 0.5) * Math.PI * 0.6; // ±54 degrees
enemyCar.freeRoamAngle += angleChange;
enemyCar.freeRoamSpeed = maxSpeed * (0.3 + Math.random() * 0.4);
break;
case 1:
// Circular - tends to turn in one direction
if (typeof enemyCar.circularDirection === "undefined") {
enemyCar.circularDirection = Math.random() < 0.5 ? -1 : 1;
}
var circularTurn = enemyCar.circularDirection * (Math.PI * 0.3 + Math.random() * Math.PI * 0.4); // 54-126 degrees
enemyCar.freeRoamAngle += circularTurn;
enemyCar.freeRoamSpeed = maxSpeed * (0.5 + Math.random() * 0.3);
// Occasionally reverse direction
if (Math.random() < 0.15) enemyCar.circularDirection *= -1;
break;
case 2:
// Aggressive - sharp turns and speed changes
var aggressiveTurn = (Math.random() - 0.5) * Math.PI * 1.2; // ±108 degrees
enemyCar.freeRoamAngle += aggressiveTurn;
enemyCar.freeRoamSpeed = maxSpeed * (0.6 + Math.random() * 0.3);
break;
}
}
// Add some target awareness even in free roam - occasionally look towards closest target
var closestTarget = null;
var closestDistance = Infinity;
// Check distance to player
var playerDistance = Math.sqrt((carPlayer.x - enemyCar.x) * (carPlayer.x - enemyCar.x) + (carPlayer.y - enemyCar.y) * (carPlayer.y - enemyCar.y));
if (playerDistance < closestDistance) {
closestDistance = playerDistance;
closestTarget = carPlayer;
}
// Check distance to other enemy cars
for (var nearIdx = 0; nearIdx < enemyCars.length; nearIdx++) {
if (nearIdx !== aiCarIdx) {
var otherCar = enemyCars[nearIdx];
var otherDistance = Math.sqrt((otherCar.x - enemyCar.x) * (otherCar.x - enemyCar.x) + (otherCar.y - enemyCar.y) * (otherCar.y - enemyCar.y));
if (otherDistance < closestDistance) {
closestDistance = otherDistance;
closestTarget = otherCar;
}
}
}
var targetInfluence = 0;
if (closestTarget && closestDistance < 400) {
// Within 400 pixels
// Closer target = more influence on direction
targetInfluence = Math.max(0, (400 - closestDistance) / 400 * 0.3); // Up to 30% influence
if (Math.random() < 0.02) {
// 2% chance per frame to look at closest target
var targetAngle = Math.atan2(closestTarget.x - enemyCar.x, -(closestTarget.y - enemyCar.y));
// Blend current angle with target angle
var currentVecX = Math.sin(enemyCar.freeRoamAngle);
var currentVecY = -Math.cos(enemyCar.freeRoamAngle);
var targetVecX = Math.sin(targetAngle) * targetInfluence;
var targetVecY = -Math.cos(targetAngle) * targetInfluence;
var blendedVecX = currentVecX * (1 - targetInfluence) + targetVecX;
var blendedVecY = currentVecY * (1 - targetInfluence) + targetVecY;
enemyCar.freeRoamAngle = Math.atan2(blendedVecX, -blendedVecY);
}
}
aiTargetRotation = enemyCar.freeRoamAngle;
aiPower = 1;
aiTargetVelocity = enemyCar.freeRoamSpeed;
// Apply edge avoidance to free roam mode with stronger influence
if (edgeAvoidanceX !== 0 || edgeAvoidanceY !== 0) {
// Calculate edge avoidance angle
var avoidanceAngle = Math.atan2(edgeAvoidanceX, -edgeAvoidanceY);
// Much stronger avoidance in free roam mode
var avoidanceWeight = Math.min(0.85, (Math.abs(edgeAvoidanceX) + Math.abs(edgeAvoidanceY)) * 1.5);
var roamWeight = 1 - avoidanceWeight;
// Convert angles to vectors for blending
var roamVecX = Math.sin(aiTargetRotation) * roamWeight;
var roamVecY = -Math.cos(aiTargetRotation) * roamWeight;
var avoidVecX = Math.sin(avoidanceAngle) * avoidanceWeight;
var avoidVecY = -Math.cos(avoidanceAngle) * avoidanceWeight;
// Combine and convert back to angle
var blendedVecX = roamVecX + avoidVecX;
var blendedVecY = roamVecY + avoidVecY;
aiTargetRotation = Math.atan2(blendedVecX, -blendedVecY);
// Update the free roam angle to new direction after avoidance
enemyCar.freeRoamAngle = aiTargetRotation;
}
}
// AI: Smoothly rotate enemy car towards target
if (typeof enemyCar.aiRotation === "undefined") enemyCar.aiRotation = enemyCar.rotation;
var aiRotationDelta = aiTargetRotation - enemyCar.aiRotation;
while (aiRotationDelta > Math.PI) aiRotationDelta -= 2 * Math.PI;
while (aiRotationDelta < -Math.PI) aiRotationDelta += 2 * Math.PI;
var aiRotationSpeed = baseRotationSpeed * 0.7; // Slightly slower turning for AI
enemyCar.aiRotation += aiRotationDelta * aiRotationSpeed;
enemyCar.rotation = enemyCar.aiRotation;
// AI: Velocity logic
if (typeof enemyCar.aiCurrentVelocity === "undefined") enemyCar.aiCurrentVelocity = 0;
if (aiPower > 0.1) {
// Accelerate
var aiVelocityDiff = aiTargetVelocity - enemyCar.aiCurrentVelocity;
var aiAccelerationRate = 0.003;
enemyCar.aiCurrentVelocity += aiVelocityDiff * aiAccelerationRate;
} else {
// Decelerate
var aiDecelerationRate = 0.045;
enemyCar.aiCurrentVelocity *= 1 - aiDecelerationRate;
if (Math.abs(enemyCar.aiCurrentVelocity) < 0.1) enemyCar.aiCurrentVelocity = 0;
}
enemyCar.aiCurrentVelocity = Math.min(enemyCar.aiCurrentVelocity, maxSpeed * 0.92);
// AI: Intended movement direction
var aiIntendedMoveX = Math.sin(enemyCar.aiRotation) * enemyCar.aiCurrentVelocity;
var aiIntendedMoveY = -Math.cos(enemyCar.aiRotation) * enemyCar.aiCurrentVelocity;
// AI: Drift physics for enemy car
if (typeof enemyCar.aiVelocityX === "undefined") enemyCar.aiVelocityX = 0;
if (typeof enemyCar.aiVelocityY === "undefined") enemyCar.aiVelocityY = 0;
enemyCar.aiVelocityX = enemyCar.aiVelocityX * driftFactor + aiIntendedMoveX * gripFactor;
enemyCar.aiVelocityY = enemyCar.aiVelocityY * driftFactor + aiIntendedMoveY * gripFactor;
// AI: Apply friction and update position
enemyCar.aiVelocityX *= 0.98;
enemyCar.aiVelocityY *= 0.98;
enemyCar.x += enemyCar.aiVelocityX + enemyCar.velocityX;
enemyCar.y += enemyCar.aiVelocityY + enemyCar.velocityY;
// --- End Enemy Car AI Movement Logic ---
// Apply smooth braking to enemy car after collision for more natural deceleration
if (!enemyCar.smoothBraking) enemyCar.smoothBraking = false;
if (!enemyCar.brakeFrames) enemyCar.brakeFrames = 0;
var currentEnemySpeed = Math.sqrt(enemyCar.velocityX * enemyCar.velocityX + enemyCar.velocityY * enemyCar.velocityY);
// If enemy car was just launched (high velocity), enable smooth braking
if (currentEnemySpeed > maxSpeed * 0.6 && !enemyCar.smoothBraking) {
enemyCar.smoothBraking = true;
enemyCar.brakeFrames = 0;
}
// Smooth braking logic: apply a gentle, progressive friction for a short period after being launched
if (enemyCar.smoothBraking) {
// Braking lasts for 18 frames (~0.3s at 60fps)
var brakeDuration = 18;
var brakeProgress = Math.min(1, enemyCar.brakeFrames / brakeDuration);
// Start with gentle friction, increase to normal friction
var minFriction = 0.96;
var maxFriction = 0.92 - currentEnemySpeed / maxSpeed * 0.05;
var frictionRate = minFriction + (maxFriction - minFriction) * brakeProgress;
enemyCar.velocityX *= frictionRate;
enemyCar.velocityY *= frictionRate;
enemyCar.brakeFrames++;
if (enemyCar.brakeFrames >= brakeDuration) {
enemyCar.smoothBraking = false;
}
} else if (currentEnemySpeed > 0.1) {
// Normal progressive friction
var frictionRate = 0.92 - currentEnemySpeed / maxSpeed * 0.05; // More friction at higher speeds
enemyCar.velocityX *= frictionRate;
enemyCar.velocityY *= frictionRate;
} else {
// Stop very slow movement to prevent endless drift
enemyCar.velocityX = 0;
enemyCar.velocityY = 0;
}
// Keep enemy car within bounds
var enemyHalfWidth = 32;
var enemyHalfHeight = 47;
if (enemyCar.x < enemyHalfWidth) {
enemyCar.x = enemyHalfWidth;
enemyCar.velocityX = -enemyCar.velocityX * 0.6;
}
if (enemyCar.x > 2048 - enemyHalfWidth) {
enemyCar.x = 2048 - enemyHalfWidth;
enemyCar.velocityX = -enemyCar.velocityX * 0.6;
}
if (enemyCar.y < enemyHalfHeight) {
enemyCar.y = enemyHalfHeight;
enemyCar.velocityY = -enemyCar.velocityY * 0.6;
}
if (enemyCar.y > 2186 - enemyHalfHeight) {
enemyCar.y = 2186 - enemyHalfHeight;
enemyCar.velocityY = -enemyCar.velocityY * 0.6;
}
}
// --- End Enemy Cars AI Movement Logic ---
// Add collision detection between enemy cars (optimized - only check every 3rd frame)
if (LK.ticks % 3 === 0) {
for (var carA = 0; carA < enemyCars.length; carA++) {
for (var carB = carA + 1; carB < enemyCars.length; carB++) {
var enemyCarA = enemyCars[carA];
var enemyCarB = enemyCars[carB];
// Initialize collision tracking if not exists
if (!enemyCarA.enemyColliding) enemyCarA.enemyColliding = [];
if (!enemyCarB.enemyColliding) enemyCarB.enemyColliding = [];
while (enemyCarA.enemyColliding.length <= carB) enemyCarA.enemyColliding.push(false);
while (enemyCarB.enemyColliding.length <= carA) enemyCarB.enemyColliding.push(false);
// Calculate collision boxes for both cars
var carALeft = enemyCarA.x - enemyCollisionWidth / 2;
var carARight = enemyCarA.x + enemyCollisionWidth / 2;
var carATop = enemyCarA.y - enemyCollisionHeight / 2;
var carABottom = enemyCarA.y + enemyCollisionHeight / 2;
var carBLeft = enemyCarB.x - enemyCollisionWidth / 2;
var carBRight = enemyCarB.x + enemyCollisionWidth / 2;
var carBTop = enemyCarB.y - enemyCollisionHeight / 2;
var carBBottom = enemyCarB.y + enemyCollisionHeight / 2;
// Check for collision
var currentColliding = !(carARight < carBLeft || carALeft > carBRight || carABottom < carBTop || carATop > carBBottom);
if (!enemyCarA.enemyColliding[carB] && currentColliding) {
// Collision just started - determine collision responsibility for enemy cars
var carASpeedX = (enemyCarA.aiVelocityX || 0) + (enemyCarA.velocityX || 0);
var carASpeedY = (enemyCarA.aiVelocityY || 0) + (enemyCarA.velocityY || 0);
var carBSpeedX = (enemyCarB.aiVelocityX || 0) + (enemyCarB.velocityX || 0);
var carBSpeedY = (enemyCarB.aiVelocityY || 0) + (enemyCarB.velocityY || 0);
var carASpeed = Math.sqrt(carASpeedX * carASpeedX + carASpeedY * carASpeedY);
var carBSpeed = Math.sqrt(carBSpeedX * carBSpeedX + carBSpeedY * carBSpeedY);
// Calculate collision direction
var collisionDeltaX = enemyCarB.x - enemyCarA.x;
var collisionDeltaY = enemyCarB.y - enemyCarA.y;
var collisionDistance = Math.sqrt(collisionDeltaX * collisionDeltaX + collisionDeltaY * collisionDeltaY);
// Normalize collision direction
if (collisionDistance > 0) {
collisionDeltaX /= collisionDistance;
collisionDeltaY /= collisionDistance;
}
// Determine collision responsibility for enemy cars
var carACrasher = false;
var carBCrasher = false;
var bothEnemiesCrashed = false;
// Calculate approach vectors
var carAApproachX = 0,
carAApproachY = 0;
var carBApproachX = 0,
carBApproachY = 0;
if (carASpeed > 0.1) {
carAApproachX = carASpeedX / carASpeed;
carAApproachY = carASpeedY / carASpeed;
}
if (carBSpeed > 0.1) {
carBApproachX = carBSpeedX / carBSpeed;
carBApproachY = carBSpeedY / carBSpeed;
}
// Calculate how much each car is moving toward the collision
var carATowardCollision = 0;
var carBTowardCollision = 0;
if (carASpeed > 0.1) {
carATowardCollision = carAApproachX * collisionDeltaX + carAApproachY * collisionDeltaY;
}
if (carBSpeed > 0.1) {
carBTowardCollision = carBApproachX * -collisionDeltaX + carBApproachY * -collisionDeltaY;
}
// Speed thresholds for determining crasher responsibility
var minCrasherSpeed = maxSpeed * 0.15;
var dominantCrasherThreshold = 0.3;
// Determine responsibility
if (carASpeed > minCrasherSpeed && carBSpeed > minCrasherSpeed) {
if (carATowardCollision > dominantCrasherThreshold && carBTowardCollision > dominantCrasherThreshold) {
bothEnemiesCrashed = true;
} else if (carATowardCollision > carBTowardCollision + dominantCrasherThreshold) {
carACrasher = true;
} else if (carBTowardCollision > carATowardCollision + dominantCrasherThreshold) {
carBCrasher = true;
} else {
bothEnemiesCrashed = true;
}
} else if (carASpeed > minCrasherSpeed && carBSpeed <= minCrasherSpeed) {
carACrasher = true;
} else if (carBSpeed > minCrasherSpeed && carASpeed <= minCrasherSpeed) {
carBCrasher = true;
} else {
bothEnemiesCrashed = true;
}
// Calculate momentum transfer using conservation of momentum
var totalMass = enemyCarA.weight + enemyCarB.weight;
var massRatioA1 = (enemyCarA.weight - enemyCarB.weight) / totalMass;
var massRatioA2 = 2 * enemyCarB.weight / totalMass;
var massRatioB1 = 2 * enemyCarA.weight / totalMass;
var massRatioB2 = (enemyCarB.weight - enemyCarA.weight) / totalMass;
// Apply different energy loss based on collision responsibility
var relativeSpeed = Math.max(carASpeed, carBSpeed);
var minLoss, maxLoss;
var speedNorm = Math.min(1, relativeSpeed / maxSpeed);
if (bothEnemiesCrashed) {
minLoss = 0.18;
maxLoss = 0.60;
} else {
minLoss = 0.12;
maxLoss = 0.50;
}
var energyLoss = minLoss + (maxLoss - minLoss) * speedNorm;
var restitution = 1 - energyLoss;
// Apply different restitution based on responsibility
var carARestitution = restitution;
var carBRestitution = restitution;
if (carACrasher) {
carARestitution = restitution * 1.15; // Car A is crasher - less penalty
carBRestitution = restitution * 0.85; // Car B crashed into - more penalty
} else if (carBCrasher) {
carBRestitution = restitution * 1.15; // Car B is crasher - less penalty
carARestitution = restitution * 0.85; // Car A crashed into - more penalty
}
// Separate cars to prevent overlap
var separationDistance = 75;
var halfSeparation = separationDistance / 2;
enemyCarA.x = enemyCarA.x - collisionDeltaX * halfSeparation;
enemyCarA.y = enemyCarA.y - collisionDeltaY * halfSeparation;
enemyCarB.x = enemyCarB.x + collisionDeltaX * halfSeparation;
enemyCarB.y = enemyCarB.y + collisionDeltaY * halfSeparation;
// Calculate new velocities for car A
var carAImpactX = carASpeedX * massRatioA1 + carBSpeedX * massRatioA2;
var carAImpactY = carASpeedY * massRatioA1 + carBSpeedY * massRatioA2;
enemyCarA.velocityX = carAImpactX * carARestitution;
enemyCarA.velocityY = carAImpactY * carARestitution;
// Calculate new velocities for car B
var carBImpactX = carASpeedX * massRatioB1 + carBSpeedX * massRatioB2;
var carBImpactY = carASpeedY * massRatioB1 + carBSpeedY * massRatioB2;
enemyCarB.velocityX = carBImpactX * carBRestitution;
enemyCarB.velocityY = carBImpactY * carBRestitution;
// Add directional push based on collision
var pushIntensity = (carASpeed + carBSpeed) * (0.4 + 0.4 * speedNorm);
var carAPushRatio = enemyCarB.weight / totalMass;
var carBPushRatio = enemyCarA.weight / totalMass;
enemyCarA.velocityX += -collisionDeltaX * pushIntensity * carAPushRatio * 0.6;
enemyCarA.velocityY += -collisionDeltaY * pushIntensity * carAPushRatio * 0.6;
enemyCarB.velocityX += collisionDeltaX * pushIntensity * carBPushRatio * 0.6;
enemyCarB.velocityY += collisionDeltaY * pushIntensity * carBPushRatio * 0.6;
// Reset acceleration state based on responsibility
if (carACrasher) {
if (typeof enemyCarA.aiCurrentVelocity !== "undefined") {
enemyCarA.aiCurrentVelocity *= 0.3; // Car A is crasher - smaller penalty
}
} else {
if (typeof enemyCarA.aiCurrentVelocity !== "undefined") {
enemyCarA.aiCurrentVelocity = 0; // Car A crashed into or mutual - full reset
}
}
if (carBCrasher) {
if (typeof enemyCarB.aiCurrentVelocity !== "undefined") {
enemyCarB.aiCurrentVelocity *= 0.3; // Car B is crasher - smaller penalty
}
} else {
if (typeof enemyCarB.aiCurrentVelocity !== "undefined") {
enemyCarB.aiCurrentVelocity = 0; // Car B crashed into or mutual - full reset
}
}
// Visual feedback based on collision responsibility
if (bothEnemiesCrashed) {
// Both crashed - flash both in orange
LK.effects.flashObject(enemyCarA, 0xff8800, 300);
LK.effects.flashObject(enemyCarB, 0xff8800, 300);
} else if (carACrasher) {
// Car A crashed into car B - flash A red, B yellow
LK.effects.flashObject(enemyCarA, 0xff6600, 250);
LK.effects.flashObject(enemyCarB, 0xffdd00, 350);
} else if (carBCrasher) {
// Car B crashed into car A - flash B red, A yellow
LK.effects.flashObject(enemyCarB, 0xff6600, 250);
LK.effects.flashObject(enemyCarA, 0xffdd00, 350);
} else {
// Default fallback
LK.effects.flashObject(enemyCarA, 0xffaa00, 300);
LK.effects.flashObject(enemyCarB, 0xffaa00, 300);
}
}
// Update collision tracking
enemyCarA.enemyColliding[carB] = currentColliding;
enemyCarB.enemyColliding[carA] = currentColliding;
}
}
}
// Update player health bar scale
var healthPercentage = playerHealth / maxHealth;
healthBar.scaleX = 4 * healthPercentage;
// Update enemy car health bars position and scale
for (var healthIdx = 0; healthIdx < enemyCars.length; healthIdx++) {
var currentCar = enemyCars[healthIdx];
// Update health bar background position (above car)
currentCar.healthBarBg.x = currentCar.x;
currentCar.healthBarBg.y = currentCar.y - 60; // 60 pixels above car
// Update health bar position (above car)
currentCar.healthBar.x = currentCar.x;
currentCar.healthBar.y = currentCar.y - 60; // 60 pixels above car
// Update health bar scale based on car's health
var carHealthPercentage = currentCar.health / maxHealth;
currentCar.healthBar.scaleX = 1.5 * carHealthPercentage;
}
// Update speed display
var totalSpeed = Math.sqrt(velocityX * velocityX + velocityY * velocityY);
speedText.setText('Speed: ' + Math.round(totalSpeed));
};