User prompt
Haz que se pierda algo de velocidad y aceleración dependiendo de la velocidad a la que se iba al chocar
User prompt
Add continuous collision separation
User prompt
Arregla el error que hace que auto jugador atraviese los autos enemigo
User prompt
Acomoda la lógica de choque actual para ni hacelo exclusivo del auto jugador y aplicarlo también al enemigo
User prompt
Utiliza el mismo sistema de choques de jugador para los choques entre autos enemigos
User prompt
Haz que aparezca entre 3 a 5 enemigos en pantalla
User prompt
Haz se transfiera más el momentum dependiendo de la velocidad
User prompt
Exagera el choque, si se va rápido la fuerza transferida es mayor, si se va lento parece un empuje
User prompt
Haz que parte de la velocidad del auto se transfiera al auto enemigo teniendo en cuenta el peso y la dirección en la que mira el auto chocador (no tweet plugin)
User prompt
Agrega peso a los autos
User prompt
Elimina la desaparición de los autos al llegar al 0 de vida. A su vez desactiva su IA (mantén daño de colisión y empuje)
User prompt
Haz que los autos al llegar a 0 de vida no desaparezcan, desactivan la IA
User prompt
Arregla el error que hace que no se puedan empujar los autos rotos como con los autos normales
User prompt
Agrega un diley para que la pantalla de muerte no sea tan repentina
User prompt
Haz que la pantalla de muerte no sea tan repentina ↪💡 Consider importing and using the following plugins: @upit/tween.v1
User prompt
Haz que los autos al tener cero de vida no desaparezcan, simplemente dejan de funcionar (no se pueden mover, girar o acelerar)
User prompt
Haz que los autos no desaparezcan al tener 0 de vida
User prompt
Cuando la vida de los enemigos lleguen a 0 se desactivan la velocidad de movimiento, aceleración y rotación
User prompt
Agrega sonido cuando chocan los autos
User prompt
Agrega sonido al chocar los autos
User prompt
Haz que la IA a la hora de perseguir o girar pueda elegir si dar giros bruscos o suaves ya que se quedan dando círculo entre ellos ↪💡 Consider importing and using the following plugins: @upit/tween.v1
User prompt
Haz que la IA a la hora de perseguir o girar pueda elegir si dar giros bruscos o suaves ya que se quedan dando círculo entre ellos
User prompt
Elimina el método de huida de círculo
User prompt
Cambia el asset de carPlayer por car y tiñelo de rojo ↪💡 Consider importing and using the following plugins: @upit/tween.v1
User prompt
Reduce el daño que recibe el chocador
/**** * 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 (no logic yet) self.velocityX = 0; self.velocityY = 0; self.rotation = 0; // Mass property for physics calculations self.mass = 800 + Math.random() * 800; // Random mass between 800-1600 kg 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 car in gameplay area var enemyCar = new EnemyCar(); enemyCar.x = 500; enemyCar.y = 500; gameplayBackground.addChild(enemyCar); // 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; // Vehicle mass properties var playerMass = 1200; // Player car mass in kg // 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) // 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); } } // Particle system for car exhaust if (!game.particles) { game.particles = []; } // Generate particles proportional to current velocity (from very little to much) var totalSpeed = Math.sqrt(velocityX * velocityX + velocityY * velocityY); var speedRatio = totalSpeed / maxSpeed; // 0 to 1 ratio of current speed to max speed // Calculate particle generation frequency based on speed (more speed = more frequent particles) var particleFrequency = Math.max(1, Math.floor(8 - speedRatio * 6)); // From every 8 ticks (slow) to every 2 ticks (fast) if (speedRatio > 0.05 && LK.ticks % particleFrequency === 0) { // Only generate particles when moving at least 5% of max speed // Calculate particle spawn position further behind the car (20% closer for smaller car) var particleSpawnX = carPlayer.x - Math.sin(carPlayer.rotation) * 55; var particleSpawnY = carPlayer.y + Math.cos(carPlayer.rotation) * 55; // Create particles based on speed - more speed = more particles var particleCount = Math.max(1, Math.floor(speedRatio * 3)); // 1-3 particles based on speed ratio for (var p = 0; p < particleCount; p++) { // Create new particle var newParticle = new Particle(); newParticle.x = particleSpawnX + (Math.random() - 0.5) * 19.2; // 20% smaller spawn area newParticle.y = particleSpawnY + (Math.random() - 0.5) * 19.2; // Add velocity variation based on car movement (20% reduced for smaller scale) newParticle.velocityX += -velocityX * 0.12 + (Math.random() - 0.5) * 2.4; newParticle.velocityY += -velocityY * 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 }); } } // 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 if (!carPlayer.lastColliding) carPlayer.lastColliding = false; var currentColliding = carPlayer.intersects(enemyCar); if (!carPlayer.lastColliding && currentColliding) { // Collision just started - calculate collision physics var carSpeed = Math.sqrt(velocityX * velocityX + velocityY * velocityY); // Calculate collision direction (from enemy car to player car) var collisionDeltaX = carPlayer.x - enemyCar.x; var collisionDeltaY = carPlayer.y - enemyCar.y; var collisionDistance = Math.sqrt(collisionDeltaX * collisionDeltaX + collisionDeltaY * collisionDeltaY); // Normalize collision direction if (collisionDistance > 0) { collisionDeltaX /= collisionDistance; collisionDeltaY /= collisionDistance; } // Calculate momentum-based collision using conservation of momentum var playerMomentumX = velocityX * playerMass; var playerMomentumY = velocityY * playerMass; var enemyMomentumX = enemyCar.velocityX * enemyCar.mass; var enemyMomentumY = enemyCar.velocityY * enemyCar.mass; var totalMass = playerMass + enemyCar.mass; var massRatioPlayer = enemyCar.mass / totalMass; // Heavier opponent = more effect on player var massRatioEnemy = playerMass / totalMass; // Heavier player = more effect on enemy // Energy loss factor based on mass difference var energyLoss = 0.4 + Math.abs(playerMass - enemyCar.mass) / (playerMass + enemyCar.mass) * 0.2; // Separate cars to prevent overlap var separationDistance = 80; // Distance to separate cars carPlayer.x = enemyCar.x + collisionDeltaX * separationDistance; carPlayer.y = enemyCar.y + collisionDeltaY * separationDistance; // Calculate collision impulse based on relative velocity and masses var relativeVelocityX = velocityX - enemyCar.velocityX; var relativeVelocityY = velocityY - enemyCar.velocityY; var relativeSpeed = Math.sqrt(relativeVelocityX * relativeVelocityX + relativeVelocityY * relativeVelocityY); // Apply realistic momentum transfer with energy loss var impulseStrength = relativeSpeed * massRatioPlayer * (1 - energyLoss); velocityX = velocityX - collisionDeltaX * impulseStrength; velocityY = velocityY - collisionDeltaY * impulseStrength; // Apply opposite impulse to enemy car var enemyImpulseStrength = relativeSpeed * massRatioEnemy * (1 - energyLoss); enemyCar.velocityX = enemyCar.velocityX + collisionDeltaX * enemyImpulseStrength; enemyCar.velocityY = enemyCar.velocityY + collisionDeltaY * enemyImpulseStrength; // Visual feedback for collision LK.effects.flashObject(carPlayer, 0xff0000, 500); } // Update last collision state carPlayer.lastColliding = currentColliding; // Update enemy car physics (simple momentum with friction) enemyCar.x += enemyCar.velocityX; enemyCar.y += enemyCar.velocityY; // Apply friction to enemy car enemyCar.velocityX *= 0.95; enemyCar.velocityY *= 0.95; // 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; } // Update speed display speedText.setText('Speed: ' + Math.round(totalSpeed)); };
===================================================================
--- original.js
+++ change.js
@@ -11,13 +11,13 @@
var enemyCarGraphics = self.attachAsset('Cars', {
anchorX: 0.5,
anchorY: 0.5
});
- // Assign random color (excluding red) - expanded palette with more variety
+ // Assign random color (excluding red)
var enemyColors = [0x0066ff,
// Blue
0x00ff66,
- // Green
+ // Green
0xffff00,
// Yellow
0xff8800,
// Orange
@@ -28,57 +28,18 @@
0xff00ff,
// Magenta
0x888888,
// Gray
- 0xffffff,
- // White
- 0x4169e1,
- // Royal Blue
- 0x32cd32,
- // Lime Green
- 0xff1493,
- // Deep Pink
- 0x00ced1,
- // Dark Turquoise
- 0x9370db,
- // Medium Purple
- 0xffd700,
- // Gold
- 0xff6347,
- // Tomato
- 0x20b2aa,
- // Light Sea Green
- 0xda70d6,
- // Orchid
- 0x87ceeb,
- // Sky Blue
- 0xf0e68c,
- // Khaki
- 0xdda0dd,
- // Plum
- 0x98fb98,
- // Pale Green
- 0xf5deb3,
- // Wheat
- 0xcd5c5c,
- // Indian Red
- 0x40e0d0,
- // Turquoise
- 0xee82ee,
- // Violet
- 0x90ee90,
- // Light Green
- 0xffb6c1,
- // Light Pink
- 0xffa500 // Orange Red
+ 0xffffff // White
];
var randomColorIndex = Math.floor(Math.random() * enemyColors.length);
enemyCarGraphics.tint = enemyColors[randomColorIndex];
- // Basic properties for enemy car with weight
+ // Basic properties for enemy car (no logic yet)
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
+ // Mass property for physics calculations
+ self.mass = 800 + Math.random() * 800; // Random mass between 800-1600 kg
return self;
});
var Particle = Container.expand(function () {
var self = Container.call(this);
@@ -142,202 +103,20 @@
// Position in lower portion of gameplay area
anchorX: 0.5,
anchorY: 0.5
});
-// Create array to store multiple enemy cars
-var enemyCars = [];
-var numEnemyCars = 5 + Math.floor(Math.random() * 10); // Random between 5-14 cars
-var totalCars = numEnemyCars + 1; // Include player in formation
-// Position player in circular formation first
-var centerX = 1024; // Center of gameplay area
-var centerY = 1093; // Center of gameplay area (2186/2)
-var spawnRadius = 700 + Math.random() * 300; // Larger spawn radius between 700-1000 pixels from center
-var angleStep = Math.PI * 2 / totalCars; // Evenly divide circle by total number of cars (including player)
-// Position player at first angle position
-var playerSpawnAngle = 0 * angleStep; // Player gets first position
-var angleVariation = (Math.random() - 0.5) * 0.2; // Smaller variation for better formation
-playerSpawnAngle += angleVariation;
-// Calculate player spawn position
-carPlayer.x = centerX + Math.cos(playerSpawnAngle) * spawnRadius;
-carPlayer.y = centerY + Math.sin(playerSpawnAngle) * spawnRadius;
-// Ensure player stays within gameplay bounds
-carPlayer.x = Math.max(80, Math.min(1968, carPlayer.x));
-carPlayer.y = Math.max(80, Math.min(2106, carPlayer.y));
-// Calculate player rotation to face the center
-var playerDeltaX = centerX - carPlayer.x;
-var playerDeltaY = centerY - carPlayer.y;
-carPlayer.rotation = Math.atan2(playerDeltaX, -playerDeltaY); // Rotation to face center
-// Create multiple enemy cars in a circular formation
-for (var carIndex = 0; carIndex < numEnemyCars; carIndex++) {
- var enemyCar = new EnemyCar();
- // Calculate circular spawn position (starting from position 1, since player is at position 0)
- var enemySpawnAngle = (carIndex + 1) * angleStep; // Enemy cars get positions 1, 2, 3, etc.
- // Add slight random offset to avoid perfect symmetry
- var angleVariation = (Math.random() - 0.5) * 0.2; // Smaller variation for better formation
- enemySpawnAngle += angleVariation;
- // Calculate spawn position
- enemyCar.x = centerX + Math.cos(enemySpawnAngle) * spawnRadius;
- enemyCar.y = centerY + Math.sin(enemySpawnAngle) * spawnRadius;
- // Ensure cars stay within gameplay bounds
- enemyCar.x = Math.max(80, Math.min(1968, enemyCar.x));
- enemyCar.y = Math.max(80, Math.min(2106, enemyCar.y));
- // Calculate rotation to face the center
- var deltaX = centerX - enemyCar.x;
- var deltaY = centerY - enemyCar.y;
- enemyCar.rotation = Math.atan2(deltaX, -deltaY); // Rotation to face center
- // Initialize enemy car health to 100
- enemyCar.health = 100; // Enemy starts with full health
- // Give each AI car individual tactical preferences and personality
- enemyCar.tacticalPersonality = {
- // Primary strategy preference probabilities (0-1)
- directPreference: 0.2 + Math.random() * 0.6,
- // 0.2-0.8 preference for direct pursuit
- ambushPreference: 0.1 + Math.random() * 0.7,
- // 0.1-0.8 preference for ambush tactics
- intimidationPreference: 0.1 + Math.random() * 0.5,
- // 0.1-0.6 preference for intimidation
- // Speed-based tactical preferences
- slowTargetStrategy: Math.floor(Math.random() * 3),
- // 0=direct, 1=intimidate, 2=ambush for slow targets
- fastTargetStrategy: Math.floor(Math.random() * 3),
- // 0=direct, 1=intimidate, 2=ambush for fast targets
- mediumTargetStrategy: Math.floor(Math.random() * 3),
- // 0=direct, 1=intimidate, 2=ambush for medium targets
- // Individual behavioral traits
- aggressiveness: 0.3 + Math.random() * 0.7,
- // 0.3-1.0 how aggressive this car is
- patience: 0.2 + Math.random() * 0.8,
- // 0.2-1.0 how long car sticks to one strategy
- adaptability: 0.1 + Math.random() * 0.9,
- // 0.1-1.0 how quickly car changes strategies
- // Individual strategy duration preferences (in frames)
- minStrategyDuration: 30 + Math.floor(Math.random() * 60),
- // 0.5-1.5 seconds minimum
- maxStrategyDuration: 90 + Math.floor(Math.random() * 180),
- // 1.5-4.5 seconds maximum
- // Personal distance preferences
- preferredAttackDistance: 100 + Math.random() * 200,
- // 100-300 preferred distance for attacks
- preferredIntimidationDistance: 120 + Math.random() * 100,
- // 120-220 preferred intimidation distance
- personalSpaceRadius: 80 + Math.random() * 120,
- // 80-200 personal space when maneuvering
- // Individual flight and evasion preferences
- flightTendency: 0.2 + Math.random() * 0.6,
- // 0.2-0.8 how likely to flee when pursued
- bravery: 0.1 + Math.random() * 0.8,
- // 0.1-0.9 how brave this car is (opposes flight tendency)
- panicThreshold: 0.3 + Math.random() * 0.5,
- // 0.3-0.8 pursuit intensity needed to trigger panic
- // Preferred evasion tactics (0-1 probability for each)
- preferSpeedEscape: Math.random(),
- // 0-1 preference for straight-line speed escapes
- preferZigzagEscape: Math.random(),
- // 0-1 preference for zigzag evasion patterns
- preferCircularEscape: Math.random(),
- // 0-1 preference for circular evasion patterns
- // Flight decision factors
- flightDistance: 150 + Math.random() * 200,
- // 150-350 distance at which flight is considered
- maxFlightDuration: 120 + Math.random() * 240,
- // 2-6 seconds maximum flight time before reconsidering
- counterAttackChance: 0.2 + Math.random() * 0.5,
- // 0.2-0.7 base chance to counter-attack instead of fleeing
- riskTolerance: 0.1 + Math.random() * 0.7,
- // 0.1-0.8 tolerance for risky situations
- // Health-based behavioral modifiers
- baseAggressiveness: 0.3 + Math.random() * 0.7,
- // Store original aggressiveness
- baseFlightTendency: 0.2 + Math.random() * 0.6,
- // Store original flight tendency
- healthAggressionBonus: 0.5 + Math.random() * 0.3,
- // 0.5-0.8 how much health affects aggression
- healthFlightBonus: 0.3 + Math.random() * 0.4,
- // 0.3-0.7 how much low health increases flight
- // Camping behavior (new personality type)
- isCamper: Math.random() < 0.25,
- // 25% chance to be a camper
- campingPatience: 0.6 + Math.random() * 0.4,
- // 0.6-1.0 how patient camper is
- campingThreshold: 0.6 + Math.random() * 0.3,
- // 0.6-0.9 health percentage to start attacking
- campingDistance: 200 + Math.random() * 150,
- // 200-350 preferred camping distance
- opportunismLevel: 0.7 + Math.random() * 0.3 // 0.7-1.0 how opportunistic the camper is
- };
- // Add to array and scene
- enemyCars.push(enemyCar);
- gameplayBackground.addChild(enemyCar);
-}
-// For backward compatibility, keep reference to first enemy car
-var enemyCar = enemyCars[0];
+// Create enemy car in gameplay area
+var enemyCar = new EnemyCar();
+enemyCar.x = 500;
+enemyCar.y = 500;
+gameplayBackground.addChild(enemyCar);
// Create UI background - 1/5 of screen height (bottom portion)
var uiBackground = game.attachAsset('uiBg', {
x: 0,
y: 2186,
anchorX: 0,
anchorY: 0
});
-// Create player health bar at the top right of UI with 20px margin
-var healthBarBg = uiBackground.attachAsset('BarBg', {
- x: 1800,
- y: 40,
- anchorX: 0.5,
- anchorY: 0.5,
- scaleX: 4,
- scaleY: 0.3
-});
-var healthBar = uiBackground.attachAsset('Bar', {
- x: 1800,
- y: 40,
- anchorX: 0.5,
- anchorY: 0.5,
- scaleX: 4,
- scaleY: 0.3
-});
-// Tint health bar with player car color (red)
-healthBar.tint = 0xff0000;
-// Create enemy health bars array
-var enemyHealthBars = [];
-var enemyHealthBarBgs = [];
-// Function to create enemy health bars
-function createEnemyHealthBar(enemyIndex, enemyColor) {
- // Use same dimensions and scaling as player health bar
- var barX = healthBarBg.x;
- var barY = healthBarBg.y + 40 + enemyIndex * 35;
- var barAnchorX = healthBarBg.anchorX || 0.5;
- var barAnchorY = healthBarBg.anchorY || 0.5;
- var barScaleX = healthBarBg.scaleX || 4;
- var barScaleY = healthBarBg.scaleY || 0.3;
- // Create background for enemy health bar
- var enemyHealthBarBg = uiBackground.attachAsset('BarBg', {
- x: barX,
- y: barY,
- anchorX: barAnchorX,
- anchorY: barAnchorY,
- scaleX: barScaleX,
- scaleY: barScaleY
- });
- // Create enemy health bar
- var enemyHealthBar = uiBackground.attachAsset('Bar', {
- x: barX,
- y: barY,
- anchorX: barAnchorX,
- anchorY: barAnchorY,
- scaleX: barScaleX,
- scaleY: barScaleY
- });
- // Tint health bar with enemy car color
- enemyHealthBar.tint = enemyColor;
- enemyHealthBarBgs.push(enemyHealthBarBg);
- enemyHealthBars.push(enemyHealthBar);
-}
-// Create health bars for all initial enemy cars
-for (var healthBarIdx = 0; healthBarIdx < enemyCars.length; healthBarIdx++) {
- var enemyCarGraphics = enemyCars[healthBarIdx].children[0]; // Get the car graphics
- var enemyColor = enemyCarGraphics.tint || 0xffffff; // Get the tint color
- createEnemyHealthBar(healthBarIdx, enemyColor);
-}
// Create speed display text
var speedText = new Text2('Speed: 0', {
size: 60,
fill: 0x000000
@@ -375,34 +154,15 @@
var currentVelocity = 0;
var acceleration = 0.16;
var deceleration = 0.44;
var maxSpeed = 15.36;
+// Vehicle mass properties
+var playerMass = 1200; // Player car mass in kg
// Variables for drift physics
var velocityX = 0;
var velocityY = 0;
var driftFactor = 0.85; // How much momentum is retained (lower = more drift)
var gripFactor = 0.3; // How quickly car aligns with direction (lower = more drift)
-// Player car weight
-var playerCarWeight = 1.2; // Player car is moderately heavy
-// Health system variables
-var maxHealth = 100; // Maximum health for all cars
-var playerHealth = maxHealth; // Player starts with full health
-// Damage system variables
-var minDamageSpeed = 2; // Minimum speed to cause damage
-var maxDamageSpeed = 12; // Speed at which maximum damage occurs
-var minDamage = 5; // Minimum damage at low speeds
-var maxDamage = 25; // Maximum damage at high speeds
-// Helper function to calculate damage based on speed
-function calculateSpeedDamage(speed) {
- if (speed < minDamageSpeed) {
- return 0; // No damage below minimum speed
- }
- // Normalize speed to 0-1 range
- var speedRatio = Math.min(1, (speed - minDamageSpeed) / (maxDamageSpeed - minDamageSpeed));
- // Calculate damage using quadratic curve for more realistic impact
- var damage = minDamage + (maxDamage - minDamage) * speedRatio * speedRatio;
- return Math.round(damage);
-}
// Handle touch down - create and show point
game.down = function (x, y, obj) {
// Create point at touch position
point = game.attachAsset('Puntero', {
@@ -585,2185 +345,122 @@
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)
+ // Particle system for car exhaust
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
- });
- }
+ // Generate particles proportional to current velocity (from very little to much)
+ var totalSpeed = Math.sqrt(velocityX * velocityX + velocityY * velocityY);
+ var speedRatio = totalSpeed / maxSpeed; // 0 to 1 ratio of current speed to max speed
+ // Calculate particle generation frequency based on speed (more speed = more frequent particles)
+ var particleFrequency = Math.max(1, Math.floor(8 - speedRatio * 6)); // From every 8 ticks (slow) to every 2 ticks (fast)
+ if (speedRatio > 0.05 && LK.ticks % particleFrequency === 0) {
+ // Only generate particles when moving at least 5% of max speed
+ // Calculate particle spawn position further behind the car (20% closer for smaller car)
+ var particleSpawnX = carPlayer.x - Math.sin(carPlayer.rotation) * 55;
+ var particleSpawnY = carPlayer.y + Math.cos(carPlayer.rotation) * 55;
+ // Create particles based on speed - more speed = more particles
+ var particleCount = Math.max(1, Math.floor(speedRatio * 3)); // 1-3 particles based on speed ratio
+ for (var p = 0; p < particleCount; p++) {
+ // Create new particle
+ var newParticle = new Particle();
+ newParticle.x = particleSpawnX + (Math.random() - 0.5) * 19.2; // 20% smaller spawn area
+ newParticle.y = particleSpawnY + (Math.random() - 0.5) * 19.2;
+ // Add velocity variation based on car movement (20% reduced for smaller scale)
+ newParticle.velocityX += -velocityX * 0.12 + (Math.random() - 0.5) * 2.4;
+ newParticle.velocityY += -velocityY * 0.12 + (Math.random() - 0.5) * 2.4;
+ game.particles.push(newParticle);
+ gameplayBackground.addChild(newParticle);
+ // Tween particle color for variety
+ var colors = [0xffffff, 0xcccccc, 0x999999, 0x666666];
+ var randomColor = colors[Math.floor(Math.random() * colors.length)];
+ tween(newParticle.children[0], {
+ tint: randomColor
+ }, {
+ duration: 100
+ });
}
}
- // Optimize particle emissions - reduce frequency
- if (LK.ticks % 2 === 0) {
- // Only emit particles every other frame
- // Emit particles for player car
- emitCarParticles(carPlayer, velocityX, velocityY, carPlayer.rotation, maxSpeed, gameplayBackground);
- // Emit particles for all enemy cars
- for (var carIdx = 0; carIdx < enemyCars.length; carIdx++) {
- var currentEnemyCar = enemyCars[carIdx];
- var enemyTotalVX = (currentEnemyCar.aiVelocityX || 0) + (currentEnemyCar.velocityX || 0);
- var enemyTotalVY = (currentEnemyCar.aiVelocityY || 0) + (currentEnemyCar.velocityY || 0);
- emitCarParticles(currentEnemyCar, enemyTotalVX, enemyTotalVY, currentEnemyCar.rotation, maxSpeed, gameplayBackground);
- }
- }
- // Update and clean up particles (batch process)
- var particlesToRemove = [];
- for (var i = 0; i < game.particles.length; i++) {
+ // 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) {
- particlesToRemove.push(i);
+ particle.destroy();
+ game.particles.splice(i, 1);
}
}
- // Remove particles in reverse order to maintain indices
- for (var r = particlesToRemove.length - 1; r >= 0; r--) {
- var idx = particlesToRemove[r];
- game.particles[idx].destroy();
- game.particles.splice(idx, 1);
- }
- // Check collision between player car and all enemy cars using smaller collision boxes (optimized)
- if (!carPlayer.lastColliding) carPlayer.lastColliding = [];
- // Initialize collision tracking array for all cars
- while (carPlayer.lastColliding.length < enemyCars.length) {
- carPlayer.lastColliding.push(false);
- }
- // Define smaller collision boxes (approximately 60% of actual asset size for more precise collision) - cache constants
- var playerCollisionWidth = 38; // Reduced from 64px width
- var playerCollisionHeight = 57; // Reduced from ~95px height
- var enemyCollisionWidth = 38; // Reduced from 64px width
- var enemyCollisionHeight = 57; // Reduced from ~94px height
- var halfPlayerWidth = playerCollisionWidth / 2;
- var halfPlayerHeight = playerCollisionHeight / 2;
- var halfEnemyWidth = enemyCollisionWidth / 2;
- var halfEnemyHeight = enemyCollisionHeight / 2;
- var playerLeft = carPlayer.x - halfPlayerWidth;
- var playerRight = carPlayer.x + halfPlayerWidth;
- var playerTop = carPlayer.y - halfPlayerHeight;
- var playerBottom = carPlayer.y + halfPlayerHeight;
- // Check collision with each enemy car
- for (var enemyIdx = 0; enemyIdx < enemyCars.length; enemyIdx++) {
- var currentEnemyCar = enemyCars[enemyIdx];
- var enemyLeft = currentEnemyCar.x - halfEnemyWidth;
- var enemyRight = currentEnemyCar.x + halfEnemyWidth;
- var enemyTop = currentEnemyCar.y - halfEnemyHeight;
- var enemyBottom = currentEnemyCar.y + halfEnemyHeight;
- // More precise collision detection using smaller bounding boxes
- var currentColliding = !(playerRight < enemyLeft || playerLeft > enemyRight || playerBottom < enemyTop || playerTop > enemyBottom);
- if (!carPlayer.lastColliding[enemyIdx] && currentColliding) {
- // Collision just started - determine collision responsibility
- var playerSpeed = Math.sqrt(velocityX * velocityX + velocityY * velocityY);
- var enemySpeed = Math.sqrt(currentEnemyCar.velocityX * currentEnemyCar.velocityX + currentEnemyCar.velocityY * currentEnemyCar.velocityY);
- // Calculate collision direction (from player car to enemy car)
- var collisionDeltaX = currentEnemyCar.x - carPlayer.x;
- var collisionDeltaY = currentEnemyCar.y - carPlayer.y;
- var collisionDistance = Math.sqrt(collisionDeltaX * collisionDeltaX + collisionDeltaY * collisionDeltaY);
- // Normalize collision direction
- if (collisionDistance > 0) {
- collisionDeltaX /= collisionDistance;
- collisionDeltaY /= collisionDistance;
- }
- // Determine collision responsibility based on velocity vectors and approach angles
- var playerCrasher = false;
- var enemyCrasher = false;
- var bothCrashed = false;
- // Calculate approach vectors (movement direction toward collision point)
- var playerApproachX = 0,
- playerApproachY = 0;
- var enemyApproachX = 0,
- enemyApproachY = 0;
- if (playerSpeed > 0.1) {
- // Normalize player velocity to get approach direction
- playerApproachX = velocityX / playerSpeed;
- playerApproachY = velocityY / playerSpeed;
- }
- if (enemySpeed > 0.1) {
- // Normalize enemy velocity to get approach direction
- var enemyTotalVelX = (currentEnemyCar.aiVelocityX || 0) + currentEnemyCar.velocityX;
- var enemyTotalVelY = (currentEnemyCar.aiVelocityY || 0) + currentEnemyCar.velocityY;
- var enemyTotalSpeed = Math.sqrt(enemyTotalVelX * enemyTotalVelX + enemyTotalVelY * enemyTotalVelY);
- if (enemyTotalSpeed > 0.1) {
- enemyApproachX = enemyTotalVelX / enemyTotalSpeed;
- enemyApproachY = enemyTotalVelY / enemyTotalSpeed;
- }
- }
- // Calculate how much each car is moving toward the collision
- var playerTowardCollision = 0;
- var enemyTowardCollision = 0;
- if (playerSpeed > 0.1) {
- // Dot product of player movement with collision direction
- playerTowardCollision = playerApproachX * collisionDeltaX + playerApproachY * collisionDeltaY;
- }
- if (enemySpeed > 0.1) {
- // Dot product of enemy movement with opposite collision direction (toward player)
- enemyTowardCollision = enemyApproachX * -collisionDeltaX + enemyApproachY * -collisionDeltaY;
- }
- // Speed thresholds for determining crasher responsibility
- var minCrasherSpeed = maxSpeed * 0.15; // 15% of max speed minimum to be considered crasher
- var dominantCrasherThreshold = 0.3; // How much more one car must be approaching to be sole crasher
- // Determine responsibility based on approach analysis
- if (playerSpeed > minCrasherSpeed && enemySpeed > minCrasherSpeed) {
- // Both cars moving significantly
- if (playerTowardCollision > dominantCrasherThreshold && enemyTowardCollision > dominantCrasherThreshold) {
- // Both approaching collision point - head-on or mutual collision
- bothCrashed = true;
- } else if (playerTowardCollision > enemyTowardCollision + dominantCrasherThreshold) {
- // Player moving much more toward collision
- playerCrasher = true;
- } else if (enemyTowardCollision > playerTowardCollision + dominantCrasherThreshold) {
- // Enemy moving much more toward collision
- enemyCrasher = true;
- } else {
- // Similar approach - treat as mutual collision
- bothCrashed = true;
- }
- } else if (playerSpeed > minCrasherSpeed && enemySpeed <= minCrasherSpeed) {
- // Only player moving significantly - player is crasher
- playerCrasher = true;
- } else if (enemySpeed > minCrasherSpeed && playerSpeed <= minCrasherSpeed) {
- // Only enemy moving significantly - enemy is crasher
- enemyCrasher = true;
- } else {
- // Both moving very slowly - treat as mutual collision
- bothCrashed = true;
- }
- // Calculate momentum transfer using conservation of momentum
- var totalMass = playerCarWeight + currentEnemyCar.weight;
- var massRatio1 = (playerCarWeight - currentEnemyCar.weight) / totalMass;
- var massRatio2 = 2 * currentEnemyCar.weight / totalMass;
- var massRatio3 = 2 * playerCarWeight / totalMass;
- var massRatio4 = (currentEnemyCar.weight - playerCarWeight) / totalMass;
- // Apply different energy loss based on collision responsibility
- var relativeSpeed = Math.max(playerSpeed, enemySpeed);
- var minLoss, maxLoss;
- var speedNorm = Math.min(1, relativeSpeed / maxSpeed); // 0 to 1
- if (bothCrashed) {
- // Mutual collision - both get significant energy loss
- minLoss = 0.22; // Higher base energy loss for mutual crashes
- maxLoss = 0.70; // Higher max energy loss for mutual crashes
- } else {
- // Single crasher scenario - crasher gets less penalty
- minLoss = 0.15; // Lower base energy loss
- maxLoss = 0.60; // Lower max energy loss
- }
- var energyLoss = minLoss + (maxLoss - minLoss) * speedNorm;
- var restitution = 1 - energyLoss;
- // Apply different restitution based on responsibility
- var playerRestitution = restitution;
- var enemyRestitution = restitution;
- if (playerCrasher) {
- // Player is crasher - gets less penalty (keeps more momentum)
- playerRestitution = restitution * 1.15; // 15% less energy loss
- enemyRestitution = restitution * 0.85; // 15% more energy loss for crashed car
- } else if (enemyCrasher) {
- // Enemy is crasher - gets less penalty
- enemyRestitution = restitution * 1.15; // 15% less energy loss
- playerRestitution = restitution * 0.85; // 15% more energy loss for crashed car
- }
- // For bothCrashed, both use same restitution
- // Separate cars to prevent overlap
- var separationDistance = 70;
- carPlayer.x = currentEnemyCar.x - collisionDeltaX * separationDistance;
- carPlayer.y = currentEnemyCar.y - collisionDeltaY * separationDistance;
- // Calculate new velocities based on mass and current motion
- var playerImpactX = velocityX * massRatio1 + currentEnemyCar.velocityX * massRatio2;
- var playerImpactY = velocityY * massRatio1 + currentEnemyCar.velocityY * massRatio2;
- velocityX = playerImpactX * playerRestitution;
- velocityY = playerImpactY * playerRestitution;
- // Enemy car velocity change
- var enemyImpactX = velocityX * massRatio3 + currentEnemyCar.velocityX * massRatio4;
- var enemyImpactY = velocityY * massRatio3 + currentEnemyCar.velocityY * massRatio4;
- currentEnemyCar.velocityX = enemyImpactX * enemyRestitution;
- currentEnemyCar.velocityY = enemyImpactY * enemyRestitution;
- // Launch enemy car away from collision
- var launchSpeed = Math.max(playerSpeed, enemySpeed);
- var minPush = 0.4;
- var maxPush = 1.5;
- var pushMultiplier = minPush + (maxPush - minPush) * speedNorm;
- var launchDirX = currentEnemyCar.x - carPlayer.x;
- var launchDirY = currentEnemyCar.y - carPlayer.y;
- var launchDist = Math.sqrt(launchDirX * launchDirX + launchDirY * launchDirY);
- if (launchDist < 0.01) {
- var playerMoveNorm = Math.sqrt(velocityX * velocityX + velocityY * velocityY);
- if (playerMoveNorm > 0.01) {
- launchDirX = velocityX / playerMoveNorm;
- launchDirY = velocityY / playerMoveNorm;
- } else {
- launchDirX = 0;
- launchDirY = -1;
- }
- } else {
- launchDirX /= launchDist;
- launchDirY /= launchDist;
- }
- currentEnemyCar.velocityX += launchDirX * launchSpeed * pushMultiplier;
- currentEnemyCar.velocityY += launchDirY * launchSpeed * pushMultiplier;
- // Add directional push based on collision angle and relative mass
- var pushIntensity = (playerSpeed + enemySpeed) * (0.5 + 0.5 * speedNorm);
- var playerPushRatio = currentEnemyCar.weight / totalMass;
- var enemyPushRatio = playerCarWeight / totalMass;
- velocityX += -collisionDeltaX * pushIntensity * playerPushRatio * 0.7;
- velocityY += -collisionDeltaY * pushIntensity * playerPushRatio * 0.7;
- currentEnemyCar.velocityX += collisionDeltaX * pushIntensity * enemyPushRatio * 0.7;
- currentEnemyCar.velocityY += collisionDeltaY * pushIntensity * enemyPushRatio * 0.7;
- // Reset acceleration and velocity state based on responsibility
- if (playerCrasher) {
- // Player is crasher - smaller penalty to acceleration
- currentVelocity *= 0.3; // Keep 30% of acceleration state
- } else {
- // Player crashed into or mutual - full reset
- currentVelocity = 0; // Reset player's acceleration state to restart from zero
- }
- if (enemyCrasher) {
- // Enemy is crasher - smaller penalty to acceleration
- if (typeof currentEnemyCar.aiCurrentVelocity !== "undefined") {
- currentEnemyCar.aiCurrentVelocity *= 0.3; // Keep 30% of AI acceleration state
- }
- } else {
- // Enemy crashed into or mutual - full reset
- if (typeof currentEnemyCar.aiCurrentVelocity !== "undefined") {
- currentEnemyCar.aiCurrentVelocity = 0; // Reset enemy's AI acceleration state
- }
- }
- // Calculate speed-based damage
- var collisionSpeed = Math.max(playerSpeed, enemySpeed);
- var baseDamage = calculateSpeedDamage(collisionSpeed);
- // Apply damage with responsibility-based penalties
- var playerDamage = 0;
- var enemyDamage = 0;
- if (bothCrashed) {
- // Both crashed - equal damage
- playerDamage = baseDamage;
- enemyDamage = baseDamage;
- } else if (playerCrasher) {
- // Player is crasher - takes less damage
- playerDamage = Math.round(baseDamage * 0.7); // 30% less damage
- enemyDamage = Math.round(baseDamage * 1.3); // 30% more damage
- } else if (enemyCrasher) {
- // Enemy is crasher - takes less damage
- enemyDamage = Math.round(baseDamage * 0.7); // 30% less damage
- playerDamage = Math.round(baseDamage * 1.3); // 30% more damage
- } else {
- // Fallback - equal damage
- playerDamage = baseDamage;
- enemyDamage = baseDamage;
- }
- // Apply damage to player
- if (playerDamage > 0) {
- playerHealth = Math.max(0, playerHealth - playerDamage);
- // Check for player death
- if (playerHealth <= 0) {
- LK.showGameOver();
- return; // Exit update loop
- }
- }
- // Apply damage to enemy
- if (enemyDamage > 0) {
- currentEnemyCar.health = Math.max(0, currentEnemyCar.health - enemyDamage);
- // Mark car as disabled when health reaches 0 but don't destroy
- if (currentEnemyCar.health <= 0) {
- currentEnemyCar.aiDisabled = true;
- }
- }
- // Play collision sound effect - randomly select from available sounds
- var collisionSounds = ['SonidoChoque', 'SonidoChoque2', 'SonidoChoque3'];
- var randomSoundIndex = Math.floor(Math.random() * collisionSounds.length);
- LK.getSound(collisionSounds[randomSoundIndex]).play();
- // Visual feedback based on collision responsibility
- if (bothCrashed) {
- // Both crashed - flash both in orange
- LK.effects.flashObject(carPlayer, 0xff8800, 500);
- LK.effects.flashObject(currentEnemyCar, 0xff8800, 500);
- } else if (playerCrasher) {
- // Player crashed into enemy - flash player red, enemy yellow
- LK.effects.flashObject(carPlayer, 0xff4400, 400);
- LK.effects.flashObject(currentEnemyCar, 0xffff00, 600);
- } else if (enemyCrasher) {
- // Enemy crashed into player - flash enemy red, player yellow
- LK.effects.flashObject(currentEnemyCar, 0xff4400, 400);
- LK.effects.flashObject(carPlayer, 0xffff00, 600);
- } else {
- // Default fallback
- LK.effects.flashObject(carPlayer, 0xff0000, 500);
- }
+ // Check collision between player car and enemy car
+ if (!carPlayer.lastColliding) carPlayer.lastColliding = false;
+ var currentColliding = carPlayer.intersects(enemyCar);
+ if (!carPlayer.lastColliding && currentColliding) {
+ // Collision just started - calculate collision physics
+ var carSpeed = Math.sqrt(velocityX * velocityX + velocityY * velocityY);
+ // Calculate collision direction (from enemy car to player car)
+ var collisionDeltaX = carPlayer.x - enemyCar.x;
+ var collisionDeltaY = carPlayer.y - enemyCar.y;
+ var collisionDistance = Math.sqrt(collisionDeltaX * collisionDeltaX + collisionDeltaY * collisionDeltaY);
+ // Normalize collision direction
+ if (collisionDistance > 0) {
+ collisionDeltaX /= collisionDistance;
+ collisionDeltaY /= collisionDistance;
}
- // Update last collision state for this enemy
- carPlayer.lastColliding[enemyIdx] = currentColliding;
+ // Calculate momentum-based collision using conservation of momentum
+ var playerMomentumX = velocityX * playerMass;
+ var playerMomentumY = velocityY * playerMass;
+ var enemyMomentumX = enemyCar.velocityX * enemyCar.mass;
+ var enemyMomentumY = enemyCar.velocityY * enemyCar.mass;
+ var totalMass = playerMass + enemyCar.mass;
+ var massRatioPlayer = enemyCar.mass / totalMass; // Heavier opponent = more effect on player
+ var massRatioEnemy = playerMass / totalMass; // Heavier player = more effect on enemy
+ // Energy loss factor based on mass difference
+ var energyLoss = 0.4 + Math.abs(playerMass - enemyCar.mass) / (playerMass + enemyCar.mass) * 0.2;
+ // Separate cars to prevent overlap
+ var separationDistance = 80; // Distance to separate cars
+ carPlayer.x = enemyCar.x + collisionDeltaX * separationDistance;
+ carPlayer.y = enemyCar.y + collisionDeltaY * separationDistance;
+ // Calculate collision impulse based on relative velocity and masses
+ var relativeVelocityX = velocityX - enemyCar.velocityX;
+ var relativeVelocityY = velocityY - enemyCar.velocityY;
+ var relativeSpeed = Math.sqrt(relativeVelocityX * relativeVelocityX + relativeVelocityY * relativeVelocityY);
+ // Apply realistic momentum transfer with energy loss
+ var impulseStrength = relativeSpeed * massRatioPlayer * (1 - energyLoss);
+ velocityX = velocityX - collisionDeltaX * impulseStrength;
+ velocityY = velocityY - collisionDeltaY * impulseStrength;
+ // Apply opposite impulse to enemy car
+ var enemyImpulseStrength = relativeSpeed * massRatioEnemy * (1 - energyLoss);
+ enemyCar.velocityX = enemyCar.velocityX + collisionDeltaX * enemyImpulseStrength;
+ enemyCar.velocityY = enemyCar.velocityY + collisionDeltaY * enemyImpulseStrength;
+ // Visual feedback for collision
+ LK.effects.flashObject(carPlayer, 0xff0000, 500);
}
- // --- Enemy Cars AI Movement Logic (Optimized) ---
- // Process AI for each enemy car - stagger AI updates to reduce load
- var aiUpdateOffset = LK.ticks % enemyCars.length; // Stagger AI updates
- for (var aiCarIdx = 0; aiCarIdx < enemyCars.length; aiCarIdx++) {
- var enemyCar = enemyCars[aiCarIdx];
- // Skip AI processing for disabled cars (0 health)
- if (enemyCar.aiDisabled) {
- continue;
- }
- // Only update AI for one car per frame (staggered)
- var shouldUpdateAI = aiCarIdx === aiUpdateOffset;
- // Dynamic target selection system - choose best target based on position and velocity
- if (typeof enemyCar.currentTarget === "undefined") enemyCar.currentTarget = null;
- if (typeof enemyCar.targetSelectionTimer === "undefined") enemyCar.targetSelectionTimer = 0;
- if (typeof enemyCar.targetSelectionInterval === "undefined") enemyCar.targetSelectionInterval = 60; // Re-evaluate every second
- // Pursuit detection system - track who is following this AI car
- if (typeof enemyCar.pursuitDetection === "undefined") {
- enemyCar.pursuitDetection = {
- pursuers: [],
- // List of potential pursuers with tracking data
- beingPursued: false,
- pursuitIntensity: 0,
- // 0-1 scale of how intensely being pursued
- pursuitDuration: 0,
- // How long being pursued in frames
- lastPursuitCheck: 0,
- evasionMode: false,
- // Whether currently evading
- evasionStrategy: 0,
- // 0=speed, 1=zigzag, 2=circles
- evasionTimer: 0,
- evasionDuration: 0,
- counterAttackMode: false,
- // Whether to turn and fight
- counterAttackTarget: null
- };
- }
- // Re-evaluate target selection periodically (only for the current AI car being updated)
- if (shouldUpdateAI) {
- enemyCar.targetSelectionTimer++;
- // Pursuit detection - analyze if other cars are targeting this one
- if (LK.ticks % 10 === 0) {
- // Check every 10 frames for performance
- enemyCar.pursuitDetection.pursuers = [];
- // Check all potential pursuers (player + other AI cars)
- var potentialPursuers = [];
- // Add player as potential pursuer
- potentialPursuers.push({
- object: carPlayer,
- velocityX: velocityX,
- velocityY: velocityY,
- isPlayer: true
- });
- // Add other enemy cars as potential pursuers
- for (var pursuerId = 0; pursuerId < enemyCars.length; pursuerId++) {
- if (pursuerId !== aiCarIdx) {
- var otherCar = enemyCars[pursuerId];
- potentialPursuers.push({
- object: otherCar,
- velocityX: (otherCar.aiVelocityX || 0) + (otherCar.velocityX || 0),
- velocityY: (otherCar.aiVelocityY || 0) + (otherCar.velocityY || 0),
- isPlayer: false
- });
- }
- }
- // Analyze each potential pursuer
- for (var pIdx = 0; pIdx < potentialPursuers.length; pIdx++) {
- var pursuer = potentialPursuers[pIdx];
- var pursuerId = pIdx;
- // Calculate distance and relative positioning
- var pursuerDist = Math.sqrt((pursuer.object.x - enemyCar.x) * (pursuer.object.x - enemyCar.x) + (pursuer.object.y - enemyCar.y) * (pursuer.object.y - enemyCar.y));
- // Only consider pursuers within reasonable range
- if (pursuerDist < 500 && pursuerDist > 50) {
- // Calculate if pursuer is moving toward this car
- var toThisCar = {
- x: enemyCar.x - pursuer.object.x,
- y: enemyCar.y - pursuer.object.y
- };
- var toThisCarMag = Math.sqrt(toThisCar.x * toThisCar.x + toThisCar.y * toThisCar.y);
- if (toThisCarMag > 0) {
- toThisCar.x /= toThisCarMag;
- toThisCar.y /= toThisCarMag;
- // Calculate pursuer's velocity direction
- var pursuerSpeed = Math.sqrt(pursuer.velocityX * pursuer.velocityX + pursuer.velocityY * pursuer.velocityY);
- if (pursuerSpeed > 1) {
- var pursuerDir = {
- x: pursuer.velocityX / pursuerSpeed,
- y: pursuer.velocityY / pursuerSpeed
- };
- // Dot product to see if pursuer is moving toward this car
- var alignment = toThisCar.x * pursuerDir.x + toThisCar.y * pursuerDir.y;
- // Consider as pursuer if moving toward this car and close enough
- if (alignment > 0.3) {
- // 30 degree cone
- var pursuitScore = alignment * (1 - pursuerDist / 500) * (pursuerSpeed / maxSpeed);
- // Additional checks for AI pursuers - see if they're targeting this car
- if (!pursuer.isPlayer && pursuer.object.currentTarget) {
- if (pursuer.object.currentTarget.object === enemyCar) {
- pursuitScore += 0.4; // Bonus if we're their target
- }
- }
- if (pursuitScore > 0.3) {
- enemyCar.pursuitDetection.pursuers.push({
- object: pursuer.object,
- distance: pursuerDist,
- score: pursuitScore,
- isPlayer: pursuer.isPlayer
- });
- }
- }
- }
- }
- }
- }
- // Determine if being pursued based on pursuer analysis
- var totalPursuitScore = 0;
- var closestPursuerDist = Infinity;
- var strongestPursuer = null;
- for (var p = 0; p < enemyCar.pursuitDetection.pursuers.length; p++) {
- var pursuerData = enemyCar.pursuitDetection.pursuers[p];
- totalPursuitScore += pursuerData.score;
- if (pursuerData.distance < closestPursuerDist) {
- closestPursuerDist = pursuerData.distance;
- strongestPursuer = pursuerData;
- }
- }
- // Update pursuit state
- var wasPursued = enemyCar.pursuitDetection.beingPursued;
- enemyCar.pursuitDetection.beingPursued = totalPursuitScore > 0.5 && enemyCar.pursuitDetection.pursuers.length > 0;
- enemyCar.pursuitDetection.pursuitIntensity = Math.min(1, totalPursuitScore);
- if (enemyCar.pursuitDetection.beingPursued) {
- enemyCar.pursuitDetection.pursuitDuration++;
- // Decide on response strategy when first detected or strategy expired
- if (!wasPursued || enemyCar.pursuitDetection.evasionTimer > enemyCar.pursuitDetection.evasionDuration) {
- var personality = enemyCar.tacticalPersonality;
- // Decision factors
- var currentSpeed = Math.sqrt((enemyCar.aiVelocityX || 0) * (enemyCar.aiVelocityX || 0) + (enemyCar.aiVelocityY || 0) * (enemyCar.aiVelocityY || 0));
- var speedRatio = currentSpeed / maxSpeed;
- var pursuerThreat = enemyCar.pursuitDetection.pursuitIntensity;
- // Personality-based decision making
- var fleeChance = 0.6 + personality.patience * 0.2 - personality.aggressiveness * 0.3;
- var counterAttackChance = 0.4 + personality.aggressiveness * 0.4 - personality.patience * 0.2;
- // Situational modifiers
- if (speedRatio > 0.7) fleeChance += 0.2; // Easier to flee when fast
- if (closestPursuerDist < 150) counterAttackChance += 0.3; // More likely to fight when cornered
- if (pursuerThreat > 0.8) counterAttackChance += 0.2; // Fight when heavily pursued
- // Individual flight decision based on personality traits and health
- var flightPrefs = enemyCar.tacticalPersonality;
- var shouldFlee = false;
- var shouldCounterAttack = false;
- // Health-based behavioral modifications
- var healthRatio = enemyCar.health / maxHealth; // 0-1, where 1 is full health
- var healthAggressionModifier = (healthRatio - 0.5) * flightPrefs.healthAggressionBonus; // +aggressive when healthy, -aggressive when hurt
- var healthFlightModifier = (0.5 - healthRatio) * flightPrefs.healthFlightBonus; // +flight when hurt, -flight when healthy
- // Update dynamic personality based on current health
- flightPrefs.aggressiveness = Math.max(0.1, Math.min(1.0, flightPrefs.baseAggressiveness + healthAggressionModifier));
- flightPrefs.flightTendency = Math.max(0.1, Math.min(0.9, flightPrefs.baseFlightTendency + healthFlightModifier));
- // Calculate individual flight decision factors
- var baseFlightChance = flightPrefs.flightTendency;
- var baseCounterChance = flightPrefs.counterAttackChance;
- // Health-based modifiers for decision making
- var healthFlightBonus = healthRatio < 0.3 ? 0.4 : healthRatio < 0.6 ? 0.2 : 0; // More likely to flee when very hurt
- var healthCounterBonus = healthRatio > 0.7 ? 0.3 : healthRatio > 0.4 ? 0.1 : 0; // More likely to fight when healthy
- // Modify chances based on individual traits
- var braveryBonus = flightPrefs.bravery * 0.3; // Brave cars more likely to fight
- var panicPenalty = pursuerThreat > flightPrefs.panicThreshold ? 0.2 : 0; // Panic reduces counter-attack
- var riskFactor = (1 - flightPrefs.riskTolerance) * 0.25; // Risk-averse cars flee more
- // Apply individual modifiers including health-based changes
- var adjustedFlightChance = baseFlightChance + riskFactor + panicPenalty + healthFlightBonus - braveryBonus * 0.5;
- var adjustedCounterChance = baseCounterChance + braveryBonus + healthCounterBonus - panicPenalty - riskFactor;
- // Situational modifiers based on individual thresholds
- if (speedRatio > 0.7 && speedRatio > flightPrefs.riskTolerance) {
- adjustedFlightChance += 0.2; // Fast cars with low risk tolerance flee more
- }
- if (closestPursuerDist < flightPrefs.flightDistance) {
- if (flightPrefs.bravery > 0.6) {
- adjustedCounterChance += 0.3; // Brave cars fight when cornered
- } else {
- adjustedFlightChance += 0.2; // Others flee when too close
- }
- }
- if (pursuerThreat > 0.8 && flightPrefs.panicThreshold < 0.5) {
- adjustedFlightChance += 0.3; // Easy-to-panic cars flee under heavy pursuit
- }
- // Make individual decision
- if (Math.random() < Math.max(0.1, Math.min(0.9, adjustedCounterChance))) {
- // Turn and fight
- enemyCar.pursuitDetection.counterAttackMode = true;
- enemyCar.pursuitDetection.counterAttackTarget = strongestPursuer ? strongestPursuer.object : null;
- enemyCar.pursuitDetection.evasionMode = false;
- enemyCar.pursuitDetection.evasionDuration = 120 + Math.floor(Math.random() * 180); // 2-5 seconds
- } else {
- // Flee - choose evasion strategy based on individual preferences
- enemyCar.pursuitDetection.evasionMode = true;
- enemyCar.pursuitDetection.counterAttackMode = false;
- // Individual evasion strategy selection based on preferences and situation
- var strategyScores = [flightPrefs.preferSpeedEscape, flightPrefs.preferZigzagEscape, flightPrefs.preferCircularEscape];
- // Situational bonuses based on individual traits
- if (speedRatio < 0.4) {
- strategyScores[0] += 0.3; // Speed strategy bonus when slow
- } else if (speedRatio > 0.7) {
- strategyScores[1] += 0.2; // Zigzag bonus when fast (harder to predict)
- strategyScores[2] += 0.15;
- }
- if (closestPursuerDist < flightPrefs.flightDistance * 0.7) {
- strategyScores[1] += 0.25; // Zigzag when close
- strategyScores[2] += 0.3; // Circles when close
- } else if (closestPursuerDist > flightPrefs.flightDistance * 1.5) {
- strategyScores[0] += 0.4; // Speed when far
- }
- // Individual trait bonuses
- if (flightPrefs.adaptability > 0.7) {
- strategyScores[1] += 0.2; // Adaptive cars prefer zigzag
- }
- if (flightPrefs.patience > 0.6) {
- strategyScores[2] += 0.2; // Patient cars can handle circles
- }
- if (flightPrefs.aggressiveness > 0.6) {
- strategyScores[0] += 0.15; // Aggressive cars prefer speed
- }
- // Select strategy with highest score
- var maxScore = Math.max(strategyScores[0], strategyScores[1], strategyScores[2]);
- var bestStrategies = [];
- for (var si = 0; si < 3; si++) {
- if (strategyScores[si] >= maxScore - 0.1) {
- bestStrategies.push(si);
- }
- }
- enemyCar.pursuitDetection.evasionStrategy = bestStrategies[Math.floor(Math.random() * bestStrategies.length)];
- // Individual evasion duration based on personality
- var baseDuration = Math.floor(flightPrefs.maxFlightDuration * 0.6); // 60% of max
- var personalityDuration = Math.floor(flightPrefs.maxFlightDuration * 0.4 * flightPrefs.patience); // Patience affects duration
- enemyCar.pursuitDetection.evasionDuration = baseDuration + personalityDuration;
- }
- enemyCar.pursuitDetection.evasionTimer = 0;
- }
- } else {
- enemyCar.pursuitDetection.pursuitDuration = 0;
- enemyCar.pursuitDetection.evasionMode = false;
- enemyCar.pursuitDetection.counterAttackMode = false;
- }
- }
- enemyCar.pursuitDetection.evasionTimer++;
- }
- if ((enemyCar.targetSelectionTimer >= enemyCar.targetSelectionInterval || enemyCar.currentTarget === null) && shouldUpdateAI) {
- enemyCar.targetSelectionTimer = 0;
- // Create list of all potential targets (player + other enemy cars)
- var potentialTargets = [];
- // Add player as potential target
- potentialTargets.push({
- object: carPlayer,
- velocityX: velocityX,
- velocityY: velocityY,
- weight: playerCarWeight,
- isPlayer: true
- });
- // Add other enemy cars as potential targets
- for (var targetIdx = 0; targetIdx < enemyCars.length; targetIdx++) {
- if (targetIdx !== aiCarIdx) {
- // Don't target self
- var otherCar = enemyCars[targetIdx];
- potentialTargets.push({
- object: otherCar,
- velocityX: (otherCar.aiVelocityX || 0) + (otherCar.velocityX || 0),
- velocityY: (otherCar.aiVelocityY || 0) + (otherCar.velocityY || 0),
- weight: otherCar.weight,
- isPlayer: false
- });
- }
- }
- // Evaluate each target and assign scores
- var bestTarget = null;
- var bestScore = -1;
- for (var evalIdx = 0; evalIdx < potentialTargets.length; evalIdx++) {
- var target = potentialTargets[evalIdx];
- var targetSpeed = Math.sqrt(target.velocityX * target.velocityX + target.velocityY * target.velocityY);
- var targetDistance = Math.sqrt((target.object.x - enemyCar.x) * (target.object.x - enemyCar.x) + (target.object.y - enemyCar.y) * (target.object.y - enemyCar.y));
- // Calculate target score based on multiple factors
- var score = 0;
- // --- Enhanced: Health factor with camper behavior ---
- var targetHealth = 100;
- if (typeof target.object.health !== "undefined") {
- targetHealth = target.object.health;
- } else if (target.isPlayer && typeof playerHealth !== "undefined") {
- targetHealth = playerHealth;
- }
- // Base health scoring - prefer weaker targets
- var healthScore = Math.max(0, 30 - targetHealth / maxHealth * 30); // 0-30 points, lower health = higher score
- // Camper behavior modification
- if (enemyCar.tacticalPersonality.isCamper) {
- var targetHealthRatio = targetHealth / maxHealth;
- var myHealthRatio = enemyCar.health / maxHealth;
- var campingThreshold = enemyCar.tacticalPersonality.campingThreshold;
- // Campers are very selective - only attack when conditions are right
- if (myHealthRatio > campingThreshold && targetHealthRatio < 0.7) {
- // Camper is healthy and target is weakened - major bonus for opportunistic attack
- healthScore += enemyCar.tacticalPersonality.opportunismLevel * 40; // Up to 40 bonus points
- } else if (myHealthRatio < campingThreshold) {
- // Camper is hurt - reduce target priority significantly unless target is very weak
- if (targetHealthRatio > 0.3) {
- healthScore *= 0.3; // Reduce priority for healthy targets when camper is hurt
- }
- } else if (targetHealthRatio > 0.8) {
- // Target is too healthy for camper's comfort - major penalty
- healthScore *= 0.1; // Almost ignore healthy targets
- }
- } else {
- // Non-camper behavior - health-based aggression affects targeting
- var myHealthRatio = enemyCar.health / maxHealth;
- var aggressionModifier = enemyCar.tacticalPersonality.aggressiveness;
- if (myHealthRatio > 0.7 && aggressionModifier > 0.6) {
- // Healthy and aggressive - willing to attack stronger targets
- healthScore *= 0.8; // Slight reduction in health preference
- } else if (myHealthRatio < 0.4) {
- // Hurt - strongly prefer weak targets
- healthScore *= 1.5; // Increase health preference
- }
- }
- score += healthScore;
- // Distance factor - prefer closer targets (0-30 points, slightly reduced to balance health)
- var distanceScore = Math.max(0, 30 - targetDistance / 1000 * 30);
- score += distanceScore;
- // Speed factor - prefer faster targets for more exciting gameplay (0-20 points)
- var speedRatio = targetSpeed / maxSpeed;
- var speedScore = speedRatio * 20;
- score += speedScore;
- // Weight factor - heavier targets are more satisfying to hit (0-10 points)
- var weightScore = Math.min(10, target.weight * 4);
- score += weightScore;
- // Player bonus - slight preference for player to maintain engagement (0-10 points)
- if (target.isPlayer) {
- score += 10;
- }
- // --- New: Distance to preferred attack range (prefer targets at optimal distance) ---
- var preferredDist = enemyCar.tacticalPersonality ? enemyCar.tacticalPersonality.preferredAttackDistance || 200 : 200;
- var distToPreferred = Math.abs(targetDistance - preferredDist);
- var optimalDistScore = Math.max(0, 10 - distToPreferred / 400 * 10); // 0-10 points, closer to preferred = higher
- score += optimalDistScore;
- // Velocity alignment factor - prefer targets moving in interesting directions (0-10 points)
- if (targetSpeed > 1) {
- var enemySpeed = Math.sqrt((enemyCar.aiVelocityX || 0) * (enemyCar.aiVelocityX || 0) + (enemyCar.aiVelocityY || 0) * (enemyCar.aiVelocityY || 0));
- if (enemySpeed > 1) {
- // Calculate if target and enemy are moving in similar or opposite directions
- var enemyVelX = enemyCar.aiVelocityX || 0;
- var enemyVelY = enemyCar.aiVelocityY || 0;
- var dotProduct = (target.velocityX * enemyVelX + target.velocityY * enemyVelY) / (targetSpeed * enemySpeed);
- // Prefer head-on collisions (opposite directions) - more exciting
- var alignmentScore = (1 - dotProduct) * 5; // 0-10 points
- score += alignmentScore;
- }
- }
- // Avoid recently targeted objects to prevent fixation (penalty)
- if (enemyCar.currentTarget && target.object === enemyCar.currentTarget.object) {
- // Apply small penalty to current target to encourage switching
- score *= 0.9;
- }
- // Select best target
- if (score > bestScore) {
- bestScore = score;
- bestTarget = target;
- }
- }
- // Update current target
- enemyCar.currentTarget = bestTarget;
- // Vary target selection interval for more dynamic behavior
- enemyCar.targetSelectionInterval = 45 + Math.floor(Math.random() * 90); // 0.75-2.25 seconds
- }
- // Enemy car can switch between 'follow target' and 'free roam' AI modes
- if (typeof enemyCar.aiMode === "undefined") enemyCar.aiMode = "follow";
- if (typeof enemyCar.aiModeTimer === "undefined") enemyCar.aiModeTimer = 0;
- if (typeof enemyCar.aiModeDuration === "undefined") enemyCar.aiModeDuration = 0;
- // AI mode switching logic: change mode at intervals with speed-based preferences
- enemyCar.aiModeTimer++;
- if (enemyCar.aiModeTimer > enemyCar.aiModeDuration) {
- // Check if being pursued - this overrides normal mode selection
- if (enemyCar.pursuitDetection.beingPursued && (enemyCar.pursuitDetection.evasionMode || enemyCar.pursuitDetection.counterAttackMode)) {
- // Being pursued - use special pursuit response mode
- if (enemyCar.pursuitDetection.counterAttackMode && enemyCar.pursuitDetection.counterAttackTarget) {
- // Turn and fight - target the pursuer
- enemyCar.aiMode = "follow";
- // Override current target to pursuer
- enemyCar.currentTarget = {
- object: enemyCar.pursuitDetection.counterAttackTarget,
- velocityX: enemyCar.pursuitDetection.counterAttackTarget === carPlayer ? velocityX : (enemyCar.pursuitDetection.counterAttackTarget.aiVelocityX || 0) + (enemyCar.pursuitDetection.counterAttackTarget.velocityX || 0),
- velocityY: enemyCar.pursuitDetection.counterAttackTarget === carPlayer ? velocityY : (enemyCar.pursuitDetection.counterAttackTarget.aiVelocityY || 0) + (enemyCar.pursuitDetection.counterAttackTarget.velocityY || 0),
- weight: enemyCar.pursuitDetection.counterAttackTarget === carPlayer ? playerCarWeight : enemyCar.pursuitDetection.counterAttackTarget.weight,
- isPlayer: enemyCar.pursuitDetection.counterAttackTarget === carPlayer
- };
- } else if (enemyCar.pursuitDetection.evasionMode) {
- // Fleeing - use special evasion mode
- enemyCar.aiMode = "evade";
- }
- } else {
- // Normal mode selection with camper behavior
- var myHealthRatio = enemyCar.health / maxHealth;
- var isCamper = enemyCar.tacticalPersonality.isCamper;
- var campingThreshold = enemyCar.tacticalPersonality.campingThreshold;
- var campingPatience = enemyCar.tacticalPersonality.campingPatience;
- if (isCamper) {
- // Camper-specific mode selection
- if (myHealthRatio > campingThreshold) {
- // Camper is healthy - check if any targets are weakened enough
- var hasWeakTargets = false;
- var weakestTargetHealth = 1.0;
- // Check all potential targets for weakness
- for (var camperTargetCheck = 0; camperTargetCheck < enemyCars.length + 1; camperTargetCheck++) {
- var checkTargetHealth = 1.0;
- if (camperTargetCheck < enemyCars.length && camperTargetCheck !== aiCarIdx) {
- checkTargetHealth = enemyCars[camperTargetCheck].health / maxHealth;
- } else if (camperTargetCheck === enemyCars.length) {
- checkTargetHealth = playerHealth / maxHealth;
- }
- if (checkTargetHealth < 0.7) {
- hasWeakTargets = true;
- }
- weakestTargetHealth = Math.min(weakestTargetHealth, checkTargetHealth);
- }
- if (hasWeakTargets && weakestTargetHealth < 0.5) {
- // Opportunity detected - switch to aggressive hunting
- enemyCar.aiMode = "follow";
- } else if (Math.random() < campingPatience) {
- // Patient waiting - use enhanced free roam to position and wait
- enemyCar.aiMode = "camp";
- } else {
- // Impatient camper - light engagement
- enemyCar.aiMode = "follow";
- }
- } else {
- // Camper is hurt - very defensive behavior
- if (myHealthRatio < 0.3) {
- // Critically hurt - pure evasion
- enemyCar.aiMode = "evade";
- } else {
- // Somewhat hurt - careful engagement with weak targets only
- if (enemyCar.currentTarget) {
- var targetHealth = 100;
- if (typeof enemyCar.currentTarget.object.health !== "undefined") {
- targetHealth = enemyCar.currentTarget.object.health;
- } else if (enemyCar.currentTarget.isPlayer) {
- targetHealth = playerHealth;
- }
- var targetHealthRatio = targetHealth / maxHealth;
- if (targetHealthRatio < myHealthRatio * 0.7) {
- // Target is significantly weaker - cautious attack
- enemyCar.aiMode = "follow";
- } else {
- // Target too strong - avoid
- enemyCar.aiMode = "evade";
- }
- } else {
- enemyCar.aiMode = "camp";
- }
- }
- }
- } else {
- // Non-camper normal mode selection with health-based modifications
- // Calculate target speed for mode preference based on current target
- var targetSpeed = 0;
- if (enemyCar.currentTarget) {
- targetSpeed = Math.sqrt(enemyCar.currentTarget.velocityX * enemyCar.currentTarget.velocityX + enemyCar.currentTarget.velocityY * enemyCar.currentTarget.velocityY);
- }
- var targetSpeedRatio = targetSpeed / maxSpeed; // 0 to 1
- var followChance;
- // Health-modified mode preferences
- var healthModifier = 0;
- if (myHealthRatio > 0.7) {
- // Healthy - more aggressive
- healthModifier = 0.1 * enemyCar.tacticalPersonality.aggressiveness;
- } else if (myHealthRatio < 0.4) {
- // Hurt - more cautious
- healthModifier = -0.2;
- }
- // Speed-based mode preferences with health modification:
- if (targetSpeedRatio > 0.7) {
- // Very fast - prefer follow mode for ambush tactics
- followChance = 0.9 + healthModifier;
- } else if (targetSpeedRatio > 0.4) {
- // Fast to medium - moderate follow preference
- followChance = 0.8 + healthModifier;
- } else if (targetSpeedRatio > 0.2) {
- // Medium - balanced but still prefer follow
- followChance = 0.7 + healthModifier;
- } else {
- // Slow - prefer follow mode for direct pursuit
- followChance = 0.85 + healthModifier;
- }
- // Clamp follow chance
- followChance = Math.max(0.1, Math.min(0.95, followChance));
- // Pick mode based on calculated preference
- if (Math.random() < followChance) {
- enemyCar.aiMode = "follow";
- } else {
- enemyCar.aiMode = "free";
- }
- }
- }
- // Duration: 1.2s to 3.5s (72 to 210 frames)
- enemyCar.aiModeDuration = 72 + Math.floor(Math.random() * 138);
- enemyCar.aiModeTimer = 0;
- }
- // --- AI movement logic depending on mode ---
- var aiDeltaX, aiDeltaY, aiDistance, aiTargetRotation, aiPower, aiTargetVelocity;
- // Edge avoidance logic - calculate repulsion from boundaries
- var edgeAvoidanceX = 0;
- var edgeAvoidanceY = 0;
- var edgeBuffer = 200; // Distance from edge to start avoiding
- var avoidanceStrength = 0.3; // How strongly to avoid edges
- // Check distance from each edge and calculate avoidance force
- if (enemyCar.x < edgeBuffer) {
- // Too close to left edge, push right
- var leftForce = (edgeBuffer - enemyCar.x) / edgeBuffer;
- edgeAvoidanceX += leftForce * avoidanceStrength;
- }
- if (enemyCar.x > 2048 - edgeBuffer) {
- // Too close to right edge, push left
- var rightForce = (enemyCar.x - (2048 - edgeBuffer)) / edgeBuffer;
- edgeAvoidanceX -= rightForce * avoidanceStrength;
- }
- if (enemyCar.y < edgeBuffer) {
- // Too close to top edge, push down
- var topForce = (edgeBuffer - enemyCar.y) / edgeBuffer;
- edgeAvoidanceY += topForce * avoidanceStrength;
- }
- if (enemyCar.y > 2186 - edgeBuffer) {
- // Too close to bottom edge, push up
- var bottomForce = (enemyCar.y - (2186 - edgeBuffer)) / edgeBuffer;
- edgeAvoidanceY -= bottomForce * avoidanceStrength;
- }
- if (enemyCar.aiMode === "follow") {
- // Initialize follow strategy if not set
- if (typeof enemyCar.followStrategy === "undefined") {
- enemyCar.followStrategy = 0;
- enemyCar.followStrategyTimer = 0;
- enemyCar.followStrategyDuration = 60 + Math.floor(Math.random() * 60); // 1-2 seconds for adaptive strategy
- }
- // Individual strategy selection based on each car's unique personality
- enemyCar.followStrategyTimer++;
- if (enemyCar.followStrategyTimer > enemyCar.followStrategyDuration) {
- // Calculate target speed for strategy selection
- var targetSpeed = 0;
- if (enemyCar.currentTarget) {
- targetSpeed = Math.sqrt(enemyCar.currentTarget.velocityX * enemyCar.currentTarget.velocityX + enemyCar.currentTarget.velocityY * enemyCar.currentTarget.velocityY);
- }
- var targetSpeedRatio = targetSpeed / maxSpeed; // 0 to 1
- var relativeDistance = Math.sqrt(aiDeltaX * aiDeltaX + aiDeltaY * aiDeltaY);
- var relativeAngle = Math.atan2(aiDeltaX, -aiDeltaY);
- var enemyAngle = enemyCar.rotation;
- var angleDifference = Math.abs(relativeAngle - enemyAngle);
- while (angleDifference > Math.PI) angleDifference = 2 * Math.PI - angleDifference;
- var isTargetAhead = angleDifference < Math.PI / 3; // Within 60 degrees ahead
- var isTargetBehind = angleDifference > 2 * Math.PI / 3; // More than 120 degrees behind
- // Individual strategy selection based on car's tactical personality
- var personality = enemyCar.tacticalPersonality;
- var strategyScores = [0, 0, 0]; // [direct, intimidation, ambush]
- // Base preference scores from personality
- strategyScores[0] = personality.directPreference;
- strategyScores[1] = personality.intimidationPreference;
- strategyScores[2] = personality.ambushPreference;
- // Modify scores based on target speed and individual preferences
- if (targetSpeedRatio < 0.3) {
- // Slow target - use individual slow target strategy preference
- var preferredStrategy = personality.slowTargetStrategy;
- strategyScores[preferredStrategy] += 0.4;
- } else if (targetSpeedRatio > 0.6) {
- // Fast target - use individual fast target strategy preference
- var preferredStrategy = personality.fastTargetStrategy;
- strategyScores[preferredStrategy] += 0.4;
- } else {
- // Medium speed target - use individual medium target strategy preference
- var preferredStrategy = personality.mediumTargetStrategy;
- strategyScores[preferredStrategy] += 0.3;
- }
- // Situational modifiers based on individual traits
- if (targetSpeedRatio < 0.05) {
- // Target is nearly stationary - intimidation gets bonus for patient cars
- if (personality.patience > 0.6) {
- strategyScores[1] += 0.3; // Patient cars prefer intimidation
- // Initialize intimidation counter if not set
- if (typeof enemyCar.intimidationCount === "undefined") {
- enemyCar.intimidationCount = 0;
- enemyCar.maxIntimidations = Math.floor(personality.patience * 4) + 1; // 1-4 based on patience
- }
- // Check if we've completed enough intimidations
- if (enemyCar.intimidationCount >= enemyCar.maxIntimidations) {
- strategyScores[0] += 0.5; // Switch to direct after enough intimidation
- strategyScores[1] -= 0.3;
- }
- } else {
- strategyScores[0] += 0.4; // Impatient cars go direct
- }
- }
- // Distance-based individual preferences
- if (relativeDistance < personality.preferredAttackDistance) {
- // Within preferred attack range - aggressive cars prefer direct
- strategyScores[0] += personality.aggressiveness * 0.3;
- } else if (relativeDistance > personality.preferredAttackDistance * 2) {
- // Far away - adaptive cars prefer ambush
- strategyScores[2] += personality.adaptability * 0.3;
- }
- // Positional modifiers with individual traits
- if (isTargetBehind || relativeDistance < personality.personalSpaceRadius) {
- // Target behind or in personal space - use intimidation for positioning
- strategyScores[1] += 0.2;
- } else if (isTargetAhead && relativeDistance < personality.preferredAttackDistance * 1.5) {
- // Target ahead and in preferred range
- if (personality.aggressiveness > 0.6) {
- strategyScores[0] += 0.3; // Aggressive cars go direct
- } else {
- strategyScores[1] += 0.2; // Less aggressive cars intimidate first
- }
- }
- // Select strategy with highest score (with some randomness based on adaptability)
- var maxScore = Math.max(strategyScores[0], strategyScores[1], strategyScores[2]);
- var candidateStrategies = [];
- for (var s = 0; s < 3; s++) {
- if (strategyScores[s] >= maxScore - personality.adaptability * 0.2) {
- candidateStrategies.push(s);
- }
- }
- enemyCar.followStrategy = candidateStrategies[Math.floor(Math.random() * candidateStrategies.length)];
- // Individual strategy duration based on personality
- var baseDuration = personality.minStrategyDuration + (personality.maxStrategyDuration - personality.minStrategyDuration) * personality.patience;
- var strategyMultiplier = 1.0;
- switch (enemyCar.followStrategy) {
- case 0:
- // Direct - duration affected by aggressiveness (aggressive = shorter bursts)
- strategyMultiplier = 1.2 - personality.aggressiveness * 0.4;
- break;
- case 1:
- // Intimidation - duration affected by patience (patient = longer intimidation)
- strategyMultiplier = 0.8 + personality.patience * 0.6;
- break;
- case 2:
- // Ambush - duration affected by adaptability (adaptive = longer planning)
- strategyMultiplier = 1.0 + personality.adaptability * 0.5;
- break;
- }
- enemyCar.followStrategyDuration = Math.floor(baseDuration * strategyMultiplier);
- enemyCar.followStrategyTimer = 0;
- }
- // Calculate base values using current target instead of always player (optimized)
- if (enemyCar.currentTarget) {
- aiDeltaX = enemyCar.currentTarget.object.x - enemyCar.x;
- aiDeltaY = enemyCar.currentTarget.object.y - enemyCar.y;
- // Cache distance calculation
- var aiDeltaXSq = aiDeltaX * aiDeltaX;
- var aiDeltaYSq = aiDeltaY * aiDeltaY;
- aiDistance = Math.sqrt(aiDeltaXSq + aiDeltaYSq);
- // Calculate relative velocity and approach angle for smarter pursuit
- var aiVelX = enemyCar.aiVelocityX || 0;
- var aiVelY = enemyCar.aiVelocityY || 0;
- var relativeVelX = enemyCar.currentTarget.velocityX - aiVelX;
- var relativeVelY = enemyCar.currentTarget.velocityY - aiVelY;
- } else {
- // Fallback to player if no target selected
- aiDeltaX = carPlayer.x - enemyCar.x;
- aiDeltaY = carPlayer.y - enemyCar.y;
- // Cache distance calculation
- var aiDeltaXSq = aiDeltaX * aiDeltaX;
- var aiDeltaYSq = aiDeltaY * aiDeltaY;
- aiDistance = Math.sqrt(aiDeltaXSq + aiDeltaYSq);
- var aiVelX = enemyCar.aiVelocityX || 0;
- var aiVelY = enemyCar.aiVelocityY || 0;
- var relativeVelX = velocityX - aiVelX;
- var relativeVelY = velocityY - aiVelY;
- }
- var approachAngle = Math.atan2(aiDeltaX, -aiDeltaY);
- var enemyCurrentAngle = enemyCar.rotation;
- var angleDiff = approachAngle - enemyCurrentAngle;
- while (angleDiff > Math.PI) angleDiff -= 2 * Math.PI;
- while (angleDiff < -Math.PI) angleDiff += 2 * Math.PI;
- // Anti-circling detection: check if we're in a circular pattern
- if (typeof enemyCar.lastPositions === "undefined") {
- enemyCar.lastPositions = [];
- enemyCar.circlingDetected = false;
- enemyCar.circlingCooldown = 0;
- }
- // Store position history for circling detection
- enemyCar.lastPositions.push({
- x: enemyCar.x,
- y: enemyCar.y,
- frame: LK.ticks
- });
- if (enemyCar.lastPositions.length > 60) {
- // Keep 1 second of history
- enemyCar.lastPositions.shift();
- }
- // Detect circling by checking if we've been in similar positions recently
- var circlingThreshold = 80; // Distance threshold for considering positions "similar"
- var circlingCount = 0;
- for (var i = 0; i < enemyCar.lastPositions.length - 20; i++) {
- var oldPos = enemyCar.lastPositions[i];
- var dist = Math.sqrt((enemyCar.x - oldPos.x) * (enemyCar.x - oldPos.x) + (enemyCar.y - oldPos.y) * (enemyCar.y - oldPos.y));
- if (dist < circlingThreshold) {
- circlingCount++;
- }
- }
- enemyCar.circlingDetected = circlingCount > 3 && aiDistance < 200; // Circling if close to player and repeating positions
- // Reduce circling cooldown
- if (enemyCar.circlingCooldown > 0) enemyCar.circlingCooldown--;
- // Initialize failure detection system for direct pursuit
- if (typeof enemyCar.directPursuitTimer === "undefined") {
- enemyCar.directPursuitTimer = 0;
- enemyCar.lastPlayerDistance = aiDistance;
- enemyCar.nearMissCount = 0;
- enemyCar.totalPursuitTime = 0;
- }
- // Apply different follow strategies with distance awareness
- switch (enemyCar.followStrategy) {
- case 0:
- // Direct pursuit - straight chase with distance management
- // Track pursuit effectiveness
- enemyCar.directPursuitTimer++;
- enemyCar.totalPursuitTime++;
- // Detect near misses - when AI gets very close but doesn't hit
- if (aiDistance < 80 && enemyCar.lastPlayerDistance > 80) {
- // Just entered close range
- enemyCar.directPursuitTimer = 0; // Reset timer on new approach
- }
- if (enemyCar.lastPlayerDistance < 80 && aiDistance > 120) {
- // Just left close range without collision - potential near miss
- enemyCar.nearMissCount++;
- }
- // Check for failure conditions in direct pursuit
- var pursuitFailed = false;
- if (enemyCar.nearMissCount >= 2) {
- // Failed after 2 near misses
- pursuitFailed = true;
- } else if (enemyCar.totalPursuitTime > 300 && !currentColliding) {
- // Failed after 5 seconds without collision
- pursuitFailed = true;
- } else if (enemyCar.directPursuitTimer > 120 && aiDistance > 200) {
- // Failed if stuck at medium distance for 2 seconds
- pursuitFailed = true;
- }
- // Switch strategy if direct pursuit failed
- if (pursuitFailed) {
- // Reset failure tracking
- enemyCar.nearMissCount = 0;
- enemyCar.totalPursuitTime = 0;
- enemyCar.directPursuitTimer = 0;
- // Calculate target speed for strategy selection
- var currentTargetSpeed = 0;
- if (enemyCar.currentTarget) {
- currentTargetSpeed = Math.sqrt(enemyCar.currentTarget.velocityX * enemyCar.currentTarget.velocityX + enemyCar.currentTarget.velocityY * enemyCar.currentTarget.velocityY);
- }
- var targetSpeedRatio = currentTargetSpeed / maxSpeed;
- // Choose new strategy based on current situation
- if (targetSpeedRatio > 0.5 || aiDistance > 300) {
- // Fast target or far away - try ambush
- enemyCar.followStrategy = 2;
- enemyCar.followStrategyDuration = 90 + Math.floor(Math.random() * 60);
- } else {
- // Slow target or close - try varied approach
- enemyCar.followStrategy = 1;
- enemyCar.followStrategyDuration = 60 + Math.floor(Math.random() * 60);
- }
- enemyCar.followStrategyTimer = 0;
- }
- // Check current AI speed to prioritize acceleration building
- var currentAISpeed = Math.sqrt((enemyCar.aiVelocityX || 0) * (enemyCar.aiVelocityX || 0) + (enemyCar.aiVelocityY || 0) * (enemyCar.aiVelocityY || 0));
- var aiSpeedRatio = currentAISpeed / maxSpeed;
- var needsAcceleration = aiSpeedRatio < 0.4; // Less than 40% of max speed
- // Check if AI can go straight to target for speed optimization
- var currentEnemyAngle = enemyCar.rotation;
- var straightLineAngle = Math.atan2(aiDeltaX, -aiDeltaY);
- var angleToTarget = straightLineAngle - currentEnemyAngle;
- while (angleToTarget > Math.PI) angleToTarget -= 2 * Math.PI;
- while (angleToTarget < -Math.PI) angleToTarget += 2 * Math.PI;
- // AI is considered "aligned" if facing within 15 degrees of target
- var isAlignedWithTarget = Math.abs(angleToTarget) < Math.PI / 12; // 15 degrees
- // Prioritize acceleration building when speed is low
- if (needsAcceleration && aiDistance > 150) {
- // Focus on building speed when far enough from target
- if (isAlignedWithTarget) {
- // Perfect alignment - accelerate in straight line toward target
- aiTargetRotation = straightLineAngle;
- aiPower = 1.0; // Maximum acceleration
- aiTargetVelocity = maxSpeed * aiPower * 1.0;
- } else if (Math.abs(angleToTarget) < Math.PI / 6) {
- // Close to aligned (within 30 degrees) - minor correction while accelerating
- var correctionFactor = Math.abs(angleToTarget) / (Math.PI / 6); // 0 to 1
- aiTargetRotation = enemyCar.rotation + angleToTarget * 0.3; // Gentle steering
- aiPower = 0.9 + correctionFactor * 0.1; // Maintain high acceleration
- aiTargetVelocity = maxSpeed * aiPower * (0.95 + correctionFactor * 0.05);
- } else {
- // Need significant turn - prioritize getting aligned for acceleration
- aiTargetRotation = straightLineAngle;
- aiPower = 0.7; // Moderate acceleration while turning
- aiTargetVelocity = maxSpeed * aiPower * 0.8;
- }
- } else if (aiDistance < enemyCar.tacticalPersonality.preferredAttackDistance * 0.6 && !enemyCar.circlingDetected) {
- // Too close for this car's preferred attack style - back off based on aggressiveness
- var backoffIntensity = 0.3 + (1 - enemyCar.tacticalPersonality.aggressiveness) * 0.4; // Less aggressive = more backoff
- var backoffAngle = approachAngle + Math.PI + (Math.random() - 0.5) * Math.PI * backoffIntensity;
- aiTargetRotation = backoffAngle;
- aiPower = 0.2 + enemyCar.tacticalPersonality.aggressiveness * 0.2;
- aiTargetVelocity = maxSpeed * aiPower * 0.6;
- } else if (aiDistance > enemyCar.tacticalPersonality.preferredAttackDistance * 2) {
- // Far away - check for straight line opportunity
- if (isAlignedWithTarget) {
- // Can go straight - use maximum speed for efficiency
- aiTargetRotation = straightLineAngle;
- aiPower = Math.min(1, aiDistance / 600);
- aiTargetVelocity = maxSpeed * aiPower * 1.0; // Full speed when aligned
- } else {
- // Need to turn first - direct approach at normal speed
- aiTargetRotation = approachAngle;
- aiPower = Math.min(1, aiDistance / 600);
- aiTargetVelocity = maxSpeed * aiPower * 0.95;
- }
- } else {
- // Medium distance - check alignment for speed boost, but prioritize acceleration if needed
- if (needsAcceleration) {
- // Still building speed - maintain acceleration focus
- if (isAlignedWithTarget) {
- aiTargetRotation = straightLineAngle;
- aiPower = 0.9;
- aiTargetVelocity = maxSpeed * aiPower * 0.95;
- } else {
- // Get aligned while building speed
- aiTargetRotation = approachAngle;
- aiPower = 0.8;
- aiTargetVelocity = maxSpeed * aiPower * 0.9;
- }
- } else if (isAlignedWithTarget && aiDistance > 200) {
- // Aligned and far enough - go straight at higher speed
- aiTargetRotation = straightLineAngle;
- aiPower = Math.min(1, aiDistance / 500);
- aiTargetVelocity = maxSpeed * aiPower * 0.98; // Near full speed when aligned
- } else {
- // Not aligned or too close - slight offset to avoid head-on collision
- var offsetAngle = (Math.random() - 0.5) * Math.PI * 0.2; // ±18 degrees
- aiTargetRotation = approachAngle + offsetAngle;
- aiPower = Math.min(1, aiDistance / 500);
- aiTargetVelocity = maxSpeed * aiPower * 0.92;
- }
- }
- // Update last distance for next frame
- enemyCar.lastPlayerDistance = aiDistance;
- break;
- case 1:
- // Varied pursuit - intimidation mode: follows like direct but maintains safe distance to scare
- // Check current AI speed for acceleration prioritization
- var currentAISpeedIntimidation = Math.sqrt((enemyCar.aiVelocityX || 0) * (enemyCar.aiVelocityX || 0) + (enemyCar.aiVelocityY || 0) * (enemyCar.aiVelocityY || 0));
- var aiSpeedRatioIntimidation = currentAISpeedIntimidation / maxSpeed;
- var needsAccelerationIntimidation = aiSpeedRatioIntimidation < 0.35; // Less than 35% of max speed
- if (enemyCar.circlingDetected && enemyCar.circlingCooldown <= 0) {
- // Break out of circling pattern
- var escapeAngle = approachAngle + Math.PI * 0.75 * (Math.random() < 0.5 ? 1 : -1);
- aiTargetRotation = escapeAngle;
- aiPower = 0.8;
- aiTargetVelocity = maxSpeed * aiPower * 0.85;
- enemyCar.circlingCooldown = 120; // 2 second cooldown
- } else if (needsAccelerationIntimidation && aiDistance > 200) {
- // Build speed before attempting intimidation tactics
- var intimidationAngle = Math.atan2(aiDeltaX, -aiDeltaY);
- var currentAngle = enemyCar.rotation;
- var angleToIntimidationTarget = intimidationAngle - currentAngle;
- while (angleToIntimidationTarget > Math.PI) angleToIntimidationTarget -= 2 * Math.PI;
- while (angleToIntimidationTarget < -Math.PI) angleToIntimidationTarget += 2 * Math.PI;
- if (Math.abs(angleToIntimidationTarget) < Math.PI / 8) {
- // Well aligned - accelerate directly toward intimidation position
- aiTargetRotation = intimidationAngle;
- aiPower = 0.9;
- aiTargetVelocity = maxSpeed * aiPower * 0.95;
- } else {
- // Need to align - gentle steering while building speed
- aiTargetRotation = currentAngle + angleToIntimidationTarget * 0.4;
- aiPower = 0.8;
- aiTargetVelocity = maxSpeed * aiPower * 0.9;
- }
- } else if (aiDistance < enemyCar.tacticalPersonality.preferredIntimidationDistance * 1.2) {
- // Individual intimidation range based on car's personality
- var intimidationDistance = enemyCar.tacticalPersonality.preferredIntimidationDistance;
- var distanceError = aiDistance - intimidationDistance;
- var toleranceRange = 20 + enemyCar.tacticalPersonality.patience * 20; // 20-40 pixel tolerance based on patience
- if (Math.abs(distanceError) < toleranceRange) {
- // Perfect intimidation distance - behavior based on individual aggressiveness and speed
- var aggressiveness = enemyCar.tacticalPersonality.aggressiveness;
- var weavingIntensity = Math.PI * 0.1 + aggressiveness * Math.PI * 0.1; // More aggressive = more weaving
- var intimidationOffset = Math.sin(LK.ticks * (0.03 + aggressiveness * 0.04)) * weavingIntensity;
- aiTargetRotation = approachAngle + intimidationOffset;
- // Speed-dependent intimidation - more aggressive when faster
- var speedBonus = Math.min(0.2, aiSpeedRatioIntimidation * 0.3);
- aiPower = 0.5 + aggressiveness * 0.3 + speedBonus;
- aiTargetVelocity = maxSpeed * aiPower * (0.8 + aggressiveness * 0.1 + speedBonus);
- // Check if target is stationary and we're intimidating
- var currentTargetSpeed = 0;
- if (enemyCar.currentTarget) {
- currentTargetSpeed = Math.sqrt(enemyCar.currentTarget.velocityX * enemyCar.currentTarget.velocityX + enemyCar.currentTarget.velocityY * enemyCar.currentTarget.velocityY);
- }
- if (currentTargetSpeed < maxSpeed * 0.05 && typeof enemyCar.intimidationCount !== "undefined") {
- // Mark intimidation as successful - duration based on patience
- if (typeof enemyCar.intimidationTimer === "undefined") enemyCar.intimidationTimer = 0;
- enemyCar.intimidationTimer++;
- // Patient cars intimidate longer, impatient cars intimidate briefly
- var intimidationDuration = 60 + enemyCar.tacticalPersonality.patience * 120; // 1-3 seconds based on patience
- if (enemyCar.intimidationTimer > intimidationDuration) {
- enemyCar.intimidationCount++;
- enemyCar.intimidationTimer = 0;
- // Force strategy recalculation
- enemyCar.followStrategyTimer = enemyCar.followStrategyDuration + 1;
- }
- }
- } else if (distanceError < 0) {
- // Too close - back off behavior based on aggressiveness
- var backoffIntensity = 0.2 + (1 - enemyCar.tacticalPersonality.aggressiveness) * 0.2; // Less aggressive = back off more
- aiTargetRotation = approachAngle + Math.PI + (Math.random() - 0.5) * Math.PI * backoffIntensity;
- aiPower = 0.3 + enemyCar.tacticalPersonality.aggressiveness * 0.2;
- aiTargetVelocity = maxSpeed * aiPower * 0.6;
- } else {
- // Too far - approach for intimidation with individual aggressiveness
- aiTargetRotation = approachAngle;
- aiPower = 0.6 + enemyCar.tacticalPersonality.aggressiveness * 0.3;
- aiTargetVelocity = maxSpeed * aiPower * (0.85 + enemyCar.tacticalPersonality.aggressiveness * 0.1);
- }
- } else {
- // Long range - approach based on individual preferred attack distance
- var approachIntensity = Math.min(1, aiDistance / (enemyCar.tacticalPersonality.preferredAttackDistance * 2));
- aiTargetRotation = approachAngle;
- aiPower = approachIntensity * (0.7 + enemyCar.tacticalPersonality.aggressiveness * 0.3);
- aiTargetVelocity = maxSpeed * aiPower * (0.9 + enemyCar.tacticalPersonality.aggressiveness * 0.05);
- }
- break;
- case 2:
- // Ambush - predict target's future position with smarter interception
- var targetVelX = 0;
- var targetVelY = 0;
- var targetPosX = enemyCar.x; // fallback position
- var targetPosY = enemyCar.y;
- if (enemyCar.currentTarget) {
- targetVelX = enemyCar.currentTarget.velocityX;
- targetVelY = enemyCar.currentTarget.velocityY;
- targetPosX = enemyCar.currentTarget.object.x;
- targetPosY = enemyCar.currentTarget.object.y;
- }
- var targetSpeed = Math.sqrt(targetVelX * targetVelX + targetVelY * targetVelY);
- // Check current AI speed for acceleration prioritization in ambush
- var currentAISpeedAmbush = Math.sqrt((enemyCar.aiVelocityX || 0) * (enemyCar.aiVelocityX || 0) + (enemyCar.aiVelocityY || 0) * (enemyCar.aiVelocityY || 0));
- var aiSpeedRatioAmbush = currentAISpeedAmbush / maxSpeed;
- var needsAccelerationAmbush = aiSpeedRatioAmbush < 0.5; // Less than 50% of max speed for ambush
- if (enemyCar.circlingDetected && enemyCar.circlingCooldown <= 0) {
- // Break circling with wide flanking maneuver
- var flankAngle = approachAngle + Math.PI * 0.8 * (Math.random() < 0.5 ? 1 : -1);
- aiTargetRotation = flankAngle;
- aiPower = 0.9;
- aiTargetVelocity = maxSpeed * aiPower * 0.9;
- enemyCar.circlingCooldown = 180; // 3 second cooldown
- } else if (needsAccelerationAmbush && aiDistance > 250) {
- // Build speed before attempting interception - find clear acceleration path
- var accelerationAngle;
- // Try to find a path that doesn't lead directly to target (for ambush positioning)
- if (aiDistance > 400) {
- // Far from target - accelerate in a flanking direction
- var flankingOffset = Math.PI * 0.3 * (Math.random() < 0.5 ? 1 : -1); // ±54 degrees
- accelerationAngle = approachAngle + flankingOffset;
- } else {
- // Medium distance - position for future interception while building speed
- var leadAngle = Math.atan2(targetVelX, -targetVelY); // Target's movement direction
- var interceptAngle = leadAngle + Math.PI * 0.6 * (Math.random() < 0.5 ? 1 : -1); // Offset for ambush
- accelerationAngle = interceptAngle;
- }
- aiTargetRotation = accelerationAngle;
- aiPower = 0.95; // High acceleration priority
- aiTargetVelocity = maxSpeed * aiPower * 0.98;
- } else if (aiDistance < 150) {
- // Close range - position for optimal angle of attack, but only if we have good speed
- if (aiSpeedRatioAmbush > 0.4) {
- // Have enough speed for attack positioning
- var attackAngle = approachAngle + Math.PI * 0.4 * (angleDiff > 0 ? 1 : -1);
- aiTargetRotation = attackAngle;
- aiPower = 0.7 + aiSpeedRatioAmbush * 0.2; // More power when faster
- aiTargetVelocity = maxSpeed * aiPower * (0.8 + aiSpeedRatioAmbush * 0.15);
- } else {
- // Need more speed - create space while accelerating
- var retreatAngle = approachAngle + Math.PI + (Math.random() - 0.5) * Math.PI * 0.4;
- aiTargetRotation = retreatAngle;
- aiPower = 0.8;
- aiTargetVelocity = maxSpeed * aiPower * 0.85;
- }
- } else {
- // Long range interception
- var predictionTime = Math.min(2.5, aiDistance / maxSpeed * 0.9);
- var interceptX = targetPosX + targetVelX * predictionTime;
- var interceptY = targetPosY + targetVelY * predictionTime;
- // Keep predicted position within bounds
- interceptX = Math.max(80, Math.min(1968, interceptX));
- interceptY = Math.max(80, Math.min(2106, interceptY));
- // Calculate optimal interception approach
- var interceptDeltaX = interceptX - enemyCar.x;
- var interceptDeltaY = interceptY - enemyCar.y;
- var interceptDistance = Math.sqrt(interceptDeltaX * interceptDeltaX + interceptDeltaY * interceptDeltaY);
- // Adjust interception based on current speed capabilities
- var speedRatio = (enemyCar.aiCurrentVelocity || 0) / Math.max(0.1, targetSpeed);
- var leadAdjustment = (1 - speedRatio) * 0.3;
- // Speed-dependent interception strategy
- if (needsAccelerationAmbush) {
- // Focus on acceleration toward interception point
- aiTargetRotation = Math.atan2(interceptDeltaX, -interceptDeltaY);
- aiPower = 0.9; // High acceleration
- var speedBoost = Math.min(0.25, targetSpeed / maxSpeed * 0.25);
- aiTargetVelocity = maxSpeed * aiPower * (0.9 + speedBoost);
- } else {
- // Have good speed - execute precise interception
- aiTargetRotation = Math.atan2(interceptDeltaX, -interceptDeltaY);
- aiPower = Math.min(1, interceptDistance / 700);
- var speedBoost = Math.min(0.2, targetSpeed / maxSpeed * 0.2);
- aiTargetVelocity = maxSpeed * aiPower * (0.85 + speedBoost + leadAdjustment);
- }
- }
- break;
- }
- // Blend in edge avoidance with follow behavior
- if (edgeAvoidanceX !== 0 || edgeAvoidanceY !== 0) {
- // Calculate edge avoidance angle
- var avoidanceAngle = Math.atan2(edgeAvoidanceX, -edgeAvoidanceY);
- // Blend the target rotation with avoidance (stronger when closer to edges)
- var avoidanceWeight = Math.min(0.7, Math.abs(edgeAvoidanceX) + Math.abs(edgeAvoidanceY));
- var followWeight = 1 - avoidanceWeight;
- // Convert angles to vectors for blending
- var followVecX = Math.sin(aiTargetRotation) * followWeight;
- var followVecY = -Math.cos(aiTargetRotation) * followWeight;
- var avoidVecX = Math.sin(avoidanceAngle) * avoidanceWeight;
- var avoidVecY = -Math.cos(avoidanceAngle) * avoidanceWeight;
- // Combine and convert back to angle
- var blendedVecX = followVecX + avoidVecX;
- var blendedVecY = followVecY + avoidVecY;
- aiTargetRotation = Math.atan2(blendedVecX, -blendedVecY);
- }
- } else if (enemyCar.aiMode === "evade") {
- // Evasion mode - flee from pursuers with different patterns
- if (typeof enemyCar.evasionState === "undefined" || enemyCar.aiModeTimer === 0) {
- // Initialize evasion variables
- enemyCar.evasionState = {
- baseAngle: Math.random() * Math.PI * 2,
- escapeTimer: 0,
- zigzagDirection: Math.random() < 0.5 ? 1 : -1,
- circleCenter: {
- x: enemyCar.x,
- y: enemyCar.y
- },
- circleRadius: 150 + Math.random() * 100,
- circleAngle: 0,
- lastPursuerAngle: 0
- };
- }
- enemyCar.evasionState.escapeTimer++;
- // Calculate average pursuer position for evasion reference
- var avgPursuerX = 0,
- avgPursuerY = 0;
- var pursuerCount = enemyCar.pursuitDetection.pursuers.length;
- if (pursuerCount > 0) {
- for (var ep = 0; ep < pursuerCount; ep++) {
- avgPursuerX += enemyCar.pursuitDetection.pursuers[ep].object.x;
- avgPursuerY += enemyCar.pursuitDetection.pursuers[ep].object.y;
- }
- avgPursuerX /= pursuerCount;
- avgPursuerY /= pursuerCount;
- } else {
- // Fallback if no pursuers detected
- avgPursuerX = enemyCar.x - 100;
- avgPursuerY = enemyCar.y - 100;
- }
- // Calculate escape direction (away from average pursuer position)
- var escapeFromX = avgPursuerX - enemyCar.x;
- var escapeFromY = avgPursuerY - enemyCar.y;
- var escapeBaseAngle = Math.atan2(-escapeFromX, escapeFromY); // Opposite direction
- // Apply different evasion strategies with individual preferences
- var flightPrefs = enemyCar.tacticalPersonality;
- switch (enemyCar.pursuitDetection.evasionStrategy) {
- case 0:
- // Speed focus - straight line escape with individual variations
- var escapeEfficiency = 0.8 + flightPrefs.aggressiveness * 0.2; // Aggressive cars escape more efficiently
- aiTargetRotation = escapeBaseAngle;
- // Look for clear path ahead with individual distance preferences
- var lookAheadDist = 150 + flightPrefs.riskTolerance * 100; // Risk-tolerant cars look further
- var pathClearX = enemyCar.x + Math.sin(escapeBaseAngle) * lookAheadDist;
- var pathClearY = enemyCar.y - Math.cos(escapeBaseAngle) * lookAheadDist;
- // Individual wall avoidance based on risk tolerance
- var wallBuffer = 80 + (1 - flightPrefs.riskTolerance) * 60; // Risk-averse cars avoid walls more
- if (pathClearX < wallBuffer || pathClearX > 2048 - wallBuffer || pathClearY < wallBuffer || pathClearY > 2186 - wallBuffer) {
- // Escape route selection based on individual preferences
- if (flightPrefs.adaptability > 0.5) {
- // Adaptive cars find better escape routes
- var toCenterX = 1024 - enemyCar.x;
- var toCenterY = 1093 - enemyCar.y;
- var toCenterAngle = Math.atan2(toCenterX, -toCenterY);
- // Blend escape with center direction based on adaptability
- var blendFactor = flightPrefs.adaptability;
- var escapeVecX = Math.sin(escapeBaseAngle) * (1 - blendFactor);
- var escapeVecY = -Math.cos(escapeBaseAngle) * (1 - blendFactor);
- var centerVecX = Math.sin(toCenterAngle) * blendFactor;
- var centerVecY = -Math.cos(toCenterAngle) * blendFactor;
- aiTargetRotation = Math.atan2(escapeVecX + centerVecX, -(escapeVecY + centerVecY));
- } else {
- // Less adaptive cars just turn toward center
- var toCenterX = 1024 - enemyCar.x;
- var toCenterY = 1093 - enemyCar.y;
- aiTargetRotation = Math.atan2(toCenterX, -toCenterY);
- }
- }
- // Individual speed based on aggressiveness and bravery
- aiPower = 0.9 + flightPrefs.aggressiveness * 0.1; // More aggressive = faster escape
- var speedMultiplier = escapeEfficiency * (0.95 + flightPrefs.bravery * 0.05); // Brave cars maintain higher speed
- aiTargetVelocity = maxSpeed * aiPower * speedMultiplier;
- break;
- case 1:
- // Zigzag pattern with individual variations
- var baseFrequency = 0.08 + flightPrefs.adaptability * 0.08; // Adaptive cars zigzag more frequently
- var pursuitFrequency = enemyCar.pursuitDetection.pursuitIntensity * 0.06;
- var zigzagFrequency = baseFrequency + pursuitFrequency;
- // Individual zigzag amplitude based on risk tolerance
- var baseAmplitude = Math.PI * (0.3 + flightPrefs.riskTolerance * 0.2); // Risk-tolerant cars make wider turns
- var panicAmplitude = flightPrefs.panicThreshold < 0.5 ? Math.PI * 0.1 : 0; // Panicky cars add erratic movement
- var zigzagAmplitude = baseAmplitude + panicAmplitude;
- var zigzagOffset = Math.sin(enemyCar.evasionState.escapeTimer * zigzagFrequency) * zigzagAmplitude;
- // Add individual unpredictability
- if (flightPrefs.adaptability > 0.7 && Math.random() < 0.05) {
- // Highly adaptive cars occasionally reverse zigzag direction
- enemyCar.evasionState.zigzagDirection *= -1;
- }
- aiTargetRotation = escapeBaseAngle + zigzagOffset * (enemyCar.evasionState.zigzagDirection || 1);
- // Individual power and speed based on personality
- var basePower = 0.75 + flightPrefs.aggressiveness * 0.15;
- var pursuitBonus = enemyCar.pursuitDetection.pursuitIntensity * 0.15;
- aiPower = basePower + pursuitBonus;
- var speedMultiplier = 0.85 + flightPrefs.bravery * 0.1; // Brave cars maintain higher speed while evading
- aiTargetVelocity = maxSpeed * aiPower * speedMultiplier;
- break;
- case 2:
- // Circular escape pattern with individual preferences
- // Individual circle updating frequency based on adaptability
- var updateFrequency = flightPrefs.adaptability > 0.6 ? 45 : 75; // Adaptive cars update circle more often
- if (enemyCar.evasionState.escapeTimer % updateFrequency === 0) {
- enemyCar.evasionState.circleCenter.x = enemyCar.x;
- enemyCar.evasionState.circleCenter.y = enemyCar.y;
- // Individual circle radius based on personality
- var baseRadius = 120 + flightPrefs.riskTolerance * 80; // Risk-tolerant cars use larger circles
- var panicRadius = flightPrefs.panicThreshold < 0.4 ? 40 : 0; // Panicky cars use smaller circles
- enemyCar.evasionState.circleRadius = baseRadius + panicRadius;
- }
- // Individual circle speed based on aggressiveness and pursuit intensity
- var baseCircleSpeed = 0.06 + flightPrefs.aggressiveness * 0.04;
- var pursuitSpeedBonus = enemyCar.pursuitDetection.pursuitIntensity * 0.04;
- var circleSpeed = baseCircleSpeed + pursuitSpeedBonus;
- // Add individual direction changes for unpredictability
- if (flightPrefs.adaptability > 0.8 && Math.random() < 0.02) {
- // Highly adaptive cars occasionally reverse circle direction
- circleSpeed *= -1;
- }
- enemyCar.evasionState.circleAngle += circleSpeed;
- var targetCircleX = enemyCar.evasionState.circleCenter.x + Math.cos(enemyCar.evasionState.circleAngle) * enemyCar.evasionState.circleRadius;
- var targetCircleY = enemyCar.evasionState.circleCenter.y + Math.sin(enemyCar.evasionState.circleAngle) * enemyCar.evasionState.circleRadius;
- // Individual boundary handling based on risk tolerance
- var boundary = 80 + (1 - flightPrefs.riskTolerance) * 40; // Risk-averse cars stay further from edges
- targetCircleX = Math.max(boundary, Math.min(2048 - boundary, targetCircleX));
- targetCircleY = Math.max(boundary, Math.min(2186 - boundary, targetCircleY));
- var toCircleX = targetCircleX - enemyCar.x;
- var toCircleY = targetCircleY - enemyCar.y;
- aiTargetRotation = Math.atan2(toCircleX, -toCircleY);
- // Individual power and speed
- var basePower = 0.65 + flightPrefs.patience * 0.15; // Patient cars better at sustained circular movement
- var pursuitBonus = enemyCar.pursuitDetection.pursuitIntensity * 0.15;
- aiPower = basePower + pursuitBonus;
- var speedMultiplier = 0.8 + flightPrefs.bravery * 0.1; // Brave cars circle at higher speeds
- aiTargetVelocity = maxSpeed * aiPower * speedMultiplier;
- break;
- }
- // Apply strong edge avoidance during evasion
- if (edgeAvoidanceX !== 0 || edgeAvoidanceY !== 0) {
- var avoidanceAngle = Math.atan2(edgeAvoidanceX, -edgeAvoidanceY);
- var avoidanceWeight = Math.min(0.8, (Math.abs(edgeAvoidanceX) + Math.abs(edgeAvoidanceY)) * 2);
- var evasionWeight = 1 - avoidanceWeight;
- var evasionVecX = Math.sin(aiTargetRotation) * evasionWeight;
- var evasionVecY = -Math.cos(aiTargetRotation) * evasionWeight;
- var avoidVecX = Math.sin(avoidanceAngle) * avoidanceWeight;
- var avoidVecY = -Math.cos(avoidanceAngle) * avoidanceWeight;
- var blendedVecX = evasionVecX + avoidVecX;
- var blendedVecY = evasionVecY + avoidVecY;
- aiTargetRotation = Math.atan2(blendedVecX, -blendedVecY);
- }
- } else if (enemyCar.aiMode === "camp") {
- // Camping mode - position strategically and wait for opportunities
- if (typeof enemyCar.campingState === "undefined" || enemyCar.aiModeTimer === 0) {
- // Initialize camping variables
- enemyCar.campingState = {
- campingPosition: {
- x: 1024 + (Math.random() - 0.5) * 800,
- // Random position near center
- y: 1093 + (Math.random() - 0.5) * 600
- },
- repositionTimer: 0,
- repositionInterval: 180 + Math.floor(Math.random() * 240),
- // 3-7 seconds
- opportunityTimer: 0,
- lastOpportunityCheck: 0,
- preferredDistance: enemyCar.tacticalPersonality.campingDistance,
- isPositioned: false,
- patrolAngle: Math.random() * Math.PI * 2,
- patrolRadius: 80 + Math.random() * 40 // 80-120 patrol radius
- };
- }
- enemyCar.campingState.repositionTimer++;
- enemyCar.campingState.opportunityTimer++;
- // Check for opportunities every 30 frames (0.5 seconds)
- if (enemyCar.campingState.opportunityTimer - enemyCar.campingState.lastOpportunityCheck > 30) {
- enemyCar.campingState.lastOpportunityCheck = enemyCar.campingState.opportunityTimer;
- // Scan for weakened targets
- var bestOpportunity = null;
- var bestOpportunityScore = 0;
- // Check all potential targets
- for (var campScanIdx = 0; campScanIdx < enemyCars.length + 1; campScanIdx++) {
- var scanTarget = null;
- var scanTargetHealth = 100;
- if (campScanIdx < enemyCars.length && campScanIdx !== aiCarIdx) {
- scanTarget = enemyCars[campScanIdx];
- scanTargetHealth = scanTarget.health;
- } else if (campScanIdx === enemyCars.length) {
- scanTarget = carPlayer;
- scanTargetHealth = playerHealth;
- }
- if (scanTarget) {
- var scanDistance = Math.sqrt((scanTarget.x - enemyCar.x) * (scanTarget.x - enemyCar.x) + (scanTarget.y - enemyCar.y) * (scanTarget.y - enemyCar.y));
- var scanHealthRatio = scanTargetHealth / maxHealth;
- var myHealthRatio = enemyCar.health / maxHealth;
- // Calculate opportunity score
- var opportunityScore = 0;
- if (scanHealthRatio < 0.5) {
- opportunityScore += (0.5 - scanHealthRatio) * 100; // Up to 50 points for very weak targets
- }
- if (scanDistance < 300) {
- opportunityScore += (300 - scanDistance) / 300 * 30; // Up to 30 points for close targets
- }
- if (myHealthRatio > scanHealthRatio + 0.2) {
- opportunityScore += 20; // 20 points for significant health advantage
- }
- // Camper-specific bonuses
- if (scanHealthRatio < 0.3 && myHealthRatio > 0.6) {
- opportunityScore += enemyCar.tacticalPersonality.opportunismLevel * 40; // Major opportunity bonus
- }
- if (opportunityScore > bestOpportunityScore && opportunityScore > 30) {
- bestOpportunityScore = opportunityScore;
- bestOpportunity = scanTarget;
- }
- }
- }
- // If excellent opportunity found, switch to hunting mode
- if (bestOpportunity && bestOpportunityScore > 70) {
- enemyCar.aiMode = "follow";
- // Override target to the opportunity
- enemyCar.currentTarget = {
- object: bestOpportunity,
- velocityX: bestOpportunity === carPlayer ? velocityX : (bestOpportunity.aiVelocityX || 0) + (bestOpportunity.velocityX || 0),
- velocityY: bestOpportunity === carPlayer ? velocityY : (bestOpportunity.aiVelocityY || 0) + (bestOpportunity.velocityY || 0),
- weight: bestOpportunity === carPlayer ? playerCarWeight : bestOpportunity.weight,
- isPlayer: bestOpportunity === carPlayer
- };
- enemyCar.aiModeTimer = 0; // Reset mode timer for immediate action
- }
- }
- // Camping positioning logic
- var toCampX = enemyCar.campingState.campingPosition.x - enemyCar.x;
- var toCampY = enemyCar.campingState.campingPosition.y - enemyCar.y;
- var distanceToCamp = Math.sqrt(toCampX * toCampX + toCampY * toCampY);
- // Check if we need to reposition the camping spot
- if (enemyCar.campingState.repositionTimer > enemyCar.campingState.repositionInterval || distanceToCamp < 50) {
- enemyCar.campingState.repositionTimer = 0;
- enemyCar.campingState.repositionInterval = 180 + Math.floor(Math.random() * 240);
- // Find new strategic camping position
- var bestCampX = 1024;
- var bestCampY = 1093;
- var maxDistance = 0;
- // Try to find position that maximizes distance to all targets while staying in bounds
- for (var campTry = 0; campTry < 8; campTry++) {
- var tryX = 200 + Math.random() * 1648; // Stay within bounds
- var tryY = 200 + Math.random() * 1786;
- var minDistanceToTargets = Infinity;
- // Check distance to all other cars
- for (var distCheck = 0; distCheck < enemyCars.length + 1; distCheck++) {
- var checkX, checkY;
- if (distCheck < enemyCars.length && distCheck !== aiCarIdx) {
- checkX = enemyCars[distCheck].x;
- checkY = enemyCars[distCheck].y;
- } else if (distCheck === enemyCars.length) {
- checkX = carPlayer.x;
- checkY = carPlayer.y;
- } else {
- continue;
- }
- var distToTarget = Math.sqrt((tryX - checkX) * (tryX - checkX) + (tryY - checkY) * (tryY - checkY));
- minDistanceToTargets = Math.min(minDistanceToTargets, distToTarget);
- }
- if (minDistanceToTargets > maxDistance && minDistanceToTargets > enemyCar.campingState.preferredDistance * 0.8) {
- maxDistance = minDistanceToTargets;
- bestCampX = tryX;
- bestCampY = tryY;
- }
- }
- enemyCar.campingState.campingPosition.x = bestCampX;
- enemyCar.campingState.campingPosition.y = bestCampY;
- toCampX = bestCampX - enemyCar.x;
- toCampY = bestCampY - enemyCar.y;
- distanceToCamp = Math.sqrt(toCampX * toCampX + toCampY * toCampY);
- }
- // Movement logic for camping
- if (distanceToCamp > 100) {
- // Move to camping position
- aiTargetRotation = Math.atan2(toCampX, -toCampY);
- aiPower = Math.min(0.7, distanceToCamp / 300);
- aiTargetVelocity = maxSpeed * aiPower * 0.8;
- enemyCar.campingState.isPositioned = false;
- } else {
- // At camping position - patrol slowly while watching
- enemyCar.campingState.isPositioned = true;
- enemyCar.campingState.patrolAngle += 0.02 + Math.random() * 0.02; // Slow patrol
- var patrolX = enemyCar.campingState.campingPosition.x + Math.cos(enemyCar.campingState.patrolAngle) * enemyCar.campingState.patrolRadius;
- var patrolY = enemyCar.campingState.campingPosition.y + Math.sin(enemyCar.campingState.patrolAngle) * enemyCar.campingState.patrolRadius;
- // Keep patrol within bounds
- patrolX = Math.max(100, Math.min(1948, patrolX));
- patrolY = Math.max(100, Math.min(2086, patrolY));
- var toPatrolX = patrolX - enemyCar.x;
- var toPatrolY = patrolY - enemyCar.y;
- aiTargetRotation = Math.atan2(toPatrolX, -toPatrolY);
- aiPower = 0.3 + Math.random() * 0.2; // Very slow, patient movement
- aiTargetVelocity = maxSpeed * aiPower * 0.5;
- }
- // Apply edge avoidance to camping mode
- if (edgeAvoidanceX !== 0 || edgeAvoidanceY !== 0) {
- var avoidanceAngle = Math.atan2(edgeAvoidanceX, -edgeAvoidanceY);
- var avoidanceWeight = Math.min(0.7, (Math.abs(edgeAvoidanceX) + Math.abs(edgeAvoidanceY)) * 1.2);
- var campWeight = 1 - avoidanceWeight;
- var campVecX = Math.sin(aiTargetRotation) * campWeight;
- var campVecY = -Math.cos(aiTargetRotation) * campWeight;
- var avoidVecX = Math.sin(avoidanceAngle) * avoidanceWeight;
- var avoidVecY = -Math.cos(avoidanceAngle) * avoidanceWeight;
- var blendedVecX = campVecX + avoidVecX;
- var blendedVecY = campVecY + avoidVecY;
- aiTargetRotation = Math.atan2(blendedVecX, -blendedVecY);
- }
- } else {
- // Enhanced Free roam logic with dynamic behavior patterns
- if (typeof enemyCar.freeRoamAngle === "undefined" || enemyCar.aiModeTimer === 0) {
- // Initialize free roam variables
- enemyCar.freeRoamAngle = Math.random() * Math.PI * 2;
- enemyCar.freeRoamSpeed = maxSpeed * (0.4 + Math.random() * 0.4); // 40%-80% of max speed
- enemyCar.freeRoamTimer = 0;
- enemyCar.freeRoamDirectionTimer = 0;
- enemyCar.freeRoamPattern = Math.floor(Math.random() * 3); // 0=wander, 1=circular, 2=aggressive
- }
- // Dynamic free roam behavior - change direction periodically for more interesting movement
- enemyCar.freeRoamTimer++;
- enemyCar.freeRoamDirectionTimer++;
- // Change direction every 1-3 seconds based on pattern
- var directionChangeInterval;
- switch (enemyCar.freeRoamPattern) {
- case 0:
- // Wander pattern - gentle direction changes
- directionChangeInterval = 90 + Math.floor(Math.random() * 60); // 1.5-2.5 seconds
- break;
- case 1:
- // Circular pattern - more frequent turns
- directionChangeInterval = 45 + Math.floor(Math.random() * 30); // 0.75-1.25 seconds
- break;
- case 2:
- // Aggressive pattern - quick direction changes
- directionChangeInterval = 30 + Math.floor(Math.random() * 40); // 0.5-1.17 seconds
- break;
- }
- if (enemyCar.freeRoamDirectionTimer > directionChangeInterval) {
- enemyCar.freeRoamDirectionTimer = 0;
- // Different behavior patterns for more variety
- switch (enemyCar.freeRoamPattern) {
- case 0:
- // Wander - small random direction changes
- var angleChange = (Math.random() - 0.5) * Math.PI * 0.6; // ±54 degrees
- enemyCar.freeRoamAngle += angleChange;
- enemyCar.freeRoamSpeed = maxSpeed * (0.3 + Math.random() * 0.4);
- break;
- case 1:
- // Circular - tends to turn in one direction
- if (typeof enemyCar.circularDirection === "undefined") {
- enemyCar.circularDirection = Math.random() < 0.5 ? -1 : 1;
- }
- var circularTurn = enemyCar.circularDirection * (Math.PI * 0.3 + Math.random() * Math.PI * 0.4); // 54-126 degrees
- enemyCar.freeRoamAngle += circularTurn;
- enemyCar.freeRoamSpeed = maxSpeed * (0.5 + Math.random() * 0.3);
- // Occasionally reverse direction
- if (Math.random() < 0.15) enemyCar.circularDirection *= -1;
- break;
- case 2:
- // Aggressive - sharp turns and speed changes
- var aggressiveTurn = (Math.random() - 0.5) * Math.PI * 1.2; // ±108 degrees
- enemyCar.freeRoamAngle += aggressiveTurn;
- enemyCar.freeRoamSpeed = maxSpeed * (0.6 + Math.random() * 0.3);
- break;
- }
- }
- // Add some target awareness even in free roam - occasionally look towards closest target
- var closestTarget = null;
- var closestDistance = Infinity;
- // Check distance to player
- var playerDistance = Math.sqrt((carPlayer.x - enemyCar.x) * (carPlayer.x - enemyCar.x) + (carPlayer.y - enemyCar.y) * (carPlayer.y - enemyCar.y));
- if (playerDistance < closestDistance) {
- closestDistance = playerDistance;
- closestTarget = carPlayer;
- }
- // Check distance to other enemy cars
- for (var nearIdx = 0; nearIdx < enemyCars.length; nearIdx++) {
- if (nearIdx !== aiCarIdx) {
- var otherCar = enemyCars[nearIdx];
- var otherDistance = Math.sqrt((otherCar.x - enemyCar.x) * (otherCar.x - enemyCar.x) + (otherCar.y - enemyCar.y) * (otherCar.y - enemyCar.y));
- if (otherDistance < closestDistance) {
- closestDistance = otherDistance;
- closestTarget = otherCar;
- }
- }
- }
- var targetInfluence = 0;
- if (closestTarget && closestDistance < 400) {
- // Within 400 pixels
- // Closer target = more influence on direction
- targetInfluence = Math.max(0, (400 - closestDistance) / 400 * 0.3); // Up to 30% influence
- if (Math.random() < 0.02) {
- // 2% chance per frame to look at closest target
- var targetAngle = Math.atan2(closestTarget.x - enemyCar.x, -(closestTarget.y - enemyCar.y));
- // Blend current angle with target angle
- var currentVecX = Math.sin(enemyCar.freeRoamAngle);
- var currentVecY = -Math.cos(enemyCar.freeRoamAngle);
- var targetVecX = Math.sin(targetAngle) * targetInfluence;
- var targetVecY = -Math.cos(targetAngle) * targetInfluence;
- var blendedVecX = currentVecX * (1 - targetInfluence) + targetVecX;
- var blendedVecY = currentVecY * (1 - targetInfluence) + targetVecY;
- enemyCar.freeRoamAngle = Math.atan2(blendedVecX, -blendedVecY);
- }
- }
- aiTargetRotation = enemyCar.freeRoamAngle;
- aiPower = 1;
- aiTargetVelocity = enemyCar.freeRoamSpeed;
- // Apply edge avoidance to free roam mode with stronger influence
- if (edgeAvoidanceX !== 0 || edgeAvoidanceY !== 0) {
- // Calculate edge avoidance angle
- var avoidanceAngle = Math.atan2(edgeAvoidanceX, -edgeAvoidanceY);
- // Much stronger avoidance in free roam mode
- var avoidanceWeight = Math.min(0.85, (Math.abs(edgeAvoidanceX) + Math.abs(edgeAvoidanceY)) * 1.5);
- var roamWeight = 1 - avoidanceWeight;
- // Convert angles to vectors for blending
- var roamVecX = Math.sin(aiTargetRotation) * roamWeight;
- var roamVecY = -Math.cos(aiTargetRotation) * roamWeight;
- var avoidVecX = Math.sin(avoidanceAngle) * avoidanceWeight;
- var avoidVecY = -Math.cos(avoidanceAngle) * avoidanceWeight;
- // Combine and convert back to angle
- var blendedVecX = roamVecX + avoidVecX;
- var blendedVecY = roamVecY + avoidVecY;
- aiTargetRotation = Math.atan2(blendedVecX, -blendedVecY);
- // Update the free roam angle to new direction after avoidance
- enemyCar.freeRoamAngle = aiTargetRotation;
- }
- }
- // AI: Smoothly rotate enemy car towards target
- if (typeof enemyCar.aiRotation === "undefined") enemyCar.aiRotation = enemyCar.rotation;
- var aiRotationDelta = aiTargetRotation - enemyCar.aiRotation;
- while (aiRotationDelta > Math.PI) aiRotationDelta -= 2 * Math.PI;
- while (aiRotationDelta < -Math.PI) aiRotationDelta += 2 * Math.PI;
- var aiRotationSpeed = baseRotationSpeed * 0.7; // Slightly slower turning for AI
- enemyCar.aiRotation += aiRotationDelta * aiRotationSpeed;
- enemyCar.rotation = enemyCar.aiRotation;
- // AI: Velocity logic
- if (typeof enemyCar.aiCurrentVelocity === "undefined") enemyCar.aiCurrentVelocity = 0;
- if (aiPower > 0.1) {
- // Accelerate
- var aiVelocityDiff = aiTargetVelocity - enemyCar.aiCurrentVelocity;
- var aiAccelerationRate = 0.003;
- enemyCar.aiCurrentVelocity += aiVelocityDiff * aiAccelerationRate;
- } else {
- // Decelerate
- var aiDecelerationRate = 0.045;
- enemyCar.aiCurrentVelocity *= 1 - aiDecelerationRate;
- if (Math.abs(enemyCar.aiCurrentVelocity) < 0.1) enemyCar.aiCurrentVelocity = 0;
- }
- enemyCar.aiCurrentVelocity = Math.min(enemyCar.aiCurrentVelocity, maxSpeed * 0.92);
- // AI: Intended movement direction
- var aiIntendedMoveX = Math.sin(enemyCar.aiRotation) * enemyCar.aiCurrentVelocity;
- var aiIntendedMoveY = -Math.cos(enemyCar.aiRotation) * enemyCar.aiCurrentVelocity;
- // AI: Drift physics for enemy car
- if (typeof enemyCar.aiVelocityX === "undefined") enemyCar.aiVelocityX = 0;
- if (typeof enemyCar.aiVelocityY === "undefined") enemyCar.aiVelocityY = 0;
- enemyCar.aiVelocityX = enemyCar.aiVelocityX * driftFactor + aiIntendedMoveX * gripFactor;
- enemyCar.aiVelocityY = enemyCar.aiVelocityY * driftFactor + aiIntendedMoveY * gripFactor;
- // AI: Apply friction and update position
- enemyCar.aiVelocityX *= 0.98;
- enemyCar.aiVelocityY *= 0.98;
- enemyCar.x += enemyCar.aiVelocityX + enemyCar.velocityX;
- enemyCar.y += enemyCar.aiVelocityY + enemyCar.velocityY;
- // --- End Enemy Car AI Movement Logic ---
- // Apply smooth braking to enemy car after collision for more natural deceleration
- if (!enemyCar.smoothBraking) enemyCar.smoothBraking = false;
- if (!enemyCar.brakeFrames) enemyCar.brakeFrames = 0;
- var currentEnemySpeed = Math.sqrt(enemyCar.velocityX * enemyCar.velocityX + enemyCar.velocityY * enemyCar.velocityY);
- // If enemy car was just launched (high velocity), enable smooth braking
- if (currentEnemySpeed > maxSpeed * 0.6 && !enemyCar.smoothBraking) {
- enemyCar.smoothBraking = true;
- enemyCar.brakeFrames = 0;
- }
- // Smooth braking logic: apply a gentle, progressive friction for a short period after being launched
- if (enemyCar.smoothBraking) {
- // Braking lasts for 18 frames (~0.3s at 60fps)
- var brakeDuration = 18;
- var brakeProgress = Math.min(1, enemyCar.brakeFrames / brakeDuration);
- // Start with gentle friction, increase to normal friction
- var minFriction = 0.96;
- var maxFriction = 0.92 - currentEnemySpeed / maxSpeed * 0.05;
- var frictionRate = minFriction + (maxFriction - minFriction) * brakeProgress;
- enemyCar.velocityX *= frictionRate;
- enemyCar.velocityY *= frictionRate;
- enemyCar.brakeFrames++;
- if (enemyCar.brakeFrames >= brakeDuration) {
- enemyCar.smoothBraking = false;
- }
- } else if (currentEnemySpeed > 0.1) {
- // Normal progressive friction
- var frictionRate = 0.92 - currentEnemySpeed / maxSpeed * 0.05; // More friction at higher speeds
- enemyCar.velocityX *= frictionRate;
- enemyCar.velocityY *= frictionRate;
- } else {
- // Stop very slow movement to prevent endless drift
- enemyCar.velocityX = 0;
- enemyCar.velocityY = 0;
- }
- // Keep enemy car within bounds
- var enemyHalfWidth = 32;
- var enemyHalfHeight = 47;
- if (enemyCar.x < enemyHalfWidth) {
- enemyCar.x = enemyHalfWidth;
- enemyCar.velocityX = -enemyCar.velocityX * 0.6;
- }
- if (enemyCar.x > 2048 - enemyHalfWidth) {
- enemyCar.x = 2048 - enemyHalfWidth;
- enemyCar.velocityX = -enemyCar.velocityX * 0.6;
- }
- if (enemyCar.y < enemyHalfHeight) {
- enemyCar.y = enemyHalfHeight;
- enemyCar.velocityY = -enemyCar.velocityY * 0.6;
- }
- if (enemyCar.y > 2186 - enemyHalfHeight) {
- enemyCar.y = 2186 - enemyHalfHeight;
- enemyCar.velocityY = -enemyCar.velocityY * 0.6;
- }
+ // Update last collision state
+ carPlayer.lastColliding = currentColliding;
+ // Update enemy car physics (simple momentum with friction)
+ enemyCar.x += enemyCar.velocityX;
+ enemyCar.y += enemyCar.velocityY;
+ // Apply friction to enemy car
+ enemyCar.velocityX *= 0.95;
+ enemyCar.velocityY *= 0.95;
+ // Keep enemy car within bounds
+ var enemyHalfWidth = 32;
+ var enemyHalfHeight = 47;
+ if (enemyCar.x < enemyHalfWidth) {
+ enemyCar.x = enemyHalfWidth;
+ enemyCar.velocityX = -enemyCar.velocityX * 0.6;
}
- // --- End Enemy Cars AI Movement Logic ---
- // Add collision detection between enemy cars (optimized - only check every 3rd frame)
- if (LK.ticks % 3 === 0) {
- for (var carA = 0; carA < enemyCars.length; carA++) {
- for (var carB = carA + 1; carB < enemyCars.length; carB++) {
- var enemyCarA = enemyCars[carA];
- var enemyCarB = enemyCars[carB];
- // Initialize collision tracking if not exists
- if (!enemyCarA.enemyColliding) enemyCarA.enemyColliding = [];
- if (!enemyCarB.enemyColliding) enemyCarB.enemyColliding = [];
- while (enemyCarA.enemyColliding.length <= carB) enemyCarA.enemyColliding.push(false);
- while (enemyCarB.enemyColliding.length <= carA) enemyCarB.enemyColliding.push(false);
- // Calculate collision boxes for both cars
- var carALeft = enemyCarA.x - enemyCollisionWidth / 2;
- var carARight = enemyCarA.x + enemyCollisionWidth / 2;
- var carATop = enemyCarA.y - enemyCollisionHeight / 2;
- var carABottom = enemyCarA.y + enemyCollisionHeight / 2;
- var carBLeft = enemyCarB.x - enemyCollisionWidth / 2;
- var carBRight = enemyCarB.x + enemyCollisionWidth / 2;
- var carBTop = enemyCarB.y - enemyCollisionHeight / 2;
- var carBBottom = enemyCarB.y + enemyCollisionHeight / 2;
- // Check for collision
- var currentColliding = !(carARight < carBLeft || carALeft > carBRight || carABottom < carBTop || carATop > carBBottom);
- if (!enemyCarA.enemyColliding[carB] && currentColliding) {
- // Collision just started - determine collision responsibility for enemy cars
- var carASpeedX = (enemyCarA.aiVelocityX || 0) + (enemyCarA.velocityX || 0);
- var carASpeedY = (enemyCarA.aiVelocityY || 0) + (enemyCarA.velocityY || 0);
- var carBSpeedX = (enemyCarB.aiVelocityX || 0) + (enemyCarB.velocityX || 0);
- var carBSpeedY = (enemyCarB.aiVelocityY || 0) + (enemyCarB.velocityY || 0);
- var carASpeed = Math.sqrt(carASpeedX * carASpeedX + carASpeedY * carASpeedY);
- var carBSpeed = Math.sqrt(carBSpeedX * carBSpeedX + carBSpeedY * carBSpeedY);
- // Calculate collision direction
- var collisionDeltaX = enemyCarB.x - enemyCarA.x;
- var collisionDeltaY = enemyCarB.y - enemyCarA.y;
- var collisionDistance = Math.sqrt(collisionDeltaX * collisionDeltaX + collisionDeltaY * collisionDeltaY);
- // Normalize collision direction
- if (collisionDistance > 0) {
- collisionDeltaX /= collisionDistance;
- collisionDeltaY /= collisionDistance;
- }
- // Determine collision responsibility for enemy cars
- var carACrasher = false;
- var carBCrasher = false;
- var bothEnemiesCrashed = false;
- // Calculate approach vectors
- var carAApproachX = 0,
- carAApproachY = 0;
- var carBApproachX = 0,
- carBApproachY = 0;
- if (carASpeed > 0.1) {
- carAApproachX = carASpeedX / carASpeed;
- carAApproachY = carASpeedY / carASpeed;
- }
- if (carBSpeed > 0.1) {
- carBApproachX = carBSpeedX / carBSpeed;
- carBApproachY = carBSpeedY / carBSpeed;
- }
- // Calculate how much each car is moving toward the collision
- var carATowardCollision = 0;
- var carBTowardCollision = 0;
- if (carASpeed > 0.1) {
- carATowardCollision = carAApproachX * collisionDeltaX + carAApproachY * collisionDeltaY;
- }
- if (carBSpeed > 0.1) {
- carBTowardCollision = carBApproachX * -collisionDeltaX + carBApproachY * -collisionDeltaY;
- }
- // Speed thresholds for determining crasher responsibility
- var minCrasherSpeed = maxSpeed * 0.15;
- var dominantCrasherThreshold = 0.3;
- // Determine responsibility
- if (carASpeed > minCrasherSpeed && carBSpeed > minCrasherSpeed) {
- if (carATowardCollision > dominantCrasherThreshold && carBTowardCollision > dominantCrasherThreshold) {
- bothEnemiesCrashed = true;
- } else if (carATowardCollision > carBTowardCollision + dominantCrasherThreshold) {
- carACrasher = true;
- } else if (carBTowardCollision > carATowardCollision + dominantCrasherThreshold) {
- carBCrasher = true;
- } else {
- bothEnemiesCrashed = true;
- }
- } else if (carASpeed > minCrasherSpeed && carBSpeed <= minCrasherSpeed) {
- carACrasher = true;
- } else if (carBSpeed > minCrasherSpeed && carASpeed <= minCrasherSpeed) {
- carBCrasher = true;
- } else {
- bothEnemiesCrashed = true;
- }
- // Calculate momentum transfer using conservation of momentum
- var totalMass = enemyCarA.weight + enemyCarB.weight;
- var massRatioA1 = (enemyCarA.weight - enemyCarB.weight) / totalMass;
- var massRatioA2 = 2 * enemyCarB.weight / totalMass;
- var massRatioB1 = 2 * enemyCarA.weight / totalMass;
- var massRatioB2 = (enemyCarB.weight - enemyCarA.weight) / totalMass;
- // Apply different energy loss based on collision responsibility
- var relativeSpeed = Math.max(carASpeed, carBSpeed);
- var minLoss, maxLoss;
- var speedNorm = Math.min(1, relativeSpeed / maxSpeed);
- if (bothEnemiesCrashed) {
- minLoss = 0.18;
- maxLoss = 0.60;
- } else {
- minLoss = 0.12;
- maxLoss = 0.50;
- }
- var energyLoss = minLoss + (maxLoss - minLoss) * speedNorm;
- var restitution = 1 - energyLoss;
- // Apply different restitution based on responsibility
- var carARestitution = restitution;
- var carBRestitution = restitution;
- if (carACrasher) {
- carARestitution = restitution * 1.15; // Car A is crasher - less penalty
- carBRestitution = restitution * 0.85; // Car B crashed into - more penalty
- } else if (carBCrasher) {
- carBRestitution = restitution * 1.15; // Car B is crasher - less penalty
- carARestitution = restitution * 0.85; // Car A crashed into - more penalty
- }
- // Separate cars to prevent overlap
- var separationDistance = 75;
- var halfSeparation = separationDistance / 2;
- enemyCarA.x = enemyCarA.x - collisionDeltaX * halfSeparation;
- enemyCarA.y = enemyCarA.y - collisionDeltaY * halfSeparation;
- enemyCarB.x = enemyCarB.x + collisionDeltaX * halfSeparation;
- enemyCarB.y = enemyCarB.y + collisionDeltaY * halfSeparation;
- // Calculate new velocities for car A
- var carAImpactX = carASpeedX * massRatioA1 + carBSpeedX * massRatioA2;
- var carAImpactY = carASpeedY * massRatioA1 + carBSpeedY * massRatioA2;
- enemyCarA.velocityX = carAImpactX * carARestitution;
- enemyCarA.velocityY = carAImpactY * carARestitution;
- // Calculate new velocities for car B
- var carBImpactX = carASpeedX * massRatioB1 + carBSpeedX * massRatioB2;
- var carBImpactY = carASpeedY * massRatioB1 + carBSpeedY * massRatioB2;
- enemyCarB.velocityX = carBImpactX * carBRestitution;
- enemyCarB.velocityY = carBImpactY * carBRestitution;
- // Add directional push based on collision
- var pushIntensity = (carASpeed + carBSpeed) * (0.4 + 0.4 * speedNorm);
- var carAPushRatio = enemyCarB.weight / totalMass;
- var carBPushRatio = enemyCarA.weight / totalMass;
- enemyCarA.velocityX += -collisionDeltaX * pushIntensity * carAPushRatio * 0.6;
- enemyCarA.velocityY += -collisionDeltaY * pushIntensity * carAPushRatio * 0.6;
- enemyCarB.velocityX += collisionDeltaX * pushIntensity * carBPushRatio * 0.6;
- enemyCarB.velocityY += collisionDeltaY * pushIntensity * carBPushRatio * 0.6;
- // Reset acceleration state based on responsibility
- if (carACrasher) {
- if (typeof enemyCarA.aiCurrentVelocity !== "undefined") {
- enemyCarA.aiCurrentVelocity *= 0.3; // Car A is crasher - smaller penalty
- }
- } else {
- if (typeof enemyCarA.aiCurrentVelocity !== "undefined") {
- enemyCarA.aiCurrentVelocity = 0; // Car A crashed into or mutual - full reset
- }
- }
- if (carBCrasher) {
- if (typeof enemyCarB.aiCurrentVelocity !== "undefined") {
- enemyCarB.aiCurrentVelocity *= 0.3; // Car B is crasher - smaller penalty
- }
- } else {
- if (typeof enemyCarB.aiCurrentVelocity !== "undefined") {
- enemyCarB.aiCurrentVelocity = 0; // Car B crashed into or mutual - full reset
- }
- }
- // Calculate speed-based damage for enemy collision
- var enemyCollisionSpeed = Math.max(carASpeed, carBSpeed);
- var baseEnemyDamage = calculateSpeedDamage(enemyCollisionSpeed);
- // Apply damage with responsibility-based penalties
- var carADamage = 0;
- var carBDamage = 0;
- if (bothEnemiesCrashed) {
- // Both crashed - equal damage
- carADamage = baseEnemyDamage;
- carBDamage = baseEnemyDamage;
- } else if (carACrasher) {
- // Car A is crasher - takes less damage
- carADamage = Math.round(baseEnemyDamage * 0.7); // 30% less damage
- carBDamage = Math.round(baseEnemyDamage * 1.3); // 30% more damage
- } else if (carBCrasher) {
- // Car B is crasher - takes less damage
- carBDamage = Math.round(baseEnemyDamage * 0.7); // 30% less damage
- carADamage = Math.round(baseEnemyDamage * 1.3); // 30% more damage
- } else {
- // Fallback - equal damage
- carADamage = baseEnemyDamage;
- carBDamage = baseEnemyDamage;
- }
- // Apply damage to car A
- if (carADamage > 0) {
- enemyCarA.health = Math.max(0, enemyCarA.health - carADamage);
- }
- // Apply damage to car B
- if (carBDamage > 0) {
- enemyCarB.health = Math.max(0, enemyCarB.health - carBDamage);
- }
- // Mark cars as disabled when health reaches 0 but don't destroy them
- if (enemyCarA.health <= 0) {
- enemyCarA.aiDisabled = true;
- }
- if (enemyCarB.health <= 0) {
- enemyCarB.aiDisabled = true;
- }
- // Play collision sound effect for enemy car collisions - randomly select from available sounds
- var enemyCollisionSounds = ['SonidoChoque', 'SonidoChoque2', 'SonidoChoque3'];
- var randomEnemySoundIndex = Math.floor(Math.random() * enemyCollisionSounds.length);
- LK.getSound(enemyCollisionSounds[randomEnemySoundIndex]).play();
- // Visual feedback based on collision responsibility
- if (bothEnemiesCrashed) {
- // Both crashed - flash both in orange
- LK.effects.flashObject(enemyCarA, 0xff8800, 300);
- LK.effects.flashObject(enemyCarB, 0xff8800, 300);
- } else if (carACrasher) {
- // Car A crashed into car B - flash A red, B yellow
- LK.effects.flashObject(enemyCarA, 0xff6600, 250);
- LK.effects.flashObject(enemyCarB, 0xffdd00, 350);
- } else if (carBCrasher) {
- // Car B crashed into car A - flash B red, A yellow
- LK.effects.flashObject(enemyCarB, 0xff6600, 250);
- LK.effects.flashObject(enemyCarA, 0xffdd00, 350);
- } else {
- // Default fallback
- LK.effects.flashObject(enemyCarA, 0xffaa00, 300);
- LK.effects.flashObject(enemyCarB, 0xffaa00, 300);
- }
- }
- // Update collision tracking
- enemyCarA.enemyColliding[carB] = currentColliding;
- enemyCarB.enemyColliding[carA] = currentColliding;
- }
- }
+ if (enemyCar.x > 2048 - enemyHalfWidth) {
+ enemyCar.x = 2048 - enemyHalfWidth;
+ enemyCar.velocityX = -enemyCar.velocityX * 0.6;
}
- // Update player health bar scale
- var healthPercentage = playerHealth / maxHealth;
- healthBar.scaleX = 4 * healthPercentage;
- // Update enemy health bars
- for (var healthIdx = 0; healthIdx < enemyCars.length && healthIdx < enemyHealthBars.length; healthIdx++) {
- var enemyHealthPercentage = enemyCars[healthIdx].health / maxHealth;
- enemyHealthBars[healthIdx].scaleX = 4 * enemyHealthPercentage;
+ if (enemyCar.y < enemyHalfHeight) {
+ enemyCar.y = enemyHalfHeight;
+ enemyCar.velocityY = -enemyCar.velocityY * 0.6;
}
- // Check for victory condition - all enemies must have 0 health
- var allEnemiesDefeated = true;
- for (var victoryCheckIdx = 0; victoryCheckIdx < enemyCars.length; victoryCheckIdx++) {
- if (enemyCars[victoryCheckIdx].health > 0) {
- allEnemiesDefeated = false;
- break;
- }
+ if (enemyCar.y > 2186 - enemyHalfHeight) {
+ enemyCar.y = 2186 - enemyHalfHeight;
+ enemyCar.velocityY = -enemyCar.velocityY * 0.6;
}
- if (allEnemiesDefeated && enemyCars.length > 0) {
- LK.showYouWin();
- return; // Exit update loop
- }
- // Remove excess health bars if we have more bars than cars
- while (enemyHealthBars.length > enemyCars.length) {
- var removedBar = enemyHealthBars.pop();
- var removedBg = enemyHealthBarBgs.pop();
- if (removedBar) removedBar.destroy();
- if (removedBg) removedBg.destroy();
- }
- // Add health bars if we have more cars than bars
- while (enemyHealthBars.length < enemyCars.length) {
- var newCarIndex = enemyHealthBars.length;
- var newEnemyCarGraphics = enemyCars[newCarIndex].children[0];
- var newEnemyColor = newEnemyCarGraphics.tint || 0xffffff;
- createEnemyHealthBar(newCarIndex, newEnemyColor);
- }
// Update speed display
- var totalSpeed = Math.sqrt(velocityX * velocityX + velocityY * velocityY);
speedText.setText('Speed: ' + Math.round(totalSpeed));
};
\ No newline at end of file