User prompt
Haz más larga la barra y súbelo hasta arriba de UI con un margen de 20pixeles
User prompt
Haz más larga la barra súbelo hasta arriba de UI y agrega por debajo en forma de columna una barra para cada auto con su color ↪💡 Consider importing and using the following plugins: @upit/tween.v1
User prompt
Agrega a la derecha de UI una barra para mostrar la vida del jugador (tiñe la barra del color del auto) sin texto ↪💡 Consider importing and using the following plugins: @upit/tween.v1
User prompt
Please fix the bug: 'ReferenceError: healthTexts is not defined' in or related to this line: 'healthTexts[0].setText('Player: ' + playerHealth);' Line Number: 2386
User prompt
Agrega a la derecha de Ui una interfaz, en columna, con barras que representa la vida de cada auto (tiñe la barra con el color del auto) el jugador siempre por encima ↪💡 Consider importing and using the following plugins: @upit/tween.v1
User prompt
Agrega a la izquierda de Ui una interfaz en columna con barra de la vida de cada enemigo (del color auto) el jugador siempre por encima
User prompt
Agrega variable de vida para jugador y enemigos (aún sin código) máximo de 100
User prompt
Haz que se pueda determinar el auto chocado, el que choca (si se golpean los dos desde frentes ambos se chocan entre si) el auto que choca tiene menos penalización
User prompt
Please fix the bug: 'TypeError: Cannot read properties of undefined (reading 'length')' in or related to this line: 'for (var isolIdx = 0; isolIdx < potentialTargets.length; isolIdx++) {' Line Number: 1187
User prompt
Agrega ma spreferencias a las IA: agresivo (ataca a todos), evasivo (esperar al momento), individualista (busca objetivos solitarios)
User prompt
Agrega preferencia de uida a la IA
User prompt
Mejora la IA detectando cuando está siendo perseguido así determinar si elige: uir (buscando velocidad, escapando en zigzag o círculos) o darle la vuelta a la situación persiguiendo el al enemigo
User prompt
Haz que la IA tienda a buscar más aceleración antes de intentar pegar
User prompt
Haz que el auto chocado pierda su velocidad y reinicie su aceleración
User prompt
Haz que el modo de persecusión sea individual y cada auto tenga sus preferencias de tacticas
User prompt
Optimiza el juego, suele haber tirons de fps
User prompt
Haz que el auto objetivo no sea el jugador si no cualquiera teniendo en cuenta su posición y velocidad
User prompt
Haz que un todo contra todos
User prompt
Please fix the bug: 'TypeError: Cannot read properties of null (reading 'x')' in or related to this line: 'targetX = enemyCar.targetCar.x;' Line Number: 920
User prompt
Haz que cada auto tenga su auto objetivo según su preferencia de velocidad y distancia
User prompt
Haz que cada auto tenga sus propias preferencias y modo de ataque
User prompt
Agrega colisión entre los autos
User prompt
Haz que puedan aparecer entre 3 a 5 autos IA
User prompt
Agrega un segundo auto IA
User prompt
Haz que la IA sepa cuando puede ir recto sin girar para golpear al objetivo así ir más rapido
/**** * 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 enemy cars array to manage multiple AI cars var enemyCars = []; // Helper function to create and spawn an enemy car function spawnEnemyCar() { 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: // 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 gameplayBackground.addChild(enemyCar); return enemyCar; } // Create first enemy car var enemyCar = spawnEnemyCar(); enemyCars.push(enemyCar); // Create second enemy car var enemyCar2 = spawnEnemyCar(); enemyCars.push(enemyCar2); // Create UI background - 1/5 of screen height (bottom portion) var uiBackground = game.attachAsset('uiBg', { x: 0, y: 2186, anchorX: 0, anchorY: 0 }); // 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 // 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 }); } } } // Emit particles for player car emitCarParticles(carPlayer, velocityX, velocityY, carPlayer.rotation, maxSpeed, gameplayBackground); // Emit particles for all enemy cars (use AI velocity + collision velocity) for (var e = 0; e < enemyCars.length; e++) { var currentEnemyCar = enemyCars[e]; var enemyTotalVX = (typeof currentEnemyCar.aiVelocityX !== "undefined" ? currentEnemyCar.aiVelocityX : 0) + (typeof currentEnemyCar.velocityX !== "undefined" ? currentEnemyCar.velocityX : 0); var enemyTotalVY = (typeof currentEnemyCar.aiVelocityY !== "undefined" ? currentEnemyCar.aiVelocityY : 0) + (typeof currentEnemyCar.velocityY !== "undefined" ? currentEnemyCar.velocityY : 0); emitCarParticles(currentEnemyCar, enemyTotalVX, enemyTotalVY, currentEnemyCar.rotation, maxSpeed, gameplayBackground); } // Update and clean up particles for (var i = game.particles.length - 1; i >= 0; i--) { var particle = game.particles[i]; if (particle.age >= particle.lifespan) { particle.destroy(); game.particles.splice(i, 1); } } // Check collision between player car and all enemy cars using smaller collision boxes if (!carPlayer.lastColliding) carPlayer.lastColliding = []; // Initialize lastColliding array for all enemy 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) 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 // Calculate player collision box var playerLeft = carPlayer.x - playerCollisionWidth / 2; var playerRight = carPlayer.x + playerCollisionWidth / 2; var playerTop = carPlayer.y - playerCollisionHeight / 2; var playerBottom = carPlayer.y + playerCollisionHeight / 2; // Check collision with each enemy car for (var ec = 0; ec < enemyCars.length; ec++) { var currentEnemyCar = enemyCars[ec]; // Calculate enemy collision box var enemyLeft = currentEnemyCar.x - enemyCollisionWidth / 2; var enemyRight = currentEnemyCar.x + enemyCollisionWidth / 2; var enemyTop = currentEnemyCar.y - enemyCollisionHeight / 2; var enemyBottom = currentEnemyCar.y + enemyCollisionHeight / 2; // More precise collision detection using smaller bounding boxes var currentColliding = !(playerRight < enemyLeft || playerLeft > enemyRight || playerBottom < enemyTop || playerTop > enemyBottom); if (!carPlayer.lastColliding[ec] && currentColliding) { // Collision just started - calculate collision physics with mass 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; } // Calculate momentum transfer using conservation of momentum // m1*v1 + m2*v2 = m1*v1' + m2*v2' (simplified elastic collision) 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; // Make energy loss proportional to current speed for more natural and realistic crash // At low speed, little loss; at high speed, much more loss var relativeSpeed = Math.max(playerSpeed, enemySpeed); var minLoss = 0.18; // minimum energy loss at very low speed var maxLoss = 0.65; // maximum energy loss at very high speed var speedNorm = Math.min(1, relativeSpeed / maxSpeed); // 0 to 1 var energyLoss = minLoss + (maxLoss - minLoss) * speedNorm; var restitution = 1 - energyLoss; // Separate cars to prevent overlap (less exaggerated separation) var separationDistance = 70; // Reduced from 110 for more natural push carPlayer.x = currentEnemyCar.x - collisionDeltaX * separationDistance; carPlayer.y = currentEnemyCar.y - collisionDeltaY * separationDistance; // Calculate new velocities based on mass and current motion // Player car velocity change (heavier cars are less affected) var playerImpactX = velocityX * massRatio1 + currentEnemyCar.velocityX * massRatio2; var playerImpactY = velocityY * massRatio1 + currentEnemyCar.velocityY * massRatio2; velocityX = playerImpactX * restitution; velocityY = playerImpactY * restitution; // Enemy car velocity change (lighter cars get pushed more) var enemyImpactX = velocityX * massRatio3 + currentEnemyCar.velocityX * massRatio4; var enemyImpactY = velocityY * massRatio3 + currentEnemyCar.velocityY * massRatio4; currentEnemyCar.velocityX = enemyImpactX * restitution; currentEnemyCar.velocityY = enemyImpactY * restitution; // --- NEW: Launch enemy car away from collision, push effect depends on collision speed --- var launchSpeed = Math.max(playerSpeed, enemySpeed); // Calculate push multiplier: gentle at low speed, exaggerated at high speed // At 0 speed: 0.4x, at maxSpeed: 1.5x (tunable) var minPush = 0.4; var maxPush = 1.5; var pushMultiplier = minPush + (maxPush - minPush) * speedNorm; // Direction from player to enemy (so enemy is launched away from player) var launchDirX = currentEnemyCar.x - carPlayer.x; var launchDirY = currentEnemyCar.y - carPlayer.y; var launchDist = Math.sqrt(launchDirX * launchDirX + launchDirY * launchDirY); // If the direction is too small (almost zero), use the player's movement direction if (launchDist < 0.01) { // Use the normalized velocity of the player (if moving) var playerMoveNorm = Math.sqrt(velocityX * velocityX + velocityY * velocityY); if (playerMoveNorm > 0.01) { launchDirX = velocityX / playerMoveNorm; launchDirY = velocityY / playerMoveNorm; } else { // Default to up if both are zero launchDirX = 0; launchDirY = -1; } } else { launchDirX /= launchDist; launchDirY /= launchDist; } // Add launch velocity proportional to collision speed and pushMultiplier currentEnemyCar.velocityX += launchDirX * launchSpeed * pushMultiplier; currentEnemyCar.velocityY += launchDirY * launchSpeed * pushMultiplier; // --- END NEW --- // Add directional push based on collision angle and relative mass var pushIntensity = (playerSpeed + enemySpeed) * (0.5 + 0.5 * speedNorm); // More push at higher speed, less at low var playerPushRatio = currentEnemyCar.weight / totalMass; // Heavier enemy = more push on player var enemyPushRatio = playerCarWeight / totalMass; // Heavier player = more push on enemy // Apply directional collision forces (less exaggerated, but still speed-dependent) 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 interpolation so acceleration after crash feels like at driving start, but more severe at high speed var postCrashSpeed = Math.sqrt(velocityX * velocityX + velocityY * velocityY); // If the crash was at high speed, reduce the post-collision velocity more (stronger reset) if (relativeSpeed > maxSpeed * 0.7) { // At very high speed, reduce velocity to 40% of post-collision value currentVelocity = postCrashSpeed * 0.4; } else if (relativeSpeed > maxSpeed * 0.4) { // At medium speed, reduce velocity to 70% of post-collision value currentVelocity = postCrashSpeed * 0.7; } else { // At low speed, keep as before currentVelocity = postCrashSpeed; } // Visual feedback for collision LK.effects.flashObject(carPlayer, 0xff0000, 500); } // Update last collision state for this enemy car carPlayer.lastColliding[ec] = currentColliding; } // --- Enemy Cars AI Movement Logic --- // Process AI for each enemy car for (var ai = 0; ai < enemyCars.length; ai++) { var currentEnemyCar = enemyCars[ai]; // Enemy car can switch between 'follow player' and 'free roam' AI modes if (typeof currentEnemyCar.aiMode === "undefined") currentEnemyCar.aiMode = "follow"; if (typeof currentEnemyCar.aiModeTimer === "undefined") currentEnemyCar.aiModeTimer = 0; if (typeof currentEnemyCar.aiModeDuration === "undefined") currentEnemyCar.aiModeDuration = 0; // AI mode switching logic: change mode at intervals with speed-based preferences enemyCar.aiModeTimer++; if (enemyCar.aiModeTimer > enemyCar.aiModeDuration) { // Calculate target speed for mode preference var targetSpeed = Math.sqrt(velocityX * velocityX + velocityY * 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 } // Adaptive strategy selection based on target speed and position enemyCar.followStrategyTimer++; if (enemyCar.followStrategyTimer > enemyCar.followStrategyDuration) { // Calculate target speed and relative position for strategy selection var targetSpeed = Math.sqrt(velocityX * velocityX + velocityY * 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 // Strategy selection with strong speed-based preferences: // DIRECT: Strongly preferred for slow targets (< 30% speed) // VARIED: For medium speeds and tactical situations // AMBUSH: Strongly preferred for fast targets (> 60% speed) if (targetSpeedRatio < 0.3) { // Slow target - strongly prefer direct pursuit (85% chance) if (Math.random() < 0.85) { enemyCar.followStrategy = 0; } else { enemyCar.followStrategy = 1; // Occasional varied approach } } else if (targetSpeedRatio > 0.6) { // Fast target - strongly prefer ambush tactics (80% chance) if (Math.random() < 0.8) { enemyCar.followStrategy = 2; // Ambush for interception } else { enemyCar.followStrategy = 1; // Occasional varied approach } } else if (targetSpeedRatio < 0.05) { // Target is nearly stationary - use intimidation system // Initialize intimidation counter if not set if (typeof enemyCar.intimidationCount === "undefined") { enemyCar.intimidationCount = 0; enemyCar.maxIntimidations = 1 + Math.floor(Math.random() * 3); // 1-3 intimidations } // Check if we've completed enough intimidations if (enemyCar.intimidationCount >= enemyCar.maxIntimidations) { // Switch to direct attack after intimidating enough times enemyCar.followStrategy = 0; // Direct pursuit for attack } else { // Use varied mode for intimidation (doesn't seek collision) enemyCar.followStrategy = 1; // Intimidation approach } } else if (isTargetBehind || relativeDistance < 200) { // Target behind or very close - use varied approach for positioning enemyCar.followStrategy = 1; } else if (isTargetAhead && relativeDistance < 300) { // Target ahead and close - direct pursuit enemyCar.followStrategy = 0; } else { // Medium speed, medium distance - balanced approach favoring strategy based on speed if (targetSpeedRatio > 0.45) { // Lean towards ambush for faster medium speeds enemyCar.followStrategy = Math.random() < 0.6 ? 2 : 1; } else { // Lean towards direct for slower medium speeds enemyCar.followStrategy = Math.random() < 0.6 ? 0 : 1; } } // Adjust duration based on selected strategy switch (enemyCar.followStrategy) { case 0: // Direct - shorter duration for quick decisions enemyCar.followStrategyDuration = 45 + Math.floor(Math.random() * 45); break; case 1: // Varied - medium duration for tactical maneuvering enemyCar.followStrategyDuration = 60 + Math.floor(Math.random() * 60); break; case 2: // Ambush - longer duration for prediction accuracy enemyCar.followStrategyDuration = 90 + Math.floor(Math.random() * 60); break; } enemyCar.followStrategyTimer = 0; } // Calculate base values aiDeltaX = carPlayer.x - enemyCar.x; aiDeltaY = carPlayer.y - enemyCar.y; aiDistance = Math.sqrt(aiDeltaX * aiDeltaX + aiDeltaY * aiDeltaY); // Calculate relative velocity and approach angle for smarter pursuit var relativeVelX = velocityX - (enemyCar.aiVelocityX || 0); var relativeVelY = velocityY - (enemyCar.aiVelocityY || 0); 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 = Math.sqrt(velocityX * velocityX + velocityY * 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 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 if (aiDistance < 120 && !enemyCar.circlingDetected) { // Too close - back off slightly at an angle var backoffAngle = approachAngle + Math.PI + (Math.random() - 0.5) * Math.PI * 0.5; aiTargetRotation = backoffAngle; aiPower = 0.3; aiTargetVelocity = maxSpeed * aiPower * 0.6; } else if (aiDistance > 500) { // 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 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 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 (aiDistance < 180) { // Intimidation range - follow closely but maintain threatening distance // Stay close enough to scare but far enough to avoid collision var intimidationDistance = 150; // Target distance for intimidation var distanceError = aiDistance - intimidationDistance; if (Math.abs(distanceError) < 30) { // Perfect intimidation distance - match player direction but slightly offset var intimidationOffset = Math.sin(LK.ticks * 0.05) * (Math.PI * 0.15); // Subtle weaving ±27 degrees aiTargetRotation = approachAngle + intimidationOffset; aiPower = 0.7; aiTargetVelocity = maxSpeed * aiPower * 0.85; // Check if target is stationary and we're intimidating var currentTargetSpeed = Math.sqrt(velocityX * velocityX + velocityY * velocityY); if (currentTargetSpeed < maxSpeed * 0.05 && typeof enemyCar.intimidationCount !== "undefined") { // Mark intimidation as successful after maintaining perfect distance for a while if (typeof enemyCar.intimidationTimer === "undefined") enemyCar.intimidationTimer = 0; enemyCar.intimidationTimer++; // Complete intimidation after 2 seconds of perfect positioning if (enemyCar.intimidationTimer > 120) { enemyCar.intimidationCount++; enemyCar.intimidationTimer = 0; // Force strategy recalculation enemyCar.followStrategyTimer = enemyCar.followStrategyDuration + 1; } } } else if (distanceError < 0) { // Too close - back off while still facing player aiTargetRotation = approachAngle + Math.PI + (Math.random() - 0.5) * Math.PI * 0.3; aiPower = 0.4; aiTargetVelocity = maxSpeed * aiPower * 0.6; } else { // Too far - approach for intimidation aiTargetRotation = approachAngle; aiPower = 0.8; aiTargetVelocity = maxSpeed * aiPower * 0.9; } } else { // Long range - approach like direct pursuit for intimidation setup aiTargetRotation = approachAngle; aiPower = Math.min(1, aiDistance / 500); aiTargetVelocity = maxSpeed * aiPower * 0.92; } break; case 2: // Ambush - predict player's future position with smarter interception var playerVelX = velocityX; var playerVelY = velocityY; var playerSpeed = Math.sqrt(playerVelX * playerVelX + playerVelY * playerVelY); 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 (aiDistance < 150) { // Close range - position for optimal angle of attack var attackAngle = approachAngle + Math.PI * 0.4 * (angleDiff > 0 ? 1 : -1); aiTargetRotation = attackAngle; aiPower = 0.7; aiTargetVelocity = maxSpeed * aiPower * 0.8; } else { // Long range interception var predictionTime = Math.min(2.5, aiDistance / maxSpeed * 0.9); var interceptX = carPlayer.x + playerVelX * predictionTime; var interceptY = carPlayer.y + playerVelY * 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); // Lead the target slightly based on relative speeds var speedRatio = (enemyCar.aiCurrentVelocity || 0) / Math.max(0.1, playerSpeed); var leadAdjustment = (1 - speedRatio) * 0.3; aiTargetRotation = Math.atan2(interceptDeltaX, -interceptDeltaY); aiPower = Math.min(1, interceptDistance / 700); var speedBoost = Math.min(0.2, playerSpeed / 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 { // 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 player awareness even in free roam - occasionally look towards player var playerDistance = Math.sqrt((carPlayer.x - enemyCar.x) * (carPlayer.x - enemyCar.x) + (carPlayer.y - enemyCar.y) * (carPlayer.y - enemyCar.y)); var playerInfluence = 0; if (playerDistance < 400) { // Within 400 pixels // Closer player = more influence on direction playerInfluence = Math.max(0, (400 - playerDistance) / 400 * 0.3); // Up to 30% influence if (Math.random() < 0.02) { // 2% chance per frame to look at player var playerAngle = Math.atan2(carPlayer.x - enemyCar.x, -(carPlayer.y - enemyCar.y)); // Blend current angle with player angle var currentVecX = Math.sin(enemyCar.freeRoamAngle); var currentVecY = -Math.cos(enemyCar.freeRoamAngle); var playerVecX = Math.sin(playerAngle) * playerInfluence; var playerVecY = -Math.cos(playerAngle) * playerInfluence; var blendedVecX = currentVecX * (1 - playerInfluence) + playerVecX; var blendedVecY = currentVecY * (1 - playerInfluence) + playerVecY; 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 (currentEnemyCar.x < enemyHalfWidth) { currentEnemyCar.x = enemyHalfWidth; currentEnemyCar.velocityX = -currentEnemyCar.velocityX * 0.6; } if (currentEnemyCar.x > 2048 - enemyHalfWidth) { currentEnemyCar.x = 2048 - enemyHalfWidth; currentEnemyCar.velocityX = -currentEnemyCar.velocityX * 0.6; } if (currentEnemyCar.y < enemyHalfHeight) { currentEnemyCar.y = enemyHalfHeight; currentEnemyCar.velocityY = -currentEnemyCar.velocityY * 0.6; } if (currentEnemyCar.y > 2186 - enemyHalfHeight) { currentEnemyCar.y = 2186 - enemyHalfHeight; currentEnemyCar.velocityY = -currentEnemyCar.velocityY * 0.6; } } // End enemy cars AI loop // --- End Enemy Cars AI Movement Logic --- // Update speed display var totalSpeed = Math.sqrt(velocityX * velocityX + velocityY * velocityY); speedText.setText('Speed: ' + Math.round(totalSpeed)); };
===================================================================
--- original.js
+++ change.js
@@ -102,41 +102,52 @@
// Position in lower portion of gameplay area
anchorX: 0.5,
anchorY: 0.5
});
-// Create enemy car in gameplay area at random position
-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:
- // 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;
+// Create enemy cars array to manage multiple AI cars
+var enemyCars = [];
+// Helper function to create and spawn an enemy car
+function spawnEnemyCar() {
+ 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:
+ // 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
+ gameplayBackground.addChild(enemyCar);
+ return enemyCar;
}
-// 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
-gameplayBackground.addChild(enemyCar);
+// Create first enemy car
+var enemyCar = spawnEnemyCar();
+enemyCars.push(enemyCar);
+// Create second enemy car
+var enemyCar2 = spawnEnemyCar();
+enemyCars.push(enemyCar2);
// Create UI background - 1/5 of screen height (bottom portion)
var uiBackground = game.attachAsset('uiBg', {
x: 0,
y: 2186,
@@ -446,734 +457,751 @@
}
}
// Emit particles for player car
emitCarParticles(carPlayer, velocityX, velocityY, carPlayer.rotation, maxSpeed, gameplayBackground);
- // Emit particles for enemy car (use AI velocity + collision velocity)
- var enemyTotalVX = (typeof enemyCar.aiVelocityX !== "undefined" ? enemyCar.aiVelocityX : 0) + (typeof enemyCar.velocityX !== "undefined" ? enemyCar.velocityX : 0);
- var enemyTotalVY = (typeof enemyCar.aiVelocityY !== "undefined" ? enemyCar.aiVelocityY : 0) + (typeof enemyCar.velocityY !== "undefined" ? enemyCar.velocityY : 0);
- emitCarParticles(enemyCar, enemyTotalVX, enemyTotalVY, enemyCar.rotation, maxSpeed, gameplayBackground);
+ // Emit particles for all enemy cars (use AI velocity + collision velocity)
+ for (var e = 0; e < enemyCars.length; e++) {
+ var currentEnemyCar = enemyCars[e];
+ var enemyTotalVX = (typeof currentEnemyCar.aiVelocityX !== "undefined" ? currentEnemyCar.aiVelocityX : 0) + (typeof currentEnemyCar.velocityX !== "undefined" ? currentEnemyCar.velocityX : 0);
+ var enemyTotalVY = (typeof currentEnemyCar.aiVelocityY !== "undefined" ? currentEnemyCar.aiVelocityY : 0) + (typeof currentEnemyCar.velocityY !== "undefined" ? currentEnemyCar.velocityY : 0);
+ emitCarParticles(currentEnemyCar, enemyTotalVX, enemyTotalVY, currentEnemyCar.rotation, maxSpeed, gameplayBackground);
+ }
// Update and clean up particles
for (var i = game.particles.length - 1; i >= 0; i--) {
var particle = game.particles[i];
if (particle.age >= particle.lifespan) {
particle.destroy();
game.particles.splice(i, 1);
}
}
- // Check collision between player car and enemy car using smaller collision boxes
- if (!carPlayer.lastColliding) carPlayer.lastColliding = false;
+ // Check collision between player car and all enemy cars using smaller collision boxes
+ if (!carPlayer.lastColliding) carPlayer.lastColliding = [];
+ // Initialize lastColliding array for all enemy 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)
var playerCollisionWidth = 38; // Reduced from 64px width
var playerCollisionHeight = 57; // Reduced from ~95px height
- var enemyCollisionWidth = 38; // Reduced from 64px width
+ var enemyCollisionWidth = 38; // Reduced from 64px width
var enemyCollisionHeight = 57; // Reduced from ~94px height
- // Calculate collision boxes centers
+ // Calculate player collision box
var playerLeft = carPlayer.x - playerCollisionWidth / 2;
var playerRight = carPlayer.x + playerCollisionWidth / 2;
var playerTop = carPlayer.y - playerCollisionHeight / 2;
var playerBottom = carPlayer.y + playerCollisionHeight / 2;
- var enemyLeft = enemyCar.x - enemyCollisionWidth / 2;
- var enemyRight = enemyCar.x + enemyCollisionWidth / 2;
- var enemyTop = enemyCar.y - enemyCollisionHeight / 2;
- var enemyBottom = enemyCar.y + enemyCollisionHeight / 2;
- // More precise collision detection using smaller bounding boxes
- var currentColliding = !(playerRight < enemyLeft || playerLeft > enemyRight || playerBottom < enemyTop || playerTop > enemyBottom);
- if (!carPlayer.lastColliding && currentColliding) {
- // Collision just started - calculate collision physics with mass
- var playerSpeed = Math.sqrt(velocityX * velocityX + velocityY * velocityY);
- var enemySpeed = Math.sqrt(enemyCar.velocityX * enemyCar.velocityX + enemyCar.velocityY * enemyCar.velocityY);
- // Calculate collision direction (from player car to enemy car)
- var collisionDeltaX = enemyCar.x - carPlayer.x;
- var collisionDeltaY = enemyCar.y - carPlayer.y;
- var collisionDistance = Math.sqrt(collisionDeltaX * collisionDeltaX + collisionDeltaY * collisionDeltaY);
- // Normalize collision direction
- if (collisionDistance > 0) {
- collisionDeltaX /= collisionDistance;
- collisionDeltaY /= collisionDistance;
- }
- // Calculate momentum transfer using conservation of momentum
- // m1*v1 + m2*v2 = m1*v1' + m2*v2' (simplified elastic collision)
- var totalMass = playerCarWeight + enemyCar.weight;
- var massRatio1 = (playerCarWeight - enemyCar.weight) / totalMass;
- var massRatio2 = 2 * enemyCar.weight / totalMass;
- var massRatio3 = 2 * playerCarWeight / totalMass;
- var massRatio4 = (enemyCar.weight - playerCarWeight) / totalMass;
- // Make energy loss proportional to current speed for more natural and realistic crash
- // At low speed, little loss; at high speed, much more loss
- var relativeSpeed = Math.max(playerSpeed, enemySpeed);
- var minLoss = 0.18; // minimum energy loss at very low speed
- var maxLoss = 0.65; // maximum energy loss at very high speed
- var speedNorm = Math.min(1, relativeSpeed / maxSpeed); // 0 to 1
- var energyLoss = minLoss + (maxLoss - minLoss) * speedNorm;
- var restitution = 1 - energyLoss;
- // Separate cars to prevent overlap (less exaggerated separation)
- var separationDistance = 70; // Reduced from 110 for more natural push
- carPlayer.x = enemyCar.x - collisionDeltaX * separationDistance;
- carPlayer.y = enemyCar.y - collisionDeltaY * separationDistance;
- // Calculate new velocities based on mass and current motion
- // Player car velocity change (heavier cars are less affected)
- var playerImpactX = velocityX * massRatio1 + enemyCar.velocityX * massRatio2;
- var playerImpactY = velocityY * massRatio1 + enemyCar.velocityY * massRatio2;
- velocityX = playerImpactX * restitution;
- velocityY = playerImpactY * restitution;
- // Enemy car velocity change (lighter cars get pushed more)
- var enemyImpactX = velocityX * massRatio3 + enemyCar.velocityX * massRatio4;
- var enemyImpactY = velocityY * massRatio3 + enemyCar.velocityY * massRatio4;
- enemyCar.velocityX = enemyImpactX * restitution;
- enemyCar.velocityY = enemyImpactY * restitution;
- // --- NEW: Launch enemy car away from collision, push effect depends on collision speed ---
- var launchSpeed = Math.max(playerSpeed, enemySpeed);
- // Calculate push multiplier: gentle at low speed, exaggerated at high speed
- // At 0 speed: 0.4x, at maxSpeed: 1.5x (tunable)
- var minPush = 0.4;
- var maxPush = 1.5;
- var pushMultiplier = minPush + (maxPush - minPush) * speedNorm;
- // Direction from player to enemy (so enemy is launched away from player)
- var launchDirX = enemyCar.x - carPlayer.x;
- var launchDirY = enemyCar.y - carPlayer.y;
- var launchDist = Math.sqrt(launchDirX * launchDirX + launchDirY * launchDirY);
- // If the direction is too small (almost zero), use the player's movement direction
- if (launchDist < 0.01) {
- // Use the normalized velocity of the player (if moving)
- var playerMoveNorm = Math.sqrt(velocityX * velocityX + velocityY * velocityY);
- if (playerMoveNorm > 0.01) {
- launchDirX = velocityX / playerMoveNorm;
- launchDirY = velocityY / playerMoveNorm;
+ // Check collision with each enemy car
+ for (var ec = 0; ec < enemyCars.length; ec++) {
+ var currentEnemyCar = enemyCars[ec];
+ // Calculate enemy collision box
+ var enemyLeft = currentEnemyCar.x - enemyCollisionWidth / 2;
+ var enemyRight = currentEnemyCar.x + enemyCollisionWidth / 2;
+ var enemyTop = currentEnemyCar.y - enemyCollisionHeight / 2;
+ var enemyBottom = currentEnemyCar.y + enemyCollisionHeight / 2;
+ // More precise collision detection using smaller bounding boxes
+ var currentColliding = !(playerRight < enemyLeft || playerLeft > enemyRight || playerBottom < enemyTop || playerTop > enemyBottom);
+ if (!carPlayer.lastColliding[ec] && currentColliding) {
+ // Collision just started - calculate collision physics with mass
+ 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;
+ }
+ // Calculate momentum transfer using conservation of momentum
+ // m1*v1 + m2*v2 = m1*v1' + m2*v2' (simplified elastic collision)
+ 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;
+ // Make energy loss proportional to current speed for more natural and realistic crash
+ // At low speed, little loss; at high speed, much more loss
+ var relativeSpeed = Math.max(playerSpeed, enemySpeed);
+ var minLoss = 0.18; // minimum energy loss at very low speed
+ var maxLoss = 0.65; // maximum energy loss at very high speed
+ var speedNorm = Math.min(1, relativeSpeed / maxSpeed); // 0 to 1
+ var energyLoss = minLoss + (maxLoss - minLoss) * speedNorm;
+ var restitution = 1 - energyLoss;
+ // Separate cars to prevent overlap (less exaggerated separation)
+ var separationDistance = 70; // Reduced from 110 for more natural push
+ carPlayer.x = currentEnemyCar.x - collisionDeltaX * separationDistance;
+ carPlayer.y = currentEnemyCar.y - collisionDeltaY * separationDistance;
+ // Calculate new velocities based on mass and current motion
+ // Player car velocity change (heavier cars are less affected)
+ var playerImpactX = velocityX * massRatio1 + currentEnemyCar.velocityX * massRatio2;
+ var playerImpactY = velocityY * massRatio1 + currentEnemyCar.velocityY * massRatio2;
+ velocityX = playerImpactX * restitution;
+ velocityY = playerImpactY * restitution;
+ // Enemy car velocity change (lighter cars get pushed more)
+ var enemyImpactX = velocityX * massRatio3 + currentEnemyCar.velocityX * massRatio4;
+ var enemyImpactY = velocityY * massRatio3 + currentEnemyCar.velocityY * massRatio4;
+ currentEnemyCar.velocityX = enemyImpactX * restitution;
+ currentEnemyCar.velocityY = enemyImpactY * restitution;
+ // --- NEW: Launch enemy car away from collision, push effect depends on collision speed ---
+ var launchSpeed = Math.max(playerSpeed, enemySpeed);
+ // Calculate push multiplier: gentle at low speed, exaggerated at high speed
+ // At 0 speed: 0.4x, at maxSpeed: 1.5x (tunable)
+ var minPush = 0.4;
+ var maxPush = 1.5;
+ var pushMultiplier = minPush + (maxPush - minPush) * speedNorm;
+ // Direction from player to enemy (so enemy is launched away from player)
+ var launchDirX = currentEnemyCar.x - carPlayer.x;
+ var launchDirY = currentEnemyCar.y - carPlayer.y;
+ var launchDist = Math.sqrt(launchDirX * launchDirX + launchDirY * launchDirY);
+ // If the direction is too small (almost zero), use the player's movement direction
+ if (launchDist < 0.01) {
+ // Use the normalized velocity of the player (if moving)
+ var playerMoveNorm = Math.sqrt(velocityX * velocityX + velocityY * velocityY);
+ if (playerMoveNorm > 0.01) {
+ launchDirX = velocityX / playerMoveNorm;
+ launchDirY = velocityY / playerMoveNorm;
+ } else {
+ // Default to up if both are zero
+ launchDirX = 0;
+ launchDirY = -1;
+ }
} else {
- // Default to up if both are zero
- launchDirX = 0;
- launchDirY = -1;
+ launchDirX /= launchDist;
+ launchDirY /= launchDist;
}
- } else {
- launchDirX /= launchDist;
- launchDirY /= launchDist;
+ // Add launch velocity proportional to collision speed and pushMultiplier
+ currentEnemyCar.velocityX += launchDirX * launchSpeed * pushMultiplier;
+ currentEnemyCar.velocityY += launchDirY * launchSpeed * pushMultiplier;
+ // --- END NEW ---
+ // Add directional push based on collision angle and relative mass
+ var pushIntensity = (playerSpeed + enemySpeed) * (0.5 + 0.5 * speedNorm); // More push at higher speed, less at low
+ var playerPushRatio = currentEnemyCar.weight / totalMass; // Heavier enemy = more push on player
+ var enemyPushRatio = playerCarWeight / totalMass; // Heavier player = more push on enemy
+ // Apply directional collision forces (less exaggerated, but still speed-dependent)
+ 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 interpolation so acceleration after crash feels like at driving start, but more severe at high speed
+ var postCrashSpeed = Math.sqrt(velocityX * velocityX + velocityY * velocityY);
+ // If the crash was at high speed, reduce the post-collision velocity more (stronger reset)
+ if (relativeSpeed > maxSpeed * 0.7) {
+ // At very high speed, reduce velocity to 40% of post-collision value
+ currentVelocity = postCrashSpeed * 0.4;
+ } else if (relativeSpeed > maxSpeed * 0.4) {
+ // At medium speed, reduce velocity to 70% of post-collision value
+ currentVelocity = postCrashSpeed * 0.7;
+ } else {
+ // At low speed, keep as before
+ currentVelocity = postCrashSpeed;
+ }
+ // Visual feedback for collision
+ LK.effects.flashObject(carPlayer, 0xff0000, 500);
}
- // Add launch velocity proportional to collision speed and pushMultiplier
- enemyCar.velocityX += launchDirX * launchSpeed * pushMultiplier;
- enemyCar.velocityY += launchDirY * launchSpeed * pushMultiplier;
- // --- END NEW ---
- // Add directional push based on collision angle and relative mass
- var pushIntensity = (playerSpeed + enemySpeed) * (0.5 + 0.5 * speedNorm); // More push at higher speed, less at low
- var playerPushRatio = enemyCar.weight / totalMass; // Heavier enemy = more push on player
- var enemyPushRatio = playerCarWeight / totalMass; // Heavier player = more push on enemy
- // Apply directional collision forces (less exaggerated, but still speed-dependent)
- velocityX += -collisionDeltaX * pushIntensity * playerPushRatio * 0.7;
- velocityY += -collisionDeltaY * pushIntensity * playerPushRatio * 0.7;
- enemyCar.velocityX += collisionDeltaX * pushIntensity * enemyPushRatio * 0.7;
- enemyCar.velocityY += collisionDeltaY * pushIntensity * enemyPushRatio * 0.7;
- // Reset acceleration interpolation so acceleration after crash feels like at driving start, but more severe at high speed
- var postCrashSpeed = Math.sqrt(velocityX * velocityX + velocityY * velocityY);
- // If the crash was at high speed, reduce the post-collision velocity more (stronger reset)
- if (relativeSpeed > maxSpeed * 0.7) {
- // At very high speed, reduce velocity to 40% of post-collision value
- currentVelocity = postCrashSpeed * 0.4;
- } else if (relativeSpeed > maxSpeed * 0.4) {
- // At medium speed, reduce velocity to 70% of post-collision value
- currentVelocity = postCrashSpeed * 0.7;
- } else {
- // At low speed, keep as before
- currentVelocity = postCrashSpeed;
- }
- // Visual feedback for collision
- LK.effects.flashObject(carPlayer, 0xff0000, 500);
+ // Update last collision state for this enemy car
+ carPlayer.lastColliding[ec] = currentColliding;
}
- // Update last collision state
- carPlayer.lastColliding = currentColliding;
- // --- Enemy Car AI Movement Logic ---
- // Enemy car can switch between 'follow player' 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) {
- // Calculate target speed for mode preference
- var targetSpeed = Math.sqrt(velocityX * velocityX + velocityY * 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
- }
- // Adaptive strategy selection based on target speed and position
- enemyCar.followStrategyTimer++;
- if (enemyCar.followStrategyTimer > enemyCar.followStrategyDuration) {
- // Calculate target speed and relative position for strategy selection
+ // --- Enemy Cars AI Movement Logic ---
+ // Process AI for each enemy car
+ for (var ai = 0; ai < enemyCars.length; ai++) {
+ var currentEnemyCar = enemyCars[ai];
+ // Enemy car can switch between 'follow player' and 'free roam' AI modes
+ if (typeof currentEnemyCar.aiMode === "undefined") currentEnemyCar.aiMode = "follow";
+ if (typeof currentEnemyCar.aiModeTimer === "undefined") currentEnemyCar.aiModeTimer = 0;
+ if (typeof currentEnemyCar.aiModeDuration === "undefined") currentEnemyCar.aiModeDuration = 0;
+ // AI mode switching logic: change mode at intervals with speed-based preferences
+ enemyCar.aiModeTimer++;
+ if (enemyCar.aiModeTimer > enemyCar.aiModeDuration) {
+ // Calculate target speed for mode preference
var targetSpeed = Math.sqrt(velocityX * velocityX + velocityY * 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
- // Strategy selection with strong speed-based preferences:
- // DIRECT: Strongly preferred for slow targets (< 30% speed)
- // VARIED: For medium speeds and tactical situations
- // AMBUSH: Strongly preferred for fast targets (> 60% speed)
- if (targetSpeedRatio < 0.3) {
- // Slow target - strongly prefer direct pursuit (85% chance)
- if (Math.random() < 0.85) {
- enemyCar.followStrategy = 0;
- } else {
- enemyCar.followStrategy = 1; // Occasional varied approach
- }
- } else if (targetSpeedRatio > 0.6) {
- // Fast target - strongly prefer ambush tactics (80% chance)
- if (Math.random() < 0.8) {
- enemyCar.followStrategy = 2; // Ambush for interception
- } else {
- enemyCar.followStrategy = 1; // Occasional varied approach
- }
- } else if (targetSpeedRatio < 0.05) {
- // Target is nearly stationary - use intimidation system
- // Initialize intimidation counter if not set
- if (typeof enemyCar.intimidationCount === "undefined") {
- enemyCar.intimidationCount = 0;
- enemyCar.maxIntimidations = 1 + Math.floor(Math.random() * 3); // 1-3 intimidations
- }
- // Check if we've completed enough intimidations
- if (enemyCar.intimidationCount >= enemyCar.maxIntimidations) {
- // Switch to direct attack after intimidating enough times
- enemyCar.followStrategy = 0; // Direct pursuit for attack
- } else {
- // Use varied mode for intimidation (doesn't seek collision)
- enemyCar.followStrategy = 1; // Intimidation approach
- }
- } else if (isTargetBehind || relativeDistance < 200) {
- // Target behind or very close - use varied approach for positioning
- enemyCar.followStrategy = 1;
- } else if (isTargetAhead && relativeDistance < 300) {
- // Target ahead and close - direct pursuit
- enemyCar.followStrategy = 0;
+ 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 {
- // Medium speed, medium distance - balanced approach favoring strategy based on speed
- if (targetSpeedRatio > 0.45) {
- // Lean towards ambush for faster medium speeds
- enemyCar.followStrategy = Math.random() < 0.6 ? 2 : 1;
- } else {
- // Lean towards direct for slower medium speeds
- enemyCar.followStrategy = Math.random() < 0.6 ? 0 : 1;
- }
+ // Slow - prefer follow mode for direct pursuit
+ followChance = 0.85;
}
- // Adjust duration based on selected strategy
- switch (enemyCar.followStrategy) {
- case 0:
- // Direct - shorter duration for quick decisions
- enemyCar.followStrategyDuration = 45 + Math.floor(Math.random() * 45);
- break;
- case 1:
- // Varied - medium duration for tactical maneuvering
- enemyCar.followStrategyDuration = 60 + Math.floor(Math.random() * 60);
- break;
- case 2:
- // Ambush - longer duration for prediction accuracy
- enemyCar.followStrategyDuration = 90 + Math.floor(Math.random() * 60);
- break;
+ // Pick mode based on calculated preference
+ if (Math.random() < followChance) {
+ enemyCar.aiMode = "follow";
+ } else {
+ enemyCar.aiMode = "free";
}
- enemyCar.followStrategyTimer = 0;
+ // Duration: 1.2s to 3.5s (72 to 210 frames)
+ enemyCar.aiModeDuration = 72 + Math.floor(Math.random() * 138);
+ enemyCar.aiModeTimer = 0;
}
- // Calculate base values
- aiDeltaX = carPlayer.x - enemyCar.x;
- aiDeltaY = carPlayer.y - enemyCar.y;
- aiDistance = Math.sqrt(aiDeltaX * aiDeltaX + aiDeltaY * aiDeltaY);
- // Calculate relative velocity and approach angle for smarter pursuit
- var relativeVelX = velocityX - (enemyCar.aiVelocityX || 0);
- var relativeVelY = velocityY - (enemyCar.aiVelocityY || 0);
- 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;
+ // --- 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;
}
- // 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();
+ if (enemyCar.x > 2048 - edgeBuffer) {
+ // Too close to right edge, push left
+ var rightForce = (enemyCar.x - (2048 - edgeBuffer)) / edgeBuffer;
+ edgeAvoidanceX -= rightForce * avoidanceStrength;
}
- // 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++;
- }
+ if (enemyCar.y < edgeBuffer) {
+ // Too close to top edge, push down
+ var topForce = (edgeBuffer - enemyCar.y) / edgeBuffer;
+ edgeAvoidanceY += topForce * avoidanceStrength;
}
- 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;
+ if (enemyCar.y > 2186 - edgeBuffer) {
+ // Too close to bottom edge, push up
+ var bottomForce = (enemyCar.y - (2186 - edgeBuffer)) / edgeBuffer;
+ edgeAvoidanceY -= bottomForce * avoidanceStrength;
}
- // 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 = Math.sqrt(velocityX * velocityX + velocityY * 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);
+ 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
+ }
+ // Adaptive strategy selection based on target speed and position
+ enemyCar.followStrategyTimer++;
+ if (enemyCar.followStrategyTimer > enemyCar.followStrategyDuration) {
+ // Calculate target speed and relative position for strategy selection
+ var targetSpeed = Math.sqrt(velocityX * velocityX + velocityY * 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
+ // Strategy selection with strong speed-based preferences:
+ // DIRECT: Strongly preferred for slow targets (< 30% speed)
+ // VARIED: For medium speeds and tactical situations
+ // AMBUSH: Strongly preferred for fast targets (> 60% speed)
+ if (targetSpeedRatio < 0.3) {
+ // Slow target - strongly prefer direct pursuit (85% chance)
+ if (Math.random() < 0.85) {
+ enemyCar.followStrategy = 0;
} else {
- // Slow target or close - try varied approach
- enemyCar.followStrategy = 1;
- enemyCar.followStrategyDuration = 60 + Math.floor(Math.random() * 60);
+ enemyCar.followStrategy = 1; // Occasional varied approach
}
- enemyCar.followStrategyTimer = 0;
- }
- // 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
- if (aiDistance < 120 && !enemyCar.circlingDetected) {
- // Too close - back off slightly at an angle
- var backoffAngle = approachAngle + Math.PI + (Math.random() - 0.5) * Math.PI * 0.5;
- aiTargetRotation = backoffAngle;
- aiPower = 0.3;
- aiTargetVelocity = maxSpeed * aiPower * 0.6;
- } else if (aiDistance > 500) {
- // 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 if (targetSpeedRatio > 0.6) {
+ // Fast target - strongly prefer ambush tactics (80% chance)
+ if (Math.random() < 0.8) {
+ enemyCar.followStrategy = 2; // Ambush for interception
} else {
- // Need to turn first - direct approach at normal speed
- aiTargetRotation = approachAngle;
- aiPower = Math.min(1, aiDistance / 600);
- aiTargetVelocity = maxSpeed * aiPower * 0.95;
+ enemyCar.followStrategy = 1; // Occasional varied approach
}
+ } else if (targetSpeedRatio < 0.05) {
+ // Target is nearly stationary - use intimidation system
+ // Initialize intimidation counter if not set
+ if (typeof enemyCar.intimidationCount === "undefined") {
+ enemyCar.intimidationCount = 0;
+ enemyCar.maxIntimidations = 1 + Math.floor(Math.random() * 3); // 1-3 intimidations
+ }
+ // Check if we've completed enough intimidations
+ if (enemyCar.intimidationCount >= enemyCar.maxIntimidations) {
+ // Switch to direct attack after intimidating enough times
+ enemyCar.followStrategy = 0; // Direct pursuit for attack
+ } else {
+ // Use varied mode for intimidation (doesn't seek collision)
+ enemyCar.followStrategy = 1; // Intimidation approach
+ }
+ } else if (isTargetBehind || relativeDistance < 200) {
+ // Target behind or very close - use varied approach for positioning
+ enemyCar.followStrategy = 1;
+ } else if (isTargetAhead && relativeDistance < 300) {
+ // Target ahead and close - direct pursuit
+ enemyCar.followStrategy = 0;
} else {
- // Medium distance - check alignment for speed boost
- 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
+ // Medium speed, medium distance - balanced approach favoring strategy based on speed
+ if (targetSpeedRatio > 0.45) {
+ // Lean towards ambush for faster medium speeds
+ enemyCar.followStrategy = Math.random() < 0.6 ? 2 : 1;
} 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;
+ // Lean towards direct for slower medium speeds
+ enemyCar.followStrategy = Math.random() < 0.6 ? 0 : 1;
}
}
- // Update last distance for next frame
+ // Adjust duration based on selected strategy
+ switch (enemyCar.followStrategy) {
+ case 0:
+ // Direct - shorter duration for quick decisions
+ enemyCar.followStrategyDuration = 45 + Math.floor(Math.random() * 45);
+ break;
+ case 1:
+ // Varied - medium duration for tactical maneuvering
+ enemyCar.followStrategyDuration = 60 + Math.floor(Math.random() * 60);
+ break;
+ case 2:
+ // Ambush - longer duration for prediction accuracy
+ enemyCar.followStrategyDuration = 90 + Math.floor(Math.random() * 60);
+ break;
+ }
+ enemyCar.followStrategyTimer = 0;
+ }
+ // Calculate base values
+ aiDeltaX = carPlayer.x - enemyCar.x;
+ aiDeltaY = carPlayer.y - enemyCar.y;
+ aiDistance = Math.sqrt(aiDeltaX * aiDeltaX + aiDeltaY * aiDeltaY);
+ // Calculate relative velocity and approach angle for smarter pursuit
+ var relativeVelX = velocityX - (enemyCar.aiVelocityX || 0);
+ var relativeVelY = velocityY - (enemyCar.aiVelocityY || 0);
+ 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;
- break;
- case 1:
- // Varied pursuit - intimidation mode: follows like direct but maintains safe distance to scare
- 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 (aiDistance < 180) {
- // Intimidation range - follow closely but maintain threatening distance
- // Stay close enough to scare but far enough to avoid collision
- var intimidationDistance = 150; // Target distance for intimidation
- var distanceError = aiDistance - intimidationDistance;
- if (Math.abs(distanceError) < 30) {
- // Perfect intimidation distance - match player direction but slightly offset
- var intimidationOffset = Math.sin(LK.ticks * 0.05) * (Math.PI * 0.15); // Subtle weaving ±27 degrees
- aiTargetRotation = approachAngle + intimidationOffset;
- aiPower = 0.7;
- aiTargetVelocity = maxSpeed * aiPower * 0.85;
- // Check if target is stationary and we're intimidating
+ 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 = Math.sqrt(velocityX * velocityX + velocityY * velocityY);
- if (currentTargetSpeed < maxSpeed * 0.05 && typeof enemyCar.intimidationCount !== "undefined") {
- // Mark intimidation as successful after maintaining perfect distance for a while
- if (typeof enemyCar.intimidationTimer === "undefined") enemyCar.intimidationTimer = 0;
- enemyCar.intimidationTimer++;
- // Complete intimidation after 2 seconds of perfect positioning
- if (enemyCar.intimidationTimer > 120) {
- enemyCar.intimidationCount++;
- enemyCar.intimidationTimer = 0;
- // Force strategy recalculation
- enemyCar.followStrategyTimer = enemyCar.followStrategyDuration + 1;
- }
+ 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);
}
- } else if (distanceError < 0) {
- // Too close - back off while still facing player
- aiTargetRotation = approachAngle + Math.PI + (Math.random() - 0.5) * Math.PI * 0.3;
- aiPower = 0.4;
+ enemyCar.followStrategyTimer = 0;
+ }
+ // 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
+ if (aiDistance < 120 && !enemyCar.circlingDetected) {
+ // Too close - back off slightly at an angle
+ var backoffAngle = approachAngle + Math.PI + (Math.random() - 0.5) * Math.PI * 0.5;
+ aiTargetRotation = backoffAngle;
+ aiPower = 0.3;
aiTargetVelocity = maxSpeed * aiPower * 0.6;
+ } else if (aiDistance > 500) {
+ // 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 {
- // Too far - approach for intimidation
- aiTargetRotation = approachAngle;
+ // Medium distance - check alignment for speed boost
+ 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
+ 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 (aiDistance < 180) {
+ // Intimidation range - follow closely but maintain threatening distance
+ // Stay close enough to scare but far enough to avoid collision
+ var intimidationDistance = 150; // Target distance for intimidation
+ var distanceError = aiDistance - intimidationDistance;
+ if (Math.abs(distanceError) < 30) {
+ // Perfect intimidation distance - match player direction but slightly offset
+ var intimidationOffset = Math.sin(LK.ticks * 0.05) * (Math.PI * 0.15); // Subtle weaving ±27 degrees
+ aiTargetRotation = approachAngle + intimidationOffset;
+ aiPower = 0.7;
+ aiTargetVelocity = maxSpeed * aiPower * 0.85;
+ // Check if target is stationary and we're intimidating
+ var currentTargetSpeed = Math.sqrt(velocityX * velocityX + velocityY * velocityY);
+ if (currentTargetSpeed < maxSpeed * 0.05 && typeof enemyCar.intimidationCount !== "undefined") {
+ // Mark intimidation as successful after maintaining perfect distance for a while
+ if (typeof enemyCar.intimidationTimer === "undefined") enemyCar.intimidationTimer = 0;
+ enemyCar.intimidationTimer++;
+ // Complete intimidation after 2 seconds of perfect positioning
+ if (enemyCar.intimidationTimer > 120) {
+ enemyCar.intimidationCount++;
+ enemyCar.intimidationTimer = 0;
+ // Force strategy recalculation
+ enemyCar.followStrategyTimer = enemyCar.followStrategyDuration + 1;
+ }
+ }
+ } else if (distanceError < 0) {
+ // Too close - back off while still facing player
+ aiTargetRotation = approachAngle + Math.PI + (Math.random() - 0.5) * Math.PI * 0.3;
+ aiPower = 0.4;
+ aiTargetVelocity = maxSpeed * aiPower * 0.6;
+ } else {
+ // Too far - approach for intimidation
+ aiTargetRotation = approachAngle;
+ aiPower = 0.8;
+ aiTargetVelocity = maxSpeed * aiPower * 0.9;
+ }
+ } else {
+ // Long range - approach like direct pursuit for intimidation setup
+ aiTargetRotation = approachAngle;
+ aiPower = Math.min(1, aiDistance / 500);
+ aiTargetVelocity = maxSpeed * aiPower * 0.92;
+ }
+ break;
+ case 2:
+ // Ambush - predict player's future position with smarter interception
+ var playerVelX = velocityX;
+ var playerVelY = velocityY;
+ var playerSpeed = Math.sqrt(playerVelX * playerVelX + playerVelY * playerVelY);
+ 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 (aiDistance < 150) {
+ // Close range - position for optimal angle of attack
+ var attackAngle = approachAngle + Math.PI * 0.4 * (angleDiff > 0 ? 1 : -1);
+ aiTargetRotation = attackAngle;
+ aiPower = 0.7;
+ aiTargetVelocity = maxSpeed * aiPower * 0.8;
+ } else {
+ // Long range interception
+ var predictionTime = Math.min(2.5, aiDistance / maxSpeed * 0.9);
+ var interceptX = carPlayer.x + playerVelX * predictionTime;
+ var interceptY = carPlayer.y + playerVelY * 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);
+ // Lead the target slightly based on relative speeds
+ var speedRatio = (enemyCar.aiCurrentVelocity || 0) / Math.max(0.1, playerSpeed);
+ var leadAdjustment = (1 - speedRatio) * 0.3;
+ aiTargetRotation = Math.atan2(interceptDeltaX, -interceptDeltaY);
+ aiPower = Math.min(1, interceptDistance / 700);
+ var speedBoost = Math.min(0.2, playerSpeed / maxSpeed * 0.2);
+ aiTargetVelocity = maxSpeed * aiPower * (0.85 + speedBoost + leadAdjustment);
}
- } else {
- // Long range - approach like direct pursuit for intimidation setup
- aiTargetRotation = approachAngle;
- aiPower = Math.min(1, aiDistance / 500);
- aiTargetVelocity = maxSpeed * aiPower * 0.92;
- }
- break;
- case 2:
- // Ambush - predict player's future position with smarter interception
- var playerVelX = velocityX;
- var playerVelY = velocityY;
- var playerSpeed = Math.sqrt(playerVelX * playerVelX + playerVelY * playerVelY);
- 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 (aiDistance < 150) {
- // Close range - position for optimal angle of attack
- var attackAngle = approachAngle + Math.PI * 0.4 * (angleDiff > 0 ? 1 : -1);
- aiTargetRotation = attackAngle;
- aiPower = 0.7;
- aiTargetVelocity = maxSpeed * aiPower * 0.8;
- } else {
- // Long range interception
- var predictionTime = Math.min(2.5, aiDistance / maxSpeed * 0.9);
- var interceptX = carPlayer.x + playerVelX * predictionTime;
- var interceptY = carPlayer.y + playerVelY * 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);
- // Lead the target slightly based on relative speeds
- var speedRatio = (enemyCar.aiCurrentVelocity || 0) / Math.max(0.1, playerSpeed);
- var leadAdjustment = (1 - speedRatio) * 0.3;
- aiTargetRotation = Math.atan2(interceptDeltaX, -interceptDeltaY);
- aiPower = Math.min(1, interceptDistance / 700);
- var speedBoost = Math.min(0.2, playerSpeed / 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 {
- // 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
+ 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 {
+ // 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 - 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);
+ // Wander pattern - gentle direction changes
+ directionChangeInterval = 90 + Math.floor(Math.random() * 60); // 1.5-2.5 seconds
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;
+ // Circular pattern - more frequent turns
+ directionChangeInterval = 45 + Math.floor(Math.random() * 30); // 0.75-1.25 seconds
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);
+ // 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 player awareness even in free roam - occasionally look towards player
+ var playerDistance = Math.sqrt((carPlayer.x - enemyCar.x) * (carPlayer.x - enemyCar.x) + (carPlayer.y - enemyCar.y) * (carPlayer.y - enemyCar.y));
+ var playerInfluence = 0;
+ if (playerDistance < 400) {
+ // Within 400 pixels
+ // Closer player = more influence on direction
+ playerInfluence = Math.max(0, (400 - playerDistance) / 400 * 0.3); // Up to 30% influence
+ if (Math.random() < 0.02) {
+ // 2% chance per frame to look at player
+ var playerAngle = Math.atan2(carPlayer.x - enemyCar.x, -(carPlayer.y - enemyCar.y));
+ // Blend current angle with player angle
+ var currentVecX = Math.sin(enemyCar.freeRoamAngle);
+ var currentVecY = -Math.cos(enemyCar.freeRoamAngle);
+ var playerVecX = Math.sin(playerAngle) * playerInfluence;
+ var playerVecY = -Math.cos(playerAngle) * playerInfluence;
+ var blendedVecX = currentVecX * (1 - playerInfluence) + playerVecX;
+ var blendedVecY = currentVecY * (1 - playerInfluence) + playerVecY;
+ 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;
+ }
}
- // Add some player awareness even in free roam - occasionally look towards player
- var playerDistance = Math.sqrt((carPlayer.x - enemyCar.x) * (carPlayer.x - enemyCar.x) + (carPlayer.y - enemyCar.y) * (carPlayer.y - enemyCar.y));
- var playerInfluence = 0;
- if (playerDistance < 400) {
- // Within 400 pixels
- // Closer player = more influence on direction
- playerInfluence = Math.max(0, (400 - playerDistance) / 400 * 0.3); // Up to 30% influence
- if (Math.random() < 0.02) {
- // 2% chance per frame to look at player
- var playerAngle = Math.atan2(carPlayer.x - enemyCar.x, -(carPlayer.y - enemyCar.y));
- // Blend current angle with player angle
- var currentVecX = Math.sin(enemyCar.freeRoamAngle);
- var currentVecY = -Math.cos(enemyCar.freeRoamAngle);
- var playerVecX = Math.sin(playerAngle) * playerInfluence;
- var playerVecY = -Math.cos(playerAngle) * playerInfluence;
- var blendedVecX = currentVecX * (1 - playerInfluence) + playerVecX;
- var blendedVecY = currentVecY * (1 - playerInfluence) + playerVecY;
- enemyCar.freeRoamAngle = Math.atan2(blendedVecX, -blendedVecY);
+ // 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;
}
- 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;
+ // Keep enemy car within bounds
+ var enemyHalfWidth = 32;
+ var enemyHalfHeight = 47;
+ if (currentEnemyCar.x < enemyHalfWidth) {
+ currentEnemyCar.x = enemyHalfWidth;
+ currentEnemyCar.velocityX = -currentEnemyCar.velocityX * 0.6;
}
- }
- // 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;
+ if (currentEnemyCar.x > 2048 - enemyHalfWidth) {
+ currentEnemyCar.x = 2048 - enemyHalfWidth;
+ currentEnemyCar.velocityX = -currentEnemyCar.velocityX * 0.6;
}
- } 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;
- }
+ if (currentEnemyCar.y < enemyHalfHeight) {
+ currentEnemyCar.y = enemyHalfHeight;
+ currentEnemyCar.velocityY = -currentEnemyCar.velocityY * 0.6;
+ }
+ if (currentEnemyCar.y > 2186 - enemyHalfHeight) {
+ currentEnemyCar.y = 2186 - enemyHalfHeight;
+ currentEnemyCar.velocityY = -currentEnemyCar.velocityY * 0.6;
+ }
+ } // End enemy cars AI loop
+ // --- End Enemy Cars AI Movement Logic ---
// Update speed display
var totalSpeed = Math.sqrt(velocityX * velocityX + velocityY * velocityY);
speedText.setText('Speed: ' + Math.round(totalSpeed));
};
\ No newline at end of file