User prompt
Add area4 for level2 on space have no grounds have small terrains 300x100px & time closer to the top by 400px time closer to the bottom by 400px, that terrains have one moving tank horizontal go to the left and to the right repeatedly.
User prompt
there is no score saved from recent game! ↪💡 Consider importing and using the following plugins: @upit/storage.v1
User prompt
Remove Highscore background that is in highscore button and add the score background that appears at the last of the game or when gameover to show the recent score gameplay.
User prompt
Add area3 in level2 with 7 enemies respawning like serpent of 7 enemies ships they reach the middle of the screen then they back diagonal at top right or bottom right then they go again to pass the player, respawn the serpent of 7 enemiesships 1 to 2 max at once.
User prompt
let the vertical wall that have 4 enemies taller to 1000
User prompt
reduce the shooting time each 3 shots in level2 for the turrets enemies
User prompt
remove the score background don't show it only if game is ended or lives x 0.
User prompt
Create level2 with another assets when boss is killed go to level2 : The first area of 500 miles have grounds from bottom and top as in level one but between it not mountains add vertical walls each distance of 50 miles respawn a wall can be destroyed by shooting its 4 enemies, the wall have 4 turrets expanded on it & can shoot as in level one but with blue color shots. The second area of 500 miles have different enemies like diagonal square in the middle of the screen can shoot from the 4 side at once, respawn this diagonal enemy each 50 miles and on the grounds do tanks each distance of 10 miles. Add new assets for level 2 including vertical walls, wall turrets, diagonal enemies, tanks, and blue bullets.
User prompt
Show only Highscores in background2 when Highscorebutton is clicked
User prompt
change the code to this one // Save high score with account ID before game over var currentScore = LK.getScore(); var accountId = storage.accountId; if (accountId) { // Save score with account ID by directly setting storage property storage['highScore_' + accountId] = Math.max(storage['highScore_' + accountId] || 0, currentScore); // Also save player name if available if (!storage['playerName_' + accountId]) { storage['playerName_' + accountId] = storage.playerName || "Player"; } } else { // Fallback for users without account ID storage.highScore = Math.max(storage.highScore || 0, currentScore); } ↪💡 Consider importing and using the following plugins: @upit/storage.v1
User prompt
Add scoretext to background2 to display new scores in it when the game is finished update the background2 with the new scores. ↪💡 Consider importing and using the following plugins: @upit/storage.v1
User prompt
Add score texts in background2 for the new scores.
User prompt
Update scores in one background 'background2' when Button Highscore is clicked not in the screen that appears after win lose.
User prompt
Don't show scores by ID show it by name of the Upit account if logged in.
User prompt
Remove background2 from Highscore button
User prompt
Remove background2 from Highscore button
User prompt
Remove background2 from Highscore button
User prompt
Duplicate the same score of the new players in background of highescorebutton that is in the intro!
User prompt
Replace the background screen of score that appears after win/lose with background2 asset.
User prompt
Replace the background2 with background that appears after win/los.
User prompt
Use the background2 for showing scores don't create background before win or lose!
User prompt
Fix score background2 when click its button nothing showing there no scores of any player!
User prompt
Don't show scores only in the end background, show it in background2 of the HighScorebutton always update it as a list there.
User prompt
save the score that the player get at the end of the game in the Highscorebutton background2. add more score texts to save in it as slots below each others. ↪💡 Consider importing and using the following plugins: @upit/storage.v1
User prompt
Don't clear the list of scores save it forever, if the same player do new highscore remove its old one and replace the highest one. Show all new scores when the button of highscore is clicked.
/****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
var storage = LK.import("@upit/storage.v1", {
highscoresData: "[]"
});
/****
* Classes
****/
// Air Enemy Class (Snake Segment - Simplified for MVP)
var AirEnemy = Container.expand(function (startX, startY) {
var self = Container.call(this);
var enemyGraphics = self.attachAsset('airEnemy', {
anchorX: 0.5,
anchorY: 0.5
});
self.x = startX;
self.y = startY;
self.speedX = 4 + Math.random() * 2; // Horizontal speed
self.amplitudeY = 100 + Math.random() * 100; // Vertical movement range
self.frequencyY = 0.01 + Math.random() * 0.01; // Vertical movement speed
self.fireRate = 150; // Ticks between shots
self.fireCooldown = Math.random() * self.fireRate;
self.update = function () {
// Basic horizontal and sinusoidal vertical movement
self.x -= self.speedX;
self.y = startY + Math.sin(LK.ticks * self.frequencyY + startX) * self.amplitudeY; // Use startX for phase offset
// Firing logic
self.fireCooldown++;
if (self.fireCooldown >= self.fireRate) {
self.fireCooldown = 0;
// Fire a single bullet straight left
var bulletSpeed = 8;
fireEnemyBullet(self.x, self.y, -bulletSpeed, 0); // vx = -speed, vy = 0
}
// Boundary check (simple respawn logic)
if (self.x < -enemyGraphics.width) {
self.x = 2048 + enemyGraphics.width; // Respawn on the right
startY = Math.random() * (2732 - 400) + 200; // Random Y position
}
};
return self;
});
// Boss Class (Basic structure for future)
var Boss = Container.expand(function () {
var self = Container.call(this);
var bossGraphics = self.attachAsset('boss', {
anchorX: 0.5,
anchorY: 0.5
});
self.x = 2048 - 300; // Position on the right
self.y = 2732 / 2; // Center vertically
self.fireRate = 180; // Ticks between laser bursts
self.fireCooldown = 0;
self.health = 100; // Example health
self.update = function () {
// Add movement logic if needed
self.fireCooldown++;
if (self.fireCooldown >= self.fireRate) {
self.fireCooldown = 0;
fireBossLasers(self.x, self.y);
}
};
return self;
});
// Boss Laser Class (Basic structure for future)
var BossLaser = Container.expand(function (startX, startY, vx, vy) {
var self = Container.call(this);
var laserGraphics = self.attachAsset('bossLaser', {
anchorX: 0.5,
anchorY: 0.5 // Anchor center
});
self.x = startX;
self.y = startY;
self.vx = vx;
self.vy = vy;
// Set rotation based on velocity direction
self.rotation = Math.atan2(vy, vx) + Math.PI / 2; // Point laser in direction of travel (+90deg adjustment needed depending on asset orientation)
self.update = function () {
self.x += self.vx;
self.y += self.vy;
};
return self;
});
// Bottom Mountain Obstacle Class
var BottomMountain = Container.expand(function () {
var self = Container.call(this);
var mountainGraphics = self.attachAsset('bottommountain', {
anchorX: 0.5,
anchorY: 0.75 // Anchor base at terrain edge
});
self.speed = 5; // Should match terrain speed
self.update = function () {
self.x -= self.speed;
};
return self;
});
// DiagonalEnemy Class for Level 2
var DiagonalEnemy = Container.expand(function (xPos, yPos) {
var self = Container.call(this);
var graphics = self.attachAsset('diagonalEnemy', {
anchorX: 0.5,
anchorY: 0.5
});
graphics.rotation = Math.PI / 4;
self.x = xPos;
self.y = yPos;
self.speed = terrainSpeed + 1; // Slightly faster than terrain
self.health = 4;
self.fireRate = 160;
self.fireCooldown = Math.random() * self.fireRate;
self.isDestroyed = false;
self.update = function () {
if (self.isDestroyed) return;
self.x -= self.speed;
self.fireCooldown++;
if (self.fireCooldown >= self.fireRate) {
self.fireCooldown = 0;
var bulletSpeed = 7;
fireEnemyBullet(self.x, self.y, bulletSpeed, 0, 'enemyBulletBlue'); // Right
fireEnemyBullet(self.x, self.y, -bulletSpeed, 0, 'enemyBulletBlue'); // Left
fireEnemyBullet(self.x, self.y, 0, bulletSpeed, 'enemyBulletBlue'); // Down
fireEnemyBullet(self.x, self.y, 0, -bulletSpeed, 'enemyBulletBlue'); // Up
}
};
self.takeDamage = function () {
if (self.isDestroyed) return false;
self.health--;
LK.effects.flashObject(self, 0xFFFFFF, 100);
if (self.health <= 0) {
self.isDestroyed = true;
LK.getSound('enemyExplosion').play();
var index = diagonalEnemies.indexOf(self);
if (index > -1) {
diagonalEnemies.splice(index, 1);
}
self.destroy();
return true;
}
return false;
};
return self;
});
// Enemy Bullet Class
var EnemyBullet = Container.expand(function (startX, startY, vx, vy, assetId) {
var self = Container.call(this);
// Added assetId
var bulletGraphics = self.attachAsset(assetId || 'enemyBullet', {
// Use assetId or default
anchorX: 0.5,
anchorY: 0.5
});
self.x = startX;
self.y = startY;
self.vx = vx; // Horizontal velocity
self.vy = vy; // Vertical velocity
self.update = function () {
self.x += self.vx;
self.y += self.vy;
};
return self;
});
// Ground Enemy Class
var GroundEnemy = Container.expand(function (isTop) {
var self = Container.call(this);
var enemyGraphics = self.attachAsset(isTop ? 'groundEnemytop' : 'groundEnemybottom', {
anchorX: 0.5,
anchorY: isTop ? 0 : 1 // Anchor base at terrain edge
});
self.isTop = isTop;
self.speed = 5; // Should match terrain speed
self.fireRate = 120; // Ticks between firing sequences (2 seconds)
self.fireCooldown = Math.random() * self.fireRate; // Random initial delay
self.shotsInBurst = 3;
self.burstDelay = 10; // Ticks between shots in a burst
self.update = function () {
self.x -= self.speed;
self.fireCooldown++;
if (self.fireCooldown >= self.fireRate) {
self.fireCooldown = 0; // Reset cooldown
// Fire burst
for (var i = 0; i < self.shotsInBurst; i++) {
LK.setTimeout(function () {
// Check if enemy still exists before firing
if (!self.destroyed) {
var bulletSpeed = 8;
// Calculate direction logic is now in fireEnemyBullet, but still need to pass initial values
var vx = -bulletSpeed * 0.707; // Default direction if player not available
var vy = self.isTop ? bulletSpeed * 0.707 : -bulletSpeed * 0.707; // Default direction
fireEnemyBullet(self.x, self.y, vx, vy);
}
}, i * self.burstDelay);
}
}
};
return self;
});
// Player Bullet Class
var PlayerBullet = Container.expand(function (startX, startY) {
var self = Container.call(this);
var bulletGraphics = self.attachAsset('playerBullet', {
anchorX: 0.5,
anchorY: 0.5
});
self.x = startX;
self.y = startY;
self.speed = 20; // Moves to the right
self.update = function () {
self.x += self.speed;
};
return self;
});
// Tank Class for Level 2
var Tank = Container.expand(function (isTop, initialX) {
var self = Container.call(this);
var terrainHeight = 200; // Assuming same terrain height as level 1
var graphics = self.attachAsset('tank', {
anchorX: 0.5,
anchorY: isTop ? 0 : 1
});
self.x = initialX;
self.y = isTop ? terrainHeight : 2732 - terrainHeight;
self.isTop = isTop;
self.speed = terrainSpeed;
self.health = 6; // Tanks are sturdy
self.fireRate = 170;
self.fireCooldown = Math.random() * self.fireRate;
self.isDestroyed = false;
self.update = function () {
if (self.isDestroyed) return;
self.x -= self.speed;
self.fireCooldown++;
if (self.fireCooldown >= self.fireRate) {
self.fireCooldown = 0;
var bulletSpeed = 8;
var vx = -bulletSpeed * 0.707; // Aim slightly forward
var vyDirection = self.isTop ? bulletSpeed * 0.707 : -bulletSpeed * 0.707;
fireEnemyBullet(self.x, self.y, vx, vyDirection, 'enemyBulletBlue');
}
};
self.takeDamage = function () {
if (self.isDestroyed) return false;
self.health--;
LK.effects.flashObject(self, 0xFFFFFF, 100);
if (self.health <= 0) {
self.isDestroyed = true;
LK.getSound('enemyExplosion').play();
var index = tanks.indexOf(self);
if (index > -1) {
tanks.splice(index, 1);
}
self.destroy();
return true;
}
return false;
};
return self;
});
// Terrain Segment Class
var TerrainSegment = Container.expand(function (isTop) {
var self = Container.call(this);
var terrainGraphics = self.attachAsset('terrain', {
anchorX: 0,
anchorY: isTop ? 0 : 1 // Anchor at top for top terrain, bottom for bottom terrain
});
self.isTop = isTop;
self.speed = 5; // Horizontal scroll speed
self.update = function () {
self.x -= self.speed;
};
return self;
});
// Top Mountain Obstacle Class
var TopMountain = Container.expand(function () {
var self = Container.call(this);
var mountainGraphics = self.attachAsset('topmountain', {
anchorX: 0.5,
anchorY: 0.3 // Anchor base at terrain edge
});
self.speed = 5; // Should match terrain speed
self.update = function () {
self.x -= self.speed;
};
return self;
});
// Player UFO Class
var UFO = Container.expand(function () {
var self = Container.call(this);
var ufoGraphics = self.attachAsset('ufo', {
anchorX: 0.5,
anchorY: 0.5
});
self.speed = 10; // Movement speed multiplier, adjust as needed
self.isDead = false;
self.invincibleUntil = 0;
// Keep UFO within game boundaries
self.clampPosition = function () {
var halfWidth = ufoGraphics.width / 2;
var halfHeight = ufoGraphics.height / 2;
if (self.x < halfWidth) {
self.x = halfWidth;
}
if (self.x > 2048 - halfWidth) {
self.x = 2048 - halfWidth;
}
if (self.y < halfHeight + 100) {
self.y = halfHeight + 100;
} // Avoid top-left menu area
if (self.y > 2732 - halfHeight) {
self.y = 2732 - halfHeight;
}
// Adjust based on terrain phase (Level 1 Phase 0 or if it's Level 2 which also has terrain)
if (gamePhase === 0 || currentLevel === 2) {
// Find the current terrain height at the UFO's x position (simplified)
var terrainHeight = 200; // Assuming constant terrain height for now
if (self.y < terrainHeight + halfHeight) {
self.y = terrainHeight + halfHeight;
}
if (self.y > 2732 - terrainHeight - halfHeight) {
self.y = 2732 - terrainHeight - halfHeight;
}
}
};
return self;
});
// VerticalWall Class for Level 2
var VerticalWall = Container.expand(function (xPos) {
var self = Container.call(this);
var wallGraphicAsset = LK.getAsset('verticalWall', {});
var graphics = self.attachAsset('verticalWall', {
anchorX: 0.5,
anchorY: 0.5
});
self.width = wallGraphicAsset.width; // Store actual width for calculations
self.height = wallGraphicAsset.height; // Store actual height
self.x = xPos;
self.y = 2732 / 2;
self.speed = terrainSpeed;
self.turrets = [];
self.activeTurrets = 4;
self.isDestroyed = false;
// Wall graphics anchor is 0.5, 0.5.
// Turrets are positioned on the front-facing side of the wall.
// Spread 4 turrets along the height.
var turretPositions = [-0.35 * self.height, -0.15 * self.height, 0.15 * self.height, 0.35 * self.height]; // Relative Y from center
for (var i = 0; i < 4; i++) {
// Turrets are on the "front" (left edge for player) of the wall
var turret = new WallTurret(self, -0.4, turretPositions[i]); // relX as factor of half-width, relY absolute
self.turrets.push(turret);
game.addChild(turret);
}
self.update = function () {
if (self.isDestroyed) return;
self.x -= self.speed;
};
self.turretDestroyed = function (turret) {
if (self.isDestroyed) return;
self.activeTurrets--;
if (self.activeTurrets <= 0) {
self.isDestroyed = true;
LK.getSound('bossExplosion').play();
var wallIndex = verticalWalls.indexOf(self);
if (wallIndex > -1) {
verticalWalls.splice(wallIndex, 1);
}
// Ensure remaining turrets are cleaned up if any edge case
self.turrets.forEach(function (t) {
if (t && !t.isDestroyed) t.destroy();
});
self.destroy();
}
};
return self;
});
// WallTurret Class for Level 2
var WallTurret = Container.expand(function (wall, relX, relY) {
var self = Container.call(this);
var graphics = self.attachAsset('wallTurret', {
anchorX: 0.5,
anchorY: 0.5
});
self.parentWall = wall;
self.relX = relX;
self.relY = relY;
self.health = 2; // Turrets are a bit tougher
self.fireRate = 130;
self.fireCooldown = Math.random() * self.fireRate;
self.isDestroyed = false;
self.update = function () {
if (self.isDestroyed || !self.parentWall || self.parentWall.isDestroyed) {
if (!self.isDestroyed) self.destroy(); // Self-cleanup if parent is gone
return;
}
// Update position relative to the moving wall
self.x = self.parentWall.x + self.relX * (self.parentWall.width / 2); // relX is factor of half-width
self.y = self.parentWall.y + self.relY;
self.fireCooldown++;
if (self.fireCooldown >= self.fireRate) {
self.fireCooldown = 0;
fireEnemyBullet(self.x, self.y, -9, 0, 'enemyBulletBlue'); // Shoots blue bullets left
}
};
self.takeDamage = function () {
if (self.isDestroyed) return false;
self.health--;
LK.effects.flashObject(self, 0xFFFFFF, 100);
if (self.health <= 0) {
self.isDestroyed = true;
LK.getSound('enemyExplosion').play();
if (self.parentWall && !self.parentWall.isDestroyed) {
self.parentWall.turretDestroyed(self);
}
self.destroy();
return true;
}
return false;
};
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0x101030 // Dark space blue background
});
/****
* Game Code
****/
// Placeholder for Level 2 music
// Deep Sky Blue
// Forest Green
// Dark Orange
// Grey turret
// Brownish wall
// Level 2 Assets
// Game State
/****
* Assets
* Assets are automatically created and loaded either dynamically during gameplay
* or via static code analysis based on their usage in the code.
****/
// Initialize assets used in this game. Scale them according to what is needed for the game.
// Player UFO
// Ground/Ceiling
// Mountain Obstacle (simplified)
// Ground Enemy
// Enemy Bullet
// Air Enemy (Placeholder shape)
// Boss
// Boss Laser Beam
// Minimalistic tween library which should be used for animations over time
// LK.init.image('mountain', {width:150, height:250, id:'6819884bc4a0c8bae9e84ae0'}) // Removed - Replaced by top/bottom mountains
var isGameStarted = false; // Flag to track if game has started
var showingHighscores = false; // Flag to track if highscore screen is shown
var gamePhase = 0; // 0: Terrain, 1: Space, 2: Boss
var phaseStartTime = LK.ticks;
var phaseDuration = 3600; // 1 minute (60 seconds * 60 fps = 3600 ticks)
var terrainSpeed = 5;
var scoreIncrementTimer = 0;
var highscoreBackground = null;
var highscoreTexts = [];
// Game Objects
var player = null;
var terrainSegmentsTop = [];
var terrainSegmentsBottom = [];
var mountains = [];
var groundEnemies = [];
var enemyBullets = [];
var airEnemies = []; // Simple air enemies for MVP phase 2
var boss = null;
var bossLasers = [];
var playerBullets = []; // Array for player bullets
// Player Control
var dragNode = null;
var playerFireRate = 15; // Ticks between shots (4 shots per second)
var playerFireCooldown = 0;
// Level 2 State and Constants
var currentLevel = 1;
var level2Progress = 0; // In pixels, for current level 2 progression
var level2Started = false;
var lastWallSpawnProgress = 0;
var lastDiagonalEnemySpawnProgress = 0;
var lastTankSpawnProgress = 0;
var verticalWalls = [];
var diagonalEnemies = [];
var tanks = [];
// enemyBulletsBlue will use the existing enemyBullets array
var PIXELS_PER_MILE = 250; // Adjusted for gameplay feel
var LEVEL2_AREA1_END_MILES = 500;
var LEVEL2_AREA2_DURATION_MILES = 500; // Duration of Area 2
var WALL_SPAWN_INTERVAL_MILES = 50;
var DIAGONAL_ENEMY_SPAWN_INTERVAL_MILES = 50;
var TANK_SPAWN_INTERVAL_MILES = 10;
// Player Lives
var MAX_PLAYER_LIVES = 5;
var playerLives = MAX_PLAYER_LIVES;
var livesTxt;
// Score Display
var scoreTxt = new Text2('0', {
size: 100,
fill: 0xFFFFFF
});
scoreTxt.anchor.set(0.5, 0);
LK.gui.top.addChild(scoreTxt); // Position score at top center
// Helper function to create terrain segments
function createTerrainSegment(isTop, xPos) {
var segment = new TerrainSegment(isTop);
segment.x = xPos;
segment.y = isTop ? 0 : 2732;
segment.speed = terrainSpeed;
game.addChild(segment);
if (isTop) {
terrainSegmentsTop.push(segment);
} else {
terrainSegmentsBottom.push(segment);
}
return segment;
}
// Helper function to spawn mountains on terrain
function spawnMountain(terrainSegment) {
var mountain;
if (terrainSegment.isTop) {
mountain = new TopMountain();
} else {
mountain = new BottomMountain();
}
// Position relative to the terrain segment
// Ensure we use the actual height of the terrain graphic for positioning
var terrainGraphic = terrainSegment.children[0]; // Assuming the graphic is the first child
mountain.x = terrainSegment.x + Math.random() * terrainGraphic.width;
// Position based on whether it's a top or bottom mountain
mountain.y = terrainSegment.isTop ? terrainGraphic.height : 2732 - terrainGraphic.height;
mountain.speed = terrainSpeed;
game.addChild(mountain);
mountains.push(mountain);
}
// Helper function to spawn ground enemies on terrain
function spawnGroundEnemy(terrainSegment) {
var enemy = new GroundEnemy(terrainSegment.isTop);
// Position relative to the terrain segment
// Ensure we use the actual height of the terrain graphic for positioning
var terrainGraphic = terrainSegment.children[0]; // Assuming the graphic is the first child
enemy.x = terrainSegment.x + Math.random() * terrainGraphic.width;
enemy.y = terrainSegment.isTop ? terrainGraphic.height : 2732 - terrainGraphic.height;
enemy.speed = terrainSpeed;
game.addChild(enemy);
groundEnemies.push(enemy);
}
// Helper function to fire enemy bullets
function fireEnemyBullet(x, y, vx, vy, assetId) {
// Added assetId parameter
// If player exists and is not dead, target the player instead of using fixed direction
if (player && !player.isDead) {
// Calculate direction vector to player
var dx = player.x - x;
var dy = player.y - y;
// Normalize the vector
var distance = Math.sqrt(dx * dx + dy * dy);
// Use the bullet speed provided (or calculate from vx and vy if assetId is for styling only)
// For simplicity, let's assume the passed vx,vy define the base speed.
var baseSpeed = Math.sqrt(vx * vx + vy * vy);
if (distance > 0 && baseSpeed > 0) {
// Ensure distance and baseSpeed are valid
vx = dx / distance * baseSpeed;
vy = dy / distance * baseSpeed;
}
// If player is not available or too close, use original vx, vy
}
var bullet = new EnemyBullet(x, y, vx, vy, assetId); // Pass assetId to constructor
game.addChild(bullet);
enemyBullets.push(bullet);
LK.getSound('enemyShoot').play();
}
// Helper function to spawn air enemies (simple version)
function spawnAirEnemy() {
var startY = Math.random() * (2732 - 400) + 200; // Avoid edges
var enemy = new AirEnemy(2048 + 100, startY); // Start off-screen right
game.addChild(enemy);
airEnemies.push(enemy);
}
// Helper function to fire boss lasers
function fireBossLasers(x, y) {
var directions = 5;
var laserSpeed = 12;
var verticalSpread = 4; // Max vertical speed component
for (var i = 0; i < directions; i++) {
// Calculate vertical velocity component for spread
// Example: i=0 -> -4, i=1 -> -2, i=2 -> 0, i=3 -> 2, i=4 -> 4
var vy = -verticalSpread + verticalSpread * 2 / (directions - 1) * i;
// Keep horizontal speed constant (moving left)
var vx = -laserSpeed;
var laser = new BossLaser(x, y, vx, vy);
game.addChild(laser);
bossLasers.push(laser);
}
// Add sound effect for laser fire (consider adding one e.g., LK.getSound('bossShoot').play(); if asset exists)
}
// Initialize Game Elements
function initGame() {
// Reset state
LK.setScore(0);
scoreTxt.setText('0');
// Hide score during intro
scoreTxt.visible = !isGameStarted;
// Initialize Lives
playerLives = MAX_PLAYER_LIVES;
if (!livesTxt) {
// Create only if it doesn't exist (for game restarts)
livesTxt = new Text2("Lives: x" + playerLives, {
size: 80,
// Slightly smaller than score
fill: 0xFFFFFF
});
livesTxt.anchor.set(1, 0); // Anchor top-right
LK.gui.topRight.addChild(livesTxt);
} else {
livesTxt.setText("Lives: x" + playerLives);
}
gamePhase = 0;
phaseStartTime = LK.ticks;
dragNode = null;
playerBullets = []; // Clear player bullets
playerFireCooldown = 0; // Reset fire cooldown
// Clear existing elements from previous game (if any)
// Note: LK engine handles full reset on GameOver/YouWin, but manual cleanup might be needed if restarting mid-game (not typical)
// Let's assume full reset is handled by LK.
// Create Player
player = new UFO();
player.x = 300;
player.y = 2732 / 2;
player.isDead = false;
player.invincibleUntil = 0;
player.alpha = 1; // Ensure player is visible
game.addChild(player);
// Create initial terrain
var terrainWidth = LK.getAsset('terrain', {}).width; // Get width from asset
for (var i = 0; i < Math.ceil(2048 / terrainWidth) + 1; i++) {
createTerrainSegment(true, i * terrainWidth);
createTerrainSegment(false, i * terrainWidth);
}
// Start Phase 1 Music
LK.playMusic('phase1Music');
}
function initLevel2() {
if (level2Started) return;
level2Started = true;
currentLevel = 2; // Explicitly set, though might be set at transition point too
level2Progress = 0; // Reset progress for Level 2
console.log("Initializing Level 2");
LK.playMusic('level2Music'); // Play Level 2 music
// Clean up any remaining Level 1 specific elements like air enemies
airEnemies.forEach(function (ae) {
if (ae && !ae.destroyed) ae.destroy();
});
airEnemies = [];
// Boss should already be gone
if (boss && !boss.destroyed) boss.destroy();
boss = null;
bossLasers.forEach(function (l) {
if (l && !l.destroyed) l.destroy();
});
bossLasers = [];
// Reset spawn progress markers for Level 2 elements
lastWallSpawnProgress = 0;
lastDiagonalEnemySpawnProgress = 0;
lastTankSpawnProgress = 0;
// Ensure terrain continues if it was cleared (e.g., after boss fight if phases stopped it)
// The main terrain update loop should be modified to run during currentLevel === 2
var terrainWidth = LK.getAsset('terrain', {}).width;
var screenWidth = 2048;
var numSegmentsNeeded = Math.ceil(screenWidth / terrainWidth) + 1;
if (terrainSegmentsTop.length < numSegmentsNeeded) {
for (var i = terrainSegmentsTop.length; i < numSegmentsNeeded; i++) {
var xPos = i * terrainWidth;
// Find max X of existing segments to append correctly
if (terrainSegmentsTop.length > 0) {
xPos = terrainSegmentsTop.reduce(function (max, s) {
return s.x > max ? s.x : max;
}, 0) + terrainWidth;
}
createTerrainSegment(true, xPos);
}
}
if (terrainSegmentsBottom.length < numSegmentsNeeded) {
for (var i = terrainSegmentsBottom.length; i < numSegmentsNeeded; i++) {
var xPos = i * terrainWidth;
if (terrainSegmentsBottom.length > 0) {
xPos = terrainSegmentsBottom.reduce(function (max, s) {
return s.x > max ? s.x : max;
}, 0) + terrainWidth;
}
createTerrainSegment(false, xPos);
}
}
// Announce Level 2 Start (Optional visual cue)
var level2Text = new Text2("LEVEL 2", {
size: 150,
fill: 0xFFFF00
});
level2Text.anchor.set(0.5, 0.5);
level2Text.x = 2048 / 2;
level2Text.y = 2732 / 3;
game.addChild(level2Text);
LK.setTimeout(function () {
if (level2Text && !level2Text.destroyed) level2Text.destroy();
}, 3000); // Display for 3 seconds
// gamePhase variable might be set to a new value (e.g., 3) if other systems rely on it.
// For now, currentLevel = 2 will gate Level 2 logic.
gamePhase = 3; // Indicate a new phase distinct from level 1's 0, 1, 2.
}
// Helper function already moved above
// Helper function to save highscore
function saveHighscore(score) {
// Save high score with account ID before game over
var currentScore = LK.getScore();
var accountId = storage.accountId;
if (accountId) {
// Save score with account ID by directly setting storage property
storage['highScore_' + accountId] = Math.max(storage['highScore_' + accountId] || 0, currentScore);
// Also save player name if available
if (!storage['playerName_' + accountId]) {
storage['playerName_' + accountId] = storage.playerName || "Player";
}
} else {
// Fallback for users without account ID
storage.highScore = Math.max(storage.highScore || 0, currentScore);
}
}
// The createScoreBackground function is now defined earlier in the code
// This duplicate definition has been moved earlier in the code
// Helper function to handle player death
function handlePlayerDeath() {
if (!player || player.isDead) {
return;
}
// Event Handlers
player.isDead = true;
player.alpha = 0; // Make player invisible during death processing
LK.getSound('playerExplosion').play();
// Optional: Flash the player before making them fully invisible, or flash screen
// LK.effects.flashObject(player, 0xFF0000, 500);
LK.effects.flashScreen(0xFF0000, 200); // Short screen flash
playerLives--;
if (livesTxt) {
livesTxt.setText("Lives: x" + playerLives);
}
LK.setTimeout(function () {
if (playerLives > 0) {
respawnPlayer();
} else {
// Save highscore before showing Game Over
saveHighscore(LK.getScore());
// Create background for scores before showing game over
createScoreBackground();
LK.showGameOver();
}
}, 1000); // 1 second delay for effects and sound
}
// Helper function to create score background when game is won or lost
function createScoreBackground() {
// Store reference to background for later access
if (highscoreBackground) {
highscoreBackground.destroy();
}
// Get current player information
var currentPlayerId = LK.profile && LK.profile.id ? LK.profile.id : "guest";
var currentPlayerName = LK.profile && LK.profile.name ? LK.profile.name : "FRVR Player";
var currentScore = LK.getScore();
// Get highscores from storage
var highscores = [];
try {
if (storage && storage.available && storage.highscoresData) {
highscores = JSON.parse(storage.highscoresData);
}
} catch (e) {
console.log("Error parsing highscores:", e);
highscores = [];
}
// Check if the current score is already in the list (for game over after saving score)
var scoreExists = false;
for (var i = 0; i < highscores.length; i++) {
if (highscores[i].id === currentPlayerId && highscores[i].score === currentScore && Date.now() - highscores[i].date < 60000) {
// Added in last minute
scoreExists = true;
highscores[i].isCurrent = true; // Mark as current game's score
break;
}
}
// Add the current score to the highscores immediately if not already there
if (!scoreExists) {
highscores.push({
id: currentPlayerId,
name: currentPlayerName,
score: currentScore,
date: Date.now(),
isCurrent: true // Mark as current game's score
});
}
// Sort highscores by date (most recent first)
highscores.sort(function (a, b) {
return b.date - a.date; // Most recent date first
});
var personalBest = 0;
// Check if player has a personal best stored
if (currentPlayerId !== "guest") {
var personalKey = "player_" + currentPlayerId;
try {
if (storage && storage.available && storage[personalKey]) {
personalBest = parseInt(storage[personalKey], 10) || 0;
}
// Also check from cloud storage
if (storage && storage.available) {
storage.load('player_score_' + currentPlayerId, function (savedScore) {
if (savedScore) {
var cloudScore = parseInt(savedScore, 10) || 0;
if (cloudScore > personalBest) {
personalBest = cloudScore;
storage[personalKey] = personalBest.toString();
}
}
});
}
} catch (e) {
console.log("Error retrieving personal best:", e);
}
}
// Create background
highscoreBackground = game.addChild(LK.getAsset('Background2', {
anchorX: 0.5,
anchorY: 0.5,
x: 2048 / 2,
y: 2732 / 2,
scaleX: 20.48,
scaleY: 27.32,
alpha: 0.9
}));
// Create title
var titleText = new Text2("GAME RESULTS", {
size: 120,
fill: 0xFFFFFF
});
titleText.anchor.set(0.5, 0);
titleText.x = 2048 / 2;
titleText.y = 200;
game.addChild(titleText);
// Current score
var currentScoreText = new Text2("Your Score: " + currentScore, {
size: 100,
fill: 0x00FF00
});
currentScoreText.anchor.set(0.5, 0);
currentScoreText.x = 2048 / 2;
currentScoreText.y = 350;
game.addChild(currentScoreText);
// Show personal best if it exists
if (personalBest > 0 && personalBest !== currentScore) {
var personalBestText = new Text2("Your Best: " + personalBest, {
size: 80,
fill: 0xFFD700
});
personalBestText.anchor.set(0.5, 0);
personalBestText.x = 2048 / 2;
personalBestText.y = 470;
game.addChild(personalBestText);
// Show new record indicator
if (currentScore > personalBest) {
var newRecordText = new Text2("NEW RECORD!", {
size: 80,
fill: 0xFF00FF
});
newRecordText.anchor.set(0.5, 0);
newRecordText.x = 2048 / 2;
newRecordText.y = 560;
game.addChild(newRecordText);
}
}
// Display up to 20 highscores instead of 10
var maxToShow = Math.min(highscores.length, 20);
if (maxToShow > 0) {
// Display title for scores
var titleScoreText = new Text2("Recent Player Scores", {
size: 90,
fill: 0xFFD700
});
titleScoreText.anchor.set(0.5, 0);
titleScoreText.x = 2048 / 2;
titleScoreText.y = 700;
game.addChild(titleScoreText);
// Adjust spacing based on number of scores
var spacing = maxToShow > 10 ? 90 : maxToShow > 5 ? 120 : 150;
var fontSize = maxToShow > 10 ? 60 : maxToShow > 5 ? 70 : 80;
// Display player scores
for (var i = 0; i < maxToShow; i++) {
var entry = highscores[i];
var isCurrentPlayer = entry.id === currentPlayerId;
var isCurrentScore = entry.isCurrent === true;
// Format score with comma separators
var formattedScore = entry.score.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ",");
// Get player name, using ID if name is missing
var playerName = entry.name || "Player " + entry.id.substring(0, 5);
var scoreEntry = new Text2(i + 1 + ". " + playerName + ": " + formattedScore, {
size: fontSize,
fill: isCurrentScore ? "#FF00FF" : isCurrentPlayer ? "#00FF00" : "#FFFFFF" // Magenta for current score, green for other scores by current player
});
// If more than 10 entries, create two columns
if (maxToShow > 10 && i >= 10) {
// Second column (for entries 11-20)
scoreEntry.anchor.set(0, 0); // Left align for second column
scoreEntry.x = 2048 * 0.25; // Position at 1/4 of screen width
scoreEntry.y = 800 + (i - 10) * spacing;
} else if (maxToShow > 10) {
// First column (for entries 1-10)
scoreEntry.anchor.set(1, 0); // Right align for first column
scoreEntry.x = 2048 * 0.75; // Position at 3/4 of screen width
scoreEntry.y = 800 + i * spacing;
} else {
// Single column layout (for 10 or fewer entries)
scoreEntry.anchor.set(0.5, 0);
scoreEntry.x = 2048 / 2;
scoreEntry.y = 800 + i * spacing;
}
game.addChild(scoreEntry);
}
} else {
// No scores message
var noScoresText = new Text2("No scores yet!", {
size: 80,
fill: 0xFFFFFF
});
noScoresText.anchor.set(0.5, 0);
noScoresText.x = 2048 / 2;
noScoresText.y = 800;
game.addChild(noScoresText);
}
}
// The createScoreBackground function has been moved earlier in the code
// Helper function to respawn player at checkpoint
function respawnPlayer() {
if (!player) {
return;
}
player.x = 300;
player.y = 2732 / 2;
player.clampPosition();
player.alpha = 1; // Make player visible again
player.isDead = false;
player.invincibleUntil = LK.ticks + 120; // 2 seconds of invincibility
// Clear active threats
for (var i = enemyBullets.length - 1; i >= 0; i--) {
if (enemyBullets[i] && !enemyBullets[i].destroyed) {
enemyBullets[i].destroy();
}
}
enemyBullets = [];
if (gamePhase === 2) {
// Boss phase
for (var i = bossLasers.length - 1; i >= 0; i--) {
if (bossLasers[i] && !bossLasers[i].destroyed) {
bossLasers[i].destroy();
}
}
bossLasers = [];
}
// Player is reset, other game elements (enemies, boss health) remain.
}
// Create intro screen elements
var backgroundAsset = LK.getAsset('Background0', {});
var scaleX = 2048 / backgroundAsset.width;
var scaleY = 2732 / backgroundAsset.height;
var background = game.addChild(LK.getAsset('Background0', {
anchorX: 0.5,
anchorY: 0.5,
x: 2048 / 2,
y: 2732 / 2,
scaleX: scaleX,
scaleY: scaleY
}));
// Play intro music
LK.playMusic('Intromusic1');
var startButton = game.addChild(LK.getAsset('Startgamebutton', {
anchorX: 0.5,
anchorY: 0.5,
x: 2048 / 2,
y: 2732 / 2 + 700,
scaleX: 1.5,
scaleY: 1.5
}));
var highscoreButton = game.addChild(LK.getAsset('Highscorebutton', {
anchorX: 0.5,
anchorY: 0.5,
x: 2048 / 2,
y: 2732 / 2 + 1050,
scaleX: 1.5,
scaleY: 1.5
}));
function startGame() {
// Remove intro elements
background.destroy();
startButton.destroy();
highscoreButton.destroy();
// Set game as started
isGameStarted = true;
// Show score
scoreTxt.visible = true;
// Initialize the game
initGame();
}
function showHighscore() {
// If already showing highscores, hide them and return to intro
if (showingHighscores) {
// Remove highscore display
if (highscoreBackground) {
highscoreBackground.destroy();
highscoreBackground = null;
}
// Remove all highscore texts
highscoreTexts.forEach(function (text) {
text.destroy();
});
highscoreTexts = [];
showingHighscores = false;
return;
}
// Show custom highscore screen
showingHighscores = true;
// Create background using Background2 asset
highscoreBackground = game.addChild(LK.getAsset('Background2', {
anchorX: 0.5,
anchorY: 0.5,
x: 2048 / 2,
y: 2732 / 2,
scaleX: 20.48,
scaleY: 27.32,
alpha: 0.85 // Slightly more transparent for better readability
}));
// Create highscore title
var titleText = new Text2("PLAYER SCORES", {
size: 120,
fill: 0xFFFFFF
});
titleText.anchor.set(0.5, 0);
titleText.x = 2048 / 2;
titleText.y = 200;
game.addChild(titleText);
highscoreTexts.push(titleText);
// Add personal best section if player is logged in
if (LK.profile && LK.profile.id) {
var personalKey = "player_" + LK.profile.id;
var personalBest = 0;
try {
if (storage && storage.available && storage[personalKey]) {
personalBest = parseInt(storage[personalKey], 10) || 0;
}
// Also try loading from cloud storage
if (storage && storage.available) {
storage.load('player_score_' + LK.profile.id, function (savedScore) {
if (savedScore) {
var cloudScore = parseInt(savedScore, 10) || 0;
if (cloudScore > personalBest) {
personalBest = cloudScore;
storage[personalKey] = personalBest.toString();
// Update personal best display if already shown
highscoreTexts.forEach(function (text) {
if (text.text && text.text === personalBest.toString()) {
text.setText(personalBest.toString());
}
});
}
}
});
}
} catch (e) {
console.log("Error retrieving personal best:", e);
}
if (personalBest > 0) {
var personalTitle = new Text2("YOUR BEST SCORE", {
size: 90,
fill: 0x00FF00
});
personalTitle.anchor.set(0.5, 0);
personalTitle.x = 2048 / 2;
personalTitle.y = 350;
game.addChild(personalTitle);
highscoreTexts.push(personalTitle);
var personalScore = new Text2(personalBest.toString(), {
size: 100,
fill: 0xFFD700
});
personalScore.anchor.set(0.5, 0);
personalScore.x = 2048 / 2;
personalScore.y = 450;
game.addChild(personalScore);
highscoreTexts.push(personalScore);
}
}
// Get highscores from storage or use default ones if none exist
var highscores = [];
try {
// Try to load from storage immediately if available
if (storage && storage.available && storage.highscoresData) {
try {
highscores = JSON.parse(storage.highscoresData || "[]");
// Add current score to display if game is in progress
var currentScore = LK.getScore();
if (isGameStarted && currentScore > 0) {
// Check if current player ID exists
var currentPlayerId = LK.profile && LK.profile.id ? LK.profile.id : "guest";
var playerName = LK.profile && LK.profile.name ? LK.profile.name : "FRVR Player";
// Check if current score is already in the list
var scoreExists = false;
for (var i = 0; i < highscores.length; i++) {
if (highscores[i].id === currentPlayerId && highscores[i].score === currentScore && Date.now() - highscores[i].date < 60000) {
scoreExists = true;
highscores[i].isCurrent = true; // Mark as current score
break;
}
}
// If current score isn't already in the list, add it temporarily for display
if (!scoreExists) {
highscores.push({
id: currentPlayerId,
name: LK.profile && LK.profile.name ? LK.profile.name : playerName,
// Use Upit name if available, otherwise use the fallback
score: currentScore,
date: Date.now(),
isCurrent: true // Mark as current game's score
});
// Re-sort with the new entry
highscores.sort(function (a, b) {
return b.date - a.date; // Most recent first
});
}
}
displayHighscores(highscores); // Display immediately from local storage
} catch (localErr) {
console.log("Error parsing local highscores:", localErr);
displayHighscores([]); // Display empty scores if error
}
} else {
displayHighscores([]); // Display empty scores if no data
}
// Then try to load from cloud storage (will update if available)
if (storage && storage.available) {
storage.load('highscores', function (savedScoresFromCloud_String) {
// Callback for async cloud load
if (savedScoresFromCloud_String) {
try {
var cloudScores_Array = JSON.parse(savedScoresFromCloud_String);
var localScores_Array = [];
// Get current local scores, which might include the most recent game's score
if (storage.highscoresData) {
try {
localScores_Array = JSON.parse(storage.highscoresData);
} catch (e) {
console.log("Error parsing local highscoresData for merge:", e);
}
}
// Merge local and cloud scores
var allScores = localScores_Array.concat(cloudScores_Array);
// Add current score for display if not saved yet
var currentScore = LK.getScore();
if (isGameStarted && currentScore > 0) {
var currentPlayerId = LK.profile && LK.profile.id ? LK.profile.id : "guest";
var playerName = LK.profile && LK.profile.name ? LK.profile.name : "FRVR Player";
// Check if current score exists
var currentScoreExists = false;
for (var i = 0; i < allScores.length; i++) {
if (allScores[i].id === currentPlayerId && allScores[i].score === currentScore && Date.now() - allScores[i].date < 60000) {
currentScoreExists = true;
allScores[i].isCurrent = true;
break;
}
}
if (!currentScoreExists) {
allScores.push({
id: currentPlayerId,
name: LK.profile && LK.profile.name ? LK.profile.name : playerName,
// Use Upit name if available, otherwise use the fallback
score: currentScore,
date: Date.now(),
isCurrent: true
});
}
}
// Create a Map to find unique entries
var tempMap = {};
// Create unique entries based on player ID and score
allScores.forEach(function (score) {
// Create a key that combines ID and score
var key = score.id + "_" + score.score;
if (!tempMap[key] || score.date > tempMap[key].date) {
tempMap[key] = score;
}
});
// Convert back to array
var mergedScores_Array = Object.values(tempMap);
// Sort by date (most recent first)
mergedScores_Array.sort(function (a, b) {
return (b.date || 0) - (a.date || 0);
});
storage.highscoresData = JSON.stringify(mergedScores_Array); // Update local cache with merged list
// Clear previously displayed score list items, preserving titles
var textsToKeep = [];
if (highscoreTexts.length > 0) textsToKeep.push(highscoreTexts[0]); // Main Title ("PLAYER SCORES")
// Check for and preserve Personal Best section if it was added
// The PB section is added if LK.profile.id exists.
if (LK.profile && LK.profile.id) {
var pbTitleInstance = highscoreTexts.find(function (t) {
return t && t.text === "YOUR BEST SCORE";
});
if (pbTitleInstance) {
textsToKeep.push(pbTitleInstance);
var pbScoreIndex = highscoreTexts.indexOf(pbTitleInstance) + 1;
// Check if the next text is likely the score for PB
if (pbScoreIndex < highscoreTexts.length && highscoreTexts[pbScoreIndex] && !isNaN(parseInt(highscoreTexts[pbScoreIndex].text.replace(/,/g, '')))) {
textsToKeep.push(highscoreTexts[pbScoreIndex]);
}
}
}
highscoreTexts.forEach(function (text) {
if (text && !textsToKeep.includes(text)) {
text.destroy();
}
});
highscoreTexts = textsToKeep.slice(); // Keep only the preserved texts (main title, PB section)
// Display the merged and sorted scores. displayHighscores will add its items to highscoreTexts.
displayHighscores(mergedScores_Array);
} catch (parseErr) {
console.log("Error parsing or merging highscores from Upit:", parseErr);
// Fallback: if cloud fails, try to display local if it exists
if (storage.highscoresData) {
try {
var fallbackLocalScores = JSON.parse(storage.highscoresData || "[]");
// Add current score for display if not saved yet
var currentScore = LK.getScore();
if (isGameStarted && currentScore > 0) {
var currentPlayerId = LK.profile && LK.profile.id ? LK.profile.id : "guest";
var playerName = LK.profile && LK.profile.name ? LK.profile.name : "FRVR Player";
// Check if current score exists
var currentScoreExists = false;
for (var i = 0; i < fallbackLocalScores.length; i++) {
if (fallbackLocalScores[i].id === currentPlayerId && fallbackLocalScores[i].score === currentScore && Date.now() - fallbackLocalScores[i].date < 60000) {
currentScoreExists = true;
fallbackLocalScores[i].isCurrent = true;
break;
}
}
if (!currentScoreExists) {
fallbackLocalScores.push({
id: currentPlayerId,
name: LK.profile && LK.profile.name ? LK.profile.name : playerName,
// Use Upit name if available, otherwise use the fallback
score: currentScore,
date: Date.now(),
isCurrent: true
});
fallbackLocalScores.sort(function (a, b) {
return b.date - a.date;
});
}
}
// Minimal clear and display for fallback
var mainTitleOnly = [];
if (highscoreTexts.length > 0) mainTitleOnly.push(highscoreTexts[0]);
highscoreTexts.forEach(function (t) {
if (t && !mainTitleOnly.includes(t)) t.destroy();
});
highscoreTexts = mainTitleOnly;
displayHighscores(fallbackLocalScores);
} catch (fallbackErr) {
console.log("Error in fallback display:", fallbackErr);
}
}
}
} else if (storage.highscoresData && JSON.parse(storage.highscoresData || "[]").length === 0) {
// No cloud scores, and local cache (which might have just been updated by saveHighscore) is also empty.
// Add current score for display if not saved yet
var currentScore = LK.getScore();
var tempScores = [];
if (isGameStarted && currentScore > 0) {
var currentPlayerId = LK.profile && LK.profile.id ? LK.profile.id : "guest";
var playerName = LK.profile && LK.profile.name ? LK.profile.name : "FRVR Player";
tempScores.push({
id: currentPlayerId,
name: LK.profile && LK.profile.name ? LK.profile.name : playerName,
// Use Upit name if available, otherwise use the fallback
score: currentScore,
date: Date.now(),
isCurrent: true
});
}
if (tempScores.length > 0) {
displayHighscores(tempScores);
} else if (highscoreTexts.length <= (textsToKeep ? textsToKeep.length : 1)) {
// only titles are present
displayHighscores([]);
}
}
});
} else {
// This 'else' corresponds to `if (storage && storage.available)` for cloud load.
// If storage is not available for cloud, add current score for display if not saved yet
var currentScore = LK.getScore();
if (isGameStarted && currentScore > 0) {
var currentPlayerId = LK.profile && LK.profile.id ? LK.profile.id : "guest";
var playerName = LK.profile && LK.profile.name ? LK.profile.name : "FRVR Player";
// Check if current score exists in highscores
var currentScoreExists = false;
for (var i = 0; i < highscores.length; i++) {
if (highscores[i].id === currentPlayerId && highscores[i].score === currentScore && Date.now() - highscores[i].date < 60000) {
currentScoreExists = true;
highscores[i].isCurrent = true;
break;
}
}
if (!currentScoreExists) {
highscores.push({
id: currentPlayerId,
name: LK.profile && LK.profile.name ? LK.profile.name : playerName,
// Use Upit name if available, otherwise use the fallback
score: currentScore,
date: Date.now(),
isCurrent: true
});
highscores.sort(function (a, b) {
return b.date - a.date;
});
displayHighscores(highscores); // Update display with current score
}
}
}
} catch (e) {
console.log("Error parsing highscores:", e);
// Try to show current score even if there's an error
var currentScore = LK.getScore();
var tempScores = [];
if (isGameStarted && currentScore > 0) {
var currentPlayerId = LK.profile && LK.profile.id ? LK.profile.id : "guest";
var playerName = LK.profile && LK.profile.name ? LK.profile.name : "FRVR Player";
tempScores.push({
id: currentPlayerId,
name: LK.profile && LK.profile.name ? LK.profile.name : playerName,
// Use Upit name if available, otherwise use the fallback
score: currentScore,
date: Date.now(),
isCurrent: true
});
}
if (tempScores.length > 0) {
displayHighscores(tempScores);
} else {
displayHighscores([]);
}
}
// Helper function to display highscores
function displayHighscores(highscores) {
// Sort highscores by date (most recent first)
highscores.sort(function (a, b) {
return b.date - a.date; // Most recent first
});
// Check if current player ID exists
var currentPlayerId = LK.profile && LK.profile.id ? LK.profile.id : "guest";
var playerName = LK.profile && LK.profile.name ? LK.profile.name : "FRVR Player";
// Add current score to display if game is in progress
var currentScore = LK.getScore();
if (isGameStarted && currentScore > 0) {
// Check if current score is already in the list
var scoreExists = false;
for (var i = 0; i < highscores.length; i++) {
if (highscores[i].id === currentPlayerId && highscores[i].score === currentScore) {
scoreExists = true;
highscores[i].isCurrent = true; // Mark as current score
break;
}
}
// If current score isn't already in the list, add it
if (!scoreExists) {
highscores.push({
id: currentPlayerId,
name: LK.profile && LK.profile.name ? LK.profile.name : playerName,
// Use Upit name if available, otherwise use the fallback
score: currentScore,
date: Date.now(),
isCurrent: true // Mark as current game's score
});
// Re-sort with the new entry
highscores.sort(function (a, b) {
return b.date - a.date; // Most recent first
});
}
}
// Get title Y position (adjust if personal best was shown)
var titleY = 600;
var startY = 700;
// Display title for scores
var titleScoreText = new Text2("All Player Scores", {
size: 90,
fill: 0xFFD700
});
titleScoreText.anchor.set(0.5, 0);
titleScoreText.x = 2048 / 2;
titleScoreText.y = titleY;
game.addChild(titleScoreText);
highscoreTexts.push(titleScoreText);
// Get total unique players and their highest scores for display count
var uniquePlayers = {};
highscores.forEach(function (score) {
if (!uniquePlayers[score.id] || score.score > uniquePlayers[score.id]) {
uniquePlayers[score.id] = score.score;
}
});
// Display all scores - no limit, show as many as possible with scrolling if needed
var maxToShow = Math.min(highscores.length, 20); // Show up to 20 scores
// Adjust spacing based on number of scores for better display
var spacing = maxToShow > 15 ? 80 : maxToShow > 10 ? 100 : 150;
var fontSize = maxToShow > 15 ? 50 : maxToShow > 10 ? 60 : 80;
// Display player scores
for (var i = 0; i < maxToShow; i++) {
var entry = highscores[i];
var isCurrentPlayer = entry.id === currentPlayerId;
var isCurrentScore = entry.isCurrent === true;
// Get player name, using name from Upit profile if available, falling back to generated name if not
var playerName = entry.name || (LK.profile && LK.profile.name ? LK.profile.name : "Player " + entry.id.substring(0, 5));
// Format score with comma separators for readability
var formattedScore = entry.score.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ",");
var scoreEntry = new Text2(i + 1 + ". " + playerName + ": " + formattedScore, {
size: fontSize,
fill: isCurrentScore ? "#FF00FF" : isCurrentPlayer ? "#00FF00" : "#FFFFFF" // Magenta for current score, green for current player
});
scoreEntry.anchor.set(0.5, 0);
scoreEntry.x = 2048 / 2;
// If more than 20 entries, create three columns
if (maxToShow > 20 && i >= 20) {
// Third column (for entries 21+)
scoreEntry.anchor.set(0, 0); // Left align for third column
scoreEntry.x = 2048 * 0.1; // Position at 10% of screen width
scoreEntry.y = startY + (i - 20) * spacing;
} else if (maxToShow > 10 && i >= 10) {
// Second column (for entries 11-20)
scoreEntry.anchor.set(0, 0); // Left align for second column
scoreEntry.x = 2048 * 0.25; // Position at 1/4 of screen width
scoreEntry.y = startY + (i - 10) * spacing;
} else if (maxToShow > 10) {
// First column (for entries 1-10)
scoreEntry.anchor.set(1, 0); // Right align for first column
scoreEntry.x = 2048 * 0.75; // Position at 3/4 of screen width
scoreEntry.y = startY + i * spacing;
} else {
// Single column layout (for 10 or fewer entries)
scoreEntry.y = startY + i * spacing;
}
game.addChild(scoreEntry);
highscoreTexts.push(scoreEntry);
}
// If no scores to show
if (maxToShow === 0) {
var noScoresText = new Text2("No scores yet!", {
size: 80,
fill: 0xFFFFFF
});
noScoresText.anchor.set(0.5, 0);
noScoresText.x = 2048 / 2;
noScoresText.y = startY;
game.addChild(noScoresText);
highscoreTexts.push(noScoresText);
}
}
// The createScoreBackground function has been moved earlier in the code
// Add back button text
var backText = new Text2("TAP ANYWHERE TO RETURN", {
size: 70,
fill: 0xFFFFFF
});
backText.anchor.set(0.5, 0);
backText.x = 2048 / 2;
backText.y = 2200;
game.addChild(backText);
highscoreTexts.push(backText);
// Check if there's a LK API for showing leaderboard as backup
if (typeof LK.showLeaderboard === 'function') {
// Add fallback to standard leaderboard
LK.setTimeout(function () {
if (showingHighscores) {
// If our custom screen hasn't been dismissed after 5 seconds, show official leaderboard
showingHighscores = false;
if (highscoreBackground) {
highscoreBackground.destroy();
highscoreBackground = null;
}
highscoreTexts.forEach(function (text) {
text.destroy();
});
highscoreTexts = [];
LK.showLeaderboard();
}
}, 5000);
}
}
// Add interactive behavior to buttons
startButton.interactive = true;
startButton.buttonMode = true;
startButton.down = function () {
startGame();
};
highscoreButton.interactive = true;
highscoreButton.buttonMode = true;
highscoreButton.down = function () {
showHighscore();
};
game.down = function (x, y, obj) {
// Handle tapping on highscore screen to return to intro
if (showingHighscores) {
showHighscore(); // This will toggle off the highscore display
return;
}
// Only process player dragging if game has started
if (isGameStarted && player) {
// Check if touch is on the player UFO
var localPos = player.toLocal(game.toGlobal({
x: x,
y: y
})); // Convert game coords to player's local coords
// Use a slightly larger hit area for easier dragging
var hitWidth = player.width * 1.5;
var hitHeight = player.height * 1.5;
if (Math.abs(localPos.x) < hitWidth / 2 && Math.abs(localPos.y) < hitHeight / 2) {
dragNode = player;
// Instantly move player to touch position for responsive feel
var gamePos = game.toLocal(obj.global); // Use obj.global for precise position
player.x = gamePos.x;
player.y = gamePos.y;
player.clampPosition();
} else {
dragNode = null;
}
}
};
game.move = function (x, y, obj) {
if (dragNode) {
var gamePos = game.toLocal(obj.global); // Convert event global position to game coordinates
dragNode.x = gamePos.x;
dragNode.y = gamePos.y;
// Clamp player position within bounds immediately after move
if (dragNode === player) {
player.clampPosition();
}
}
};
game.up = function (x, y, obj) {
dragNode = null;
};
// Game Update Logic
game.update = function () {
// If game hasn't started yet or player is gone, don't process game logic
if (!isGameStarted || !player || player.destroyed) {
// Check if we need to initialize level 2 due to a reload/restart mid-level 2
// This is an edge case, main L2 init is after boss defeat.
// if (currentLevel === 2 && !level2Started && isGameStarted && player) { initLevel2(); }
// Make sure score is hidden during intro
scoreTxt.visible = false;
return; // Game not started or player fully gone, nothing to do.
}
// Make sure score is visible during gameplay
scoreTxt.visible = true;
// Initialize Level 2 if conditions are met (transitioned from Level 1)
if (currentLevel === 2 && !level2Started && isGameStarted && player && !player.isDead) {
initLevel2();
}
// If player is dead, stop their specific logic & wait for respawn/game over timeout
// Other game elements (enemies, bullets) might still update.
// Player input and collisions will be gated by player.isDead or invincibility.
// Player invincibility blinking
if (player && !player.isDead && LK.ticks < player.invincibleUntil) {
player.alpha = LK.ticks % 20 < 10 ? 0.5 : 1; // Blink
} else if (player && !player.isDead && player.alpha !== 1) {
player.alpha = 1; // Ensure alpha is reset
}
// --- Global Updates ---
// Player Shooting
playerFireCooldown++;
if (!player.isDead && dragNode === player && playerFireCooldown >= playerFireRate) {
// Only shoot while dragging/controlling and alive
playerFireCooldown = 0;
var bullet = new PlayerBullet(player.x + player.width / 2, player.y);
game.addChild(bullet);
playerBullets.push(bullet);
LK.getSound('playerShoot').play();
}
// Score increases over time
scoreIncrementTimer++;
if (scoreIncrementTimer >= 60) {
// Add 10 points every second
scoreIncrementTimer = 0;
LK.setScore(LK.getScore() + 10);
scoreTxt.setText(LK.getScore());
}
// Increment distance scrolled (used by Level 2)
if (player && !player.isDead) {
// Only scroll if player is active and game is running
// gameDistanceScrolled += terrainSpeed; // This was for overall game, level2Progress is specific
if (currentLevel === 2 && level2Started) {
level2Progress += terrainSpeed;
}
}
// --- Phase Management ---
var elapsedTicks = LK.ticks - phaseStartTime;
if (gamePhase === 0 && elapsedTicks >= phaseDuration) {
// Transition to Phase 1 (Space)
gamePhase = 1;
phaseStartTime = LK.ticks; // Reset timer for next phase if needed
console.log("Transitioning to Phase 1: Space");
// Clean up terrain-specific elements
terrainSegmentsTop.forEach(function (s) {
return s.destroy();
});
terrainSegmentsBottom.forEach(function (s) {
return s.destroy();
});
mountains.forEach(function (m) {
return m.destroy();
});
groundEnemies.forEach(function (ge) {
return ge.destroy();
});
terrainSegmentsTop = [];
terrainSegmentsBottom = [];
mountains = [];
groundEnemies = [];
// Spawn initial air enemies
for (var i = 0; i < 5; i++) {
// Start with 5 air enemies
spawnAirEnemy();
}
LK.playMusic('phase2Music'); // Switch music
} else if (gamePhase === 1 && elapsedTicks >= phaseDuration * 1.5) {
// Example: Boss after 1.5x phase duration
// Transition to Phase 2 (Boss)
if (!boss) {
// Ensure boss only spawns once
gamePhase = 2;
phaseStartTime = LK.ticks;
console.log("Transitioning to Phase 2: Boss");
// Clean up air enemies
airEnemies.forEach(function (ae) {
return ae.destroy();
});
airEnemies = [];
// Spawn Boss
boss = new Boss();
game.addChild(boss);
LK.playMusic('bossMusic'); // Boss music
}
}
// --- Terrain Logic (Active in Level 1 Phase 0 and throughout Level 2) ---
if (gamePhase === 0 || currentLevel === 2) {
// Update and manage terrain segments
var terrainWidth = LK.getAsset('terrain', {}).width;
for (var i = terrainSegmentsTop.length - 1; i >= 0; i--) {
var segment = terrainSegmentsTop[i];
if (segment.x < -terrainWidth) {
// Reposition segment to the right
var maxX = 0;
terrainSegmentsTop.forEach(function (s) {
if (s.x > maxX) {
maxX = s.x;
}
});
segment.x = maxX + terrainWidth;
// Always spawn an enemy on the reused segment
spawnGroundEnemy(segment);
// Additionally, 50% chance for a mountain
if (Math.random() < 0.5) {
spawnMountain(segment);
}
}
}
// Repeat for bottom terrain
for (var i = terrainSegmentsBottom.length - 1; i >= 0; i--) {
var segment = terrainSegmentsBottom[i];
if (segment.x < -terrainWidth) {
var maxX = 0;
terrainSegmentsBottom.forEach(function (s) {
if (s.x > maxX) {
maxX = s.x;
}
});
segment.x = maxX + terrainWidth;
// Always spawn an enemy on the reused segment
spawnGroundEnemy(segment);
// Additionally, 50% chance for a mountain
if (Math.random() < 0.5) {
spawnMountain(segment);
}
}
}
// Update mountains & check collision
for (var i = mountains.length - 1; i >= 0; i--) {
var mountain = mountains[i];
if (mountain.x < -mountain.width) {
mountain.destroy();
mountains.splice(i, 1);
} else {
if (!player.isDead && LK.ticks > player.invincibleUntil && player.intersects(mountain)) {
handlePlayerDeath();
return; // Stop update processing
}
}
}
// Update ground enemies & check collision
for (var i = groundEnemies.length - 1; i >= 0; i--) {
var enemy = groundEnemies[i];
if (enemy.x < -enemy.width) {
enemy.destroy();
groundEnemies.splice(i, 1);
} else {
// Collision check: Player vs Ground Enemy
if (!player.isDead && LK.ticks > player.invincibleUntil && player.intersects(enemy)) {
LK.getSound('enemyExplosion').play(); //{3L} // Enemy explodes
enemy.destroy(); // Destroy enemy on collision too
groundEnemies.splice(i, 1);
handlePlayerDeath();
return;
}
}
}
// Re-clamp player position based on potentially moving terrain (simple clamp for now)
player.clampPosition();
}
// --- Phase 1/2: Air Enemy Logic ---
if (gamePhase === 1) {
// Add more air enemies periodically?
if (LK.ticks % 180 === 0 && airEnemies.length < 10) {
// Spawn if less than 10, every 3 seconds
spawnAirEnemy();
}
// Update air enemies & check collision
for (var i = airEnemies.length - 1; i >= 0; i--) {
var enemy = airEnemies[i];
// Air enemies handle their own off-screen logic (respawn) in their update
// Collision check: Player vs Air Enemy
if (!player.isDead && LK.ticks > player.invincibleUntil && player.intersects(enemy)) {
LK.getSound('enemyExplosion').play(); //{41} // Enemy explodes
enemy.destroy(); // Destroy enemy on collision
airEnemies.splice(i, 1);
// LK.setScore(LK.getScore() + 50); // Score for destroying enemy - player died, maybe no score for this? Or keep it. Let's keep for now.
// scoreTxt.setText(LK.getScore());
handlePlayerDeath();
return;
}
}
}
// --- Phase 3: Boss Logic ---
if (gamePhase === 2 && boss) {
// Check collision: Player vs Boss
if (!player.isDead && LK.ticks > player.invincibleUntil && player.intersects(boss)) {
// Don't destroy boss on collision
handlePlayerDeath();
return;
}
// Update boss lasers & check collision
for (var i = bossLasers.length - 1; i >= 0; i--) {
var laser = bossLasers[i];
// Check if laser is off-screen
if (laser.x < -laser.width || laser.x > 2048 + laser.width || laser.y < -laser.height || laser.y > 2732 + laser.height) {
laser.destroy();
bossLasers.splice(i, 1);
} else {
// Collision check: Player vs Boss Laser
if (!player.isDead && LK.ticks > player.invincibleUntil && player.intersects(laser)) {
laser.destroy(); // Destroy laser
bossLasers.splice(i, 1);
handlePlayerDeath();
return;
}
}
}
// Note: Boss defeat condition is now handled within the player bullet collision check loop.
}
// --- Update Enemy Bullets & Check Collision (All Phases) ---
for (var i = enemyBullets.length - 1; i >= 0; i--) {
var bullet = enemyBullets[i];
// Check if bullet is off-screen
if (bullet.y < -bullet.height || bullet.y > 2732 + bullet.height) {
bullet.destroy();
enemyBullets.splice(i, 1);
} else {
// Collision check: Player vs Enemy Bullet
if (!player.isDead && LK.ticks > player.invincibleUntil && player.intersects(bullet)) {
bullet.destroy(); // Destroy bullet
enemyBullets.splice(i, 1);
handlePlayerDeath(); // This function now handles the delay and sound
return; // Stop update processing
}
}
}
// The saveHighscore function has been moved before handlePlayerDeath to fix the undefined error
// --- Update Player Bullets & Check Collision ---
for (var i = playerBullets.length - 1; i >= 0; i--) {
var bullet = playerBullets[i];
// Check if bullet is off-screen (right side)
if (bullet.x > 2048 + bullet.width) {
bullet.destroy();
playerBullets.splice(i, 1);
continue; // Skip collision checks if off-screen
}
// Collision Check: Player Bullet vs Ground Enemy (Phase 0)
if (gamePhase === 0) {
for (var j = groundEnemies.length - 1; j >= 0; j--) {
var enemy = groundEnemies[j];
if (bullet.intersects(enemy)) {
LK.getSound('enemyExplosion').play();
LK.effects.flashObject(enemy, 0xFFFFFF, 100); // Flash enemy white
enemy.destroy();
groundEnemies.splice(j, 1);
bullet.destroy();
playerBullets.splice(i, 1);
LK.setScore(LK.getScore() + 100); // Score for destroying ground enemy
scoreTxt.setText(LK.getScore());
break; // Bullet can only hit one enemy
}
}
if (!bullet.exists) {
continue;
} // Check if bullet was destroyed in previous loop
}
// Collision Check: Player Bullet vs Air Enemy (Phase 1)
if (gamePhase === 1) {
for (var j = airEnemies.length - 1; j >= 0; j--) {
var enemy = airEnemies[j];
if (bullet.intersects(enemy)) {
LK.getSound('enemyExplosion').play();
LK.effects.flashObject(enemy, 0xFFFFFF, 100); // Flash enemy white
enemy.destroy();
airEnemies.splice(j, 1);
bullet.destroy();
playerBullets.splice(i, 1);
LK.setScore(LK.getScore() + 150); // Score for destroying air enemy
scoreTxt.setText(LK.getScore());
break; // Bullet can only hit one enemy
}
}
if (!bullet.exists) {
continue;
} // Check if bullet was destroyed in previous loop
}
// Collision Check: Player Bullet vs Boss (Phase 2)
if (gamePhase === 2 && boss) {
if (bullet.intersects(boss)) {
LK.getSound('enemyExplosion').play(); // Use enemy explosion for hit sound
LK.effects.flashObject(boss, 0xFFFFFF, 100); // Flash boss white
bullet.destroy();
playerBullets.splice(i, 1);
boss.health -= 1; // Decrease boss health
LK.setScore(LK.getScore() + 10); // Small score for hitting boss
scoreTxt.setText(LK.getScore());
// Boss defeat check is now inside the bullet loop
if (boss.health <= 0) {
LK.getSound('bossExplosion').play(); // Play boss explosion sound
boss.destroy();
boss = null;
// Cleanup remaining lasers
bossLasers.forEach(function (l) {
return l.destroy();
});
bossLasers = [];
LK.setScore(LK.getScore() + 5000); // Big score bonus
scoreTxt.setText(LK.getScore());
// Save highscore as a milestone for defeating the boss
saveHighscore(LK.getScore());
// Transition to Level 2
currentLevel = 2;
level2Started = false; // Flag to trigger initLevel2() once at the start of next update cycle
// Note: Removed createScoreBackground(); call here as game transitions to Level 2
LK.setScore(LK.getScore() + 5000); // Bonus for defeating boss (applied again, consider if this is intended or consolidate)
scoreTxt.setText(LK.getScore());
// saveHighscore(LK.getScore()); // Score is already saved above, this might be redundant unless intended for separate tracking
// Clear boss specific elements
if (boss && !boss.destroyed) boss.destroy();
boss = null;
bossLasers.forEach(function (l) {
if (l && !l.destroyed) l.destroy();
});
bossLasers = [];
console.log("Boss defeated! Preparing Level 2.");
// initLevel2() will be called in the next game.update() loop at the top
return; // Stop update processing this frame to allow initLevel2 to run cleanly
}
break; // Bullet is destroyed after hitting boss
}
}
// --- Level 2 Logic ---
if (currentLevel === 2 && level2Started) {
var LEVEL2_AREA1_END_PX = LEVEL2_AREA1_END_MILES * PIXELS_PER_MILE;
var LEVEL2_TOTAL_DURATION_PX = (LEVEL2_AREA1_END_MILES + LEVEL2_AREA2_DURATION_MILES) * PIXELS_PER_MILE;
// --- Level 2, Area 1 (Vertical Walls) ---
if (level2Progress < LEVEL2_AREA1_END_PX) {
if (level2Progress > lastWallSpawnProgress + WALL_SPAWN_INTERVAL_MILES * PIXELS_PER_MILE) {
lastWallSpawnProgress = level2Progress;
var wall = new VerticalWall(2048 + 200); // Spawn off-screen right
game.addChild(wall);
verticalWalls.push(wall);
}
}
// --- Level 2, Area 2 (Diagonal Enemies, Tanks) ---
else if (level2Progress < LEVEL2_TOTAL_DURATION_PX) {
// Clear any remaining walls from Area 1 when transitioning
if (verticalWalls.length > 0 && level2Progress >= LEVEL2_AREA1_END_PX && level2Progress < LEVEL2_AREA1_END_PX + terrainSpeed * 2) {
// Buffer for transition
verticalWalls.forEach(function (w) {
if (w && !w.isDestroyed) {
w.turrets.forEach(function (t) {
if (t && !t.isDestroyed) t.destroy();
});
w.destroy();
}
});
verticalWalls = [];
}
if (level2Progress > lastDiagonalEnemySpawnProgress + DIAGONAL_ENEMY_SPAWN_INTERVAL_MILES * PIXELS_PER_MILE) {
lastDiagonalEnemySpawnProgress = level2Progress;
var diagEnemy = new DiagonalEnemy(2048 + 150, 2732 / 2);
game.addChild(diagEnemy);
diagonalEnemies.push(diagEnemy);
}
if (level2Progress > lastTankSpawnProgress + TANK_SPAWN_INTERVAL_MILES * PIXELS_PER_MILE) {
lastTankSpawnProgress = level2Progress;
var tankTop = new Tank(true, 2048 + 100);
var tankBottom = new Tank(false, 2048 + 100);
game.addChild(tankTop);
game.addChild(tankBottom);
tanks.push(tankTop);
tanks.push(tankBottom);
}
} else {
// Level 2 Completed
if (player && !player.isDead) {
// Ensure win condition only if player alive
console.log("Level 2 Complete!");
saveHighscore(LK.getScore());
createScoreBackground();
LK.setTimeout(function () {
LK.showYouWin(); // Game ends after Level 2
}, 500);
player.isDead = true; // Prevent further actions
return;
}
}
// Update and Collision for Level 2 Elements
// Vertical Walls (and their turrets)
for (var i = verticalWalls.length - 1; i >= 0; i--) {
var wall = verticalWalls[i];
if (wall.isDestroyed) {
// Already handled by wall itself
// verticalWalls.splice(i, 1); // Wall removes itself from array
continue;
}
if (wall.x < -wall.width) {
// Off-screen left
wall.turrets.forEach(function (t) {
if (t && !t.isDestroyed) t.destroy();
});
wall.destroy();
verticalWalls.splice(i, 1);
} else if (!player.isDead && LK.ticks > player.invincibleUntil && player.intersects(wall)) {
handlePlayerDeath();
return;
}
}
// Turrets are updated via their own class or Wall. Bullets handled by main enemyBullets loop.
// Diagonal Enemies
for (var i = diagonalEnemies.length - 1; i >= 0; i--) {
var diagEnemy = diagonalEnemies[i];
if (diagEnemy.isDestroyed) {
// diagonalEnemies.splice(i, 1); // Enemy removes itself
continue;
}
if (diagEnemy.x < -diagEnemy.width) {
diagEnemy.destroy();
diagonalEnemies.splice(i, 1);
} else if (!player.isDead && LK.ticks > player.invincibleUntil && player.intersects(diagEnemy)) {
diagEnemy.takeDamage();
handlePlayerDeath();
return;
}
}
// Tanks
for (var i = tanks.length - 1; i >= 0; i--) {
var tank = tanks[i];
if (tank.isDestroyed) {
// tanks.splice(i, 1); // Enemy removes itself
continue;
}
if (tank.x < -tank.width) {
tank.destroy();
tanks.splice(i, 1);
} else if (!player.isDead && LK.ticks > player.invincibleUntil && player.intersects(tank)) {
tank.takeDamage();
handlePlayerDeath();
return;
}
}
} // End if (currentLevel === 2 && level2Started)
// --- Update Player Bullets & Check Collision --- (Continued with Level 2 targets)
// (This section is appended to the existing playerBullets loop, before the final closing brace of the loop)
// The existing player bullet loop needs to be modified to include these checks.
// This block should be inserted *inside* the `for (var i = playerBullets.length - 1; i >= 0; i--)` loop for player bullets,
// after existing collision checks or as part of a conditional block for `currentLevel === 2`.
// Existing player bullet loop:
// for (var i = playerBullets.length - 1; i >= 0; i--) {
// var bullet = playerBullets[i];
// ...
// if (gamePhase === 2 && boss) { ... } // Existing boss collision
// // ADD LEVEL 2 COLLISION LOGIC HERE:
if (currentLevel === 2 && bullet.exists) {
// Check bullet.exists in case prior collision destroyed it
// Vs WallTurrets (part of VerticalWalls)
for (var k = verticalWalls.length - 1; k >= 0; k--) {
var wall = verticalWalls[k];
if (wall.isDestroyed) continue;
for (var l = wall.turrets.length - 1; l >= 0; l--) {
var turret = wall.turrets[l];
if (turret.isDestroyed) continue;
if (bullet.intersects(turret)) {
if (turret.takeDamage()) {
// true if destroyed
LK.setScore(LK.getScore() + 75);
scoreTxt.setText(LK.getScore());
}
bullet.destroy();
playerBullets.splice(i, 1);
break;
}
}
if (!bullet.exists) break;
}
if (!bullet.exists) continue; // To next player bullet if this one was destroyed
// Vs DiagonalEnemies
for (var k = diagonalEnemies.length - 1; k >= 0; k--) {
var diagEnemy = diagonalEnemies[k];
if (diagEnemy.isDestroyed) continue;
if (bullet.intersects(diagEnemy)) {
if (diagEnemy.takeDamage()) {
LK.setScore(LK.getScore() + 250);
scoreTxt.setText(LK.getScore());
}
bullet.destroy();
playerBullets.splice(i, 1);
break;
}
}
if (!bullet.exists) continue;
// Vs Tanks
for (var k = tanks.length - 1; k >= 0; k--) {
var tank = tanks[k];
if (tank.isDestroyed) continue;
if (bullet.intersects(tank)) {
if (tank.takeDamage()) {
LK.setScore(LK.getScore() + 175);
scoreTxt.setText(LK.getScore());
}
bullet.destroy();
playerBullets.splice(i, 1);
break;
}
}
if (!bullet.exists) continue;
}
// } // End of playerBullets loop (this closing brace is illustrative, don't add a new one)
} // End of playerBullets loop
}; ===================================================================
--- original.js
+++ change.js
@@ -1868,18 +1868,17 @@
});
bossLasers = [];
LK.setScore(LK.getScore() + 5000); // Big score bonus
scoreTxt.setText(LK.getScore());
- // Save highscore before showing You Win
+ // Save highscore as a milestone for defeating the boss
saveHighscore(LK.getScore());
- // Create background for scores before showing you win
- createScoreBackground();
// Transition to Level 2
currentLevel = 2;
level2Started = false; // Flag to trigger initLevel2() once at the start of next update cycle
- LK.setScore(LK.getScore() + 5000); // Bonus for defeating boss
+ // Note: Removed createScoreBackground(); call here as game transitions to Level 2
+ LK.setScore(LK.getScore() + 5000); // Bonus for defeating boss (applied again, consider if this is intended or consolidate)
scoreTxt.setText(LK.getScore());
- saveHighscore(LK.getScore()); // Save score as a milestone
+ // saveHighscore(LK.getScore()); // Score is already saved above, this might be redundant unless intended for separate tracking
// Clear boss specific elements
if (boss && !boss.destroyed) boss.destroy();
boss = null;
bossLasers.forEach(function (l) {
Alien Airships of boss, HD colors. In-Game asset. 2d. High contrast. No shadows
pack mountain, yellow, HD colors. In-Game asset. 2d. High contrast. No shadows.no black lines
shot circle. blur, light, HD colors In-Game asset. 2d. High contrast. No shadows
gunTurret aiming diagonal. yellow, HD colors. In-Game asset. 2d. High contrast. No shadows
Square & diagonal tank enemies from the future have 4 turrets on its 4 sides, HD colors. In-Game asset. 2d. High contrast. No shadows