/**** * Plugins ****/ var tween = LK.import("@upit/tween.v1"); /**** * Classes ****/ var Ally = Container.expand(function (ownerPlayerNumber, spawnX, spawnY) { var self = Container.call(this); // Use owner's asset but with different tint to distinguish var assetName = 'player1'; if (ownerPlayerNumber === 2) { assetName = 'player2'; } else if (ownerPlayerNumber === 3) { assetName = 'player3'; } else if (ownerPlayerNumber === 4) { assetName = 'player4'; } var allyGraphics = self.attachAsset(assetName, { anchorX: 0.5, anchorY: 1.0 }); // Tint ally to make it distinguishable (golden tint) allyGraphics.tint = 0xffdd00; allyGraphics.alpha = 0.8; self.ownerPlayerNumber = ownerPlayerNumber; self.velocityX = 0; self.velocityY = 0; self.onGround = false; self.jumpsRemaining = 2; self.moveSpeed = 6; // Slightly slower than players self.jumpPower = 20; self.lifeTime = 0; self.maxLifeTime = 1800; // 30 seconds at 60fps self.attackCooldown = 0; self.targetEnemy = null; self.lastTargetScanTime = 0; // Spawn position self.x = spawnX; self.y = spawnY; self.findNearestEnemy = function () { var nearestEnemy = null; var nearestDistance = Infinity; for (var i = 0; i < fighters.length; i++) { var fighter = fighters[i]; if (!fighter || fighter.destroyed) { continue; } if (fighter.playerNumber === self.ownerPlayerNumber) { continue; } // Don't target owner // Check if fighter is alive var isAlive = false; if (fighter.playerNumber === 1 && player1Lives > 0) { isAlive = true; } else if (fighter.playerNumber === 2 && player2Lives > 0) { isAlive = true; } else if (fighter.playerNumber === 3 && player3Lives > 0) { isAlive = true; } else if (fighter.playerNumber === 4 && player4Lives > 0) { isAlive = true; } if (!isAlive) { continue; } var distance = Math.sqrt(Math.pow(fighter.x - self.x, 2) + Math.pow(fighter.y - self.y, 2)); if (distance < nearestDistance) { nearestDistance = distance; nearestEnemy = fighter; } } return nearestEnemy; }; self.attack = function (targetX, targetY) { var attackRange = 120; var damage = 12; // Lower damage than players var knockback = 15; for (var i = 0; i < fighters.length; i++) { var target = fighters[i]; if (!target || target.destroyed) { continue; } if (target.playerNumber === self.ownerPlayerNumber) { continue; } // Don't attack owner var distance = Math.sqrt(Math.pow(target.x - self.x, 2) + Math.pow(target.y - self.y, 2)); if (distance <= attackRange) { var angle = Math.atan2(target.y - self.y, target.x - self.x); var knockbackX = Math.cos(angle) * knockback; var knockbackY = Math.sin(angle) * knockback - 3; target.takeDamage(damage, knockbackX, knockbackY); } } }; self.update = function () { self.lifeTime++; self.attackCooldown--; if (self.attackCooldown < 0) { self.attackCooldown = 0; } // Fade out as ally approaches end of life if (self.lifeTime >= self.maxLifeTime - 300) { var fadeProgress = (self.lifeTime - (self.maxLifeTime - 300)) / 300; self.alpha = 0.8 * (1 - fadeProgress); } // Remove ally when lifetime expires if (self.lifeTime >= self.maxLifeTime) { self.destroy(); for (var j = allies.length - 1; j >= 0; j--) { if (allies[j] === self) { allies.splice(j, 1); break; } } return; } // Scan for enemies every 30 frames if (LK.ticks - self.lastTargetScanTime > 30) { self.targetEnemy = self.findNearestEnemy(); self.lastTargetScanTime = LK.ticks; } // AI behavior - follow and attack enemies if (self.targetEnemy && !self.targetEnemy.destroyed) { var distanceToTarget = Math.sqrt(Math.pow(self.targetEnemy.x - self.x, 2) + Math.pow(self.targetEnemy.y - self.y, 2)); // Attack if close enough and cooldown is over if (distanceToTarget < 150 && self.attackCooldown === 0) { self.attack(self.targetEnemy.x, self.targetEnemy.y); self.attackCooldown = 90; // 1.5 second cooldown } // Move toward target else { var deltaX = self.targetEnemy.x - self.x; var deltaY = self.targetEnemy.y - self.y; if (Math.abs(deltaX) > 60) { self.velocityX += deltaX * 0.15; } // Jump if target is above if (deltaY < -80 && self.jumpsRemaining > 0 && self.onGround) { self.velocityY = -self.jumpPower; self.jumpsRemaining--; self.onGround = false; LK.getSound('jump').play(); } } } // Apply gravity if (!self.onGround) { self.velocityY += 1.2; } // Apply friction self.velocityX *= 0.85; if (self.onGround) { self.velocityX *= 0.75; } // Limit velocities var maxVelocityX = 15; var maxVelocityY = 25; if (self.velocityX > maxVelocityX) { self.velocityX = maxVelocityX; } if (self.velocityX < -maxVelocityX) { self.velocityX = -maxVelocityX; } if (self.velocityY > maxVelocityY) { self.velocityY = maxVelocityY; } if (self.velocityY < -maxVelocityY) { self.velocityY = -maxVelocityY; } // Update position self.x += self.velocityX; self.y += self.velocityY; // Handle sprite direction if (Math.abs(self.velocityX) > 1) { if (self.velocityX > 0) { allyGraphics.scaleX = Math.abs(allyGraphics.scaleX); } else { allyGraphics.scaleX = -Math.abs(allyGraphics.scaleX); } } // Platform collision (same as Fighter class) var wasOnGround = self.onGround; self.onGround = false; for (var i = 0; i < platforms.length; i++) { var platform = platforms[i]; var platformLeft = platform.x - platform.width / 2 + 10; var platformRight = platform.x + platform.width / 2 - 10; var platformTop = platform.y - platform.height / 2; var platformBottom = platform.y + platform.height / 2; if (self.x >= platformLeft && self.x <= platformRight && self.y >= platformTop - 20 && self.y <= platformBottom + 5 && self.velocityY >= 0) { self.y = platformTop; self.velocityY = 0; if (!wasOnGround) { LK.getSound('landing').play(); } self.onGround = true; self.jumpsRemaining = 2; break; } } // Check if fallen off screen if (self.y > 2900) { self.destroy(); for (var j = allies.length - 1; j >= 0; j--) { if (allies[j] === self) { allies.splice(j, 1); break; } } return; } // Screen boundaries if (self.x < 60) { self.x = 60; self.velocityX = Math.abs(self.velocityX) * 0.5; } if (self.x > 2700) { self.x = 2700; self.velocityX = -Math.abs(self.velocityX) * 0.5; } }; return self; }); var AllyPowerUp = Container.expand(function () { var self = Container.call(this); var allyPowerupGraphics = self.attachAsset('allyPowerup', { anchorX: 0.5, anchorY: 0.5 }); // Tint powerup golden to distinguish from other powerups allyPowerupGraphics.tint = 0xffdd00; self.lifeTime = 0; self.maxLifeTime = 600; // 10 seconds at 60fps self.collected = false; self.velocityY = 0; // Falling velocity self.onGround = false; // Track if powerup has landed self.update = function () { self.lifeTime++; self.rotation += 0.05; // Apply gravity if not on ground if (!self.onGround) { self.velocityY += 0.8; // Gravity effect self.y += self.velocityY; // Check platform collision for landing for (var i = 0; i < platforms.length; i++) { var platform = platforms[i]; var platformLeft = platform.x - platform.width / 2; var platformRight = platform.x + platform.width / 2; var platformTop = platform.y - platform.height / 2; var platformBottom = platform.y + platform.height / 2; // Check if powerup is within platform bounds and landing on top if (self.x >= platformLeft && self.x <= platformRight && self.y >= platformTop - 30 && self.y <= platformBottom && self.velocityY >= 0) { self.y = platformTop - 30; // Position on top of platform self.velocityY = 0; self.onGround = true; break; } } // Remove if fallen off screen if (self.y > 2800) { self.destroy(); for (var j = allyPowerups.length - 1; j >= 0; j--) { if (allyPowerups[j] === self) { allyPowerups.splice(j, 1); break; } } return; } } else { // Bounce effect when on ground self.y += Math.sin(self.lifeTime * 0.1) * 0.5; } if (self.lifeTime > self.maxLifeTime) { self.destroy(); for (var i = allyPowerups.length - 1; i >= 0; i--) { if (allyPowerups[i] === self) { allyPowerups.splice(i, 1); break; } } } // Check collection by players if (!self.collected) { for (var i = 0; i < fighters.length; i++) { var fighter = fighters[i]; var distance = Math.sqrt(Math.pow(fighter.x - self.x, 2) + Math.pow(fighter.y - self.y, 2)); if (distance < 80) { self.collected = true; // Create ally for the player who collected it var ally = new Ally(fighter.playerNumber, self.x + 100, self.y); game.addChild(ally); allies.push(ally); // Visual effects for powerup collection LK.getSound('powerup').play(); LK.effects.flashObject(fighter, 0xffdd00, 800); // Create magical spawning effect with tween tween(ally, { scaleX: 1.0, scaleY: 1.0, alpha: 1.0 }, { duration: 500, easing: tween.bounceOut }); // Start ally small and fade in ally.scaleX = 0.1; ally.scaleY = 0.1; ally.alpha = 0.3; self.destroy(); for (var j = allyPowerups.length - 1; j >= 0; j--) { if (allyPowerups[j] === self) { allyPowerups.splice(j, 1); break; } } break; } } } }; return self; }); var Fighter = Container.expand(function (playerNumber, color) { var self = Container.call(this); var assetName = 'player1'; if (playerNumber === 2) { assetName = 'player2'; } else if (playerNumber === 3) { assetName = 'player3'; } else if (playerNumber === 4) { assetName = 'player4'; } var fighterGraphics = self.attachAsset(assetName, { anchorX: 0.5, anchorY: 1.0 }); self.playerNumber = playerNumber; self.velocityX = 0; self.velocityY = 0; self.onGround = false; self.jumpsRemaining = 2; // Allow double jump self.health = 0; self.maxHealth = 100; self.damage = 0; // New damage accumulation property self.moveSpeed = 8; self.jumpPower = 25; self.knockbackResistance = 1.0; self.lastHitTime = 0; self.invulnerable = false; self.powerupCount = 0; // Track number of powerups collected self.baseMoveSpeed = 8; // Store original move speed self.baseJumpPower = 25; // Store original jump power self.baseDamage = 15; // Store original damage self.baseKnockback = 20; // Store original knockback self.lastVelocityX = 0; // Track last velocity for rotation self.isWheelMode = false; // Track if in wheel mode self.wheelModeTimer = 0; // Timer for wheel mode duration self.wheelGraphics = null; // Reference to wheel graphics // Create damage bar background var damageBarBg = self.addChild(LK.getAsset('platform', { width: 100, height: 12, anchorX: 0.5, anchorY: 1.0, x: 0, y: -130 })); damageBarBg.tint = 0x333333; damageBarBg.scaleX = 0.8; damageBarBg.scaleY = 0.3; // Create damage bar fill self.damageBar = self.addChild(LK.getAsset('platform', { width: 100, height: 12, anchorX: 0, anchorY: 1.0, x: -40, y: -130 })); self.damageBar.tint = 0xff4444; self.damageBar.scaleX = 0; self.damageBar.scaleY = 0.3; // Create damage text self.damageText = self.addChild(new Text2('0%', { size: 30, fill: 0xFFFFFF, stroke: 0x000000, strokeThickness: 4 })); self.damageText.anchor.set(0.5, 1); self.damageText.x = 0; self.damageText.y = -140; self.takeDamage = function (damage, knockbackX, knockbackY) { if (self.invulnerable) { return; } self.health += damage; self.damage += damage; // Accumulate damage for knockback scaling // Cap damage at 999% if (self.damage > 999) { self.damage = 999; } // Update damage display self.damageText.setText(Math.floor(self.damage) + '%'); // Reapply stroke properties to maintain black background texture if (self.damageText.style) { self.damageText.style.stroke = 0x000000; self.damageText.style.strokeThickness = 4; } // Update damage bar scale (0 to 1 based on damage up to 200%) var barScale = Math.min(self.damage / 200, 1); self.damageBar.scaleX = barScale * 0.8; // Change bar color based on damage level if (self.damage < 50) { self.damageBar.tint = 0x44ff44; // Green for low damage } else if (self.damage < 100) { self.damageBar.tint = 0xffff44; // Yellow for medium damage } else if (self.damage < 150) { self.damageBar.tint = 0xff8844; // Orange for high damage } else { self.damageBar.tint = 0xff4444; // Red for very high damage } // Check if player has reached 200 damage - explosion and life loss if (self.damage >= 200) { // Create paint explosion effect var explosionCenter = { x: self.x, y: self.y }; var numberOfDrops = 15 + Math.random() * 10; // 15-25 paint drops for (var i = 0; i < numberOfDrops; i++) { // Create circular spread pattern var angle = i / numberOfDrops * Math.PI * 2 + (Math.random() - 0.5) * 0.5; var speed = 8 + Math.random() * 12; // Random speed for variety var velocityX = Math.cos(angle) * speed; var velocityY = Math.sin(angle) * speed - 5; // Slight upward bias // Create paint drop var paintDrop = new PaintDrop(self.playerNumber, explosionCenter.x, explosionCenter.y, velocityX, velocityY); game.addChild(paintDrop); paintDrops.push(paintDrop); } // Create explosion effect with tween tween(self, { scaleX: 2.0, scaleY: 2.0, alpha: 0 }, { duration: 500, easing: tween.bounceOut, onFinish: function onFinish() { // Reset scale and alpha after explosion self.scaleX = 1.0; self.scaleY = 1.0; self.alpha = 1.0; // Trigger respawn which handles life loss self.respawn(); } }); // Flash screen effect for dramatic explosion LK.effects.flashScreen(0xff8800, 600); // Play explosion sound effect LK.getSound('explosion').play(); // Reset damage to prevent multiple explosions self.damage = 0; return; // Exit early to prevent further processing this frame } // Knockback multiplier based on accumulated damage - exponential scaling for stronger hits at higher damage var knockbackMultiplier = 1.5 + self.damage / 20 + Math.pow(self.damage / 100, 1.5); // Exponential scaling that increases dramatically with damage self.velocityX += knockbackX * knockbackMultiplier / self.knockbackResistance; self.velocityY += knockbackY * knockbackMultiplier / self.knockbackResistance; // Flash effect when hit LK.effects.flashObject(self, 0xff0000, 300); self.invulnerable = true; LK.setTimeout(function () { self.invulnerable = false; }, 500); LK.getSound('hit').play(); }; self.attack = function (targetX, targetY) { var attackRange = 150; var damage = self.baseDamage + self.powerupCount * 8; // Increase damage by 8 per powerup var baseKnockback = self.baseKnockback + self.powerupCount * 10; // Increase knockback by 10 per powerup for (var i = 0; i < fighters.length; i++) { var target = fighters[i]; if (target === self) { continue; } var distance = Math.sqrt(Math.pow(target.x - self.x, 2) + Math.pow(target.y - self.y, 2)); if (distance <= attackRange) { var angle = Math.atan2(target.y - self.y, target.x - self.x); var knockbackX = Math.cos(angle) * baseKnockback; var knockbackY = Math.sin(angle) * baseKnockback - 5; // Slight upward angle target.takeDamage(damage, knockbackX, knockbackY); } } }; self.update = function () { // Handle wheel mode timer if (self.isWheelMode) { self.wheelModeTimer--; if (self.wheelModeTimer <= 0) { self.deactivateWheelMode(); } } // Update movement stats based on powerups self.moveSpeed = self.baseMoveSpeed + self.powerupCount * 3; // Increase move speed by 3 per powerup self.jumpPower = self.baseJumpPower + self.powerupCount * 5; // Increase jump power by 5 per powerup // Apply wheel mode bonuses if (self.isWheelMode) { self.moveSpeed *= 1.5; self.jumpPower *= 1.3; } // Apply gravity if (!self.onGround) { self.velocityY += 1.2; } // Apply friction self.velocityX *= 0.85; if (self.onGround) { self.velocityX *= 0.75; } // Limit velocities to prevent teleportation var maxVelocityX = 20; var maxVelocityY = 30; if (self.velocityX > maxVelocityX) { self.velocityX = maxVelocityX; } if (self.velocityX < -maxVelocityX) { self.velocityX = -maxVelocityX; } if (self.velocityY > maxVelocityY) { self.velocityY = maxVelocityY; } if (self.velocityY < -maxVelocityY) { self.velocityY = -maxVelocityY; } // Track last position for smoke trail if (self.lastX === undefined) { self.lastX = self.x; } if (self.lastY === undefined) { self.lastY = self.y; } // Update position self.x += self.velocityX; self.y += self.velocityY; // Generate smoke trail when moving var movementSpeed = Math.sqrt(Math.pow(self.x - self.lastX, 2) + Math.pow(self.y - self.lastY, 2)); if (movementSpeed > 3 && LK.ticks % 3 === 0) { // Calculate movement direction var movementDirectionX = self.x - self.lastX; var movementDirectionY = self.y - self.lastY; // Normalize the direction var directionLength = Math.sqrt(movementDirectionX * movementDirectionX + movementDirectionY * movementDirectionY); if (directionLength > 0) { movementDirectionX /= directionLength; movementDirectionY /= directionLength; } // Position smoke trail behind the movement direction var smokeDistance = 30; // Distance behind the player var smokeX = self.x - movementDirectionX * smokeDistance; var smokeY = self.y - movementDirectionY * smokeDistance + 20; var smoke = new SmokeTrail(self.playerNumber, smokeX, smokeY); game.addChild(smoke); smokeTrails.push(smoke); } // Update last position self.lastX = self.x; self.lastY = self.y; // Update visual size based on powerup count var targetScale = 1.0 + self.powerupCount * 0.2; // Increase size by 20% per powerup if (Math.abs(fighterGraphics.scaleX) !== targetScale) { var isFlipped = fighterGraphics.scaleX < 0; fighterGraphics.scaleX = isFlipped ? -targetScale : targetScale; fighterGraphics.scaleY = targetScale; } // Handle wheel mode rotation and physics if (self.isWheelMode && self.wheelGraphics) { // Rotate wheel image based on movement direction only if (Math.abs(self.velocityX) > 1) { self.wheelGraphics.rotation += self.velocityX * 0.1; } // Continuous bounce effect when hitting ground - jump every time touching ground if (self.onGround) { self.velocityY = -self.jumpPower; // Jump with same height as normal jump } // Enhanced friction for wheel mode if (self.onGround) { self.velocityX *= 0.95; // More responsive control } } else { // Rotate player based on movement direction (normal mode) if (Math.abs(self.velocityX) > 1) { // Only rotate if moving with significant velocity if (self.velocityX > 0) { // Moving right - face right (normal orientation) fighterGraphics.scaleX = Math.abs(fighterGraphics.scaleX); } else { // Moving left - face left (flip horizontally) fighterGraphics.scaleX = -Math.abs(fighterGraphics.scaleX); } } } // Platform collision var wasOnGround = self.onGround; self.onGround = false; for (var i = 0; i < platforms.length; i++) { var platform = platforms[i]; // More precise collision detection with tighter bounds var platformLeft = platform.x - platform.width / 2 + 10; // Add small margin var platformRight = platform.x + platform.width / 2 - 10; // Add small margin var platformTop = platform.y - platform.height / 2; var platformBottom = platform.y + platform.height / 2; // Check if player is within platform horizontal bounds and approaching from above if (self.x >= platformLeft && self.x <= platformRight && self.y >= platformTop - 20 && self.y <= platformBottom + 5 && self.velocityY >= 0) { // Adjust ground position for wheel mode to prevent clipping var groundOffset = self.isWheelMode ? -60 : 0; // Offset wheel players higher to prevent clipping // Snap player to exact platform surface self.y = platformTop + groundOffset; self.velocityY = 0; // Play landing sound if just landed (wasn't on ground before) if (!wasOnGround) { LK.getSound('landing').play(); } self.onGround = true; self.jumpsRemaining = 2; // Reset jumps when landing break; } } // Check if fallen off screen if (self.y > 2900) { // Play fall sound effect LK.getSound('fall').play(); self.respawn(); } // Fighter-to-fighter collision detection and pushing for (var i = 0; i < fighters.length; i++) { var otherFighter = fighters[i]; if (otherFighter === self || otherFighter.destroyed) { continue; } // Calculate distance between fighters var deltaX = otherFighter.x - self.x; var deltaY = otherFighter.y - self.y; var distance = Math.sqrt(deltaX * deltaX + deltaY * deltaY); // Check if fighters are colliding (within collision radius) // Use larger radius for wheel mode players var collisionRadius = 80; // Default collision radius if (self.isWheelMode || otherFighter.isWheelMode) { collisionRadius = 120; // Increased radius when either player is in wheel mode } if (distance < collisionRadius && distance > 0) { // Check for wheel mode damage dealing if (self.isWheelMode && !self.lastWheelDamageTime) { self.lastWheelDamageTime = 0; } if (otherFighter.isWheelMode && !otherFighter.lastWheelDamageTime) { otherFighter.lastWheelDamageTime = 0; } // Initialize lastWheelDamageTime if not set if (self.lastWheelDamageTime === undefined) { self.lastWheelDamageTime = 0; } if (otherFighter.lastWheelDamageTime === undefined) { otherFighter.lastWheelDamageTime = 0; } // Deal damage if current player is in wheel mode and enough time has passed if (self.isWheelMode && LK.ticks - self.lastWheelDamageTime > 60) { var wheelDamage = 25 + self.powerupCount * 5; // Base wheel damage + powerup bonus var angle = Math.atan2(deltaY, deltaX); var knockbackX = Math.cos(angle) * 15; var knockbackY = Math.sin(angle) * 15 - 3; // Slight upward angle otherFighter.takeDamage(wheelDamage, knockbackX, knockbackY); self.lastWheelDamageTime = LK.ticks; } // Deal damage if other player is in wheel mode and enough time has passed if (otherFighter.isWheelMode && LK.ticks - otherFighter.lastWheelDamageTime > 60) { var wheelDamage = 25 + otherFighter.powerupCount * 5; // Base wheel damage + powerup bonus var angle = Math.atan2(-deltaY, -deltaX); var knockbackX = Math.cos(angle) * 15; var knockbackY = Math.sin(angle) * 15 - 3; // Slight upward angle self.takeDamage(wheelDamage, knockbackX, knockbackY); otherFighter.lastWheelDamageTime = LK.ticks; } // Calculate push force based on overlap var overlap = collisionRadius - distance; var pushStrength = overlap * 0.3; // Adjust push strength // Normalize direction vector var normalX = deltaX / distance; var normalY = deltaY / distance; // Apply push forces to both fighters var pushX = normalX * pushStrength; var pushY = normalY * pushStrength * 0.3; // Reduced vertical push // Push both fighters apart self.velocityX -= pushX; self.velocityY -= pushY; otherFighter.velocityX += pushX; otherFighter.velocityY += pushY; // Separate fighters to prevent overlap var separationX = normalX * (overlap * 0.5); var separationY = normalY * (overlap * 0.3); self.x -= separationX; self.y -= separationY; otherFighter.x += separationX; otherFighter.y += separationY; } } // Screen boundaries (horizontal bouncing) if (self.x < 60) { self.x = 60; if (self.isWheelMode) { self.velocityX = Math.abs(self.velocityX) * 0.8; // Enhanced bounce in wheel mode } else { self.velocityX = Math.abs(self.velocityX) * 0.5; } } if (self.x > 2700) { self.x = 2700; if (self.isWheelMode) { self.velocityX = -Math.abs(self.velocityX) * 0.8; // Enhanced bounce in wheel mode } else { self.velocityX = -Math.abs(self.velocityX) * 0.5; } } }; self.respawn = function () { // Decrease lives for the player who died if (self.playerNumber === 1) { player1Lives--; } else if (self.playerNumber === 2) { player2Lives--; } else if (self.playerNumber === 3) { player3Lives--; } else if (self.playerNumber === 4) { player4Lives--; } updateScoreDisplay(); // Check if player is eliminated (no lives left) var isEliminated = false; if (self.playerNumber === 1 && player1Lives <= 0) { isEliminated = true; } else if (self.playerNumber === 2 && player2Lives <= 0) { isEliminated = true; } else if (self.playerNumber === 3 && player3Lives <= 0) { isEliminated = true; } else if (self.playerNumber === 4 && player4Lives <= 0) { isEliminated = true; } if (isEliminated) { // Track if player 1 defeated an enemy if (self.playerNumber !== 1) { player1EnemiesDefeated++; } // Remove player from the game completely self.destroy(); // Remove from fighters array for (var j = fighters.length - 1; j >= 0; j--) { if (fighters[j] === self) { fighters.splice(j, 1); break; } } activePlayers--; // Check if only one player remains if (activePlayers <= 1) { // Find the winner var winner = ""; if (player1Lives > 0) { winner = "Player 1"; } else if (player2Lives > 0) { winner = "Player 2"; } else if (player3Lives > 0) { winner = "Player 3"; } else if (player4Lives > 0) { winner = "Player 4"; } LK.setScore(player1EnemiesDefeated); LK.showGameOver(); } } else { // Player still has lives, respawn normally if (self.playerNumber === 1) { self.x = 1048; // Respawn on left platform self.y = 1950; // Just above left platform surface } else if (self.playerNumber === 2) { self.x = 1648; // Respawn on main platform self.y = 2300; // Just above main platform surface } else if (self.playerNumber === 3) { self.x = 1648; // Respawn on top platform self.y = 1600; // Just above top platform surface } else if (self.playerNumber === 4) { self.x = 2248; // Respawn on right platform self.y = 1950; // Just above right platform surface } self.velocityX = 0; self.velocityY = 0; self.health = 0; self.damage = 0; // Reset damage on respawn self.powerupCount = 0; // Reset powerup count on respawn self.damageText.setText('0%'); // Reapply stroke properties to maintain black background texture if (self.damageText.style) { self.damageText.style.stroke = 0x000000; self.damageText.style.strokeThickness = 4; } self.damageBar.scaleX = 0; self.damageBar.tint = 0x44ff44; self.jumpsRemaining = 2; // Reset jumps on respawn } }; self.activateWheelMode = function () { if (self.isWheelMode) { return; } // Already in wheel mode self.isWheelMode = true; self.wheelModeTimer = 600; // 10 seconds at 60fps // Hide original fighter graphics fighterGraphics.visible = false; // Create wheel graphics based on player number var wheelAsset = 'wheel1'; if (self.playerNumber === 2) { wheelAsset = 'wheel2'; } else if (self.playerNumber === 3) { wheelAsset = 'wheel3'; } else if (self.playerNumber === 4) { wheelAsset = 'wheel4'; } self.wheelGraphics = self.attachAsset(wheelAsset, { anchorX: 0.5, anchorY: 0.5 }); // Increase move speed and add bounce physics self.moveSpeed *= 1.5; self.jumpPower *= 1.3; }; self.deactivateWheelMode = function () { if (!self.isWheelMode) { return; } self.isWheelMode = false; self.wheelModeTimer = 0; // Show original fighter graphics fighterGraphics.visible = true; // Remove wheel graphics if (self.wheelGraphics) { self.wheelGraphics.destroy(); self.wheelGraphics = null; } // Reset move speed self.moveSpeed = self.baseMoveSpeed + self.powerupCount * 3; self.jumpPower = self.baseJumpPower + self.powerupCount * 5; }; return self; }); var Firefly = Container.expand(function () { var self = Container.call(this); // Create firefly body var fireflyBody = self.attachAsset('firefly', { anchorX: 0.5, anchorY: 0.5 }); // Create glow effect with 200x200 illumination radius var fireflyGlow = self.attachAsset('fireflyGlow', { anchorX: 0.5, anchorY: 0.5 }); fireflyGlow.alpha = 0.15; // Soft glow effect fireflyGlow.scaleX = 1.0; fireflyGlow.scaleY = 1.0; self.lifeTime = 0; self.maxLifeTime = 2400; // 40 seconds at 60fps - longer than max night cycle self.velocityX = (Math.random() - 0.5) * 2; self.velocityY = (Math.random() - 0.5) * 2; self.glowIntensity = 0.15; self.flickerTimer = 0; self.update = function () { self.lifeTime++; self.flickerTimer++; // Gentle movement pattern self.velocityX += (Math.random() - 0.5) * 0.3; self.velocityY += (Math.random() - 0.5) * 0.3; // Limit velocity for gentle floating if (self.velocityX > 1.5) { self.velocityX = 1.5; } if (self.velocityX < -1.5) { self.velocityX = -1.5; } if (self.velocityY > 1.5) { self.velocityY = 1.5; } if (self.velocityY < -1.5) { self.velocityY = -1.5; } self.x += self.velocityX; self.y += self.velocityY; // Flicker effect - vary glow intensity var flickerValue = Math.sin(self.flickerTimer * 0.2) * 0.05; fireflyGlow.alpha = self.glowIntensity + flickerValue; fireflyBody.alpha = 0.8 + flickerValue; // Keep within screen bounds if (self.x < 100) { self.x = 100; self.velocityX = Math.abs(self.velocityX); } if (self.x > 1948) { self.x = 1948; self.velocityX = -Math.abs(self.velocityX); } if (self.y < 100) { self.y = 100; self.velocityY = Math.abs(self.velocityY); } if (self.y > 2632) { self.y = 2632; self.velocityY = -Math.abs(self.velocityY); } // Fade out when approaching end of life if (self.lifeTime >= self.maxLifeTime - 180) { // Start fading 3 seconds before end var fadeProgress = (self.lifeTime - (self.maxLifeTime - 180)) / 180; fireflyGlow.alpha = (self.glowIntensity + flickerValue) * (1 - fadeProgress); fireflyBody.alpha = (0.8 + flickerValue) * (1 - fadeProgress); } // Remove when life time is over if (self.lifeTime >= self.maxLifeTime) { self.destroy(); for (var i = fireflies.length - 1; i >= 0; i--) { if (fireflies[i] === self) { fireflies.splice(i, 1); break; } } } }; return self; }); var Flower = Container.expand(function (x, y) { var self = Container.call(this); // Create flower stem var stem = self.attachAsset('flowerStem', { anchorX: 0.5, anchorY: 1.0 }); // Create flower bloom var bloom = self.attachAsset('flower', { anchorX: 0.5, anchorY: 1.0, y: -30 }); self.x = x; self.y = y; self.lifeTime = 0; self.maxLifeTime = 3600; // 60 seconds at 60fps self.growthPhase = 0; // 0=growing, 1=blooming, 2=swaying // Start small and grow stem.scaleX = 0.1; stem.scaleY = 0.1; bloom.scaleX = 0.1; bloom.scaleY = 0.1; bloom.alpha = 0; // Growth animation tween(stem, { scaleX: 1.0, scaleY: 1.0 }, { duration: 1000, easing: tween.easeOut }); // Delayed bloom animation LK.setTimeout(function () { self.growthPhase = 1; tween(bloom, { scaleX: 1.2, scaleY: 1.2, alpha: 1.0 }, { duration: 1500, easing: tween.bounceOut, onFinish: function onFinish() { self.growthPhase = 2; } }); }, 800); self.update = function () { self.lifeTime++; // Gentle swaying motion when fully grown if (self.growthPhase === 2) { var swayAmount = Math.sin(self.lifeTime * 0.05) * 0.1; bloom.rotation = swayAmount; stem.rotation = swayAmount * 0.3; } // Fade out before disappearing if (self.lifeTime >= self.maxLifeTime - 300) { var fadeProgress = (self.lifeTime - (self.maxLifeTime - 300)) / 300; self.alpha = 1 - fadeProgress; } // Remove when life time is over if (self.lifeTime >= self.maxLifeTime) { self.destroy(); for (var i = flowers.length - 1; i >= 0; i--) { if (flowers[i] === self) { flowers.splice(i, 1); break; } } } }; return self; }); var Lightning = Container.expand(function () { var self = Container.call(this); var lightningGraphics = self.attachAsset('lightning', { anchorX: 0.5, anchorY: 0 }); self.lifeTime = 0; self.maxLifeTime = 15; // Very short flash lightningGraphics.alpha = 0.8; self.update = function () { self.lifeTime++; // Flicker effect lightningGraphics.alpha = 0.8 * Math.random(); // Remove after short time if (self.lifeTime >= self.maxLifeTime) { self.destroy(); for (var i = lightningBolts.length - 1; i >= 0; i--) { if (lightningBolts[i] === self) { lightningBolts.splice(i, 1); break; } } } }; return self; }); var PaintDrop = Container.expand(function (playerNumber, startX, startY, velocityX, velocityY) { var self = Container.call(this); // Choose paint drop asset based on player number var assetName = 'paintDrop1'; if (playerNumber === 2) { assetName = 'paintDrop2'; } else if (playerNumber === 3) { assetName = 'paintDrop3'; } else if (playerNumber === 4) { assetName = 'paintDrop4'; } var paintGraphics = self.attachAsset(assetName, { anchorX: 0.5, anchorY: 0.5 }); self.x = startX; self.y = startY; self.velocityX = velocityX; self.velocityY = velocityY; self.hasLanded = false; self.lifeTime = 0; self.maxLifeTime = 600; // Stay visible for 10 seconds after landing self.update = function () { if (!self.hasLanded) { // Apply gravity and movement self.velocityY += 0.8; // Gravity self.velocityX *= 0.98; // Air resistance self.x += self.velocityX; self.y += self.velocityY; // Check collision with platforms for (var i = 0; i < platforms.length; i++) { var platform = platforms[i]; var platformLeft = platform.x - platform.width / 2; var platformRight = platform.x + platform.width / 2; var platformTop = platform.y - platform.height / 2; var platformBottom = platform.y + platform.height / 2; if (self.x >= platformLeft && self.x <= platformRight && self.y >= platformTop - 10 && self.y <= platformBottom && self.velocityY >= 0) { // Paint drop hit platform self.y = platformTop - 6; // Position on top of platform self.velocityX = 0; self.velocityY = 0; self.hasLanded = true; break; } } // Remove if fallen off screen if (self.y > 2900) { self.destroy(); for (var j = paintDrops.length - 1; j >= 0; j--) { if (paintDrops[j] === self) { paintDrops.splice(j, 1); break; } } return; } } else { // Paint drop has landed, start fade timer self.lifeTime++; if (self.lifeTime >= self.maxLifeTime) { // Fade out effect var fadeProgress = (self.lifeTime - self.maxLifeTime) / 60; // 1 second fade paintGraphics.alpha = Math.max(0, 1 - fadeProgress); if (fadeProgress >= 1) { self.destroy(); for (var j = paintDrops.length - 1; j >= 0; j--) { if (paintDrops[j] === self) { paintDrops.splice(j, 1); break; } } } } } }; return self; }); var PowerUp = Container.expand(function () { var self = Container.call(this); var powerupGraphics = self.attachAsset('powerup', { anchorX: 0.5, anchorY: 0.5 }); self.lifeTime = 0; self.maxLifeTime = 600; // 10 seconds at 60fps self.collected = false; self.velocityY = 0; // Falling velocity self.onGround = false; // Track if powerup has landed self.update = function () { self.lifeTime++; self.rotation += 0.05; // Apply gravity if not on ground if (!self.onGround) { self.velocityY += 0.8; // Gravity effect self.y += self.velocityY; // Check platform collision for landing for (var i = 0; i < platforms.length; i++) { var platform = platforms[i]; var platformLeft = platform.x - platform.width / 2; var platformRight = platform.x + platform.width / 2; var platformTop = platform.y - platform.height / 2; var platformBottom = platform.y + platform.height / 2; // Check if powerup is within platform bounds and landing on top if (self.x >= platformLeft && self.x <= platformRight && self.y >= platformTop - 30 && self.y <= platformBottom && self.velocityY >= 0) { self.y = platformTop - 30; // Position on top of platform self.velocityY = 0; self.onGround = true; break; } } // Remove if fallen off screen if (self.y > 2800) { self.destroy(); for (var j = powerups.length - 1; j >= 0; j--) { if (powerups[j] === self) { powerups.splice(j, 1); break; } } return; } } else { // Bounce effect when on ground self.y += Math.sin(self.lifeTime * 0.1) * 0.5; } if (self.lifeTime > self.maxLifeTime) { self.destroy(); for (var i = powerups.length - 1; i >= 0; i--) { if (powerups[i] === self) { powerups.splice(i, 1); break; } } } // Check collection by players if (!self.collected) { for (var i = 0; i < fighters.length; i++) { var fighter = fighters[i]; var distance = Math.sqrt(Math.pow(fighter.x - self.x, 2) + Math.pow(fighter.y - self.y, 2)); if (distance < 80) { self.collected = true; // Increase powerup count for stacking effects fighter.powerupCount++; // Visual effects for powerup collection LK.getSound('powerup').play(); LK.effects.flashObject(fighter, 0xffd700, 800); // Create scaling animation effect tween(fighter, { scaleX: 1.3, scaleY: 1.3 }, { duration: 200, easing: tween.bounceOut, onFinish: function onFinish() { // Scale back down slightly tween(fighter, { scaleX: 1.0 + fighter.powerupCount * 0.2, scaleY: 1.0 + fighter.powerupCount * 0.2 }, { duration: 300, easing: tween.easeOut }); } }); // Temporary knockback resistance boost (stacks with permanent effects) var originalResistance = fighter.knockbackResistance; fighter.knockbackResistance *= 0.7; // 30% less knockback temporarily LK.setTimeout(function () { fighter.knockbackResistance = originalResistance; }, 3000); self.destroy(); for (var j = powerups.length - 1; j >= 0; j--) { if (powerups[j] === self) { powerups.splice(j, 1); break; } } break; } } } }; return self; }); var RainDrop = Container.expand(function () { var self = Container.call(this); var rainGraphics = self.attachAsset('rainDrop', { anchorX: 0.5, anchorY: 0.5 }); self.velocityY = 15 + Math.random() * 10; // Fast falling speed self.velocityX = -2 + Math.random() * 4; // Slight horizontal drift self.lifeTime = 0; self.maxLifeTime = 300; // 5 seconds max life self.update = function () { self.lifeTime++; // Move the raindrop self.x += self.velocityX; self.y += self.velocityY; // Check collision with platforms for (var i = 0; i < platforms.length; i++) { var platform = platforms[i]; var platformLeft = platform.x - platform.width / 2; var platformRight = platform.x + platform.width / 2; var platformTop = platform.y - platform.height / 2; var platformBottom = platform.y + platform.height / 2; // Check if raindrop hits platform if (self.x >= platformLeft && self.x <= platformRight && self.y >= platformTop && self.y <= platformBottom && self.velocityY > 0) { // Track rain drop location for flower spawning rainDropLocations.push({ x: self.x, y: platformTop - 20, // Position flowers on top of platform time: LK.ticks }); // Rain drop hit platform, destroy it self.destroy(); for (var j = rainDrops.length - 1; j >= 0; j--) { if (rainDrops[j] === self) { rainDrops.splice(j, 1); break; } } return; // Exit early to prevent further processing } } // Check collision with players for (var i = 0; i < fighters.length; i++) { var fighter = fighters[i]; if (!fighter || fighter.destroyed) { continue; } var distance = Math.sqrt(Math.pow(fighter.x - self.x, 2) + Math.pow(fighter.y - self.y, 2)); if (distance < 60) { // Hit detection radius // Reduce player size by 1.0% var currentScale = Math.abs(fighter.children[0].scaleX); var newScale = currentScale * 0.99; // Reduce by 1.0% // Apply the scale reduction while maintaining orientation var isFlipped = fighter.children[0].scaleX < 0; fighter.children[0].scaleX = isFlipped ? -newScale : newScale; fighter.children[0].scaleY = newScale; // Destroy the rain drop self.destroy(); for (var j = rainDrops.length - 1; j >= 0; j--) { if (rainDrops[j] === self) { rainDrops.splice(j, 1); break; } } return; // Exit early to prevent further processing } } // Remove if off screen or exceeded life time if (self.y > 2800 || self.x < -100 || self.x > 2148 || self.lifeTime >= self.maxLifeTime) { self.destroy(); for (var i = rainDrops.length - 1; i >= 0; i--) { if (rainDrops[i] === self) { rainDrops.splice(i, 1); break; } } } }; return self; }); var SmokeTrail = Container.expand(function (playerNumber, x, y) { var self = Container.call(this); var assetName = 'smokeTrail1'; if (playerNumber === 2) { assetName = 'smokeTrail2'; } else if (playerNumber === 3) { assetName = 'smokeTrail3'; } else if (playerNumber === 4) { assetName = 'smokeTrail4'; } var smokeGraphics = self.attachAsset(assetName, { anchorX: 0.5, anchorY: 0.5 }); self.x = x; self.y = y; self.lifeTime = 0; self.maxLifeTime = 30; // 0.5 seconds at 60fps self.startScale = 0.6 + Math.random() * 0.4; self.endScale = 1.2 + Math.random() * 0.8; smokeGraphics.scaleX = self.startScale; smokeGraphics.scaleY = self.startScale; smokeGraphics.alpha = 0.9; self.velocityX = (Math.random() - 0.5) * 2; self.velocityY = -2 - Math.random() * 3; self.update = function () { self.lifeTime++; var progress = self.lifeTime / self.maxLifeTime; // Move the smoke particle self.x += self.velocityX; self.y += self.velocityY; // Fade out and scale up over time smokeGraphics.alpha = 0.9 * (1 - progress); var currentScale = self.startScale + (self.endScale - self.startScale) * progress; smokeGraphics.scaleX = currentScale; smokeGraphics.scaleY = currentScale; // Remove when life time is over if (self.lifeTime >= self.maxLifeTime) { self.destroy(); for (var i = smokeTrails.length - 1; i >= 0; i--) { if (smokeTrails[i] === self) { smokeTrails.splice(i, 1); break; } } } }; return self; }); var WheelPowerUp = Container.expand(function () { var self = Container.call(this); var wheelPowerupGraphics = self.attachAsset('wheelPowerup', { anchorX: 0.5, anchorY: 0.5 }); self.lifeTime = 0; self.maxLifeTime = 600; // 10 seconds at 60fps self.collected = false; self.velocityY = 0; // Falling velocity self.onGround = false; // Track if powerup has landed self.update = function () { self.lifeTime++; self.rotation += 0.05; // Apply gravity if not on ground if (!self.onGround) { self.velocityY += 0.8; // Gravity effect self.y += self.velocityY; // Check platform collision for landing for (var i = 0; i < platforms.length; i++) { var platform = platforms[i]; var platformLeft = platform.x - platform.width / 2; var platformRight = platform.x + platform.width / 2; var platformTop = platform.y - platform.height / 2; var platformBottom = platform.y + platform.height / 2; // Check if powerup is within platform bounds and landing on top if (self.x >= platformLeft && self.x <= platformRight && self.y >= platformTop - 30 && self.y <= platformBottom && self.velocityY >= 0) { self.y = platformTop - 30; // Position on top of platform self.velocityY = 0; self.onGround = true; break; } } // Remove if fallen off screen if (self.y > 2800) { self.destroy(); for (var j = wheelPowerups.length - 1; j >= 0; j--) { if (wheelPowerups[j] === self) { wheelPowerups.splice(j, 1); break; } } return; } } else { // Bounce effect when on ground self.y += Math.sin(self.lifeTime * 0.1) * 0.5; } if (self.lifeTime > self.maxLifeTime) { self.destroy(); for (var i = wheelPowerups.length - 1; i >= 0; i--) { if (wheelPowerups[i] === self) { wheelPowerups.splice(i, 1); break; } } } // Check collection by players if (!self.collected) { for (var i = 0; i < fighters.length; i++) { var fighter = fighters[i]; var distance = Math.sqrt(Math.pow(fighter.x - self.x, 2) + Math.pow(fighter.y - self.y, 2)); if (distance < 80) { self.collected = true; // Convert fighter to wheel mode fighter.activateWheelMode(); // Visual effects for powerup collection LK.getSound('powerup').play(); LK.effects.flashObject(fighter, 0x00ff00, 800); self.destroy(); for (var j = wheelPowerups.length - 1; j >= 0; j--) { if (wheelPowerups[j] === self) { wheelPowerups.splice(j, 1); break; } } break; } } } }; return self; }); /**** * Initialize Game ****/ var game = new LK.Game({ backgroundColor: 0x000000 }); /**** * Game Code ****/ // Game variables var fighters = []; var platforms = []; var powerups = []; var wheelPowerups = []; var allyPowerups = []; var allies = []; var smokeTrails = []; var fireflies = []; var rainDrops = []; var lightningBolts = []; var flowers = []; var paintDrops = []; // Track paint drops from explosions var rainDropLocations = []; // Track where rain drops have fallen var player1Lives = 3; var player2Lives = 3; var player3Lives = 3; var player4Lives = 3; var activePlayers = 4; var draggedFighter = null; var lastTapTime = 0; var powerupSpawnTimer = 0; var player1EnemiesDefeated = 0; // Track enemies defeated by player 1 // Night mode variables var nightModeTimer = 0; var nightModeDuration = 1200 + Math.random() * 600; // 20-30 seconds at 60fps var isNightMode = false; var nightOverlay = null; // Storm mode variables var stormModeTimer = 0; var stormStartTime = 300 + Math.random() * 2700; // Random time between 5-50 seconds var isStormMode = false; var stormHasTriggered = false; var stormOverlay = null; // Camera variables var cameraX = 1024; // Center of screen var cameraY = 1366; // Center of screen var cameraScale = 1.0; var targetCameraX = 1024; var targetCameraY = 1366; var targetCameraScale = 1.0; var cameraUpdateTimer = 0; // Add background image with parallax tracking var backgroundImage = game.addChild(LK.getAsset('background', { anchorX: 0.5, anchorY: 0.5, x: 1648, y: 2400 })); backgroundImage.originalX = 1648; backgroundImage.originalY = 2400; backgroundImage.scrollOffset = 0; // Track horizontal scroll position // Add duplicate background image for infinite scrolling var backgroundImageDuplicate = game.addChild(LK.getAsset('background', { anchorX: 0.5, anchorY: 0.5, x: 1648 + 4948, // Position one background width to the right y: 2400, scaleX: -1 // Flip horizontally })); backgroundImageDuplicate.originalX = 1648 + 4948; backgroundImageDuplicate.originalY = 2400; backgroundImageDuplicate.scrollOffset = 0; // Add second background image on top of the first with parallax tracking var backgroundOverlay = game.addChild(LK.getAsset('backgroundOverlay', { anchorX: 0.5, anchorY: 0.5, x: 1648, y: 2400 })); backgroundOverlay.originalX = 1648; backgroundOverlay.originalY = 2400; // Add third background image above the previous two with parallax tracking var backgroundTop = game.addChild(LK.getAsset('backgroundTop', { anchorX: 0.5, anchorY: 0.5, x: 1648, y: 3200 })); backgroundTop.originalX = 1648; backgroundTop.originalY = 3200; // Add dynamic overlay background above all other elements var dynamicOverlay = game.addChild(LK.getAsset('dynamicOverlay', { anchorX: 0.5, anchorY: 0.5, x: 1024, y: 1366 })); dynamicOverlay.originalX = 1024; dynamicOverlay.originalY = 1366; dynamicOverlay.alpha = 1.0; // Fully opaque overlay dynamicOverlay.scaleX = 1.5; dynamicOverlay.scaleY = 1.5; // Initialize movement variables for dynamic overlay dynamicOverlay.moveSpeed = 0.5; dynamicOverlay.direction = 0; dynamicOverlay.rotationSpeed = 0; // Add second dynamic overlay background 4000 pixels below the first var dynamicOverlay2 = game.addChild(LK.getAsset('dynamicOverlay2', { anchorX: 0.5, anchorY: 0.5, x: 1024, y: 5366 // 4000 pixels below the first overlay (1366 + 4000) })); dynamicOverlay2.originalX = 1024; dynamicOverlay2.originalY = 5366; dynamicOverlay2.alpha = 1.0; // Fully opaque overlay dynamicOverlay2.scaleX = 1.5; dynamicOverlay2.scaleY = 1.5; // Initialize movement variables for second dynamic overlay dynamicOverlay2.moveSpeed = 0.3; // Slightly different speed for variation dynamicOverlay2.direction = Math.PI; // Start with opposite direction dynamicOverlay2.rotationSpeed = 0; // Button press states var leftButtonPressed = false; var rightButtonPressed = false; var jumpButtonPressed = false; var attackButtonPressed = false; // Create platforms var mainPlatform = game.addChild(LK.getAsset('mainPlatform', { anchorX: 0.5, anchorY: 0.5, x: 1648, y: 2400 })); platforms.push(mainPlatform); var leftPlatform = game.addChild(LK.getAsset('platform', { anchorX: 0.5, anchorY: 0.5, x: 1048, y: 2050 })); platforms.push(leftPlatform); var rightPlatform = game.addChild(LK.getAsset('platform', { anchorX: 0.5, anchorY: 0.5, x: 2248, y: 2050 })); platforms.push(rightPlatform); var topPlatform = game.addChild(LK.getAsset('platform', { anchorX: 0.5, anchorY: 0.5, x: 1648, y: 1700 })); platforms.push(topPlatform); // Add decorative textured images directly on top of platforms var mainPlatformTexture = game.addChild(LK.getAsset('platformTexture', { anchorX: 0.5, anchorY: 1.0, x: 1648, y: 2250 })); mainPlatformTexture.scaleX = 2.5; // Scale to match main platform width mainPlatformTexture.scaleY = 0.8; var leftPlatformTexture = game.addChild(LK.getAsset('platformTexture', { anchorX: 0.5, anchorY: 1.0, x: 1048, y: 1950 })); leftPlatformTexture.scaleX = 1.25; // Scale to match platform width leftPlatformTexture.scaleY = 0.6; var rightPlatformTexture = game.addChild(LK.getAsset('platformTexture', { anchorX: 0.5, anchorY: 1.0, x: 2248, y: 1950 })); rightPlatformTexture.scaleX = 1.25; // Scale to match platform width rightPlatformTexture.scaleY = 0.6; var topPlatformTexture = game.addChild(LK.getAsset('platformTexture', { anchorX: 0.5, anchorY: 1.0, x: 1648, y: 1600 })); topPlatformTexture.scaleX = 1.25; // Scale to match platform width topPlatformTexture.scaleY = 0.6; // Create night mode overlay nightOverlay = game.addChild(LK.getAsset('platform', { width: 3000, height: 4000, anchorX: 0.5, anchorY: 0.5, x: 1500, y: 2000 })); nightOverlay.tint = 0x000022; // Darker blue tint for stronger night effect nightOverlay.alpha = 0; // Start invisible nightOverlay.scaleX = 2; nightOverlay.scaleY = 2; // Create storm mode overlay stormOverlay = game.addChild(LK.getAsset('platform', { width: 3000, height: 4000, anchorX: 0.5, anchorY: 0.5, x: 1500, y: 2000 })); stormOverlay.tint = 0x444444; // Dark gray tint for storm atmosphere stormOverlay.alpha = 0; // Start invisible stormOverlay.scaleX = 2; stormOverlay.scaleY = 2; // Create fighters var player1 = game.addChild(new Fighter(1)); player1.x = 1048; // Position on left platform player1.y = 1950; // Just above the platform surface fighters.push(player1); var player2 = game.addChild(new Fighter(2)); player2.x = 1648; // Position on main platform player2.y = 2300; // Just above the main platform surface fighters.push(player2); var player3 = game.addChild(new Fighter(3)); player3.x = 1648; // Position on top platform player3.y = 1600; // Just above the top platform surface fighters.push(player3); var player4 = game.addChild(new Fighter(4)); player4.x = 2248; // Position on right platform player4.y = 1950; // Just above the right platform surface fighters.push(player4); // UI Elements - Individual player displays var player1Avatar = LK.getAsset('player1Avatar', { anchorX: 0.5, anchorY: 0.5, x: -200, y: 100, scaleX: 0.8, scaleY: 0.8 }); LK.gui.topRight.addChild(player1Avatar); var player1LivesBackground = LK.getAsset('livesBackground', { anchorX: 0.5, anchorY: 0.5, x: -200, y: 180, scaleX: 0.8, scaleY: 0.8 }); LK.gui.topRight.addChild(player1LivesBackground); var player1LivesBackground; var player1LivesImage = LK.getAsset('number3', { anchorX: 0.5, anchorY: 0.5, x: -200, y: 180, scaleX: 1.0, scaleY: 1.0 }); LK.gui.topRight.addChild(player1LivesImage); // Player 2, 3, and 4 UI elements are hidden - only Player 1 UI is visible // Create left movement button for player 1 var leftButton = LK.getAsset('leftButtonIcon', { anchorX: 0, anchorY: 1, x: 30, y: -160, scaleX: 3, scaleY: 3 }); LK.gui.bottomLeft.addChild(leftButton); // Create right movement button for player 1 var rightButton = LK.getAsset('rightButtonIcon', { anchorX: 0, anchorY: 1, x: 320, y: -160, scaleX: 3, scaleY: 3 }); LK.gui.bottomLeft.addChild(rightButton); // Create jump button for player 1 var jumpButton = LK.getAsset('jumpButtonIcon', { anchorX: 1, anchorY: 1, x: -100, y: -300, scaleX: 3, scaleY: 3 }); LK.gui.bottomRight.addChild(jumpButton); // Create attack button for player 1 var attackButton = LK.getAsset('attackButtonIcon', { anchorX: 1, anchorY: 1, x: -100, y: -160, scaleX: 3, scaleY: 3 }); LK.gui.bottomRight.addChild(attackButton); function updateScoreDisplay() { // Remove current lives image and background LK.gui.topRight.removeChild(player1LivesImage); LK.gui.topRight.removeChild(player1LivesBackground); player1LivesImage.destroy(); player1LivesBackground.destroy(); // Recreate lives background player1LivesBackground = LK.getAsset('livesBackground', { anchorX: 0.5, anchorY: 0.5, x: -200, y: 180, scaleX: 0.8, scaleY: 0.8 }); LK.gui.topRight.addChild(player1LivesBackground); // Create new lives image based on current lives count var numberAsset = 'number0'; if (player1Lives === 1) { numberAsset = 'number1'; } else if (player1Lives === 2) { numberAsset = 'number2'; } else if (player1Lives === 3) { numberAsset = 'number3'; } player1LivesImage = LK.getAsset(numberAsset, { anchorX: 0.5, anchorY: 0.5, x: -200, y: 180, scaleX: 1.0, scaleY: 1.0 }); LK.gui.topRight.addChild(player1LivesImage); // Update avatar opacity based on lives remaining player1Avatar.alpha = player1Lives > 0 ? 1.0 : 0.3; // Update lives image opacity based on lives remaining player1LivesImage.alpha = player1Lives > 0 ? 1.0 : 0.5; // Update background opacity based on lives remaining player1LivesBackground.alpha = player1Lives > 0 ? 1.0 : 0.5; } // Left button event handler leftButton.down = function (x, y, obj) { leftButtonPressed = true; leftButton.alpha = 0.6; tween(leftButton, { scaleX: 2.7, scaleY: 2.7 }, { duration: 100 }); }; leftButton.up = function (x, y, obj) { leftButtonPressed = false; leftButton.alpha = 1.0; tween(leftButton, { scaleX: 3.0, scaleY: 3.0 }, { duration: 100 }); }; // Right button event handler rightButton.down = function (x, y, obj) { rightButtonPressed = true; rightButton.alpha = 0.6; tween(rightButton, { scaleX: 2.7, scaleY: 2.7 }, { duration: 100 }); }; rightButton.up = function (x, y, obj) { rightButtonPressed = false; rightButton.alpha = 1.0; tween(rightButton, { scaleX: 3.0, scaleY: 3.0 }, { duration: 100 }); }; // Jump button event handler jumpButton.down = function (x, y, obj) { jumpButtonPressed = true; jumpButton.alpha = 0.6; tween(jumpButton, { scaleX: 2.7, scaleY: 2.7 }, { duration: 100 }); }; jumpButton.up = function (x, y, obj) { jumpButtonPressed = false; jumpButton.alpha = 1.0; tween(jumpButton, { scaleX: 3.0, scaleY: 3.0 }, { duration: 100 }); }; // Attack button event handler attackButton.down = function (x, y, obj) { attackButtonPressed = true; attackButton.alpha = 0.6; tween(attackButton, { scaleX: 2.7, scaleY: 2.7 }, { duration: 100 }); }; attackButton.up = function (x, y, obj) { attackButtonPressed = false; attackButton.alpha = 1.0; tween(attackButton, { scaleX: 3.0, scaleY: 3.0 }, { duration: 100 }); }; function updateCamera() { // Only update camera if there are living fighters var aliveFighters = []; for (var i = 0; i < fighters.length; i++) { var fighter = fighters[i]; if (fighter && !fighter.destroyed) { // Check if fighter is alive based on lives var isAlive = false; if (fighter.playerNumber === 1 && player1Lives > 0) { isAlive = true; } else if (fighter.playerNumber === 2 && player2Lives > 0) { isAlive = true; } else if (fighter.playerNumber === 3 && player3Lives > 0) { isAlive = true; } else if (fighter.playerNumber === 4 && player4Lives > 0) { isAlive = true; } if (isAlive) { aliveFighters.push(fighter); } } } if (aliveFighters.length === 0) { return; } // Calculate center point of all alive fighters var minX = Infinity; var maxX = -Infinity; var minY = Infinity; var maxY = -Infinity; var totalX = 0; var totalY = 0; for (var i = 0; i < aliveFighters.length; i++) { var fighter = aliveFighters[i]; totalX += fighter.x; totalY += fighter.y; if (fighter.x < minX) { minX = fighter.x; } if (fighter.x > maxX) { maxX = fighter.x; } if (fighter.y < minY) { minY = fighter.y; } if (fighter.y > maxY) { maxY = fighter.y; } } // Calculate center point targetCameraX = totalX / aliveFighters.length; targetCameraY = totalY / aliveFighters.length; // Calculate required zoom based on spread of players var spreadX = maxX - minX; var spreadY = maxY - minY; // Base zoom calculation - zoom in more for closer view of players var baseScale = 1.2; // Increased base scale for more zoom var maxSpreadX = 400; // Further reduced max spread for tighter framing var maxSpreadY = 300; // Further reduced max spread for tighter framing var scaleX = maxSpreadX / Math.max(spreadX + 100, maxSpreadX); // Reduced padding for closer view var scaleY = maxSpreadY / Math.max(spreadY + 100, maxSpreadY); // Reduced padding for closer view // Use the smaller scale to ensure all players fit targetCameraScale = Math.min(scaleX, scaleY, 1.3); // Increased max zoom to 1.3x for closer view // Minimum zoom to prevent over-zooming - increased minimum for closer gameplay if (targetCameraScale < 0.9) { targetCameraScale = 0.9; } // Keep camera within reasonable bounds var padding = 200; if (targetCameraX < padding) { targetCameraX = padding; } if (targetCameraX > 2048 - padding) { targetCameraX = 2048 - padding; } if (targetCameraY < 400) { targetCameraY = 400; } if (targetCameraY > 2400) { targetCameraY = 2400; } } function applyCameraSmoothing() { // Smooth camera movement using tweening var smoothingSpeed = 0.05; // How fast camera follows (0.01 = very slow, 0.1 = fast) // Interpolate camera position cameraX += (targetCameraX - cameraX) * smoothingSpeed; cameraY += (targetCameraY - cameraY) * smoothingSpeed; cameraScale += (targetCameraScale - cameraScale) * smoothingSpeed; // Apply camera transformation to game game.x = (1024 - cameraX) * cameraScale + 1024 * (1 - cameraScale); game.y = (1366 - cameraY) * cameraScale + 1366 * (1 - cameraScale); game.scaleX = cameraScale; game.scaleY = cameraScale; // Apply parallax scrolling to background layers updateParallax(); } function updateParallax() { // Calculate camera movement offset from center var cameraMoveX = cameraX - 1024; // How much camera moved from center horizontally var cameraMoveY = cameraY - 1366; // How much camera moved from center vertically // Background infinite scrolling - move left slowly var scrollSpeed = 0.5; // Pixels per frame to move left backgroundImage.scrollOffset += scrollSpeed; backgroundImageDuplicate.scrollOffset += scrollSpeed; // Background image (furthest back) - moves slowest (20% of camera movement) plus infinite scroll backgroundImage.x = backgroundImage.originalX - cameraMoveX * 0.2 - backgroundImage.scrollOffset; backgroundImage.y = backgroundImage.originalY - cameraMoveY * 0.15; // Duplicate background image positioning backgroundImageDuplicate.x = backgroundImageDuplicate.originalX - cameraMoveX * 0.2 - backgroundImageDuplicate.scrollOffset; backgroundImageDuplicate.y = backgroundImageDuplicate.originalY - cameraMoveY * 0.15; // Reset positions when backgrounds scroll off screen for infinite loop var backgroundWidth = 4948; // Width of background image if (backgroundImage.x <= -backgroundWidth) { backgroundImage.x = backgroundImageDuplicate.x + backgroundWidth; backgroundImage.scrollOffset = backgroundImageDuplicate.scrollOffset; } if (backgroundImageDuplicate.x <= -backgroundWidth) { backgroundImageDuplicate.x = backgroundImage.x + backgroundWidth; backgroundImageDuplicate.scrollOffset = backgroundImage.scrollOffset; } // Background overlay (middle layer) - moves at medium speed (40% of camera movement) backgroundOverlay.x = backgroundOverlay.originalX - cameraMoveX * 0.4; backgroundOverlay.y = backgroundOverlay.originalY - cameraMoveY * 0.3; // Background top (closest to foreground) - moves faster (60% of camera movement) backgroundTop.x = backgroundTop.originalX - cameraMoveX * 0.6; backgroundTop.y = backgroundTop.originalY - cameraMoveY * 0.45; // Dynamic overlay - autonomous movement with slight camera influence dynamicOverlay.direction += 0.01; // Slow rotation of movement direction var autonomousX = Math.cos(dynamicOverlay.direction) * dynamicOverlay.moveSpeed; var autonomousY = Math.sin(dynamicOverlay.direction) * dynamicOverlay.moveSpeed; dynamicOverlay.x = dynamicOverlay.originalX + autonomousX * 100 - cameraMoveX * 0.1; dynamicOverlay.y = dynamicOverlay.originalY + autonomousY * 80 - cameraMoveY * 0.08; // Second dynamic overlay - autonomous movement with different pattern dynamicOverlay2.direction += 0.008; // Slightly different rotation speed var autonomousX2 = Math.cos(dynamicOverlay2.direction) * dynamicOverlay2.moveSpeed; var autonomousY2 = Math.sin(dynamicOverlay2.direction) * dynamicOverlay2.moveSpeed; dynamicOverlay2.x = dynamicOverlay2.originalX + autonomousX2 * 120 - cameraMoveX * 0.12; dynamicOverlay2.y = dynamicOverlay2.originalY + autonomousY2 * 90 - cameraMoveY * 0.09; } function spawnPowerUp() { if (powerups.length + wheelPowerups.length + allyPowerups.length < 2) { // 33% chance for each powerup type: regular, wheel, ally var rand = Math.random(); if (rand < 0.33) { var powerup = game.addChild(new PowerUp()); // Spawn from random position above the screen aligned with new platform positions powerup.x = 1048 + Math.random() * 1200; // Random X within extended platform bounds (left to right platform) powerup.y = -100; // Start above screen powerup.velocityY = 2 + Math.random() * 3; // Initial falling speed powerups.push(powerup); } else if (rand < 0.66) { var wheelPowerup = game.addChild(new WheelPowerUp()); // Spawn from random position above the screen aligned with new platform positions wheelPowerup.x = 1048 + Math.random() * 1200; // Random X within extended platform bounds (left to right platform) wheelPowerup.y = -100; // Start above screen wheelPowerup.velocityY = 2 + Math.random() * 3; // Initial falling speed wheelPowerups.push(wheelPowerup); } else { var allyPowerup = game.addChild(new AllyPowerUp()); // Spawn from random position above the screen aligned with new platform positions allyPowerup.x = 1048 + Math.random() * 1200; // Random X within extended platform bounds (left to right platform) allyPowerup.y = -100; // Start above screen allyPowerup.velocityY = 2 + Math.random() * 3; // Initial falling speed allyPowerups.push(allyPowerup); } } } // Game controls game.down = function (x, y, obj) { var currentTime = LK.ticks; var tapSpeed = 20; // frames between taps for double tap // Find closest fighter to touch point (only player 1) var closestFighter = null; var closestDistance = Infinity; for (var i = 0; i < fighters.length; i++) { var fighter = fighters[i]; // Only allow touch control for player 1 if they're still alive if (fighter.playerNumber !== 1 || player1Lives <= 0) { continue; } var distance = Math.sqrt(Math.pow(fighter.x - x, 2) + Math.pow(fighter.y - y, 2)); if (distance < closestDistance && distance < 200) { closestDistance = distance; closestFighter = fighter; } } if (closestFighter) { // Check for double tap (attack) if (currentTime - lastTapTime < tapSpeed) { closestFighter.attack(x, y); draggedFighter = null; } else { draggedFighter = closestFighter; } lastTapTime = currentTime; } }; game.move = function (x, y, obj) { if (draggedFighter && draggedFighter.playerNumber === 1) { var deltaX = x - draggedFighter.x; var deltaY = y - draggedFighter.y; // Move fighter towards touch point draggedFighter.velocityX += deltaX * 0.3; // Jump if dragging upward and has jumps remaining if (deltaY < -50 && draggedFighter.jumpsRemaining > 0) { draggedFighter.velocityY = -draggedFighter.jumpPower; draggedFighter.jumpsRemaining--; draggedFighter.onGround = false; LK.getSound('jump').play(); } } }; game.up = function (x, y, obj) { draggedFighter = null; }; // Function to determine which platform a fighter is on function getPlayerPlatform(fighter) { for (var i = 0; i < platforms.length; i++) { var platform = platforms[i]; var platformLeft = platform.x - platform.width / 2; var platformRight = platform.x + platform.width / 2; var platformTop = platform.y - platform.height / 2; var platformBottom = platform.y + platform.height / 2; // Check if fighter is within platform bounds and close to platform surface if (fighter.x >= platformLeft && fighter.x <= platformRight && fighter.y >= platformTop - 100 && fighter.y <= platformBottom + 50) { return platform; } } return null; } // Function to find closest target for AI with platform priority function findClosestTarget(aiPlayer) { var closestPlayer = null; var closestDistance = Infinity; var closestSamePlatformPlayer = null; var closestSamePlatformDistance = Infinity; var aiPlatform = getPlayerPlatform(aiPlayer); for (var i = 0; i < fighters.length; i++) { var fighter = fighters[i]; // Skip self, dead players, and destroyed fighters if (fighter === aiPlayer) { continue; } if (fighter.destroyed) { continue; } if (fighter.playerNumber === 1 && player1Lives <= 0) { continue; } if (fighter.playerNumber === 2 && player2Lives <= 0) { continue; } if (fighter.playerNumber === 3 && player3Lives <= 0) { continue; } if (fighter.playerNumber === 4 && player4Lives <= 0) { continue; } var distance = Math.sqrt(Math.pow(fighter.x - aiPlayer.x, 2) + Math.pow(fighter.y - aiPlayer.y, 2)); var fighterPlatform = getPlayerPlatform(fighter); var onSamePlatform = aiPlatform && fighterPlatform && aiPlatform === fighterPlatform; // Track closest enemy on same platform if (onSamePlatform && distance < closestSamePlatformDistance) { closestSamePlatformDistance = distance; closestSamePlatformPlayer = fighter; } // Track overall closest enemy if (distance < closestDistance) { closestDistance = distance; closestPlayer = fighter; } } // Prioritize same platform enemy if one exists if (closestSamePlatformPlayer) { return { player: closestSamePlatformPlayer, distance: closestSamePlatformDistance, samePlatform: true }; } return { player: closestPlayer, distance: closestDistance, samePlatform: false }; } // AI variables for player 2 var aiDecisionTimer = 0; var aiAction = 'idle'; var aiTargetX = player2.x; var aiCooldown = 0; // AI variables for player 3 var ai3DecisionTimer = 0; var ai3Action = 'idle'; var ai3TargetX = 1648; var ai3Cooldown = 0; // AI variables for player 4 var ai4DecisionTimer = 0; var ai4Action = 'idle'; var ai4TargetX = 2248; var ai4Cooldown = 0; game.update = function () { // Start background music on first update if (LK.ticks === 1) { LK.playMusic('backgroundMusic'); } // Handle continuous button presses for player 1 (only if alive) if (player1Lives > 0) { if (leftButtonPressed) { player1.velocityX -= player1.moveSpeed * 0.5; } if (rightButtonPressed) { player1.velocityX += player1.moveSpeed * 0.5; } if (jumpButtonPressed && player1.jumpsRemaining > 0) { player1.velocityY = -player1.jumpPower; player1.jumpsRemaining--; player1.onGround = false; LK.getSound('jump').play(); jumpButtonPressed = false; // Prevent continuous jumping } if (attackButtonPressed) { player1.attack(player1.x, player1.y); attackButtonPressed = false; // Prevent continuous attacking } } // Spawn power-ups occasionally powerupSpawnTimer++; if (powerupSpawnTimer > 120) { // Every 2 seconds if (Math.random() < 0.5) { spawnPowerUp(); } powerupSpawnTimer = 0; } // AI for player 2 (only if alive) if (player2Lives > 0) { aiDecisionTimer++; if (aiCooldown > 0) { aiCooldown--; } // Make AI decisions every 30 frames (half second) if (aiDecisionTimer > 30) { aiDecisionTimer = 0; // Check if AI is below main platform and needs to move to center var mainPlatformY = 2400; // Main platform Y position var isBelowMainPlatform = player2.y > mainPlatformY + 200; if (isBelowMainPlatform) { // Force move to center platform to avoid falling aiAction = 'moveToCenter'; aiTargetX = 1648; // Main platform center X } else { // Find closest target var closestTarget = findClosestTarget(player2); var distanceToClosest = closestTarget.distance; // Check for nearby powerups var nearestPowerup = null; var nearestPowerupDistance = Infinity; for (var i = 0; i < powerups.length; i++) { var powerup = powerups[i]; var distance = Math.sqrt(Math.pow(powerup.x - player2.x, 2) + Math.pow(powerup.y - player2.y, 2)); if (distance < nearestPowerupDistance) { nearestPowerupDistance = distance; nearestPowerup = powerup; } } // AI decision making with enhanced platform awareness if (aiCooldown === 0 && closestTarget.player) { if (distanceToClosest < 200 && Math.random() < 0.8) { // Attack if close to closest target aiAction = 'attack'; aiCooldown = 60; // Attack cooldown } else if (nearestPowerup && nearestPowerupDistance < 300 && Math.random() < 0.3) { // Go for powerup if nearby (lower priority) aiAction = 'moveToPowerup'; aiTargetX = nearestPowerup.x; } else if (closestTarget.samePlatform) { // Same platform enemy detected - move towards closest enemy on same platform aiAction = 'moveToPlayer'; aiTargetX = closestTarget.player.x; } else if (!closestTarget.samePlatform && closestTarget.player) { // Different platform - jump towards target aiAction = 'jumpToPlayer'; aiTargetX = closestTarget.player.x; } else { // Random movement (rare) aiAction = 'randomMove'; aiTargetX = 1200 + Math.random() * 900; // Random X within platform area } } else if (!closestTarget.player) { aiAction = 'idle'; } } } // Execute AI actions if (aiAction === 'attack' && closestTarget.player) { player2.attack(closestTarget.player.x, closestTarget.player.y); aiAction = 'idle'; } else if (aiAction === 'moveToPowerup' || aiAction === 'moveToPlayer' || aiAction === 'randomMove' || aiAction === 'jumpToPlayer' || aiAction === 'moveToCenter') { // Move towards target var deltaX = aiTargetX - player2.x; if (Math.abs(deltaX) > 50) { player2.velocityX += deltaX * 0.25; } // Enhanced jumping logic for different platforms if (aiAction === 'jumpToPlayer' && closestTarget && closestTarget.player) { // Jump towards player on different platform if (player2.jumpsRemaining > 0 && player2.onGround) { player2.velocityY = -player2.jumpPower; player2.jumpsRemaining--; player2.onGround = false; LK.getSound('jump').play(); // Add horizontal velocity towards target player2.velocityX += deltaX * 0.15; } } else if (aiAction === 'moveToCenter') { // Jump upward aggressively to get back to main platform if (player2.jumpsRemaining > 0) { player2.velocityY = -player2.jumpPower; player2.jumpsRemaining--; player2.onGround = false; LK.getSound('jump').play(); } } else if (aiAction === 'moveToPlayer' && closestTarget && closestTarget.player && closestTarget.player.y < player2.y - 100) { // Jump if target is above on same platform if (player2.jumpsRemaining > 0) { player2.velocityY = -player2.jumpPower; player2.jumpsRemaining--; player2.onGround = false; LK.getSound('jump').play(); } } else if (Math.abs(player2.velocityX) < 1 && player2.onGround && Math.random() < 0.1) { // Jump if stuck if (player2.jumpsRemaining > 0) { player2.velocityY = -player2.jumpPower; player2.jumpsRemaining--; player2.onGround = false; LK.getSound('jump').play(); } } // Reset action if close enough to target if (Math.abs(deltaX) < 100) { aiAction = 'idle'; } } // AI logic: double jump if can't reach player who is much higher var heightDifference = player2.y - player1.y; var horizontalDistance = Math.abs(player2.x - player1.x); if (heightDifference > 200 && horizontalDistance < 400 && player2.jumpsRemaining > 0) { // Check if AI is below player and perform double jump to reach them player2.velocityY = -player2.jumpPower; player2.jumpsRemaining--; player2.onGround = false; LK.getSound('jump').play(); } // AI emergency jump if falling off platform if (player2.y > 2500 && player2.jumpsRemaining > 0) { player2.velocityY = -player2.jumpPower; player2.jumpsRemaining--; player2.onGround = false; LK.getSound('jump').play(); } } // AI for player 3 (only if alive) if (player3Lives > 0) { ai3DecisionTimer++; if (ai3Cooldown > 0) { ai3Cooldown--; } if (ai3DecisionTimer > 45) { ai3DecisionTimer = 0; // Check if AI is below main platform and needs to move to center var mainPlatformY = 2400; // Main platform Y position var isBelowMainPlatform = player3.y > mainPlatformY + 200; if (isBelowMainPlatform) { // Force move to center platform to avoid falling ai3Action = 'moveToCenter'; ai3TargetX = 1648; // Main platform center X } else { // Find closest target var closestTarget_3 = findClosestTarget(player3); var closestDistance_3 = closestTarget_3.distance; if (ai3Cooldown === 0 && closestTarget_3.player) { if (closestDistance_3 < 200 && Math.random() < 0.7) { ai3Action = 'attack'; ai3Cooldown = 70; } else if (closestTarget_3.samePlatform) { // Same platform enemy detected - move towards closest enemy on same platform ai3Action = 'moveToPlayer'; ai3TargetX = closestTarget_3.player.x; } else if (!closestTarget_3.samePlatform && closestTarget_3.player) { // Different platform - jump towards target ai3Action = 'jumpToPlayer'; ai3TargetX = closestTarget_3.player.x; } else if (Math.random() < 0.3) { ai3Action = 'randomMove'; ai3TargetX = 1200 + Math.random() * 900; } else { ai3Action = 'idle'; } } else if (!closestTarget_3.player) { ai3Action = 'idle'; } } } if (ai3Action === 'attack' && closestTarget_3.player) { player3.attack(closestTarget_3.player.x, closestTarget_3.player.y); ai3Action = 'idle'; } else if (ai3Action === 'moveToPlayer' || ai3Action === 'randomMove' || ai3Action === 'jumpToPlayer' || ai3Action === 'moveToCenter') { var deltaX_3 = ai3TargetX - player3.x; if (Math.abs(deltaX_3) > 50) { player3.velocityX += deltaX_3 * 0.22; } // Enhanced jumping logic for different platforms if (ai3Action === 'jumpToPlayer' && closestTarget_3 && closestTarget_3.player) { // Jump towards player on different platform if (player3.jumpsRemaining > 0 && player3.onGround) { player3.velocityY = -player3.jumpPower; player3.jumpsRemaining--; player3.onGround = false; LK.getSound('jump').play(); // Add horizontal velocity towards target player3.velocityX += deltaX_3 * 0.18; } } else if (ai3Action === 'moveToCenter') { // Jump upward aggressively to get back to main platform if (player3.jumpsRemaining > 0) { player3.velocityY = -player3.jumpPower; player3.jumpsRemaining--; player3.onGround = false; LK.getSound('jump').play(); } } else if (Math.abs(deltaX_3) > 200 && player3.onGround && Math.random() < 0.15) { if (player3.jumpsRemaining > 0) { player3.velocityY = -player3.jumpPower; player3.jumpsRemaining--; player3.onGround = false; LK.getSound('jump').play(); } } if (Math.abs(deltaX_3) < 100) { ai3Action = 'idle'; } } if (player3.y > 2500 && player3.jumpsRemaining > 0) { player3.velocityY = -player3.jumpPower; player3.jumpsRemaining--; player3.onGround = false; LK.getSound('jump').play(); } } // AI for player 4 (only if alive) if (player4Lives > 0) { ai4DecisionTimer++; if (ai4Cooldown > 0) { ai4Cooldown--; } if (ai4DecisionTimer > 35) { ai4DecisionTimer = 0; // Check if AI is below main platform and needs to move to center var mainPlatformY = 2400; // Main platform Y position var isBelowMainPlatform = player4.y > mainPlatformY + 200; if (isBelowMainPlatform) { // Force move to center platform to avoid falling ai4Action = 'moveToCenter'; ai4TargetX = 1648; // Main platform center X } else { // Find closest target var closestTarget_4 = findClosestTarget(player4); var closestDistance_4 = closestTarget_4.distance; if (ai4Cooldown === 0 && closestTarget_4.player) { if (closestDistance_4 < 180 && Math.random() < 0.85) { ai4Action = 'attack'; ai4Cooldown = 50; } else if (closestTarget_4.samePlatform) { // Same platform enemy detected - move towards closest enemy on same platform aggressively ai4Action = 'moveToPlayer'; ai4TargetX = closestTarget_4.player.x; } else if (!closestTarget_4.samePlatform && closestTarget_4.player) { // Different platform - jump towards target ai4Action = 'jumpToPlayer'; ai4TargetX = closestTarget_4.player.x; } else if (Math.random() < 0.4) { ai4Action = 'randomMove'; ai4TargetX = 1200 + Math.random() * 900; } else { ai4Action = 'idle'; } } else if (!closestTarget_4.player) { ai4Action = 'idle'; } } } if (ai4Action === 'attack' && closestTarget_4.player) { player4.attack(closestTarget_4.player.x, closestTarget_4.player.y); ai4Action = 'idle'; } else if (ai4Action === 'moveToPlayer' || ai4Action === 'randomMove' || ai4Action === 'jumpToPlayer' || ai4Action === 'moveToCenter') { var deltaX_4 = ai4TargetX - player4.x; if (Math.abs(deltaX_4) > 50) { player4.velocityX += deltaX_4 * 0.28; } // Enhanced jumping logic for different platforms if (ai4Action === 'jumpToPlayer' && closestTarget_4 && closestTarget_4.player) { // Jump towards player on different platform if (player4.jumpsRemaining > 0 && player4.onGround) { player4.velocityY = -player4.jumpPower; player4.jumpsRemaining--; player4.onGround = false; LK.getSound('jump').play(); // Add horizontal velocity towards target player4.velocityX += deltaX_4 * 0.2; } } else if (ai4Action === 'moveToCenter') { // Jump upward aggressively to get back to main platform if (player4.jumpsRemaining > 0) { player4.velocityY = -player4.jumpPower; player4.jumpsRemaining--; player4.onGround = false; LK.getSound('jump').play(); } } else if (Math.abs(deltaX_4) > 150 && player4.onGround && Math.random() < 0.2) { if (player4.jumpsRemaining > 0) { player4.velocityY = -player4.jumpPower; player4.jumpsRemaining--; player4.onGround = false; LK.getSound('jump').play(); } } if (Math.abs(deltaX_4) < 100) { ai4Action = 'idle'; } } if (player4.y > 2500 && player4.jumpsRemaining > 0) { player4.velocityY = -player4.jumpPower; player4.jumpsRemaining--; player4.onGround = false; LK.getSound('jump').play(); } } // Update all fighters for (var i = fighters.length - 1; i >= 0; i--) { if (fighters[i] && !fighters[i].destroyed) { fighters[i].update(); } } // Update all powerups for (var i = powerups.length - 1; i >= 0; i--) { if (powerups[i] && !powerups[i].destroyed) { powerups[i].update(); } } // Update all wheel powerups for (var i = wheelPowerups.length - 1; i >= 0; i--) { if (wheelPowerups[i] && !wheelPowerups[i].destroyed) { wheelPowerups[i].update(); } } // Update all ally powerups for (var i = allyPowerups.length - 1; i >= 0; i--) { if (allyPowerups[i] && !allyPowerups[i].destroyed) { allyPowerups[i].update(); } } // Update all allies for (var i = allies.length - 1; i >= 0; i--) { if (allies[i] && !allies[i].destroyed) { allies[i].update(); } } // Update all smoke trails for (var i = smokeTrails.length - 1; i >= 0; i--) { if (smokeTrails[i] && !smokeTrails[i].destroyed) { smokeTrails[i].update(); } } // Update all fireflies for (var i = fireflies.length - 1; i >= 0; i--) { if (fireflies[i] && !fireflies[i].destroyed) { fireflies[i].update(); } } // Update all rain drops for (var i = rainDrops.length - 1; i >= 0; i--) { if (rainDrops[i] && !rainDrops[i].destroyed) { rainDrops[i].update(); } } // Update all lightning bolts for (var i = lightningBolts.length - 1; i >= 0; i--) { if (lightningBolts[i] && !lightningBolts[i].destroyed) { lightningBolts[i].update(); } } // Update all flowers for (var i = flowers.length - 1; i >= 0; i--) { if (flowers[i] && !flowers[i].destroyed) { flowers[i].update(); } } // Update all paint drops for (var i = paintDrops.length - 1; i >= 0; i--) { if (paintDrops[i] && !paintDrops[i].destroyed) { paintDrops[i].update(); } } // Update camera every 10 frames for smooth performance cameraUpdateTimer++; if (cameraUpdateTimer >= 10) { cameraUpdateTimer = 0; updateCamera(); } // Storm mode cycle - triggers once at random time if (!stormHasTriggered && !isNightMode) { // Only start storm if night mode is not active stormModeTimer++; // Only increment timer when night mode is not active if (stormModeTimer >= stormStartTime) { stormHasTriggered = true; isStormMode = true; // Start storm mode with dramatic darkening tween(stormOverlay, { alpha: 0.6 }, { duration: 1500, // 1.5 seconds to darken quickly easing: tween.easeInOut }); // Flash screen effect to simulate lightning LK.effects.flashScreen(0xffffff, 300); // End storm mode after 20 seconds LK.setTimeout(function () { isStormMode = false; tween(stormOverlay, { alpha: 0 }, { duration: 2000, // 2 seconds to clear easing: tween.easeOut }); // Spawn flowers where rain drops fell most frequently var flowerSpawnAreas = []; var gridSize = 150; // Size of each grid cell for density calculation // Create density map based on rain drop locations for (var r = 0; r < rainDropLocations.length; r++) { var location = rainDropLocations[r]; var gridX = Math.floor(location.x / gridSize); var gridY = Math.floor(location.y / gridSize); var gridKey = gridX + "," + gridY; if (!flowerSpawnAreas[gridKey]) { flowerSpawnAreas[gridKey] = { count: 0, totalX: 0, totalY: 0, avgX: 0, avgY: 0 }; } flowerSpawnAreas[gridKey].count++; flowerSpawnAreas[gridKey].totalX += location.x; flowerSpawnAreas[gridKey].totalY += location.y; } // Calculate average positions and spawn flowers in high-density areas for (var key in flowerSpawnAreas) { var area = flowerSpawnAreas[key]; if (area.count >= 5) { // Minimum 5 rain drops to spawn a flower area.avgX = area.totalX / area.count; area.avgY = area.totalY / area.count; // Spawn multiple flowers based on density var flowerCount = Math.min(Math.floor(area.count / 3), 4); // 1 flower per 3 rain drops, max 4 for (var f = 0; f < flowerCount; f++) { var flower = game.addChild(new Flower(area.avgX + (Math.random() - 0.5) * 80, // Small random offset area.avgY + (Math.random() - 0.5) * 40)); // Position flower behind players but in front of background elements // Count background elements (background, backgroundOverlay, backgroundTop) = 3 var backgroundElementCount = 3; var platformCount = 4; // mainPlatform, leftPlatform, rightPlatform, topPlatform var textureCount = 4; // platform textures var overlayCount = 2; // nightOverlay, stormOverlay var totalBackgroundElements = backgroundElementCount + platformCount + textureCount + overlayCount; game.setChildIndex(flower, totalBackgroundElements); flowers.push(flower); } } } // Clear rain drop tracking for next storm rainDropLocations = []; // Clear all rain and lightning when storm ends for (var r = rainDrops.length - 1; r >= 0; r--) { if (rainDrops[r]) { rainDrops[r].destroy(); rainDrops.splice(r, 1); } } for (var l = lightningBolts.length - 1; l >= 0; l--) { if (lightningBolts[l]) { lightningBolts[l].destroy(); lightningBolts.splice(l, 1); } } }, 20000); // 20 seconds duration } } // Storm rain and lightning effects if (isStormMode) { // Fixed rain frequency - no increase over time var rainFrequency = 3; // Spawn rain every 3 frames // Spawn rain drops at fixed rate if (LK.ticks % rainFrequency === 0) { var rainDrop = game.addChild(new RainDrop()); // Center rain drop spawn around main platform (1648 is main platform X center) var mainPlatformX = 1648; var spawnRadius = 800; // Spawn radius around main platform // Calculate camera movement to spawn rain ahead of camera direction var cameraDeltaX = targetCameraX - cameraX; var cameraDeltaY = targetCameraY - cameraY; // Base spawn area centered on main platform var baseSpawnX = mainPlatformX - spawnRadius / 2 + Math.random() * spawnRadius; var baseSpawnY = cameraY - 600; // Offset spawn position based on camera movement direction var movementMultiplier = 2.0; // How much to lead the camera movement rainDrop.x = baseSpawnX + cameraDeltaX * movementMultiplier; rainDrop.y = baseSpawnY + cameraDeltaY * movementMultiplier * 0.5; // Ensure rain spawns within bounds centered around main platform var minX = mainPlatformX - spawnRadius; var maxX = mainPlatformX + spawnRadius; if (rainDrop.x < minX) { rainDrop.x = minX; } if (rainDrop.x > maxX) { rainDrop.x = maxX; } if (rainDrop.y > -50) { rainDrop.y = -50; } rainDrops.push(rainDrop); } // Random lightning strikes if (Math.random() < 0.005) { // 0.5% chance per frame for lightning var lightning = game.addChild(new Lightning()); lightning.x = 200 + Math.random() * 1648; // Random X position lightning.y = 0; // Start at top of screen lightningBolts.push(lightning); // Flash screen white for lightning effect LK.effects.flashScreen(0xffffff, 200); } } // Night mode cycle if (!isStormMode) { // Only increment timer when storm mode is not active nightModeTimer++; } if (nightModeTimer >= nightModeDuration && !isStormMode) { // Only proceed if storm mode is not active nightModeTimer = 0; // Reset duration for next cycle (20-30 seconds) nightModeDuration = 1200 + Math.random() * 600; if (!isNightMode) { // Start night mode - darken slowly isNightMode = true; tween(nightOverlay, { alpha: 0.75 }, { duration: 3000, // 3 seconds to darken easing: tween.easeInOut }); // Spawn fireflies when night mode starts - they appear from outside screen and move in for (var f = 0; f < 5; f++) { var firefly = game.addChild(new Firefly()); // Start fireflies outside the visible screen area var side = Math.floor(Math.random() * 4); // 0=top, 1=right, 2=bottom, 3=left var startX, startY, targetX, targetY; if (side === 0) { // From top startX = Math.random() * 2048; startY = -200; targetX = startX + (Math.random() - 0.5) * 400; // Some horizontal drift targetY = 200 + Math.random() * 1000; } else if (side === 1) { // From right startX = 2248; startY = Math.random() * 2732; targetX = 1400 + Math.random() * 600; targetY = startY + (Math.random() - 0.5) * 400; // Some vertical drift } else if (side === 2) { // From bottom startX = Math.random() * 2048; startY = 2932; targetX = startX + (Math.random() - 0.5) * 400; // Some horizontal drift targetY = 1800 + Math.random() * 800; } else { // From left startX = -200; startY = Math.random() * 2732; targetX = 200 + Math.random() * 600; targetY = startY + (Math.random() - 0.5) * 400; // Some vertical drift } // Ensure target positions are within reasonable scene bounds if (targetX < 100) { targetX = 100; } if (targetX > 1948) { targetX = 1948; } if (targetY < 200) { targetY = 200; } if (targetY > 2500) { targetY = 2500; } // Set starting position firefly.x = startX; firefly.y = startY; // Animate firefly moving into the scene var duration = 2000 + Math.random() * 3000; // 2-5 seconds to enter tween(firefly, { x: targetX, y: targetY }, { duration: duration, easing: tween.easeOut }); fireflies.push(firefly); } } else { // End night mode - brighten slowly isNightMode = false; tween(nightOverlay, { alpha: 0 }, { duration: 2000, // 2 seconds to brighten easing: tween.easeInOut }); // Fade out all fireflies when night mode ends for (var f = fireflies.length - 1; f >= 0; f--) { if (fireflies[f]) { var firefly = fireflies[f]; tween(firefly, { alpha: 0 }, { duration: 2000, easing: tween.easeOut, onFinish: function onFinish() { firefly.destroy(); // Remove from array for (var i = fireflies.length - 1; i >= 0; i--) { if (fireflies[i] === firefly) { fireflies.splice(i, 1); break; } } } }); } } } } // Keep dynamic overlays on top of all elements if (dynamicOverlay && dynamicOverlay.parent) { var topIndex = game.children.length - 1; if (game.getChildIndex(dynamicOverlay) !== topIndex) { game.setChildIndex(dynamicOverlay, topIndex); } } // Keep second dynamic overlay on top as well if (dynamicOverlay2 && dynamicOverlay2.parent) { var topIndex2 = game.children.length - 1; if (game.getChildIndex(dynamicOverlay2) !== topIndex2 - 1) { // Position just below first overlay game.setChildIndex(dynamicOverlay2, topIndex2 - 1); } } // Apply smooth camera movement applyCameraSmoothing(); };
/****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
/****
* Classes
****/
var Ally = Container.expand(function (ownerPlayerNumber, spawnX, spawnY) {
var self = Container.call(this);
// Use owner's asset but with different tint to distinguish
var assetName = 'player1';
if (ownerPlayerNumber === 2) {
assetName = 'player2';
} else if (ownerPlayerNumber === 3) {
assetName = 'player3';
} else if (ownerPlayerNumber === 4) {
assetName = 'player4';
}
var allyGraphics = self.attachAsset(assetName, {
anchorX: 0.5,
anchorY: 1.0
});
// Tint ally to make it distinguishable (golden tint)
allyGraphics.tint = 0xffdd00;
allyGraphics.alpha = 0.8;
self.ownerPlayerNumber = ownerPlayerNumber;
self.velocityX = 0;
self.velocityY = 0;
self.onGround = false;
self.jumpsRemaining = 2;
self.moveSpeed = 6; // Slightly slower than players
self.jumpPower = 20;
self.lifeTime = 0;
self.maxLifeTime = 1800; // 30 seconds at 60fps
self.attackCooldown = 0;
self.targetEnemy = null;
self.lastTargetScanTime = 0;
// Spawn position
self.x = spawnX;
self.y = spawnY;
self.findNearestEnemy = function () {
var nearestEnemy = null;
var nearestDistance = Infinity;
for (var i = 0; i < fighters.length; i++) {
var fighter = fighters[i];
if (!fighter || fighter.destroyed) {
continue;
}
if (fighter.playerNumber === self.ownerPlayerNumber) {
continue;
} // Don't target owner
// Check if fighter is alive
var isAlive = false;
if (fighter.playerNumber === 1 && player1Lives > 0) {
isAlive = true;
} else if (fighter.playerNumber === 2 && player2Lives > 0) {
isAlive = true;
} else if (fighter.playerNumber === 3 && player3Lives > 0) {
isAlive = true;
} else if (fighter.playerNumber === 4 && player4Lives > 0) {
isAlive = true;
}
if (!isAlive) {
continue;
}
var distance = Math.sqrt(Math.pow(fighter.x - self.x, 2) + Math.pow(fighter.y - self.y, 2));
if (distance < nearestDistance) {
nearestDistance = distance;
nearestEnemy = fighter;
}
}
return nearestEnemy;
};
self.attack = function (targetX, targetY) {
var attackRange = 120;
var damage = 12; // Lower damage than players
var knockback = 15;
for (var i = 0; i < fighters.length; i++) {
var target = fighters[i];
if (!target || target.destroyed) {
continue;
}
if (target.playerNumber === self.ownerPlayerNumber) {
continue;
} // Don't attack owner
var distance = Math.sqrt(Math.pow(target.x - self.x, 2) + Math.pow(target.y - self.y, 2));
if (distance <= attackRange) {
var angle = Math.atan2(target.y - self.y, target.x - self.x);
var knockbackX = Math.cos(angle) * knockback;
var knockbackY = Math.sin(angle) * knockback - 3;
target.takeDamage(damage, knockbackX, knockbackY);
}
}
};
self.update = function () {
self.lifeTime++;
self.attackCooldown--;
if (self.attackCooldown < 0) {
self.attackCooldown = 0;
}
// Fade out as ally approaches end of life
if (self.lifeTime >= self.maxLifeTime - 300) {
var fadeProgress = (self.lifeTime - (self.maxLifeTime - 300)) / 300;
self.alpha = 0.8 * (1 - fadeProgress);
}
// Remove ally when lifetime expires
if (self.lifeTime >= self.maxLifeTime) {
self.destroy();
for (var j = allies.length - 1; j >= 0; j--) {
if (allies[j] === self) {
allies.splice(j, 1);
break;
}
}
return;
}
// Scan for enemies every 30 frames
if (LK.ticks - self.lastTargetScanTime > 30) {
self.targetEnemy = self.findNearestEnemy();
self.lastTargetScanTime = LK.ticks;
}
// AI behavior - follow and attack enemies
if (self.targetEnemy && !self.targetEnemy.destroyed) {
var distanceToTarget = Math.sqrt(Math.pow(self.targetEnemy.x - self.x, 2) + Math.pow(self.targetEnemy.y - self.y, 2));
// Attack if close enough and cooldown is over
if (distanceToTarget < 150 && self.attackCooldown === 0) {
self.attack(self.targetEnemy.x, self.targetEnemy.y);
self.attackCooldown = 90; // 1.5 second cooldown
}
// Move toward target
else {
var deltaX = self.targetEnemy.x - self.x;
var deltaY = self.targetEnemy.y - self.y;
if (Math.abs(deltaX) > 60) {
self.velocityX += deltaX * 0.15;
}
// Jump if target is above
if (deltaY < -80 && self.jumpsRemaining > 0 && self.onGround) {
self.velocityY = -self.jumpPower;
self.jumpsRemaining--;
self.onGround = false;
LK.getSound('jump').play();
}
}
}
// Apply gravity
if (!self.onGround) {
self.velocityY += 1.2;
}
// Apply friction
self.velocityX *= 0.85;
if (self.onGround) {
self.velocityX *= 0.75;
}
// Limit velocities
var maxVelocityX = 15;
var maxVelocityY = 25;
if (self.velocityX > maxVelocityX) {
self.velocityX = maxVelocityX;
}
if (self.velocityX < -maxVelocityX) {
self.velocityX = -maxVelocityX;
}
if (self.velocityY > maxVelocityY) {
self.velocityY = maxVelocityY;
}
if (self.velocityY < -maxVelocityY) {
self.velocityY = -maxVelocityY;
}
// Update position
self.x += self.velocityX;
self.y += self.velocityY;
// Handle sprite direction
if (Math.abs(self.velocityX) > 1) {
if (self.velocityX > 0) {
allyGraphics.scaleX = Math.abs(allyGraphics.scaleX);
} else {
allyGraphics.scaleX = -Math.abs(allyGraphics.scaleX);
}
}
// Platform collision (same as Fighter class)
var wasOnGround = self.onGround;
self.onGround = false;
for (var i = 0; i < platforms.length; i++) {
var platform = platforms[i];
var platformLeft = platform.x - platform.width / 2 + 10;
var platformRight = platform.x + platform.width / 2 - 10;
var platformTop = platform.y - platform.height / 2;
var platformBottom = platform.y + platform.height / 2;
if (self.x >= platformLeft && self.x <= platformRight && self.y >= platformTop - 20 && self.y <= platformBottom + 5 && self.velocityY >= 0) {
self.y = platformTop;
self.velocityY = 0;
if (!wasOnGround) {
LK.getSound('landing').play();
}
self.onGround = true;
self.jumpsRemaining = 2;
break;
}
}
// Check if fallen off screen
if (self.y > 2900) {
self.destroy();
for (var j = allies.length - 1; j >= 0; j--) {
if (allies[j] === self) {
allies.splice(j, 1);
break;
}
}
return;
}
// Screen boundaries
if (self.x < 60) {
self.x = 60;
self.velocityX = Math.abs(self.velocityX) * 0.5;
}
if (self.x > 2700) {
self.x = 2700;
self.velocityX = -Math.abs(self.velocityX) * 0.5;
}
};
return self;
});
var AllyPowerUp = Container.expand(function () {
var self = Container.call(this);
var allyPowerupGraphics = self.attachAsset('allyPowerup', {
anchorX: 0.5,
anchorY: 0.5
});
// Tint powerup golden to distinguish from other powerups
allyPowerupGraphics.tint = 0xffdd00;
self.lifeTime = 0;
self.maxLifeTime = 600; // 10 seconds at 60fps
self.collected = false;
self.velocityY = 0; // Falling velocity
self.onGround = false; // Track if powerup has landed
self.update = function () {
self.lifeTime++;
self.rotation += 0.05;
// Apply gravity if not on ground
if (!self.onGround) {
self.velocityY += 0.8; // Gravity effect
self.y += self.velocityY;
// Check platform collision for landing
for (var i = 0; i < platforms.length; i++) {
var platform = platforms[i];
var platformLeft = platform.x - platform.width / 2;
var platformRight = platform.x + platform.width / 2;
var platformTop = platform.y - platform.height / 2;
var platformBottom = platform.y + platform.height / 2;
// Check if powerup is within platform bounds and landing on top
if (self.x >= platformLeft && self.x <= platformRight && self.y >= platformTop - 30 && self.y <= platformBottom && self.velocityY >= 0) {
self.y = platformTop - 30; // Position on top of platform
self.velocityY = 0;
self.onGround = true;
break;
}
}
// Remove if fallen off screen
if (self.y > 2800) {
self.destroy();
for (var j = allyPowerups.length - 1; j >= 0; j--) {
if (allyPowerups[j] === self) {
allyPowerups.splice(j, 1);
break;
}
}
return;
}
} else {
// Bounce effect when on ground
self.y += Math.sin(self.lifeTime * 0.1) * 0.5;
}
if (self.lifeTime > self.maxLifeTime) {
self.destroy();
for (var i = allyPowerups.length - 1; i >= 0; i--) {
if (allyPowerups[i] === self) {
allyPowerups.splice(i, 1);
break;
}
}
}
// Check collection by players
if (!self.collected) {
for (var i = 0; i < fighters.length; i++) {
var fighter = fighters[i];
var distance = Math.sqrt(Math.pow(fighter.x - self.x, 2) + Math.pow(fighter.y - self.y, 2));
if (distance < 80) {
self.collected = true;
// Create ally for the player who collected it
var ally = new Ally(fighter.playerNumber, self.x + 100, self.y);
game.addChild(ally);
allies.push(ally);
// Visual effects for powerup collection
LK.getSound('powerup').play();
LK.effects.flashObject(fighter, 0xffdd00, 800);
// Create magical spawning effect with tween
tween(ally, {
scaleX: 1.0,
scaleY: 1.0,
alpha: 1.0
}, {
duration: 500,
easing: tween.bounceOut
});
// Start ally small and fade in
ally.scaleX = 0.1;
ally.scaleY = 0.1;
ally.alpha = 0.3;
self.destroy();
for (var j = allyPowerups.length - 1; j >= 0; j--) {
if (allyPowerups[j] === self) {
allyPowerups.splice(j, 1);
break;
}
}
break;
}
}
}
};
return self;
});
var Fighter = Container.expand(function (playerNumber, color) {
var self = Container.call(this);
var assetName = 'player1';
if (playerNumber === 2) {
assetName = 'player2';
} else if (playerNumber === 3) {
assetName = 'player3';
} else if (playerNumber === 4) {
assetName = 'player4';
}
var fighterGraphics = self.attachAsset(assetName, {
anchorX: 0.5,
anchorY: 1.0
});
self.playerNumber = playerNumber;
self.velocityX = 0;
self.velocityY = 0;
self.onGround = false;
self.jumpsRemaining = 2; // Allow double jump
self.health = 0;
self.maxHealth = 100;
self.damage = 0; // New damage accumulation property
self.moveSpeed = 8;
self.jumpPower = 25;
self.knockbackResistance = 1.0;
self.lastHitTime = 0;
self.invulnerable = false;
self.powerupCount = 0; // Track number of powerups collected
self.baseMoveSpeed = 8; // Store original move speed
self.baseJumpPower = 25; // Store original jump power
self.baseDamage = 15; // Store original damage
self.baseKnockback = 20; // Store original knockback
self.lastVelocityX = 0; // Track last velocity for rotation
self.isWheelMode = false; // Track if in wheel mode
self.wheelModeTimer = 0; // Timer for wheel mode duration
self.wheelGraphics = null; // Reference to wheel graphics
// Create damage bar background
var damageBarBg = self.addChild(LK.getAsset('platform', {
width: 100,
height: 12,
anchorX: 0.5,
anchorY: 1.0,
x: 0,
y: -130
}));
damageBarBg.tint = 0x333333;
damageBarBg.scaleX = 0.8;
damageBarBg.scaleY = 0.3;
// Create damage bar fill
self.damageBar = self.addChild(LK.getAsset('platform', {
width: 100,
height: 12,
anchorX: 0,
anchorY: 1.0,
x: -40,
y: -130
}));
self.damageBar.tint = 0xff4444;
self.damageBar.scaleX = 0;
self.damageBar.scaleY = 0.3;
// Create damage text
self.damageText = self.addChild(new Text2('0%', {
size: 30,
fill: 0xFFFFFF,
stroke: 0x000000,
strokeThickness: 4
}));
self.damageText.anchor.set(0.5, 1);
self.damageText.x = 0;
self.damageText.y = -140;
self.takeDamage = function (damage, knockbackX, knockbackY) {
if (self.invulnerable) {
return;
}
self.health += damage;
self.damage += damage; // Accumulate damage for knockback scaling
// Cap damage at 999%
if (self.damage > 999) {
self.damage = 999;
}
// Update damage display
self.damageText.setText(Math.floor(self.damage) + '%');
// Reapply stroke properties to maintain black background texture
if (self.damageText.style) {
self.damageText.style.stroke = 0x000000;
self.damageText.style.strokeThickness = 4;
}
// Update damage bar scale (0 to 1 based on damage up to 200%)
var barScale = Math.min(self.damage / 200, 1);
self.damageBar.scaleX = barScale * 0.8;
// Change bar color based on damage level
if (self.damage < 50) {
self.damageBar.tint = 0x44ff44; // Green for low damage
} else if (self.damage < 100) {
self.damageBar.tint = 0xffff44; // Yellow for medium damage
} else if (self.damage < 150) {
self.damageBar.tint = 0xff8844; // Orange for high damage
} else {
self.damageBar.tint = 0xff4444; // Red for very high damage
}
// Check if player has reached 200 damage - explosion and life loss
if (self.damage >= 200) {
// Create paint explosion effect
var explosionCenter = {
x: self.x,
y: self.y
};
var numberOfDrops = 15 + Math.random() * 10; // 15-25 paint drops
for (var i = 0; i < numberOfDrops; i++) {
// Create circular spread pattern
var angle = i / numberOfDrops * Math.PI * 2 + (Math.random() - 0.5) * 0.5;
var speed = 8 + Math.random() * 12; // Random speed for variety
var velocityX = Math.cos(angle) * speed;
var velocityY = Math.sin(angle) * speed - 5; // Slight upward bias
// Create paint drop
var paintDrop = new PaintDrop(self.playerNumber, explosionCenter.x, explosionCenter.y, velocityX, velocityY);
game.addChild(paintDrop);
paintDrops.push(paintDrop);
}
// Create explosion effect with tween
tween(self, {
scaleX: 2.0,
scaleY: 2.0,
alpha: 0
}, {
duration: 500,
easing: tween.bounceOut,
onFinish: function onFinish() {
// Reset scale and alpha after explosion
self.scaleX = 1.0;
self.scaleY = 1.0;
self.alpha = 1.0;
// Trigger respawn which handles life loss
self.respawn();
}
});
// Flash screen effect for dramatic explosion
LK.effects.flashScreen(0xff8800, 600);
// Play explosion sound effect
LK.getSound('explosion').play();
// Reset damage to prevent multiple explosions
self.damage = 0;
return; // Exit early to prevent further processing this frame
}
// Knockback multiplier based on accumulated damage - exponential scaling for stronger hits at higher damage
var knockbackMultiplier = 1.5 + self.damage / 20 + Math.pow(self.damage / 100, 1.5); // Exponential scaling that increases dramatically with damage
self.velocityX += knockbackX * knockbackMultiplier / self.knockbackResistance;
self.velocityY += knockbackY * knockbackMultiplier / self.knockbackResistance;
// Flash effect when hit
LK.effects.flashObject(self, 0xff0000, 300);
self.invulnerable = true;
LK.setTimeout(function () {
self.invulnerable = false;
}, 500);
LK.getSound('hit').play();
};
self.attack = function (targetX, targetY) {
var attackRange = 150;
var damage = self.baseDamage + self.powerupCount * 8; // Increase damage by 8 per powerup
var baseKnockback = self.baseKnockback + self.powerupCount * 10; // Increase knockback by 10 per powerup
for (var i = 0; i < fighters.length; i++) {
var target = fighters[i];
if (target === self) {
continue;
}
var distance = Math.sqrt(Math.pow(target.x - self.x, 2) + Math.pow(target.y - self.y, 2));
if (distance <= attackRange) {
var angle = Math.atan2(target.y - self.y, target.x - self.x);
var knockbackX = Math.cos(angle) * baseKnockback;
var knockbackY = Math.sin(angle) * baseKnockback - 5; // Slight upward angle
target.takeDamage(damage, knockbackX, knockbackY);
}
}
};
self.update = function () {
// Handle wheel mode timer
if (self.isWheelMode) {
self.wheelModeTimer--;
if (self.wheelModeTimer <= 0) {
self.deactivateWheelMode();
}
}
// Update movement stats based on powerups
self.moveSpeed = self.baseMoveSpeed + self.powerupCount * 3; // Increase move speed by 3 per powerup
self.jumpPower = self.baseJumpPower + self.powerupCount * 5; // Increase jump power by 5 per powerup
// Apply wheel mode bonuses
if (self.isWheelMode) {
self.moveSpeed *= 1.5;
self.jumpPower *= 1.3;
}
// Apply gravity
if (!self.onGround) {
self.velocityY += 1.2;
}
// Apply friction
self.velocityX *= 0.85;
if (self.onGround) {
self.velocityX *= 0.75;
}
// Limit velocities to prevent teleportation
var maxVelocityX = 20;
var maxVelocityY = 30;
if (self.velocityX > maxVelocityX) {
self.velocityX = maxVelocityX;
}
if (self.velocityX < -maxVelocityX) {
self.velocityX = -maxVelocityX;
}
if (self.velocityY > maxVelocityY) {
self.velocityY = maxVelocityY;
}
if (self.velocityY < -maxVelocityY) {
self.velocityY = -maxVelocityY;
}
// Track last position for smoke trail
if (self.lastX === undefined) {
self.lastX = self.x;
}
if (self.lastY === undefined) {
self.lastY = self.y;
}
// Update position
self.x += self.velocityX;
self.y += self.velocityY;
// Generate smoke trail when moving
var movementSpeed = Math.sqrt(Math.pow(self.x - self.lastX, 2) + Math.pow(self.y - self.lastY, 2));
if (movementSpeed > 3 && LK.ticks % 3 === 0) {
// Calculate movement direction
var movementDirectionX = self.x - self.lastX;
var movementDirectionY = self.y - self.lastY;
// Normalize the direction
var directionLength = Math.sqrt(movementDirectionX * movementDirectionX + movementDirectionY * movementDirectionY);
if (directionLength > 0) {
movementDirectionX /= directionLength;
movementDirectionY /= directionLength;
}
// Position smoke trail behind the movement direction
var smokeDistance = 30; // Distance behind the player
var smokeX = self.x - movementDirectionX * smokeDistance;
var smokeY = self.y - movementDirectionY * smokeDistance + 20;
var smoke = new SmokeTrail(self.playerNumber, smokeX, smokeY);
game.addChild(smoke);
smokeTrails.push(smoke);
}
// Update last position
self.lastX = self.x;
self.lastY = self.y;
// Update visual size based on powerup count
var targetScale = 1.0 + self.powerupCount * 0.2; // Increase size by 20% per powerup
if (Math.abs(fighterGraphics.scaleX) !== targetScale) {
var isFlipped = fighterGraphics.scaleX < 0;
fighterGraphics.scaleX = isFlipped ? -targetScale : targetScale;
fighterGraphics.scaleY = targetScale;
}
// Handle wheel mode rotation and physics
if (self.isWheelMode && self.wheelGraphics) {
// Rotate wheel image based on movement direction only
if (Math.abs(self.velocityX) > 1) {
self.wheelGraphics.rotation += self.velocityX * 0.1;
}
// Continuous bounce effect when hitting ground - jump every time touching ground
if (self.onGround) {
self.velocityY = -self.jumpPower; // Jump with same height as normal jump
}
// Enhanced friction for wheel mode
if (self.onGround) {
self.velocityX *= 0.95; // More responsive control
}
} else {
// Rotate player based on movement direction (normal mode)
if (Math.abs(self.velocityX) > 1) {
// Only rotate if moving with significant velocity
if (self.velocityX > 0) {
// Moving right - face right (normal orientation)
fighterGraphics.scaleX = Math.abs(fighterGraphics.scaleX);
} else {
// Moving left - face left (flip horizontally)
fighterGraphics.scaleX = -Math.abs(fighterGraphics.scaleX);
}
}
}
// Platform collision
var wasOnGround = self.onGround;
self.onGround = false;
for (var i = 0; i < platforms.length; i++) {
var platform = platforms[i];
// More precise collision detection with tighter bounds
var platformLeft = platform.x - platform.width / 2 + 10; // Add small margin
var platformRight = platform.x + platform.width / 2 - 10; // Add small margin
var platformTop = platform.y - platform.height / 2;
var platformBottom = platform.y + platform.height / 2;
// Check if player is within platform horizontal bounds and approaching from above
if (self.x >= platformLeft && self.x <= platformRight && self.y >= platformTop - 20 && self.y <= platformBottom + 5 && self.velocityY >= 0) {
// Adjust ground position for wheel mode to prevent clipping
var groundOffset = self.isWheelMode ? -60 : 0; // Offset wheel players higher to prevent clipping
// Snap player to exact platform surface
self.y = platformTop + groundOffset;
self.velocityY = 0;
// Play landing sound if just landed (wasn't on ground before)
if (!wasOnGround) {
LK.getSound('landing').play();
}
self.onGround = true;
self.jumpsRemaining = 2; // Reset jumps when landing
break;
}
}
// Check if fallen off screen
if (self.y > 2900) {
// Play fall sound effect
LK.getSound('fall').play();
self.respawn();
}
// Fighter-to-fighter collision detection and pushing
for (var i = 0; i < fighters.length; i++) {
var otherFighter = fighters[i];
if (otherFighter === self || otherFighter.destroyed) {
continue;
}
// Calculate distance between fighters
var deltaX = otherFighter.x - self.x;
var deltaY = otherFighter.y - self.y;
var distance = Math.sqrt(deltaX * deltaX + deltaY * deltaY);
// Check if fighters are colliding (within collision radius)
// Use larger radius for wheel mode players
var collisionRadius = 80; // Default collision radius
if (self.isWheelMode || otherFighter.isWheelMode) {
collisionRadius = 120; // Increased radius when either player is in wheel mode
}
if (distance < collisionRadius && distance > 0) {
// Check for wheel mode damage dealing
if (self.isWheelMode && !self.lastWheelDamageTime) {
self.lastWheelDamageTime = 0;
}
if (otherFighter.isWheelMode && !otherFighter.lastWheelDamageTime) {
otherFighter.lastWheelDamageTime = 0;
}
// Initialize lastWheelDamageTime if not set
if (self.lastWheelDamageTime === undefined) {
self.lastWheelDamageTime = 0;
}
if (otherFighter.lastWheelDamageTime === undefined) {
otherFighter.lastWheelDamageTime = 0;
}
// Deal damage if current player is in wheel mode and enough time has passed
if (self.isWheelMode && LK.ticks - self.lastWheelDamageTime > 60) {
var wheelDamage = 25 + self.powerupCount * 5; // Base wheel damage + powerup bonus
var angle = Math.atan2(deltaY, deltaX);
var knockbackX = Math.cos(angle) * 15;
var knockbackY = Math.sin(angle) * 15 - 3; // Slight upward angle
otherFighter.takeDamage(wheelDamage, knockbackX, knockbackY);
self.lastWheelDamageTime = LK.ticks;
}
// Deal damage if other player is in wheel mode and enough time has passed
if (otherFighter.isWheelMode && LK.ticks - otherFighter.lastWheelDamageTime > 60) {
var wheelDamage = 25 + otherFighter.powerupCount * 5; // Base wheel damage + powerup bonus
var angle = Math.atan2(-deltaY, -deltaX);
var knockbackX = Math.cos(angle) * 15;
var knockbackY = Math.sin(angle) * 15 - 3; // Slight upward angle
self.takeDamage(wheelDamage, knockbackX, knockbackY);
otherFighter.lastWheelDamageTime = LK.ticks;
}
// Calculate push force based on overlap
var overlap = collisionRadius - distance;
var pushStrength = overlap * 0.3; // Adjust push strength
// Normalize direction vector
var normalX = deltaX / distance;
var normalY = deltaY / distance;
// Apply push forces to both fighters
var pushX = normalX * pushStrength;
var pushY = normalY * pushStrength * 0.3; // Reduced vertical push
// Push both fighters apart
self.velocityX -= pushX;
self.velocityY -= pushY;
otherFighter.velocityX += pushX;
otherFighter.velocityY += pushY;
// Separate fighters to prevent overlap
var separationX = normalX * (overlap * 0.5);
var separationY = normalY * (overlap * 0.3);
self.x -= separationX;
self.y -= separationY;
otherFighter.x += separationX;
otherFighter.y += separationY;
}
}
// Screen boundaries (horizontal bouncing)
if (self.x < 60) {
self.x = 60;
if (self.isWheelMode) {
self.velocityX = Math.abs(self.velocityX) * 0.8; // Enhanced bounce in wheel mode
} else {
self.velocityX = Math.abs(self.velocityX) * 0.5;
}
}
if (self.x > 2700) {
self.x = 2700;
if (self.isWheelMode) {
self.velocityX = -Math.abs(self.velocityX) * 0.8; // Enhanced bounce in wheel mode
} else {
self.velocityX = -Math.abs(self.velocityX) * 0.5;
}
}
};
self.respawn = function () {
// Decrease lives for the player who died
if (self.playerNumber === 1) {
player1Lives--;
} else if (self.playerNumber === 2) {
player2Lives--;
} else if (self.playerNumber === 3) {
player3Lives--;
} else if (self.playerNumber === 4) {
player4Lives--;
}
updateScoreDisplay();
// Check if player is eliminated (no lives left)
var isEliminated = false;
if (self.playerNumber === 1 && player1Lives <= 0) {
isEliminated = true;
} else if (self.playerNumber === 2 && player2Lives <= 0) {
isEliminated = true;
} else if (self.playerNumber === 3 && player3Lives <= 0) {
isEliminated = true;
} else if (self.playerNumber === 4 && player4Lives <= 0) {
isEliminated = true;
}
if (isEliminated) {
// Track if player 1 defeated an enemy
if (self.playerNumber !== 1) {
player1EnemiesDefeated++;
}
// Remove player from the game completely
self.destroy();
// Remove from fighters array
for (var j = fighters.length - 1; j >= 0; j--) {
if (fighters[j] === self) {
fighters.splice(j, 1);
break;
}
}
activePlayers--;
// Check if only one player remains
if (activePlayers <= 1) {
// Find the winner
var winner = "";
if (player1Lives > 0) {
winner = "Player 1";
} else if (player2Lives > 0) {
winner = "Player 2";
} else if (player3Lives > 0) {
winner = "Player 3";
} else if (player4Lives > 0) {
winner = "Player 4";
}
LK.setScore(player1EnemiesDefeated);
LK.showGameOver();
}
} else {
// Player still has lives, respawn normally
if (self.playerNumber === 1) {
self.x = 1048; // Respawn on left platform
self.y = 1950; // Just above left platform surface
} else if (self.playerNumber === 2) {
self.x = 1648; // Respawn on main platform
self.y = 2300; // Just above main platform surface
} else if (self.playerNumber === 3) {
self.x = 1648; // Respawn on top platform
self.y = 1600; // Just above top platform surface
} else if (self.playerNumber === 4) {
self.x = 2248; // Respawn on right platform
self.y = 1950; // Just above right platform surface
}
self.velocityX = 0;
self.velocityY = 0;
self.health = 0;
self.damage = 0; // Reset damage on respawn
self.powerupCount = 0; // Reset powerup count on respawn
self.damageText.setText('0%');
// Reapply stroke properties to maintain black background texture
if (self.damageText.style) {
self.damageText.style.stroke = 0x000000;
self.damageText.style.strokeThickness = 4;
}
self.damageBar.scaleX = 0;
self.damageBar.tint = 0x44ff44;
self.jumpsRemaining = 2; // Reset jumps on respawn
}
};
self.activateWheelMode = function () {
if (self.isWheelMode) {
return;
} // Already in wheel mode
self.isWheelMode = true;
self.wheelModeTimer = 600; // 10 seconds at 60fps
// Hide original fighter graphics
fighterGraphics.visible = false;
// Create wheel graphics based on player number
var wheelAsset = 'wheel1';
if (self.playerNumber === 2) {
wheelAsset = 'wheel2';
} else if (self.playerNumber === 3) {
wheelAsset = 'wheel3';
} else if (self.playerNumber === 4) {
wheelAsset = 'wheel4';
}
self.wheelGraphics = self.attachAsset(wheelAsset, {
anchorX: 0.5,
anchorY: 0.5
});
// Increase move speed and add bounce physics
self.moveSpeed *= 1.5;
self.jumpPower *= 1.3;
};
self.deactivateWheelMode = function () {
if (!self.isWheelMode) {
return;
}
self.isWheelMode = false;
self.wheelModeTimer = 0;
// Show original fighter graphics
fighterGraphics.visible = true;
// Remove wheel graphics
if (self.wheelGraphics) {
self.wheelGraphics.destroy();
self.wheelGraphics = null;
}
// Reset move speed
self.moveSpeed = self.baseMoveSpeed + self.powerupCount * 3;
self.jumpPower = self.baseJumpPower + self.powerupCount * 5;
};
return self;
});
var Firefly = Container.expand(function () {
var self = Container.call(this);
// Create firefly body
var fireflyBody = self.attachAsset('firefly', {
anchorX: 0.5,
anchorY: 0.5
});
// Create glow effect with 200x200 illumination radius
var fireflyGlow = self.attachAsset('fireflyGlow', {
anchorX: 0.5,
anchorY: 0.5
});
fireflyGlow.alpha = 0.15; // Soft glow effect
fireflyGlow.scaleX = 1.0;
fireflyGlow.scaleY = 1.0;
self.lifeTime = 0;
self.maxLifeTime = 2400; // 40 seconds at 60fps - longer than max night cycle
self.velocityX = (Math.random() - 0.5) * 2;
self.velocityY = (Math.random() - 0.5) * 2;
self.glowIntensity = 0.15;
self.flickerTimer = 0;
self.update = function () {
self.lifeTime++;
self.flickerTimer++;
// Gentle movement pattern
self.velocityX += (Math.random() - 0.5) * 0.3;
self.velocityY += (Math.random() - 0.5) * 0.3;
// Limit velocity for gentle floating
if (self.velocityX > 1.5) {
self.velocityX = 1.5;
}
if (self.velocityX < -1.5) {
self.velocityX = -1.5;
}
if (self.velocityY > 1.5) {
self.velocityY = 1.5;
}
if (self.velocityY < -1.5) {
self.velocityY = -1.5;
}
self.x += self.velocityX;
self.y += self.velocityY;
// Flicker effect - vary glow intensity
var flickerValue = Math.sin(self.flickerTimer * 0.2) * 0.05;
fireflyGlow.alpha = self.glowIntensity + flickerValue;
fireflyBody.alpha = 0.8 + flickerValue;
// Keep within screen bounds
if (self.x < 100) {
self.x = 100;
self.velocityX = Math.abs(self.velocityX);
}
if (self.x > 1948) {
self.x = 1948;
self.velocityX = -Math.abs(self.velocityX);
}
if (self.y < 100) {
self.y = 100;
self.velocityY = Math.abs(self.velocityY);
}
if (self.y > 2632) {
self.y = 2632;
self.velocityY = -Math.abs(self.velocityY);
}
// Fade out when approaching end of life
if (self.lifeTime >= self.maxLifeTime - 180) {
// Start fading 3 seconds before end
var fadeProgress = (self.lifeTime - (self.maxLifeTime - 180)) / 180;
fireflyGlow.alpha = (self.glowIntensity + flickerValue) * (1 - fadeProgress);
fireflyBody.alpha = (0.8 + flickerValue) * (1 - fadeProgress);
}
// Remove when life time is over
if (self.lifeTime >= self.maxLifeTime) {
self.destroy();
for (var i = fireflies.length - 1; i >= 0; i--) {
if (fireflies[i] === self) {
fireflies.splice(i, 1);
break;
}
}
}
};
return self;
});
var Flower = Container.expand(function (x, y) {
var self = Container.call(this);
// Create flower stem
var stem = self.attachAsset('flowerStem', {
anchorX: 0.5,
anchorY: 1.0
});
// Create flower bloom
var bloom = self.attachAsset('flower', {
anchorX: 0.5,
anchorY: 1.0,
y: -30
});
self.x = x;
self.y = y;
self.lifeTime = 0;
self.maxLifeTime = 3600; // 60 seconds at 60fps
self.growthPhase = 0; // 0=growing, 1=blooming, 2=swaying
// Start small and grow
stem.scaleX = 0.1;
stem.scaleY = 0.1;
bloom.scaleX = 0.1;
bloom.scaleY = 0.1;
bloom.alpha = 0;
// Growth animation
tween(stem, {
scaleX: 1.0,
scaleY: 1.0
}, {
duration: 1000,
easing: tween.easeOut
});
// Delayed bloom animation
LK.setTimeout(function () {
self.growthPhase = 1;
tween(bloom, {
scaleX: 1.2,
scaleY: 1.2,
alpha: 1.0
}, {
duration: 1500,
easing: tween.bounceOut,
onFinish: function onFinish() {
self.growthPhase = 2;
}
});
}, 800);
self.update = function () {
self.lifeTime++;
// Gentle swaying motion when fully grown
if (self.growthPhase === 2) {
var swayAmount = Math.sin(self.lifeTime * 0.05) * 0.1;
bloom.rotation = swayAmount;
stem.rotation = swayAmount * 0.3;
}
// Fade out before disappearing
if (self.lifeTime >= self.maxLifeTime - 300) {
var fadeProgress = (self.lifeTime - (self.maxLifeTime - 300)) / 300;
self.alpha = 1 - fadeProgress;
}
// Remove when life time is over
if (self.lifeTime >= self.maxLifeTime) {
self.destroy();
for (var i = flowers.length - 1; i >= 0; i--) {
if (flowers[i] === self) {
flowers.splice(i, 1);
break;
}
}
}
};
return self;
});
var Lightning = Container.expand(function () {
var self = Container.call(this);
var lightningGraphics = self.attachAsset('lightning', {
anchorX: 0.5,
anchorY: 0
});
self.lifeTime = 0;
self.maxLifeTime = 15; // Very short flash
lightningGraphics.alpha = 0.8;
self.update = function () {
self.lifeTime++;
// Flicker effect
lightningGraphics.alpha = 0.8 * Math.random();
// Remove after short time
if (self.lifeTime >= self.maxLifeTime) {
self.destroy();
for (var i = lightningBolts.length - 1; i >= 0; i--) {
if (lightningBolts[i] === self) {
lightningBolts.splice(i, 1);
break;
}
}
}
};
return self;
});
var PaintDrop = Container.expand(function (playerNumber, startX, startY, velocityX, velocityY) {
var self = Container.call(this);
// Choose paint drop asset based on player number
var assetName = 'paintDrop1';
if (playerNumber === 2) {
assetName = 'paintDrop2';
} else if (playerNumber === 3) {
assetName = 'paintDrop3';
} else if (playerNumber === 4) {
assetName = 'paintDrop4';
}
var paintGraphics = self.attachAsset(assetName, {
anchorX: 0.5,
anchorY: 0.5
});
self.x = startX;
self.y = startY;
self.velocityX = velocityX;
self.velocityY = velocityY;
self.hasLanded = false;
self.lifeTime = 0;
self.maxLifeTime = 600; // Stay visible for 10 seconds after landing
self.update = function () {
if (!self.hasLanded) {
// Apply gravity and movement
self.velocityY += 0.8; // Gravity
self.velocityX *= 0.98; // Air resistance
self.x += self.velocityX;
self.y += self.velocityY;
// Check collision with platforms
for (var i = 0; i < platforms.length; i++) {
var platform = platforms[i];
var platformLeft = platform.x - platform.width / 2;
var platformRight = platform.x + platform.width / 2;
var platformTop = platform.y - platform.height / 2;
var platformBottom = platform.y + platform.height / 2;
if (self.x >= platformLeft && self.x <= platformRight && self.y >= platformTop - 10 && self.y <= platformBottom && self.velocityY >= 0) {
// Paint drop hit platform
self.y = platformTop - 6; // Position on top of platform
self.velocityX = 0;
self.velocityY = 0;
self.hasLanded = true;
break;
}
}
// Remove if fallen off screen
if (self.y > 2900) {
self.destroy();
for (var j = paintDrops.length - 1; j >= 0; j--) {
if (paintDrops[j] === self) {
paintDrops.splice(j, 1);
break;
}
}
return;
}
} else {
// Paint drop has landed, start fade timer
self.lifeTime++;
if (self.lifeTime >= self.maxLifeTime) {
// Fade out effect
var fadeProgress = (self.lifeTime - self.maxLifeTime) / 60; // 1 second fade
paintGraphics.alpha = Math.max(0, 1 - fadeProgress);
if (fadeProgress >= 1) {
self.destroy();
for (var j = paintDrops.length - 1; j >= 0; j--) {
if (paintDrops[j] === self) {
paintDrops.splice(j, 1);
break;
}
}
}
}
}
};
return self;
});
var PowerUp = Container.expand(function () {
var self = Container.call(this);
var powerupGraphics = self.attachAsset('powerup', {
anchorX: 0.5,
anchorY: 0.5
});
self.lifeTime = 0;
self.maxLifeTime = 600; // 10 seconds at 60fps
self.collected = false;
self.velocityY = 0; // Falling velocity
self.onGround = false; // Track if powerup has landed
self.update = function () {
self.lifeTime++;
self.rotation += 0.05;
// Apply gravity if not on ground
if (!self.onGround) {
self.velocityY += 0.8; // Gravity effect
self.y += self.velocityY;
// Check platform collision for landing
for (var i = 0; i < platforms.length; i++) {
var platform = platforms[i];
var platformLeft = platform.x - platform.width / 2;
var platformRight = platform.x + platform.width / 2;
var platformTop = platform.y - platform.height / 2;
var platformBottom = platform.y + platform.height / 2;
// Check if powerup is within platform bounds and landing on top
if (self.x >= platformLeft && self.x <= platformRight && self.y >= platformTop - 30 && self.y <= platformBottom && self.velocityY >= 0) {
self.y = platformTop - 30; // Position on top of platform
self.velocityY = 0;
self.onGround = true;
break;
}
}
// Remove if fallen off screen
if (self.y > 2800) {
self.destroy();
for (var j = powerups.length - 1; j >= 0; j--) {
if (powerups[j] === self) {
powerups.splice(j, 1);
break;
}
}
return;
}
} else {
// Bounce effect when on ground
self.y += Math.sin(self.lifeTime * 0.1) * 0.5;
}
if (self.lifeTime > self.maxLifeTime) {
self.destroy();
for (var i = powerups.length - 1; i >= 0; i--) {
if (powerups[i] === self) {
powerups.splice(i, 1);
break;
}
}
}
// Check collection by players
if (!self.collected) {
for (var i = 0; i < fighters.length; i++) {
var fighter = fighters[i];
var distance = Math.sqrt(Math.pow(fighter.x - self.x, 2) + Math.pow(fighter.y - self.y, 2));
if (distance < 80) {
self.collected = true;
// Increase powerup count for stacking effects
fighter.powerupCount++;
// Visual effects for powerup collection
LK.getSound('powerup').play();
LK.effects.flashObject(fighter, 0xffd700, 800);
// Create scaling animation effect
tween(fighter, {
scaleX: 1.3,
scaleY: 1.3
}, {
duration: 200,
easing: tween.bounceOut,
onFinish: function onFinish() {
// Scale back down slightly
tween(fighter, {
scaleX: 1.0 + fighter.powerupCount * 0.2,
scaleY: 1.0 + fighter.powerupCount * 0.2
}, {
duration: 300,
easing: tween.easeOut
});
}
});
// Temporary knockback resistance boost (stacks with permanent effects)
var originalResistance = fighter.knockbackResistance;
fighter.knockbackResistance *= 0.7; // 30% less knockback temporarily
LK.setTimeout(function () {
fighter.knockbackResistance = originalResistance;
}, 3000);
self.destroy();
for (var j = powerups.length - 1; j >= 0; j--) {
if (powerups[j] === self) {
powerups.splice(j, 1);
break;
}
}
break;
}
}
}
};
return self;
});
var RainDrop = Container.expand(function () {
var self = Container.call(this);
var rainGraphics = self.attachAsset('rainDrop', {
anchorX: 0.5,
anchorY: 0.5
});
self.velocityY = 15 + Math.random() * 10; // Fast falling speed
self.velocityX = -2 + Math.random() * 4; // Slight horizontal drift
self.lifeTime = 0;
self.maxLifeTime = 300; // 5 seconds max life
self.update = function () {
self.lifeTime++;
// Move the raindrop
self.x += self.velocityX;
self.y += self.velocityY;
// Check collision with platforms
for (var i = 0; i < platforms.length; i++) {
var platform = platforms[i];
var platformLeft = platform.x - platform.width / 2;
var platformRight = platform.x + platform.width / 2;
var platformTop = platform.y - platform.height / 2;
var platformBottom = platform.y + platform.height / 2;
// Check if raindrop hits platform
if (self.x >= platformLeft && self.x <= platformRight && self.y >= platformTop && self.y <= platformBottom && self.velocityY > 0) {
// Track rain drop location for flower spawning
rainDropLocations.push({
x: self.x,
y: platformTop - 20,
// Position flowers on top of platform
time: LK.ticks
});
// Rain drop hit platform, destroy it
self.destroy();
for (var j = rainDrops.length - 1; j >= 0; j--) {
if (rainDrops[j] === self) {
rainDrops.splice(j, 1);
break;
}
}
return; // Exit early to prevent further processing
}
}
// Check collision with players
for (var i = 0; i < fighters.length; i++) {
var fighter = fighters[i];
if (!fighter || fighter.destroyed) {
continue;
}
var distance = Math.sqrt(Math.pow(fighter.x - self.x, 2) + Math.pow(fighter.y - self.y, 2));
if (distance < 60) {
// Hit detection radius
// Reduce player size by 1.0%
var currentScale = Math.abs(fighter.children[0].scaleX);
var newScale = currentScale * 0.99; // Reduce by 1.0%
// Apply the scale reduction while maintaining orientation
var isFlipped = fighter.children[0].scaleX < 0;
fighter.children[0].scaleX = isFlipped ? -newScale : newScale;
fighter.children[0].scaleY = newScale;
// Destroy the rain drop
self.destroy();
for (var j = rainDrops.length - 1; j >= 0; j--) {
if (rainDrops[j] === self) {
rainDrops.splice(j, 1);
break;
}
}
return; // Exit early to prevent further processing
}
}
// Remove if off screen or exceeded life time
if (self.y > 2800 || self.x < -100 || self.x > 2148 || self.lifeTime >= self.maxLifeTime) {
self.destroy();
for (var i = rainDrops.length - 1; i >= 0; i--) {
if (rainDrops[i] === self) {
rainDrops.splice(i, 1);
break;
}
}
}
};
return self;
});
var SmokeTrail = Container.expand(function (playerNumber, x, y) {
var self = Container.call(this);
var assetName = 'smokeTrail1';
if (playerNumber === 2) {
assetName = 'smokeTrail2';
} else if (playerNumber === 3) {
assetName = 'smokeTrail3';
} else if (playerNumber === 4) {
assetName = 'smokeTrail4';
}
var smokeGraphics = self.attachAsset(assetName, {
anchorX: 0.5,
anchorY: 0.5
});
self.x = x;
self.y = y;
self.lifeTime = 0;
self.maxLifeTime = 30; // 0.5 seconds at 60fps
self.startScale = 0.6 + Math.random() * 0.4;
self.endScale = 1.2 + Math.random() * 0.8;
smokeGraphics.scaleX = self.startScale;
smokeGraphics.scaleY = self.startScale;
smokeGraphics.alpha = 0.9;
self.velocityX = (Math.random() - 0.5) * 2;
self.velocityY = -2 - Math.random() * 3;
self.update = function () {
self.lifeTime++;
var progress = self.lifeTime / self.maxLifeTime;
// Move the smoke particle
self.x += self.velocityX;
self.y += self.velocityY;
// Fade out and scale up over time
smokeGraphics.alpha = 0.9 * (1 - progress);
var currentScale = self.startScale + (self.endScale - self.startScale) * progress;
smokeGraphics.scaleX = currentScale;
smokeGraphics.scaleY = currentScale;
// Remove when life time is over
if (self.lifeTime >= self.maxLifeTime) {
self.destroy();
for (var i = smokeTrails.length - 1; i >= 0; i--) {
if (smokeTrails[i] === self) {
smokeTrails.splice(i, 1);
break;
}
}
}
};
return self;
});
var WheelPowerUp = Container.expand(function () {
var self = Container.call(this);
var wheelPowerupGraphics = self.attachAsset('wheelPowerup', {
anchorX: 0.5,
anchorY: 0.5
});
self.lifeTime = 0;
self.maxLifeTime = 600; // 10 seconds at 60fps
self.collected = false;
self.velocityY = 0; // Falling velocity
self.onGround = false; // Track if powerup has landed
self.update = function () {
self.lifeTime++;
self.rotation += 0.05;
// Apply gravity if not on ground
if (!self.onGround) {
self.velocityY += 0.8; // Gravity effect
self.y += self.velocityY;
// Check platform collision for landing
for (var i = 0; i < platforms.length; i++) {
var platform = platforms[i];
var platformLeft = platform.x - platform.width / 2;
var platformRight = platform.x + platform.width / 2;
var platformTop = platform.y - platform.height / 2;
var platformBottom = platform.y + platform.height / 2;
// Check if powerup is within platform bounds and landing on top
if (self.x >= platformLeft && self.x <= platformRight && self.y >= platformTop - 30 && self.y <= platformBottom && self.velocityY >= 0) {
self.y = platformTop - 30; // Position on top of platform
self.velocityY = 0;
self.onGround = true;
break;
}
}
// Remove if fallen off screen
if (self.y > 2800) {
self.destroy();
for (var j = wheelPowerups.length - 1; j >= 0; j--) {
if (wheelPowerups[j] === self) {
wheelPowerups.splice(j, 1);
break;
}
}
return;
}
} else {
// Bounce effect when on ground
self.y += Math.sin(self.lifeTime * 0.1) * 0.5;
}
if (self.lifeTime > self.maxLifeTime) {
self.destroy();
for (var i = wheelPowerups.length - 1; i >= 0; i--) {
if (wheelPowerups[i] === self) {
wheelPowerups.splice(i, 1);
break;
}
}
}
// Check collection by players
if (!self.collected) {
for (var i = 0; i < fighters.length; i++) {
var fighter = fighters[i];
var distance = Math.sqrt(Math.pow(fighter.x - self.x, 2) + Math.pow(fighter.y - self.y, 2));
if (distance < 80) {
self.collected = true;
// Convert fighter to wheel mode
fighter.activateWheelMode();
// Visual effects for powerup collection
LK.getSound('powerup').play();
LK.effects.flashObject(fighter, 0x00ff00, 800);
self.destroy();
for (var j = wheelPowerups.length - 1; j >= 0; j--) {
if (wheelPowerups[j] === self) {
wheelPowerups.splice(j, 1);
break;
}
}
break;
}
}
}
};
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0x000000
});
/****
* Game Code
****/
// Game variables
var fighters = [];
var platforms = [];
var powerups = [];
var wheelPowerups = [];
var allyPowerups = [];
var allies = [];
var smokeTrails = [];
var fireflies = [];
var rainDrops = [];
var lightningBolts = [];
var flowers = [];
var paintDrops = []; // Track paint drops from explosions
var rainDropLocations = []; // Track where rain drops have fallen
var player1Lives = 3;
var player2Lives = 3;
var player3Lives = 3;
var player4Lives = 3;
var activePlayers = 4;
var draggedFighter = null;
var lastTapTime = 0;
var powerupSpawnTimer = 0;
var player1EnemiesDefeated = 0; // Track enemies defeated by player 1
// Night mode variables
var nightModeTimer = 0;
var nightModeDuration = 1200 + Math.random() * 600; // 20-30 seconds at 60fps
var isNightMode = false;
var nightOverlay = null;
// Storm mode variables
var stormModeTimer = 0;
var stormStartTime = 300 + Math.random() * 2700; // Random time between 5-50 seconds
var isStormMode = false;
var stormHasTriggered = false;
var stormOverlay = null;
// Camera variables
var cameraX = 1024; // Center of screen
var cameraY = 1366; // Center of screen
var cameraScale = 1.0;
var targetCameraX = 1024;
var targetCameraY = 1366;
var targetCameraScale = 1.0;
var cameraUpdateTimer = 0;
// Add background image with parallax tracking
var backgroundImage = game.addChild(LK.getAsset('background', {
anchorX: 0.5,
anchorY: 0.5,
x: 1648,
y: 2400
}));
backgroundImage.originalX = 1648;
backgroundImage.originalY = 2400;
backgroundImage.scrollOffset = 0; // Track horizontal scroll position
// Add duplicate background image for infinite scrolling
var backgroundImageDuplicate = game.addChild(LK.getAsset('background', {
anchorX: 0.5,
anchorY: 0.5,
x: 1648 + 4948,
// Position one background width to the right
y: 2400,
scaleX: -1 // Flip horizontally
}));
backgroundImageDuplicate.originalX = 1648 + 4948;
backgroundImageDuplicate.originalY = 2400;
backgroundImageDuplicate.scrollOffset = 0;
// Add second background image on top of the first with parallax tracking
var backgroundOverlay = game.addChild(LK.getAsset('backgroundOverlay', {
anchorX: 0.5,
anchorY: 0.5,
x: 1648,
y: 2400
}));
backgroundOverlay.originalX = 1648;
backgroundOverlay.originalY = 2400;
// Add third background image above the previous two with parallax tracking
var backgroundTop = game.addChild(LK.getAsset('backgroundTop', {
anchorX: 0.5,
anchorY: 0.5,
x: 1648,
y: 3200
}));
backgroundTop.originalX = 1648;
backgroundTop.originalY = 3200;
// Add dynamic overlay background above all other elements
var dynamicOverlay = game.addChild(LK.getAsset('dynamicOverlay', {
anchorX: 0.5,
anchorY: 0.5,
x: 1024,
y: 1366
}));
dynamicOverlay.originalX = 1024;
dynamicOverlay.originalY = 1366;
dynamicOverlay.alpha = 1.0; // Fully opaque overlay
dynamicOverlay.scaleX = 1.5;
dynamicOverlay.scaleY = 1.5;
// Initialize movement variables for dynamic overlay
dynamicOverlay.moveSpeed = 0.5;
dynamicOverlay.direction = 0;
dynamicOverlay.rotationSpeed = 0;
// Add second dynamic overlay background 4000 pixels below the first
var dynamicOverlay2 = game.addChild(LK.getAsset('dynamicOverlay2', {
anchorX: 0.5,
anchorY: 0.5,
x: 1024,
y: 5366 // 4000 pixels below the first overlay (1366 + 4000)
}));
dynamicOverlay2.originalX = 1024;
dynamicOverlay2.originalY = 5366;
dynamicOverlay2.alpha = 1.0; // Fully opaque overlay
dynamicOverlay2.scaleX = 1.5;
dynamicOverlay2.scaleY = 1.5;
// Initialize movement variables for second dynamic overlay
dynamicOverlay2.moveSpeed = 0.3; // Slightly different speed for variation
dynamicOverlay2.direction = Math.PI; // Start with opposite direction
dynamicOverlay2.rotationSpeed = 0;
// Button press states
var leftButtonPressed = false;
var rightButtonPressed = false;
var jumpButtonPressed = false;
var attackButtonPressed = false;
// Create platforms
var mainPlatform = game.addChild(LK.getAsset('mainPlatform', {
anchorX: 0.5,
anchorY: 0.5,
x: 1648,
y: 2400
}));
platforms.push(mainPlatform);
var leftPlatform = game.addChild(LK.getAsset('platform', {
anchorX: 0.5,
anchorY: 0.5,
x: 1048,
y: 2050
}));
platforms.push(leftPlatform);
var rightPlatform = game.addChild(LK.getAsset('platform', {
anchorX: 0.5,
anchorY: 0.5,
x: 2248,
y: 2050
}));
platforms.push(rightPlatform);
var topPlatform = game.addChild(LK.getAsset('platform', {
anchorX: 0.5,
anchorY: 0.5,
x: 1648,
y: 1700
}));
platforms.push(topPlatform);
// Add decorative textured images directly on top of platforms
var mainPlatformTexture = game.addChild(LK.getAsset('platformTexture', {
anchorX: 0.5,
anchorY: 1.0,
x: 1648,
y: 2250
}));
mainPlatformTexture.scaleX = 2.5; // Scale to match main platform width
mainPlatformTexture.scaleY = 0.8;
var leftPlatformTexture = game.addChild(LK.getAsset('platformTexture', {
anchorX: 0.5,
anchorY: 1.0,
x: 1048,
y: 1950
}));
leftPlatformTexture.scaleX = 1.25; // Scale to match platform width
leftPlatformTexture.scaleY = 0.6;
var rightPlatformTexture = game.addChild(LK.getAsset('platformTexture', {
anchorX: 0.5,
anchorY: 1.0,
x: 2248,
y: 1950
}));
rightPlatformTexture.scaleX = 1.25; // Scale to match platform width
rightPlatformTexture.scaleY = 0.6;
var topPlatformTexture = game.addChild(LK.getAsset('platformTexture', {
anchorX: 0.5,
anchorY: 1.0,
x: 1648,
y: 1600
}));
topPlatformTexture.scaleX = 1.25; // Scale to match platform width
topPlatformTexture.scaleY = 0.6;
// Create night mode overlay
nightOverlay = game.addChild(LK.getAsset('platform', {
width: 3000,
height: 4000,
anchorX: 0.5,
anchorY: 0.5,
x: 1500,
y: 2000
}));
nightOverlay.tint = 0x000022; // Darker blue tint for stronger night effect
nightOverlay.alpha = 0; // Start invisible
nightOverlay.scaleX = 2;
nightOverlay.scaleY = 2;
// Create storm mode overlay
stormOverlay = game.addChild(LK.getAsset('platform', {
width: 3000,
height: 4000,
anchorX: 0.5,
anchorY: 0.5,
x: 1500,
y: 2000
}));
stormOverlay.tint = 0x444444; // Dark gray tint for storm atmosphere
stormOverlay.alpha = 0; // Start invisible
stormOverlay.scaleX = 2;
stormOverlay.scaleY = 2;
// Create fighters
var player1 = game.addChild(new Fighter(1));
player1.x = 1048; // Position on left platform
player1.y = 1950; // Just above the platform surface
fighters.push(player1);
var player2 = game.addChild(new Fighter(2));
player2.x = 1648; // Position on main platform
player2.y = 2300; // Just above the main platform surface
fighters.push(player2);
var player3 = game.addChild(new Fighter(3));
player3.x = 1648; // Position on top platform
player3.y = 1600; // Just above the top platform surface
fighters.push(player3);
var player4 = game.addChild(new Fighter(4));
player4.x = 2248; // Position on right platform
player4.y = 1950; // Just above the right platform surface
fighters.push(player4);
// UI Elements - Individual player displays
var player1Avatar = LK.getAsset('player1Avatar', {
anchorX: 0.5,
anchorY: 0.5,
x: -200,
y: 100,
scaleX: 0.8,
scaleY: 0.8
});
LK.gui.topRight.addChild(player1Avatar);
var player1LivesBackground = LK.getAsset('livesBackground', {
anchorX: 0.5,
anchorY: 0.5,
x: -200,
y: 180,
scaleX: 0.8,
scaleY: 0.8
});
LK.gui.topRight.addChild(player1LivesBackground);
var player1LivesBackground;
var player1LivesImage = LK.getAsset('number3', {
anchorX: 0.5,
anchorY: 0.5,
x: -200,
y: 180,
scaleX: 1.0,
scaleY: 1.0
});
LK.gui.topRight.addChild(player1LivesImage);
// Player 2, 3, and 4 UI elements are hidden - only Player 1 UI is visible
// Create left movement button for player 1
var leftButton = LK.getAsset('leftButtonIcon', {
anchorX: 0,
anchorY: 1,
x: 30,
y: -160,
scaleX: 3,
scaleY: 3
});
LK.gui.bottomLeft.addChild(leftButton);
// Create right movement button for player 1
var rightButton = LK.getAsset('rightButtonIcon', {
anchorX: 0,
anchorY: 1,
x: 320,
y: -160,
scaleX: 3,
scaleY: 3
});
LK.gui.bottomLeft.addChild(rightButton);
// Create jump button for player 1
var jumpButton = LK.getAsset('jumpButtonIcon', {
anchorX: 1,
anchorY: 1,
x: -100,
y: -300,
scaleX: 3,
scaleY: 3
});
LK.gui.bottomRight.addChild(jumpButton);
// Create attack button for player 1
var attackButton = LK.getAsset('attackButtonIcon', {
anchorX: 1,
anchorY: 1,
x: -100,
y: -160,
scaleX: 3,
scaleY: 3
});
LK.gui.bottomRight.addChild(attackButton);
function updateScoreDisplay() {
// Remove current lives image and background
LK.gui.topRight.removeChild(player1LivesImage);
LK.gui.topRight.removeChild(player1LivesBackground);
player1LivesImage.destroy();
player1LivesBackground.destroy();
// Recreate lives background
player1LivesBackground = LK.getAsset('livesBackground', {
anchorX: 0.5,
anchorY: 0.5,
x: -200,
y: 180,
scaleX: 0.8,
scaleY: 0.8
});
LK.gui.topRight.addChild(player1LivesBackground);
// Create new lives image based on current lives count
var numberAsset = 'number0';
if (player1Lives === 1) {
numberAsset = 'number1';
} else if (player1Lives === 2) {
numberAsset = 'number2';
} else if (player1Lives === 3) {
numberAsset = 'number3';
}
player1LivesImage = LK.getAsset(numberAsset, {
anchorX: 0.5,
anchorY: 0.5,
x: -200,
y: 180,
scaleX: 1.0,
scaleY: 1.0
});
LK.gui.topRight.addChild(player1LivesImage);
// Update avatar opacity based on lives remaining
player1Avatar.alpha = player1Lives > 0 ? 1.0 : 0.3;
// Update lives image opacity based on lives remaining
player1LivesImage.alpha = player1Lives > 0 ? 1.0 : 0.5;
// Update background opacity based on lives remaining
player1LivesBackground.alpha = player1Lives > 0 ? 1.0 : 0.5;
}
// Left button event handler
leftButton.down = function (x, y, obj) {
leftButtonPressed = true;
leftButton.alpha = 0.6;
tween(leftButton, {
scaleX: 2.7,
scaleY: 2.7
}, {
duration: 100
});
};
leftButton.up = function (x, y, obj) {
leftButtonPressed = false;
leftButton.alpha = 1.0;
tween(leftButton, {
scaleX: 3.0,
scaleY: 3.0
}, {
duration: 100
});
};
// Right button event handler
rightButton.down = function (x, y, obj) {
rightButtonPressed = true;
rightButton.alpha = 0.6;
tween(rightButton, {
scaleX: 2.7,
scaleY: 2.7
}, {
duration: 100
});
};
rightButton.up = function (x, y, obj) {
rightButtonPressed = false;
rightButton.alpha = 1.0;
tween(rightButton, {
scaleX: 3.0,
scaleY: 3.0
}, {
duration: 100
});
};
// Jump button event handler
jumpButton.down = function (x, y, obj) {
jumpButtonPressed = true;
jumpButton.alpha = 0.6;
tween(jumpButton, {
scaleX: 2.7,
scaleY: 2.7
}, {
duration: 100
});
};
jumpButton.up = function (x, y, obj) {
jumpButtonPressed = false;
jumpButton.alpha = 1.0;
tween(jumpButton, {
scaleX: 3.0,
scaleY: 3.0
}, {
duration: 100
});
};
// Attack button event handler
attackButton.down = function (x, y, obj) {
attackButtonPressed = true;
attackButton.alpha = 0.6;
tween(attackButton, {
scaleX: 2.7,
scaleY: 2.7
}, {
duration: 100
});
};
attackButton.up = function (x, y, obj) {
attackButtonPressed = false;
attackButton.alpha = 1.0;
tween(attackButton, {
scaleX: 3.0,
scaleY: 3.0
}, {
duration: 100
});
};
function updateCamera() {
// Only update camera if there are living fighters
var aliveFighters = [];
for (var i = 0; i < fighters.length; i++) {
var fighter = fighters[i];
if (fighter && !fighter.destroyed) {
// Check if fighter is alive based on lives
var isAlive = false;
if (fighter.playerNumber === 1 && player1Lives > 0) {
isAlive = true;
} else if (fighter.playerNumber === 2 && player2Lives > 0) {
isAlive = true;
} else if (fighter.playerNumber === 3 && player3Lives > 0) {
isAlive = true;
} else if (fighter.playerNumber === 4 && player4Lives > 0) {
isAlive = true;
}
if (isAlive) {
aliveFighters.push(fighter);
}
}
}
if (aliveFighters.length === 0) {
return;
}
// Calculate center point of all alive fighters
var minX = Infinity;
var maxX = -Infinity;
var minY = Infinity;
var maxY = -Infinity;
var totalX = 0;
var totalY = 0;
for (var i = 0; i < aliveFighters.length; i++) {
var fighter = aliveFighters[i];
totalX += fighter.x;
totalY += fighter.y;
if (fighter.x < minX) {
minX = fighter.x;
}
if (fighter.x > maxX) {
maxX = fighter.x;
}
if (fighter.y < minY) {
minY = fighter.y;
}
if (fighter.y > maxY) {
maxY = fighter.y;
}
}
// Calculate center point
targetCameraX = totalX / aliveFighters.length;
targetCameraY = totalY / aliveFighters.length;
// Calculate required zoom based on spread of players
var spreadX = maxX - minX;
var spreadY = maxY - minY;
// Base zoom calculation - zoom in more for closer view of players
var baseScale = 1.2; // Increased base scale for more zoom
var maxSpreadX = 400; // Further reduced max spread for tighter framing
var maxSpreadY = 300; // Further reduced max spread for tighter framing
var scaleX = maxSpreadX / Math.max(spreadX + 100, maxSpreadX); // Reduced padding for closer view
var scaleY = maxSpreadY / Math.max(spreadY + 100, maxSpreadY); // Reduced padding for closer view
// Use the smaller scale to ensure all players fit
targetCameraScale = Math.min(scaleX, scaleY, 1.3); // Increased max zoom to 1.3x for closer view
// Minimum zoom to prevent over-zooming - increased minimum for closer gameplay
if (targetCameraScale < 0.9) {
targetCameraScale = 0.9;
}
// Keep camera within reasonable bounds
var padding = 200;
if (targetCameraX < padding) {
targetCameraX = padding;
}
if (targetCameraX > 2048 - padding) {
targetCameraX = 2048 - padding;
}
if (targetCameraY < 400) {
targetCameraY = 400;
}
if (targetCameraY > 2400) {
targetCameraY = 2400;
}
}
function applyCameraSmoothing() {
// Smooth camera movement using tweening
var smoothingSpeed = 0.05; // How fast camera follows (0.01 = very slow, 0.1 = fast)
// Interpolate camera position
cameraX += (targetCameraX - cameraX) * smoothingSpeed;
cameraY += (targetCameraY - cameraY) * smoothingSpeed;
cameraScale += (targetCameraScale - cameraScale) * smoothingSpeed;
// Apply camera transformation to game
game.x = (1024 - cameraX) * cameraScale + 1024 * (1 - cameraScale);
game.y = (1366 - cameraY) * cameraScale + 1366 * (1 - cameraScale);
game.scaleX = cameraScale;
game.scaleY = cameraScale;
// Apply parallax scrolling to background layers
updateParallax();
}
function updateParallax() {
// Calculate camera movement offset from center
var cameraMoveX = cameraX - 1024; // How much camera moved from center horizontally
var cameraMoveY = cameraY - 1366; // How much camera moved from center vertically
// Background infinite scrolling - move left slowly
var scrollSpeed = 0.5; // Pixels per frame to move left
backgroundImage.scrollOffset += scrollSpeed;
backgroundImageDuplicate.scrollOffset += scrollSpeed;
// Background image (furthest back) - moves slowest (20% of camera movement) plus infinite scroll
backgroundImage.x = backgroundImage.originalX - cameraMoveX * 0.2 - backgroundImage.scrollOffset;
backgroundImage.y = backgroundImage.originalY - cameraMoveY * 0.15;
// Duplicate background image positioning
backgroundImageDuplicate.x = backgroundImageDuplicate.originalX - cameraMoveX * 0.2 - backgroundImageDuplicate.scrollOffset;
backgroundImageDuplicate.y = backgroundImageDuplicate.originalY - cameraMoveY * 0.15;
// Reset positions when backgrounds scroll off screen for infinite loop
var backgroundWidth = 4948; // Width of background image
if (backgroundImage.x <= -backgroundWidth) {
backgroundImage.x = backgroundImageDuplicate.x + backgroundWidth;
backgroundImage.scrollOffset = backgroundImageDuplicate.scrollOffset;
}
if (backgroundImageDuplicate.x <= -backgroundWidth) {
backgroundImageDuplicate.x = backgroundImage.x + backgroundWidth;
backgroundImageDuplicate.scrollOffset = backgroundImage.scrollOffset;
}
// Background overlay (middle layer) - moves at medium speed (40% of camera movement)
backgroundOverlay.x = backgroundOverlay.originalX - cameraMoveX * 0.4;
backgroundOverlay.y = backgroundOverlay.originalY - cameraMoveY * 0.3;
// Background top (closest to foreground) - moves faster (60% of camera movement)
backgroundTop.x = backgroundTop.originalX - cameraMoveX * 0.6;
backgroundTop.y = backgroundTop.originalY - cameraMoveY * 0.45;
// Dynamic overlay - autonomous movement with slight camera influence
dynamicOverlay.direction += 0.01; // Slow rotation of movement direction
var autonomousX = Math.cos(dynamicOverlay.direction) * dynamicOverlay.moveSpeed;
var autonomousY = Math.sin(dynamicOverlay.direction) * dynamicOverlay.moveSpeed;
dynamicOverlay.x = dynamicOverlay.originalX + autonomousX * 100 - cameraMoveX * 0.1;
dynamicOverlay.y = dynamicOverlay.originalY + autonomousY * 80 - cameraMoveY * 0.08;
// Second dynamic overlay - autonomous movement with different pattern
dynamicOverlay2.direction += 0.008; // Slightly different rotation speed
var autonomousX2 = Math.cos(dynamicOverlay2.direction) * dynamicOverlay2.moveSpeed;
var autonomousY2 = Math.sin(dynamicOverlay2.direction) * dynamicOverlay2.moveSpeed;
dynamicOverlay2.x = dynamicOverlay2.originalX + autonomousX2 * 120 - cameraMoveX * 0.12;
dynamicOverlay2.y = dynamicOverlay2.originalY + autonomousY2 * 90 - cameraMoveY * 0.09;
}
function spawnPowerUp() {
if (powerups.length + wheelPowerups.length + allyPowerups.length < 2) {
// 33% chance for each powerup type: regular, wheel, ally
var rand = Math.random();
if (rand < 0.33) {
var powerup = game.addChild(new PowerUp());
// Spawn from random position above the screen aligned with new platform positions
powerup.x = 1048 + Math.random() * 1200; // Random X within extended platform bounds (left to right platform)
powerup.y = -100; // Start above screen
powerup.velocityY = 2 + Math.random() * 3; // Initial falling speed
powerups.push(powerup);
} else if (rand < 0.66) {
var wheelPowerup = game.addChild(new WheelPowerUp());
// Spawn from random position above the screen aligned with new platform positions
wheelPowerup.x = 1048 + Math.random() * 1200; // Random X within extended platform bounds (left to right platform)
wheelPowerup.y = -100; // Start above screen
wheelPowerup.velocityY = 2 + Math.random() * 3; // Initial falling speed
wheelPowerups.push(wheelPowerup);
} else {
var allyPowerup = game.addChild(new AllyPowerUp());
// Spawn from random position above the screen aligned with new platform positions
allyPowerup.x = 1048 + Math.random() * 1200; // Random X within extended platform bounds (left to right platform)
allyPowerup.y = -100; // Start above screen
allyPowerup.velocityY = 2 + Math.random() * 3; // Initial falling speed
allyPowerups.push(allyPowerup);
}
}
}
// Game controls
game.down = function (x, y, obj) {
var currentTime = LK.ticks;
var tapSpeed = 20; // frames between taps for double tap
// Find closest fighter to touch point (only player 1)
var closestFighter = null;
var closestDistance = Infinity;
for (var i = 0; i < fighters.length; i++) {
var fighter = fighters[i];
// Only allow touch control for player 1 if they're still alive
if (fighter.playerNumber !== 1 || player1Lives <= 0) {
continue;
}
var distance = Math.sqrt(Math.pow(fighter.x - x, 2) + Math.pow(fighter.y - y, 2));
if (distance < closestDistance && distance < 200) {
closestDistance = distance;
closestFighter = fighter;
}
}
if (closestFighter) {
// Check for double tap (attack)
if (currentTime - lastTapTime < tapSpeed) {
closestFighter.attack(x, y);
draggedFighter = null;
} else {
draggedFighter = closestFighter;
}
lastTapTime = currentTime;
}
};
game.move = function (x, y, obj) {
if (draggedFighter && draggedFighter.playerNumber === 1) {
var deltaX = x - draggedFighter.x;
var deltaY = y - draggedFighter.y;
// Move fighter towards touch point
draggedFighter.velocityX += deltaX * 0.3;
// Jump if dragging upward and has jumps remaining
if (deltaY < -50 && draggedFighter.jumpsRemaining > 0) {
draggedFighter.velocityY = -draggedFighter.jumpPower;
draggedFighter.jumpsRemaining--;
draggedFighter.onGround = false;
LK.getSound('jump').play();
}
}
};
game.up = function (x, y, obj) {
draggedFighter = null;
};
// Function to determine which platform a fighter is on
function getPlayerPlatform(fighter) {
for (var i = 0; i < platforms.length; i++) {
var platform = platforms[i];
var platformLeft = platform.x - platform.width / 2;
var platformRight = platform.x + platform.width / 2;
var platformTop = platform.y - platform.height / 2;
var platformBottom = platform.y + platform.height / 2;
// Check if fighter is within platform bounds and close to platform surface
if (fighter.x >= platformLeft && fighter.x <= platformRight && fighter.y >= platformTop - 100 && fighter.y <= platformBottom + 50) {
return platform;
}
}
return null;
}
// Function to find closest target for AI with platform priority
function findClosestTarget(aiPlayer) {
var closestPlayer = null;
var closestDistance = Infinity;
var closestSamePlatformPlayer = null;
var closestSamePlatformDistance = Infinity;
var aiPlatform = getPlayerPlatform(aiPlayer);
for (var i = 0; i < fighters.length; i++) {
var fighter = fighters[i];
// Skip self, dead players, and destroyed fighters
if (fighter === aiPlayer) {
continue;
}
if (fighter.destroyed) {
continue;
}
if (fighter.playerNumber === 1 && player1Lives <= 0) {
continue;
}
if (fighter.playerNumber === 2 && player2Lives <= 0) {
continue;
}
if (fighter.playerNumber === 3 && player3Lives <= 0) {
continue;
}
if (fighter.playerNumber === 4 && player4Lives <= 0) {
continue;
}
var distance = Math.sqrt(Math.pow(fighter.x - aiPlayer.x, 2) + Math.pow(fighter.y - aiPlayer.y, 2));
var fighterPlatform = getPlayerPlatform(fighter);
var onSamePlatform = aiPlatform && fighterPlatform && aiPlatform === fighterPlatform;
// Track closest enemy on same platform
if (onSamePlatform && distance < closestSamePlatformDistance) {
closestSamePlatformDistance = distance;
closestSamePlatformPlayer = fighter;
}
// Track overall closest enemy
if (distance < closestDistance) {
closestDistance = distance;
closestPlayer = fighter;
}
}
// Prioritize same platform enemy if one exists
if (closestSamePlatformPlayer) {
return {
player: closestSamePlatformPlayer,
distance: closestSamePlatformDistance,
samePlatform: true
};
}
return {
player: closestPlayer,
distance: closestDistance,
samePlatform: false
};
}
// AI variables for player 2
var aiDecisionTimer = 0;
var aiAction = 'idle';
var aiTargetX = player2.x;
var aiCooldown = 0;
// AI variables for player 3
var ai3DecisionTimer = 0;
var ai3Action = 'idle';
var ai3TargetX = 1648;
var ai3Cooldown = 0;
// AI variables for player 4
var ai4DecisionTimer = 0;
var ai4Action = 'idle';
var ai4TargetX = 2248;
var ai4Cooldown = 0;
game.update = function () {
// Start background music on first update
if (LK.ticks === 1) {
LK.playMusic('backgroundMusic');
}
// Handle continuous button presses for player 1 (only if alive)
if (player1Lives > 0) {
if (leftButtonPressed) {
player1.velocityX -= player1.moveSpeed * 0.5;
}
if (rightButtonPressed) {
player1.velocityX += player1.moveSpeed * 0.5;
}
if (jumpButtonPressed && player1.jumpsRemaining > 0) {
player1.velocityY = -player1.jumpPower;
player1.jumpsRemaining--;
player1.onGround = false;
LK.getSound('jump').play();
jumpButtonPressed = false; // Prevent continuous jumping
}
if (attackButtonPressed) {
player1.attack(player1.x, player1.y);
attackButtonPressed = false; // Prevent continuous attacking
}
}
// Spawn power-ups occasionally
powerupSpawnTimer++;
if (powerupSpawnTimer > 120) {
// Every 2 seconds
if (Math.random() < 0.5) {
spawnPowerUp();
}
powerupSpawnTimer = 0;
}
// AI for player 2 (only if alive)
if (player2Lives > 0) {
aiDecisionTimer++;
if (aiCooldown > 0) {
aiCooldown--;
}
// Make AI decisions every 30 frames (half second)
if (aiDecisionTimer > 30) {
aiDecisionTimer = 0;
// Check if AI is below main platform and needs to move to center
var mainPlatformY = 2400; // Main platform Y position
var isBelowMainPlatform = player2.y > mainPlatformY + 200;
if (isBelowMainPlatform) {
// Force move to center platform to avoid falling
aiAction = 'moveToCenter';
aiTargetX = 1648; // Main platform center X
} else {
// Find closest target
var closestTarget = findClosestTarget(player2);
var distanceToClosest = closestTarget.distance;
// Check for nearby powerups
var nearestPowerup = null;
var nearestPowerupDistance = Infinity;
for (var i = 0; i < powerups.length; i++) {
var powerup = powerups[i];
var distance = Math.sqrt(Math.pow(powerup.x - player2.x, 2) + Math.pow(powerup.y - player2.y, 2));
if (distance < nearestPowerupDistance) {
nearestPowerupDistance = distance;
nearestPowerup = powerup;
}
}
// AI decision making with enhanced platform awareness
if (aiCooldown === 0 && closestTarget.player) {
if (distanceToClosest < 200 && Math.random() < 0.8) {
// Attack if close to closest target
aiAction = 'attack';
aiCooldown = 60; // Attack cooldown
} else if (nearestPowerup && nearestPowerupDistance < 300 && Math.random() < 0.3) {
// Go for powerup if nearby (lower priority)
aiAction = 'moveToPowerup';
aiTargetX = nearestPowerup.x;
} else if (closestTarget.samePlatform) {
// Same platform enemy detected - move towards closest enemy on same platform
aiAction = 'moveToPlayer';
aiTargetX = closestTarget.player.x;
} else if (!closestTarget.samePlatform && closestTarget.player) {
// Different platform - jump towards target
aiAction = 'jumpToPlayer';
aiTargetX = closestTarget.player.x;
} else {
// Random movement (rare)
aiAction = 'randomMove';
aiTargetX = 1200 + Math.random() * 900; // Random X within platform area
}
} else if (!closestTarget.player) {
aiAction = 'idle';
}
}
}
// Execute AI actions
if (aiAction === 'attack' && closestTarget.player) {
player2.attack(closestTarget.player.x, closestTarget.player.y);
aiAction = 'idle';
} else if (aiAction === 'moveToPowerup' || aiAction === 'moveToPlayer' || aiAction === 'randomMove' || aiAction === 'jumpToPlayer' || aiAction === 'moveToCenter') {
// Move towards target
var deltaX = aiTargetX - player2.x;
if (Math.abs(deltaX) > 50) {
player2.velocityX += deltaX * 0.25;
}
// Enhanced jumping logic for different platforms
if (aiAction === 'jumpToPlayer' && closestTarget && closestTarget.player) {
// Jump towards player on different platform
if (player2.jumpsRemaining > 0 && player2.onGround) {
player2.velocityY = -player2.jumpPower;
player2.jumpsRemaining--;
player2.onGround = false;
LK.getSound('jump').play();
// Add horizontal velocity towards target
player2.velocityX += deltaX * 0.15;
}
} else if (aiAction === 'moveToCenter') {
// Jump upward aggressively to get back to main platform
if (player2.jumpsRemaining > 0) {
player2.velocityY = -player2.jumpPower;
player2.jumpsRemaining--;
player2.onGround = false;
LK.getSound('jump').play();
}
} else if (aiAction === 'moveToPlayer' && closestTarget && closestTarget.player && closestTarget.player.y < player2.y - 100) {
// Jump if target is above on same platform
if (player2.jumpsRemaining > 0) {
player2.velocityY = -player2.jumpPower;
player2.jumpsRemaining--;
player2.onGround = false;
LK.getSound('jump').play();
}
} else if (Math.abs(player2.velocityX) < 1 && player2.onGround && Math.random() < 0.1) {
// Jump if stuck
if (player2.jumpsRemaining > 0) {
player2.velocityY = -player2.jumpPower;
player2.jumpsRemaining--;
player2.onGround = false;
LK.getSound('jump').play();
}
}
// Reset action if close enough to target
if (Math.abs(deltaX) < 100) {
aiAction = 'idle';
}
}
// AI logic: double jump if can't reach player who is much higher
var heightDifference = player2.y - player1.y;
var horizontalDistance = Math.abs(player2.x - player1.x);
if (heightDifference > 200 && horizontalDistance < 400 && player2.jumpsRemaining > 0) {
// Check if AI is below player and perform double jump to reach them
player2.velocityY = -player2.jumpPower;
player2.jumpsRemaining--;
player2.onGround = false;
LK.getSound('jump').play();
}
// AI emergency jump if falling off platform
if (player2.y > 2500 && player2.jumpsRemaining > 0) {
player2.velocityY = -player2.jumpPower;
player2.jumpsRemaining--;
player2.onGround = false;
LK.getSound('jump').play();
}
}
// AI for player 3 (only if alive)
if (player3Lives > 0) {
ai3DecisionTimer++;
if (ai3Cooldown > 0) {
ai3Cooldown--;
}
if (ai3DecisionTimer > 45) {
ai3DecisionTimer = 0;
// Check if AI is below main platform and needs to move to center
var mainPlatformY = 2400; // Main platform Y position
var isBelowMainPlatform = player3.y > mainPlatformY + 200;
if (isBelowMainPlatform) {
// Force move to center platform to avoid falling
ai3Action = 'moveToCenter';
ai3TargetX = 1648; // Main platform center X
} else {
// Find closest target
var closestTarget_3 = findClosestTarget(player3);
var closestDistance_3 = closestTarget_3.distance;
if (ai3Cooldown === 0 && closestTarget_3.player) {
if (closestDistance_3 < 200 && Math.random() < 0.7) {
ai3Action = 'attack';
ai3Cooldown = 70;
} else if (closestTarget_3.samePlatform) {
// Same platform enemy detected - move towards closest enemy on same platform
ai3Action = 'moveToPlayer';
ai3TargetX = closestTarget_3.player.x;
} else if (!closestTarget_3.samePlatform && closestTarget_3.player) {
// Different platform - jump towards target
ai3Action = 'jumpToPlayer';
ai3TargetX = closestTarget_3.player.x;
} else if (Math.random() < 0.3) {
ai3Action = 'randomMove';
ai3TargetX = 1200 + Math.random() * 900;
} else {
ai3Action = 'idle';
}
} else if (!closestTarget_3.player) {
ai3Action = 'idle';
}
}
}
if (ai3Action === 'attack' && closestTarget_3.player) {
player3.attack(closestTarget_3.player.x, closestTarget_3.player.y);
ai3Action = 'idle';
} else if (ai3Action === 'moveToPlayer' || ai3Action === 'randomMove' || ai3Action === 'jumpToPlayer' || ai3Action === 'moveToCenter') {
var deltaX_3 = ai3TargetX - player3.x;
if (Math.abs(deltaX_3) > 50) {
player3.velocityX += deltaX_3 * 0.22;
}
// Enhanced jumping logic for different platforms
if (ai3Action === 'jumpToPlayer' && closestTarget_3 && closestTarget_3.player) {
// Jump towards player on different platform
if (player3.jumpsRemaining > 0 && player3.onGround) {
player3.velocityY = -player3.jumpPower;
player3.jumpsRemaining--;
player3.onGround = false;
LK.getSound('jump').play();
// Add horizontal velocity towards target
player3.velocityX += deltaX_3 * 0.18;
}
} else if (ai3Action === 'moveToCenter') {
// Jump upward aggressively to get back to main platform
if (player3.jumpsRemaining > 0) {
player3.velocityY = -player3.jumpPower;
player3.jumpsRemaining--;
player3.onGround = false;
LK.getSound('jump').play();
}
} else if (Math.abs(deltaX_3) > 200 && player3.onGround && Math.random() < 0.15) {
if (player3.jumpsRemaining > 0) {
player3.velocityY = -player3.jumpPower;
player3.jumpsRemaining--;
player3.onGround = false;
LK.getSound('jump').play();
}
}
if (Math.abs(deltaX_3) < 100) {
ai3Action = 'idle';
}
}
if (player3.y > 2500 && player3.jumpsRemaining > 0) {
player3.velocityY = -player3.jumpPower;
player3.jumpsRemaining--;
player3.onGround = false;
LK.getSound('jump').play();
}
}
// AI for player 4 (only if alive)
if (player4Lives > 0) {
ai4DecisionTimer++;
if (ai4Cooldown > 0) {
ai4Cooldown--;
}
if (ai4DecisionTimer > 35) {
ai4DecisionTimer = 0;
// Check if AI is below main platform and needs to move to center
var mainPlatformY = 2400; // Main platform Y position
var isBelowMainPlatform = player4.y > mainPlatformY + 200;
if (isBelowMainPlatform) {
// Force move to center platform to avoid falling
ai4Action = 'moveToCenter';
ai4TargetX = 1648; // Main platform center X
} else {
// Find closest target
var closestTarget_4 = findClosestTarget(player4);
var closestDistance_4 = closestTarget_4.distance;
if (ai4Cooldown === 0 && closestTarget_4.player) {
if (closestDistance_4 < 180 && Math.random() < 0.85) {
ai4Action = 'attack';
ai4Cooldown = 50;
} else if (closestTarget_4.samePlatform) {
// Same platform enemy detected - move towards closest enemy on same platform aggressively
ai4Action = 'moveToPlayer';
ai4TargetX = closestTarget_4.player.x;
} else if (!closestTarget_4.samePlatform && closestTarget_4.player) {
// Different platform - jump towards target
ai4Action = 'jumpToPlayer';
ai4TargetX = closestTarget_4.player.x;
} else if (Math.random() < 0.4) {
ai4Action = 'randomMove';
ai4TargetX = 1200 + Math.random() * 900;
} else {
ai4Action = 'idle';
}
} else if (!closestTarget_4.player) {
ai4Action = 'idle';
}
}
}
if (ai4Action === 'attack' && closestTarget_4.player) {
player4.attack(closestTarget_4.player.x, closestTarget_4.player.y);
ai4Action = 'idle';
} else if (ai4Action === 'moveToPlayer' || ai4Action === 'randomMove' || ai4Action === 'jumpToPlayer' || ai4Action === 'moveToCenter') {
var deltaX_4 = ai4TargetX - player4.x;
if (Math.abs(deltaX_4) > 50) {
player4.velocityX += deltaX_4 * 0.28;
}
// Enhanced jumping logic for different platforms
if (ai4Action === 'jumpToPlayer' && closestTarget_4 && closestTarget_4.player) {
// Jump towards player on different platform
if (player4.jumpsRemaining > 0 && player4.onGround) {
player4.velocityY = -player4.jumpPower;
player4.jumpsRemaining--;
player4.onGround = false;
LK.getSound('jump').play();
// Add horizontal velocity towards target
player4.velocityX += deltaX_4 * 0.2;
}
} else if (ai4Action === 'moveToCenter') {
// Jump upward aggressively to get back to main platform
if (player4.jumpsRemaining > 0) {
player4.velocityY = -player4.jumpPower;
player4.jumpsRemaining--;
player4.onGround = false;
LK.getSound('jump').play();
}
} else if (Math.abs(deltaX_4) > 150 && player4.onGround && Math.random() < 0.2) {
if (player4.jumpsRemaining > 0) {
player4.velocityY = -player4.jumpPower;
player4.jumpsRemaining--;
player4.onGround = false;
LK.getSound('jump').play();
}
}
if (Math.abs(deltaX_4) < 100) {
ai4Action = 'idle';
}
}
if (player4.y > 2500 && player4.jumpsRemaining > 0) {
player4.velocityY = -player4.jumpPower;
player4.jumpsRemaining--;
player4.onGround = false;
LK.getSound('jump').play();
}
}
// Update all fighters
for (var i = fighters.length - 1; i >= 0; i--) {
if (fighters[i] && !fighters[i].destroyed) {
fighters[i].update();
}
}
// Update all powerups
for (var i = powerups.length - 1; i >= 0; i--) {
if (powerups[i] && !powerups[i].destroyed) {
powerups[i].update();
}
}
// Update all wheel powerups
for (var i = wheelPowerups.length - 1; i >= 0; i--) {
if (wheelPowerups[i] && !wheelPowerups[i].destroyed) {
wheelPowerups[i].update();
}
}
// Update all ally powerups
for (var i = allyPowerups.length - 1; i >= 0; i--) {
if (allyPowerups[i] && !allyPowerups[i].destroyed) {
allyPowerups[i].update();
}
}
// Update all allies
for (var i = allies.length - 1; i >= 0; i--) {
if (allies[i] && !allies[i].destroyed) {
allies[i].update();
}
}
// Update all smoke trails
for (var i = smokeTrails.length - 1; i >= 0; i--) {
if (smokeTrails[i] && !smokeTrails[i].destroyed) {
smokeTrails[i].update();
}
}
// Update all fireflies
for (var i = fireflies.length - 1; i >= 0; i--) {
if (fireflies[i] && !fireflies[i].destroyed) {
fireflies[i].update();
}
}
// Update all rain drops
for (var i = rainDrops.length - 1; i >= 0; i--) {
if (rainDrops[i] && !rainDrops[i].destroyed) {
rainDrops[i].update();
}
}
// Update all lightning bolts
for (var i = lightningBolts.length - 1; i >= 0; i--) {
if (lightningBolts[i] && !lightningBolts[i].destroyed) {
lightningBolts[i].update();
}
}
// Update all flowers
for (var i = flowers.length - 1; i >= 0; i--) {
if (flowers[i] && !flowers[i].destroyed) {
flowers[i].update();
}
}
// Update all paint drops
for (var i = paintDrops.length - 1; i >= 0; i--) {
if (paintDrops[i] && !paintDrops[i].destroyed) {
paintDrops[i].update();
}
}
// Update camera every 10 frames for smooth performance
cameraUpdateTimer++;
if (cameraUpdateTimer >= 10) {
cameraUpdateTimer = 0;
updateCamera();
}
// Storm mode cycle - triggers once at random time
if (!stormHasTriggered && !isNightMode) {
// Only start storm if night mode is not active
stormModeTimer++; // Only increment timer when night mode is not active
if (stormModeTimer >= stormStartTime) {
stormHasTriggered = true;
isStormMode = true;
// Start storm mode with dramatic darkening
tween(stormOverlay, {
alpha: 0.6
}, {
duration: 1500,
// 1.5 seconds to darken quickly
easing: tween.easeInOut
});
// Flash screen effect to simulate lightning
LK.effects.flashScreen(0xffffff, 300);
// End storm mode after 20 seconds
LK.setTimeout(function () {
isStormMode = false;
tween(stormOverlay, {
alpha: 0
}, {
duration: 2000,
// 2 seconds to clear
easing: tween.easeOut
});
// Spawn flowers where rain drops fell most frequently
var flowerSpawnAreas = [];
var gridSize = 150; // Size of each grid cell for density calculation
// Create density map based on rain drop locations
for (var r = 0; r < rainDropLocations.length; r++) {
var location = rainDropLocations[r];
var gridX = Math.floor(location.x / gridSize);
var gridY = Math.floor(location.y / gridSize);
var gridKey = gridX + "," + gridY;
if (!flowerSpawnAreas[gridKey]) {
flowerSpawnAreas[gridKey] = {
count: 0,
totalX: 0,
totalY: 0,
avgX: 0,
avgY: 0
};
}
flowerSpawnAreas[gridKey].count++;
flowerSpawnAreas[gridKey].totalX += location.x;
flowerSpawnAreas[gridKey].totalY += location.y;
}
// Calculate average positions and spawn flowers in high-density areas
for (var key in flowerSpawnAreas) {
var area = flowerSpawnAreas[key];
if (area.count >= 5) {
// Minimum 5 rain drops to spawn a flower
area.avgX = area.totalX / area.count;
area.avgY = area.totalY / area.count;
// Spawn multiple flowers based on density
var flowerCount = Math.min(Math.floor(area.count / 3), 4); // 1 flower per 3 rain drops, max 4
for (var f = 0; f < flowerCount; f++) {
var flower = game.addChild(new Flower(area.avgX + (Math.random() - 0.5) * 80,
// Small random offset
area.avgY + (Math.random() - 0.5) * 40));
// Position flower behind players but in front of background elements
// Count background elements (background, backgroundOverlay, backgroundTop) = 3
var backgroundElementCount = 3;
var platformCount = 4; // mainPlatform, leftPlatform, rightPlatform, topPlatform
var textureCount = 4; // platform textures
var overlayCount = 2; // nightOverlay, stormOverlay
var totalBackgroundElements = backgroundElementCount + platformCount + textureCount + overlayCount;
game.setChildIndex(flower, totalBackgroundElements);
flowers.push(flower);
}
}
}
// Clear rain drop tracking for next storm
rainDropLocations = [];
// Clear all rain and lightning when storm ends
for (var r = rainDrops.length - 1; r >= 0; r--) {
if (rainDrops[r]) {
rainDrops[r].destroy();
rainDrops.splice(r, 1);
}
}
for (var l = lightningBolts.length - 1; l >= 0; l--) {
if (lightningBolts[l]) {
lightningBolts[l].destroy();
lightningBolts.splice(l, 1);
}
}
}, 20000); // 20 seconds duration
}
}
// Storm rain and lightning effects
if (isStormMode) {
// Fixed rain frequency - no increase over time
var rainFrequency = 3; // Spawn rain every 3 frames
// Spawn rain drops at fixed rate
if (LK.ticks % rainFrequency === 0) {
var rainDrop = game.addChild(new RainDrop());
// Center rain drop spawn around main platform (1648 is main platform X center)
var mainPlatformX = 1648;
var spawnRadius = 800; // Spawn radius around main platform
// Calculate camera movement to spawn rain ahead of camera direction
var cameraDeltaX = targetCameraX - cameraX;
var cameraDeltaY = targetCameraY - cameraY;
// Base spawn area centered on main platform
var baseSpawnX = mainPlatformX - spawnRadius / 2 + Math.random() * spawnRadius;
var baseSpawnY = cameraY - 600;
// Offset spawn position based on camera movement direction
var movementMultiplier = 2.0; // How much to lead the camera movement
rainDrop.x = baseSpawnX + cameraDeltaX * movementMultiplier;
rainDrop.y = baseSpawnY + cameraDeltaY * movementMultiplier * 0.5;
// Ensure rain spawns within bounds centered around main platform
var minX = mainPlatformX - spawnRadius;
var maxX = mainPlatformX + spawnRadius;
if (rainDrop.x < minX) {
rainDrop.x = minX;
}
if (rainDrop.x > maxX) {
rainDrop.x = maxX;
}
if (rainDrop.y > -50) {
rainDrop.y = -50;
}
rainDrops.push(rainDrop);
}
// Random lightning strikes
if (Math.random() < 0.005) {
// 0.5% chance per frame for lightning
var lightning = game.addChild(new Lightning());
lightning.x = 200 + Math.random() * 1648; // Random X position
lightning.y = 0; // Start at top of screen
lightningBolts.push(lightning);
// Flash screen white for lightning effect
LK.effects.flashScreen(0xffffff, 200);
}
}
// Night mode cycle
if (!isStormMode) {
// Only increment timer when storm mode is not active
nightModeTimer++;
}
if (nightModeTimer >= nightModeDuration && !isStormMode) {
// Only proceed if storm mode is not active
nightModeTimer = 0;
// Reset duration for next cycle (20-30 seconds)
nightModeDuration = 1200 + Math.random() * 600;
if (!isNightMode) {
// Start night mode - darken slowly
isNightMode = true;
tween(nightOverlay, {
alpha: 0.75
}, {
duration: 3000,
// 3 seconds to darken
easing: tween.easeInOut
});
// Spawn fireflies when night mode starts - they appear from outside screen and move in
for (var f = 0; f < 5; f++) {
var firefly = game.addChild(new Firefly());
// Start fireflies outside the visible screen area
var side = Math.floor(Math.random() * 4); // 0=top, 1=right, 2=bottom, 3=left
var startX, startY, targetX, targetY;
if (side === 0) {
// From top
startX = Math.random() * 2048;
startY = -200;
targetX = startX + (Math.random() - 0.5) * 400; // Some horizontal drift
targetY = 200 + Math.random() * 1000;
} else if (side === 1) {
// From right
startX = 2248;
startY = Math.random() * 2732;
targetX = 1400 + Math.random() * 600;
targetY = startY + (Math.random() - 0.5) * 400; // Some vertical drift
} else if (side === 2) {
// From bottom
startX = Math.random() * 2048;
startY = 2932;
targetX = startX + (Math.random() - 0.5) * 400; // Some horizontal drift
targetY = 1800 + Math.random() * 800;
} else {
// From left
startX = -200;
startY = Math.random() * 2732;
targetX = 200 + Math.random() * 600;
targetY = startY + (Math.random() - 0.5) * 400; // Some vertical drift
}
// Ensure target positions are within reasonable scene bounds
if (targetX < 100) {
targetX = 100;
}
if (targetX > 1948) {
targetX = 1948;
}
if (targetY < 200) {
targetY = 200;
}
if (targetY > 2500) {
targetY = 2500;
}
// Set starting position
firefly.x = startX;
firefly.y = startY;
// Animate firefly moving into the scene
var duration = 2000 + Math.random() * 3000; // 2-5 seconds to enter
tween(firefly, {
x: targetX,
y: targetY
}, {
duration: duration,
easing: tween.easeOut
});
fireflies.push(firefly);
}
} else {
// End night mode - brighten slowly
isNightMode = false;
tween(nightOverlay, {
alpha: 0
}, {
duration: 2000,
// 2 seconds to brighten
easing: tween.easeInOut
});
// Fade out all fireflies when night mode ends
for (var f = fireflies.length - 1; f >= 0; f--) {
if (fireflies[f]) {
var firefly = fireflies[f];
tween(firefly, {
alpha: 0
}, {
duration: 2000,
easing: tween.easeOut,
onFinish: function onFinish() {
firefly.destroy();
// Remove from array
for (var i = fireflies.length - 1; i >= 0; i--) {
if (fireflies[i] === firefly) {
fireflies.splice(i, 1);
break;
}
}
}
});
}
}
}
}
// Keep dynamic overlays on top of all elements
if (dynamicOverlay && dynamicOverlay.parent) {
var topIndex = game.children.length - 1;
if (game.getChildIndex(dynamicOverlay) !== topIndex) {
game.setChildIndex(dynamicOverlay, topIndex);
}
}
// Keep second dynamic overlay on top as well
if (dynamicOverlay2 && dynamicOverlay2.parent) {
var topIndex2 = game.children.length - 1;
if (game.getChildIndex(dynamicOverlay2) !== topIndex2 - 1) {
// Position just below first overlay
game.setChildIndex(dynamicOverlay2, topIndex2 - 1);
}
}
// Apply smooth camera movement
applyCameraSmoothing();
};
Slime azul, pixelart. In-Game asset. 2d. High contrast. No shadows
Slime rojo, pixelart. In-Game asset. 2d. High contrast. No shadows
Slime amarillo, pixelart. In-Game asset. 2d. High contrast. No shadows
Slime verde, pixelart. In-Game asset. 2d. High contrast. No shadows
Montañas en atardecer pixelart. In-Game asset. 2d. High contrast. No shadows
Elimina la montaña
Burbuja azul, una , pixelart. In-Game asset. 2d. High contrast. No shadows
Esfera amarilla con la letra P , pixelart. In-Game asset. 2d. High contrast. No shadows
Flecha azul de Slime , pixelart. In-Game asset. 2d. High contrast. No shadows
Cámbiale el color a rojo , pixelart
Cámbiale el color a amarillo, pixelart
Cámbiale el color a verde, pixelart
Apaga las luces de la ventanas
Luciérnaga, pixelart. In-Game asset. 2d. High contrast. No shadows
Cabeza de girasol, pixelart. In-Game asset. 2d. High contrast. No shadows
Número 1 azul , pixelart. In-Game asset. 2d. High contrast. No shadows
Número 0 azul, pixelart. In-Game asset. 2d. High contrast. No shadows
Número 3 azul, pixelart. In-Game asset. 2d. High contrast. No shadows
Retro, pixelart