/**** * Plugins ****/ var tween = LK.import("@upit/tween.v1"); var storage = LK.import("@upit/storage.v1"); /**** * Classes ****/ // Backpack class (for weapon upgrades) var Backpack = Container.expand(function () { var self = Container.call(this); // Determine color and shape by type after instantiation self.type = 0; // 1=laser, 2=plasma, 3=bazooka self.collected = false; self._bag = null; function updateBagVisual() { // Remove old bag if exists if (self._bag) { self.removeChild(self._bag); self._bag = null; } var color = 0x888888; if (self.type === 1) color = 0xff0000; // Laser: red else if (self.type === 2) color = 0xff9800; // Plasma: orange else if (self.type === 3) color = 0x9c27b0; // Bazooka: purple self._bag = self.attachAsset('coin', { anchorX: 0.5, anchorY: 0.5, width: 90, height: 90, color: color, shape: 'box' }); } updateBagVisual(); self.update = function () { // Bobbing animation self.y += Math.sin(LK.ticks / 10 + self.x) * 0.7; }; self.collect = function () { if (self.collected) return; self.collected = true; tween(self, { alpha: 0, y: self.y - 60 }, { duration: 300, onFinish: function onFinish() { self.destroy(); } }); }; return self; }); // Bazooka projectile (green, can destroy two giants) var Bazooka = Container.expand(function () { var self = Container.call(this); var bazooka = self.attachAsset('slingStone', { anchorX: 0.5, anchorY: 0.5, width: 60, height: 60, color: 0x00ff00 }); self.vx = 0; self.vy = -28; self.active = true; self.damage = 2; // Bazooka deals 2 damage (kills a giant in 1 hit) self.pierce = 2; // can hit two targets self.update = function () { if (!self.active) return; self.x += self.vx; self.y += self.vy; }; return self; }); // Boss Bomb projectile (thrown by boss) var BossBomb = Container.expand(function () { var self = Container.call(this); var bomb = self.attachAsset('coin', { anchorX: 0.5, anchorY: 0.5, width: 60, height: 60, color: 0x333333, shape: 'ellipse' }); self.vx = 0; self.vy = 0; self.hit = false; self._exploded = false; self.update = function () { if (self._exploded) return; self.x += self.vx; self.y += self.vy; self.vy += 1.2; // gravity // Simple spin bomb.rotation += 0.2; }; self.explode = function () { if (self._exploded) return; self._exploded = true; tween(self, { scaleX: 2, scaleY: 2, alpha: 0 }, { duration: 300, onFinish: function onFinish() { self.destroy(); } }); // Optional: play explosion sound here }; return self; }); // Boss Giant var BossGiant = Container.expand(function () { var self = Container.call(this); var body = self.attachAsset('bossBody', { anchorX: 0.5, anchorY: 1 }); var head = self.attachAsset('bossHead', { anchorX: 0.5, anchorY: 1, y: -body.height }); self.hp = 15; self.speed = 2.2; self.active = true; self.update = function () { if (!self.active) return; // Move toward David (slightly faster than before) if (david && david.y < self.y) { // Move down (with world scrolling up) self.y += self.speed + 1.2; // Track David horizontally if (Math.abs(self.x - david.x) > 10) { if (self.x < david.x) self.x += 2.5;else self.x -= 2.5; } } else { self.y += self.speed + 1.2; } // Keep boss inside sky image area if (self.x < 150) self.x = 150; if (self.x > 2048 - 150) self.x = 2048 - 150; if (self.y < 200) self.y = 200; if (self.y > 1800) self.y = 1800; // --- Boss Stick (right hand) --- // Add stick if not present if (!self._stick) { self._stick = self.attachAsset('davidArm', { anchorX: 0.5, anchorY: 0.1, width: 30, height: 180, color: 0x8d5524, x: self.width / 2 + 80, y: -self.height + 120 }); self._stickSwingAngle = 0; self._stickSwingDown = true; self._stickBaseX = self._stick.x; self._stickBaseY = self._stick.y; } // Animate stick swinging downward to hit David if (typeof self._stickSwingAngle !== "number") self._stickSwingAngle = 0; if (typeof self._stickSwingDown !== "boolean") self._stickSwingDown = true; if (self._stickSwingDown) { self._stickSwingAngle += 0.10 + Math.random() * 0.03; if (self._stickSwingAngle > 1.2) self._stickSwingDown = false; } else { self._stickSwingAngle -= 0.08 + Math.random() * 0.02; if (self._stickSwingAngle < -0.3) self._stickSwingDown = true; } self._stick.rotation = self._stickSwingAngle + 1.1; // Keep stick attached to boss's right side self._stick.x = self.width / 2 + 80; self._stick.y = -self.height + 120; // --- End Boss Stick --- // Axe swing animation (right arm) if (!self._axe) { self._axe = self.attachAsset('davidArm', { anchorX: 0.5, anchorY: 0.1, x: self.width / 2 + 60, y: -self.height + 80, color: 0x444444 }); } if (!self._axeAngle) self._axeAngle = 0; self._axeAngle += 0.13 + Math.random() * 0.04; self._axe.rotation = Math.sin(self._axeAngle) * 1.2 + 1.2; // Boss health bar (tube) above boss if (!self._hpBarBg) { self._hpBarBg = self.attachAsset('coin', { anchorX: 0.5, anchorY: 0.5, width: 220, height: 32, color: 0x222222, shape: 'box', y: -self.height - 60 }); } if (!self._hpBar) { self._hpBar = self.attachAsset('coin', { anchorX: 0, anchorY: 0.5, width: 210, height: 20, color: 0x43a047, shape: 'box', x: -105, y: -self.height - 60 }); } if (self._hpBar) { var maxHp = self.maxHp || (self.hp > 0 ? self.hp : 1); if (!self.maxHp) self.maxHp = self.hp; var frac = Math.max(0, Math.min(1, self.hp / self.maxHp)); self._hpBar.width = 210 * frac; // Color: green to red var col = 0x43a047; if (frac < 0.5) col = 0xffa000; if (frac < 0.25) col = 0xd32f2f; self._hpBar.tint = col; } }; self.takeDamage = function (dmg) { self.hp -= dmg; if (self.hp <= 0) { self.active = false; LK.getSound('giantVanish').play(); tween(self, { alpha: 0 }, { duration: 600, onFinish: function onFinish() { // Remove health bar assets if present if (self._hpBarBg) { self.removeChild(self._hpBarBg); self._hpBarBg = null; } if (self._hpBar) { self.removeChild(self._hpBar); self._hpBar = null; } self.destroy(); } }); } else { LK.getSound('hit').play(); tween(self, { tint: 0xff7043 }, { duration: 180, onFinish: function onFinish() { tween(self, { tint: 0xffffff }, { duration: 180 }); } }); } }; return self; }); // Boss Whip hitbox (short-lived, for boss whip attack) var BossWhip = Container.expand(function () { var self = Container.call(this); var whip = self.attachAsset('coin', { anchorX: 0.5, anchorY: 0.5, width: 180, height: 40, color: 0x8d5524, shape: 'box' }); self._life = 0; self.hit = false; return self; }); // Chicken class var Chicken = Container.expand(function () { var self = Container.call(this); var chicken = self.attachAsset('chicken', { anchorX: 0.5, anchorY: 0.5 }); self.collected = false; self.animTimer = 0; self.update = function () { // Simple idle animation (bobbing) self.animTimer++; self.y += Math.sin(self.animTimer / 10) * 1.2; }; self.collect = function () { if (self.collected) return; self.collected = true; tween(self, { alpha: 0, y: self.y - 60 }, { duration: 300, onFinish: function onFinish() { self.destroy(); } }); }; return self; }); // Coin class var Coin = Container.expand(function () { var self = Container.call(this); var coin = self.attachAsset('coin', { anchorX: 0.5, anchorY: 0.5 }); self.collected = false; self.animTimer = 0; self.update = function () { // Simple spin animation self.animTimer++; coin.rotation += 0.15; self.y += Math.sin(self.animTimer / 12) * 0.8; }; self.collect = function () { if (self.collected) return; self.collected = true; tween(self, { alpha: 0, y: self.y - 40 }, { duration: 200, onFinish: function onFinish() { self.destroy(); } }); }; return self; }); // Spawn chickens and coins for the current wave // David (player) var David = Container.expand(function () { var self = Container.call(this); var body = self.attachAsset('davidBody', { anchorX: 0.5, anchorY: 1 }); // Arm removed (no davidArm asset) self.arm = { width: 100, height: 100 }; // dummy object to prevent errors self.armAngle = 0; self.armSpeed = 0.18; // radians per frame self.armRadius = 70; self.slingReady = true; self.stoneCooldown = 0; self.hp = 5; self.maxHp = 5; self.regenTimer = 0; self.update = function () { // Spin arm self.armAngle += self.armSpeed; if (self.armAngle > Math.PI * 2) self.armAngle -= Math.PI * 2; // Arm rotates in a circle self.arm.rotation = self.armAngle; // Stone cooldown if (self.stoneCooldown > 0) self.stoneCooldown--; // Health regen if (self.hp < self.maxHp) { self.regenTimer++; if (self.regenTimer > 180) { // 3 seconds self.hp++; self.regenTimer = 0; } } else { self.regenTimer = 0; } }; self.throwStone = function () { if (!self.slingReady || self.stoneCooldown > 0) return null; self.slingReady = false; self.stoneCooldown = 5; // fast fire rate for machine gun LK.getSound('throw').play(); // Fire from the middle of David's body var sx = self.x; var sy = self.y - body.height / 2; // Always fire a machine gun bullet straight upwards var proj = new Stone(); proj.x = sx; proj.y = sy; proj.vx = 0; proj.vy = -38; // faster than stone for machine gun return proj; }; self.takeDamage = function (dmg) { self.hp -= dmg; if (self.hp < 0) self.hp = 0; LK.effects.flashObject(self, 0xff0000, 400); // No weapon revert on damage in machine gun only mode }; return self; }); // Enemy spaceship bullet (missile-shaped, slower, 1 damage per hit) var EnemyShipBullet = Container.expand(function () { var self = Container.call(this); // Use a longer, missile-like shape var bullet = self.attachAsset('davidArm', { anchorX: 0.5, anchorY: 0.5, width: 24, height: 64, color: 0xff2222 }); self.vx = 0; self.vy = 0; self.active = true; self._life = 0; self.lastX = self.x; self.lastY = self.y; self.damage = 1; // Only 1 damage per hit self.update = function () { self._life++; self.x += self.vx; self.y += self.vy; // Rotate to face direction of movement if (self.vx !== 0 || self.vy !== 0) { bullet.rotation = Math.atan2(self.vy, self.vx) + Math.PI / 2; } // Remove if off screen if (self.x < -100 || self.x > 2200 || self.y < -100 || self.y > 2800) { self.active = false; self.destroy(); } self.lastX = self.x; self.lastY = self.y; }; return self; }); // Enemy Spaceship (appears every 2 minutes, shoots at David, exits after 10s) var EnemySpaceship = Container.expand(function () { var self = Container.call(this); // Use a box for the ship body (gray) var ship = self.attachAsset('bossBody', { anchorX: 0.5, anchorY: 0.5, width: 220, height: 90, color: 0x8888ff, shape: 'box' }); // Gun visual (front) var gun = self.attachAsset('davidArm', { anchorX: 0.5, anchorY: 0.5, width: 40, height: 60, color: 0xff2222, x: 0, y: 40 }); self.state = "entering"; // entering, firing, exiting self.fireTimer = 0; self.exitDir = 1; // 1 = right, -1 = left self.speed = 18; self.fireInterval = 18; // frames between shots self.lastFireTick = 0; self.boundaryX = 1024; // center of screen self.targetY = 600; // y position to hover at self.lastX = self.x; self.lastY = self.y; self._life = 0; self.update = function () { self._life++; // State machine if (self.state === "entering") { // Move horizontally toward boundaryX, stop at boundary if (self.exitDir === 1 && self.x < self.boundaryX || self.exitDir === -1 && self.x > self.boundaryX) { self.x += self.speed * self.exitDir; // Clamp to boundary if (self.exitDir === 1 && self.x > self.boundaryX || self.exitDir === -1 && self.x < self.boundaryX) { self.x = self.boundaryX; } } // Move vertically to targetY if (Math.abs(self.y - self.targetY) > 8) { self.y += (self.targetY - self.y) * 0.18; } // Arrived at boundary and targetY if (self.x === self.boundaryX && Math.abs(self.y - self.targetY) < 10) { self.state = "firing"; self.fireTimer = 0; } } else if (self.state === "firing") { // Stay at boundary, fire at David every fireInterval frames self.fireTimer++; if (self.fireTimer % self.fireInterval === 0) { self.fireAtDavid(); } // After 10 seconds (600 frames), exit if (self.fireTimer > 600) { self.state = "exiting"; } } else if (self.state === "exiting") { // Move quickly off screen in exitDir self.x += self.speed * 2.5 * self.exitDir; if (self.exitDir === 1 && self.x > 2200 || self.exitDir === -1 && self.x < -200) { self.destroy(); } } // Defensive: update lastX/lastY for event triggers self.lastX = self.x; self.lastY = self.y; }; // Fire a bullet at David's current position self.fireAtDavid = function () { if (!david || !gameActive) return; var bullet = new EnemyShipBullet(); bullet.x = self.x; bullet.y = self.y + 40; // Aim at David's current position var dx = david.x - self.x; var dy = david.y - 60 - bullet.y; var dist = Math.sqrt(dx * dx + dy * dy); // Make missile slower than normal (e.g. 18 instead of 32) var speed = 18; bullet.vx = dx / dist * speed; bullet.vy = dy / dist * speed; enemyShipBullets.push(bullet); game.addChild(bullet); }; return self; }); // Giant enemy var Giant = Container.expand(function () { var self = Container.call(this); var body = self.attachAsset('giantBody', { anchorX: 0.5, anchorY: 1 }); self.hp = 2; // Each giant requires 2 hits to die self.speed = 3 + Math.random() * 2; self.active = true; self.update = function () { if (!self.active) return; // Giants move downward (with world scrolling up, so add speed to y) self.y += self.speed; }; self.takeDamage = function (dmg) { self.hp -= dmg; if (self.hp <= 0) { self.active = false; LK.getSound('giantVanish').play(); tween(self, { alpha: 0 }, { duration: 300, onFinish: function onFinish() { self.destroy(); } }); } else { LK.getSound('hit').play(); tween(self, { tint: 0xffe082 }, { duration: 120, onFinish: function onFinish() { tween(self, { tint: 0xffffff }, { duration: 120 }); } }); } }; return self; }); // Heart class (for health pickup) var Heart = Container.expand(function () { var self = Container.call(this); var heart = self.attachAsset('coin', { anchorX: 0.5, anchorY: 0.5, width: 90, height: 90, color: 0xff69b4, // pink shape: 'box' }); self.collected = false; self.update = function () { self.y += Math.sin(LK.ticks / 12 + self.x) * 0.8; }; self.collect = function () { if (self.collected) return; self.collected = true; tween(self, { alpha: 0, y: self.y - 60 }, { duration: 300, onFinish: function onFinish() { self.destroy(); } }); }; return self; }); // Laser projectile (covers whole screen, instant) var Laser = Container.expand(function () { var self = Container.call(this); var laser = self.attachAsset('slingStone', { anchorX: 0.5, anchorY: 1, width: 30, height: 400, // Match catapult (stone) range color: 0x9c27b0 // purple }); self.active = true; self.damage = 1; // Laser deals 1 damage to giants, so 2 hits are required self.update = function () { // Laser is instant, doesn't move, but can fade out if (!self.active) return; // Fade out after a few frames if (!self._life) self._life = 0; self._life++; if (self._life > 10) { self.active = false; self.destroy(); } }; return self; }); // Plasma projectile (fast, yellow, destroys instantly) var Plasma = Container.expand(function () { var self = Container.call(this); var plasma = self.attachAsset('slingStone', { anchorX: 0.5, anchorY: 0.5, width: 44, height: 44, color: 0xffe600 }); self.vx = 0; self.vy = -40; self.active = true; self.damage = 99; // always kills (plasma is instant kill) self.update = function () { if (!self.active) return; self.x += self.vx; self.y += self.vy; }; return self; }); // Stone projectile var Stone = Container.expand(function () { var self = Container.call(this); var stone = self.attachAsset('slingStone', { anchorX: 0.5, anchorY: 0.5 }); self.vx = 0; self.vy = 0; self.active = true; self.damage = 1; // Stone deals 1 damage to giants self.update = function () { if (!self.active) return; if (typeof self._startY === "undefined") self._startY = self.y; self.x += self.vx; self.y += self.vy; // Gravity self.vy += 0.7; // Remove if exceeded maximum range (e.g. 900px upwards from start) if (self._startY - self.y > 900) { self.active = false; if (typeof self.destroy === "function") self.destroy(); } }; return self; }); /**** * Initialize Game ****/ var game = new LK.Game({ backgroundColor: 0x87ceeb }); /**** * Game Code ****/ enemySpaceshipTimer = 0; // --- PLAYER --- var enemySpaceshipActive = false; var enemySpaceship = null; var enemyShipBullets = []; var enemySpaceshipTimer = 0; // --- ENVIRONMENT --- // Character (David) var sky = LK.getAsset('sky', { x: 0, y: 0 }); game.addChild(sky); // Ensure sky is always at the bottom of the display list and never removed if (typeof game._skyLock === "undefined") { game._skyLock = true; var originalRemoveChild = game.removeChild; game.removeChild = function (child) { if (child === sky) { // Prevent sky from being removed return; } return originalRemoveChild.call(this, child); }; // Always keep sky at the bottom var originalAddChild = game.addChild; game.addChild = function (child) { if (child === sky) { // Only add sky if not already present if (this.children && this.children[0] !== sky) { originalAddChild.call(this, child); // Move sky to index 0 if (this.children && this.children.length > 1) { this.children.splice(this.children.indexOf(sky), 1); this.children.unshift(sky); } } return sky; } var result = originalAddChild.call(this, child); // After adding any child, ensure sky is at index 0 if (this.children && this.children[0] !== sky && this.children.indexOf(sky) !== -1) { this.children.splice(this.children.indexOf(sky), 1); this.children.unshift(sky); } return result; }; } var ground = LK.getAsset('ground', { x: 0, y: 2532 }); ground.y = 2532; // bottom of screen game.addChild(ground); // --- PLAYER --- var david = new David(); david.x = 1024; // center horizontally david.y = 2300; // near bottom, above ground game.addChild(david); // --- WEAPON SYSTEM --- // Machine Gun is the only weapon var WEAPON_MACHINEGUN = 0; var weaponNames = ["Machine Gun"]; var weaponColors = [0x888888]; // Start with Machine Gun only var weapon = WEAPON_MACHINEGUN; var weaponUnlocked = [true]; // Only Machine Gun unlocked var weaponLast = WEAPON_MACHINEGUN; var weaponTimeouts = [0]; // Weapon UI var weaponTxt = new Text2('Machine Gun', { size: 80, fill: "#fff" }); weaponTxt.anchor.set(0.5, 0); LK.gui.top.addChild(weaponTxt); weaponTxt.y = 220; // --- BACKPACKS & HEART --- var backpacks = []; var heart = null; var heartCollected = false; // --- SCROLLING STATE --- var scrollY = 0; // how much the world has scrolled up var scrollSpeed = 4; // px per frame, adjust for desired speed // --- UI --- var score = 0; var scoreTxt = new Text2('0', { size: 120, fill: "#fff" }); scoreTxt.anchor.set(0.5, 0); LK.gui.top.addChild(scoreTxt); var hpTxt = new Text2('♥♥♥♥♥', { size: 90, fill: 0xE53935 }); hpTxt.anchor.set(0.5, 0); LK.gui.top.addChild(hpTxt); hpTxt.y = 120; // --- LEADERBOARD --- // Leaderboard UI and logic removed as per new requirements // --- GAME STATE --- var stones = []; var giants = []; var boss = null; var chickens = []; var coins = []; var wave = 1; var giantsToSpawn = 5; var giantsSpawned = 0; var spawnTimer = 0; var gameActive = true; var bossActive = false; var bossDefeated = false; // --- CHAPTER STATE --- var chapter = 1; var bossScale = 1; // Chicken and coin spawn positions per level (max 5 coins per level) var chickenSpawns = [ // For each wave, array of {x, y} [{ x: 600, y: 1800 }, { x: 1500, y: 800 }], [{ x: 400, y: 1200 }, { x: 1700, y: 600 }, { x: 1200, y: 2000 }], [{ x: 900, y: 900 }, { x: 1800, y: 400 }, { x: 300, y: 2200 }]]; var coinSpawns = [[{ x: 400, y: 400 }, { x: 800, y: 600 }, { x: 1200, y: 800 }, { x: 1600, y: 1000 }, { x: 200, y: 2000 }], [{ x: 600, y: 500 }, { x: 1000, y: 700 }, { x: 1400, y: 900 }, { x: 1800, y: 1100 }, { x: 400, y: 2100 }], [{ x: 800, y: 300 }, { x: 1200, y: 500 }, { x: 1600, y: 700 }, { x: 200, y: 900 }, { x: 1800, y: 2300 }]]; // Spawn chickens and coins for the current wave function spawnCollectiblesForWave(waveIdx) { // Remove old for (var i = chickens.length - 1; i >= 0; i--) { chickens[i].destroy(); chickens.splice(i, 1); } for (var i = coins.length - 1; i >= 0; i--) { coins[i].destroy(); coins.splice(i, 1); } // Chickens var cSpawns = chickenSpawns[waveIdx % chickenSpawns.length]; for (var i = 0; i < cSpawns.length; i++) { var c = new Chicken(); c.x = cSpawns[i].x; c.y = cSpawns[i].y; chickens.push(c); game.addChild(c); } // Coins var coinSp = coinSpawns[waveIdx % coinSpawns.length]; for (var i = 0; i < coinSp.length; i++) { var coin = new Coin(); coin.x = coinSp[i].x; coin.y = coinSp[i].y; coins.push(coin); game.addChild(coin); } } // --- MUSIC --- LK.playMusic('mainTheme'); // --- MAPS/ENVIRONMENTS --- var environments = [{ sky: 0x87ceeb, ground: 0x7ec850 }, // blue sky, green grass { sky: 0xf7e9a0, ground: 0xe0c97f }, // desert { sky: 0x9ecae1, ground: 0x8c8c8c } // mountain/rocky ]; var currentEnv = 0; function setEnvironment(idx) { sky.tint = environments[idx].sky; ground.tint = environments[idx].ground; } setEnvironment(currentEnv); // Leaderboard name entry/update function removed as per new requirements // --- UI UPDATE --- function updateScore() { scoreTxt.setText(score); } function updateHp() { var s = ''; for (var i = 0; i < david.hp; i++) s += '♥'; for (var i = david.hp; i < david.maxHp; i++) s += '♡'; hpTxt.setText(s); } updateScore(); updateHp(); // --- INPUT --- var isTouching = false; var lastTouchTick = 0; // --- MACHINE GUN (AUTOMATIC FIRE) STATE --- var autoFireInterval = 5; // frames between shots (12 shots/sec at 60fps) var autoFireTick = 0; var autoFireActive = false; var autoFireTimeout = null; game.down = function (x, y, obj) { isTouching = true; lastTouchTick = LK.ticks; // Move David to pointer/touch position (centered, free movement) if (gameActive && !bossDefeated) { var minX = david.arm.width / 2 + 60; var maxX = 2048 - david.arm.width / 2 - 60; var minY = 0 + david.arm.height / 2 + 60; var maxY = 2532; var newX = x; var newY = y; if (newX < minX) newX = minX; if (newX > maxX) newX = maxX; if (newY < minY) newY = minY; if (newY > maxY) newY = maxY; david.x = newX; david.y = newY; } // Start automatic fire if (gameActive && !bossDefeated && !autoFireActive) { autoFireActive = true; autoFireTick = 0; // Fire immediately fireMachineGun(); // Set up interval for continuous fire autoFireTimeout = LK.setInterval(fireMachineGun, autoFireInterval * 1000 / 60); } }; game.up = function (x, y, obj) { isTouching = false; // Stop automatic fire if (autoFireActive) { autoFireActive = false; if (autoFireTimeout) { LK.clearInterval(autoFireTimeout); autoFireTimeout = null; } } // Only allow throw if touch was short (tap) if (gameActive && !bossDefeated && LK.ticks - lastTouchTick < 30) { // Do not fire a single shot here, as auto fire already handles it } }; // Helper function for machine gun fire function fireMachineGun() { if (!gameActive || bossDefeated) return; if (!isTouching) return; var stone = david.throwStone(); if (stone) { stones.push(stone); // If the stone is a Laser, add it after all giants to ensure it's above them if (stone instanceof Laser) { // Always add the laser to the top of the display list so it is visible above all giants and other objects // Remove if already present, then add to top var idx = game.children.indexOf(stone); if (idx !== -1) { game.children.splice(idx, 1); } game.addChild(stone); if (game.children[game.children.length - 1] !== stone) { var idx2 = game.children.indexOf(stone); if (idx2 !== -1) { game.children.splice(idx2, 1); game.children.push(stone); } } stone.parent = game; if (typeof stone.onAdded === "function") stone.onAdded(); } else { game.addChild(stone); } // Arm can't throw again until next full rotation LK.setTimeout(function () { david.slingReady = true; }, 400); } } game.move = function (x, y, obj) { // Move David to pointer/touch position (centered, free movement) if (gameActive && !bossDefeated) { var minX = david.arm.width / 2 + 60; var maxX = 2048 - david.arm.width / 2 - 60; var minY = 0 + david.arm.height / 2 + 60; var maxY = 2532; // bottom of screen var newX = x; var newY = y; if (newX < minX) newX = minX; if (newX > maxX) newX = maxX; if (newY < minY) newY = minY; if (newY > maxY) newY = maxY; david.x = newX; david.y = newY; } }; // --- GAME LOOP --- // --- GIANT GROUP SPAWN EVERY 5 SECONDS (3-5 GIANTS PER GROUP) --- var giantGroupTimer = 0; var giantGroupInterval = 5 * 60; // 5 seconds * 60 FPS game.update = function () { if (!gameActive) return; // --- ENEMY SPACESHIP TIMELINE LOGIC --- // Only spawn if not bossActive and not already present if (!bossActive && !enemySpaceshipActive) { enemySpaceshipTimer++; if (enemySpaceshipTimer >= 2 * 60 * 60) { // every 2 minutes (7200 frames) // Spawn from same side as giants (randomly left or right) var side = Math.random() < 0.5 ? -1 : 1; enemySpaceship = new EnemySpaceship(); enemySpaceship.exitDir = side; enemySpaceship.x = side === 1 ? -200 : 2248; enemySpaceship.y = 400; enemySpaceshipActive = true; enemySpaceshipTimer = 0; game.addChild(enemySpaceship); } } // Update enemy spaceship if present if (enemySpaceshipActive && enemySpaceship) { enemySpaceship.update(); // Remove if destroyed if (enemySpaceship.destroyed || typeof enemySpaceship.parent === "undefined" || enemySpaceship.parent === null) { enemySpaceship = null; enemySpaceshipActive = false; // Remove all its bullets for (var i = enemyShipBullets.length - 1; i >= 0; i--) { if (enemyShipBullets[i]) { enemyShipBullets[i].destroy(); } } enemyShipBullets.length = 0; } } // Update enemy spaceship bullets for (var i = enemyShipBullets.length - 1; i >= 0; i--) { var b = enemyShipBullets[i]; if (!b || !b.active) { enemyShipBullets.splice(i, 1); continue; } b.update(); b.y -= scrollSpeed; // move up with world // Collision with David if (b.active && david && b.intersects(david)) { b.active = false; b.destroy(); enemyShipBullets.splice(i, 1); // Always only 1 life per hit, regardless of bullet properties david.takeDamage(1); updateHp(); if (david.hp <= 0) { LK.effects.flashScreen(0xff0000, 1000); LK.showGameOver(); gameActive = false; return; } } } // Spawn a group of 3-5 giants every 5 seconds, unless boss is active if (!bossActive) { giantGroupTimer++; if (giantGroupTimer >= giantGroupInterval) { var groupSize = 3 + Math.floor(Math.random() * 3); // 3, 4, or 5 for (var i = 0; i < groupSize; i++) { var g = new Giant(); g.x = 400 + Math.random() * 1200; g.y = -200 - i * 80; // stagger vertically so they don't overlap exactly giants.push(g); game.addChild(g); // Also spawn a gold coin for this giant var newCoin = new Coin(); newCoin.x = 400 + Math.random() * 1200; newCoin.y = 400 + Math.random() * 1800; coins.push(newCoin); game.addChild(newCoin); } giantGroupTimer = 0; } } else { // Reset timer if boss is active so we don't get a burst after boss giantGroupTimer = 0; } // --- SCROLLING LOGIC --- scrollY += scrollSpeed; // Move all world elements up by scrollSpeed sky.y = 0; // Sky remains static and always visible ground.y = 2532 - scrollY; david.y -= scrollSpeed; // Move enemy spaceship and its bullets up with world if (enemySpaceshipActive && enemySpaceship) { enemySpaceship.y -= scrollSpeed; } for (var i = 0; i < enemyShipBullets.length; i++) { if (enemyShipBullets[i]) { enemyShipBullets[i].y -= scrollSpeed; } } // Environment change per wave if (wave - 1 < environments.length) { setEnvironment(wave - 1); } // --- WEAPON/ITEM SPAWNS --- // No weapon unlocks or backpacks for machine gun only mode // Heart: appears at 5:00, only once, near center // Calculate minutes based on ticks (60 ticks per second) var minutes = Math.floor(LK.ticks / (60 * 60)); if (minutes === 5 && !heart && !heartCollected) { heart = new Heart(); heart.x = 1024; heart.y = 1366; game.addChild(heart); } // David update david.update(); // Stones update for (var i = stones.length - 1; i >= 0; i--) { var s = stones[i]; s.update(); s.y -= scrollSpeed; // move stone up with world // Remove if off screen (top or bottom) if (s.x > 2048 || s.y < -100 || s.x < -100 || s.y > 2732) { s.destroy(); stones.splice(i, 1); continue; } } // Chickens update and collect for (var i = chickens.length - 1; i >= 0; i--) { var c = chickens[i]; c.update(); c.y -= scrollSpeed; // move up with world if (!c.collected && c.intersects(david)) { c.collect(); chickens.splice(i, 1); if (david.hp < david.maxHp) { david.hp++; updateHp(); } // Optional: play sound here if you have a chicken collect sound } } // Backpacks update and collect // (No backpacks in machine gun only mode) // Heart update and collect if (heart && !heart.collected) { heart.update(); heart.y -= scrollSpeed; if (heart.intersects(david)) { heart.collect(); heartCollected = true; if (david.hp < david.maxHp) { david.hp++; updateHp(); } heart = null; } } // Coins update and collect for (var i = coins.length - 1; i >= 0; i--) { var coin = coins[i]; coin.update(); coin.y -= scrollSpeed; // move up with world if (!coin.collected && coin.intersects(david)) { coin.collect(); coins.splice(i, 1); score += 5; updateScore(); // Optional: play sound here if you have a coin collect sound // Respawn a new coin at a random location after collection var newCoin = new Coin(); newCoin.x = 400 + Math.random() * 1200; newCoin.y = 400 + Math.random() * 1800; coins.push(newCoin); game.addChild(newCoin); } } // Giants update for (var j = giants.length - 1; j >= 0; j--) { var g = giants[j]; g.update(); g.y -= scrollSpeed; // move up with world // Remove if off screen (bottom) if (g.y < -300) { g.destroy(); giants.splice(j, 1); continue; } // Collision with David if (g.active && g.intersects(david)) { g.active = false; g.destroy(); giants.splice(j, 1); david.takeDamage(1); updateHp(); if (david.hp <= 0) { LK.effects.flashScreen(0xff0000, 1000); LK.showGameOver(); gameActive = false; return; } continue; } // Stones/projectiles hit giant // --- Laser: only damage the closest giant in front of David, and always visible above giants --- for (var k = stones.length - 1; k >= 0; k--) { var s2 = stones[k]; if (s2 instanceof Laser && s2.active) { // Find the closest active giant in front of David (y > 0, lowest y) var closestGiant = null; var minDist = Infinity; for (var m = 0; m < giants.length; m++) { var g2 = giants[m]; if (g2.active && g2.y < david.y && g2.y > -300) { var dist = Math.abs(g2.y - david.y); if (dist < minDist) { minDist = dist; closestGiant = g2; } } } if (closestGiant && closestGiant.active && s2.active && closestGiant.intersects(s2)) { closestGiant.takeDamage(s2.damage); s2.active = false; s2.destroy(); stones.splice(k, 1); if (!closestGiant.active) { score += 1; updateScore(); } } // Always keep the laser above all giants and other objects, every frame if (s2.parent === game) { var idx = game.children.indexOf(s2); if (idx !== -1 && idx !== game.children.length - 1) { game.children.splice(idx, 1); game.children.push(s2); } } continue; } // Non-laser projectiles if (g.active && s2.active && g.intersects(s2)) { g.takeDamage(s2.damage); if (s2 instanceof Bazooka) { s2.pierce--; if (s2.pierce <= 0) { s2.active = false; s2.destroy(); stones.splice(k, 1); } } else { s2.active = false; s2.destroy(); stones.splice(k, 1); } if (!g.active) { score += 1; updateScore(); } break; } } } // Boss update if (bossActive && boss && boss.active) { boss.update(); boss.y -= scrollSpeed; // move up with world // David can shoot at boss as soon as boss appears david.slingReady = true; // --- BOSS BOMB THROWING LOGIC --- // Boss throws bombs if David is far away (horizontally) if (!boss._bombCooldown) boss._bombCooldown = 0; boss._bombCooldown--; var bossToDavidDist = Math.abs(boss.x - david.x); if (bossToDavidDist > 400 && boss._bombCooldown <= 0) { // Throw a bomb toward David if (!boss._bombs) boss._bombs = []; var bomb = new BossBomb(); bomb.x = boss.x; bomb.y = boss.y - boss.height * boss.scale.y / 2; // Calculate velocity toward David's current position var dx = david.x - boss.x; var dy = david.y - boss.y; var dist = Math.sqrt(dx * dx + dy * dy); var speed = 18 + Math.random() * 4; bomb.vx = dx / dist * speed; bomb.vy = dy / dist * speed * 0.7 - 6; // arc upward boss._bombs.push(bomb); game.addChild(bomb); boss._bombCooldown = 90 + Math.floor(Math.random() * 60); // 1.5s cooldown } // Update boss bombs if (boss._bombs) { for (var b = boss._bombs.length - 1; b >= 0; b--) { var bomb = boss._bombs[b]; bomb.update(); bomb.y -= scrollSpeed; // Bomb hits David if (!bomb.hit && bomb.intersects(david)) { bomb.hit = true; bomb.explode(); boss._bombs.splice(b, 1); david.takeDamage(1); updateHp(); if (david.hp <= 0) { LK.effects.flashScreen(0xff0000, 1000); LK.showGameOver(); gameActive = false; return; } continue; } // Remove if off screen or exploded if (bomb.y > 2732 || bomb.y < -100 || bomb._exploded) { bomb.destroy(); boss._bombs.splice(b, 1); } } } // --- BOSS WHIP ATTACK LOGIC --- // Boss whips if David is close horizontally if (!boss._whipCooldown) boss._whipCooldown = 0; boss._whipCooldown--; if (bossToDavidDist <= 400 && boss._whipCooldown <= 0) { // Whip attack: create a whip hitbox in front of boss if (!boss._whipHitbox) { boss._whipHitbox = new BossWhip(); boss._whipHitbox.x = boss.x + (boss.x < david.x ? 120 * boss.scale.x : -120 * boss.scale.x); boss._whipHitbox.y = boss.y - boss.height * boss.scale.y / 2 + 80 * boss.scale.y; boss._whipHitbox.scale.x = boss.scale.x; boss._whipHitbox.scale.y = boss.scale.y; game.addChild(boss._whipHitbox); } boss._whipHitbox._life = 0; boss._whipCooldown = 60 + Math.floor(Math.random() * 30); // 1s cooldown } // Update whip hitbox if (boss._whipHitbox) { boss._whipHitbox._life++; boss._whipHitbox.y -= scrollSpeed; // Whip lasts for 15 frames if (boss._whipHitbox._life > 15) { boss._whipHitbox.destroy(); boss._whipHitbox = null; } else if (!boss._whipHitbox.hit && boss._whipHitbox.intersects(david)) { boss._whipHitbox.hit = true; david.takeDamage(1); updateHp(); if (david.hp <= 0) { LK.effects.flashScreen(0xff0000, 1000); LK.showGameOver(); gameActive = false; return; } } } // Boss hits David (body collision) if (boss.intersects(david)) { boss.active = false; boss.destroy(); david.takeDamage(2); updateHp(); LK.effects.flashScreen(0xff0000, 1000); LK.showGameOver(); gameActive = false; return; } // Stones/projectiles hit boss for (var k = stones.length - 1; k >= 0; k--) { var s2 = stones[k]; if (boss.active && s2.active && boss.intersects(s2)) { boss.takeDamage(s2.damage); if (s2 instanceof Bazooka) { s2.pierce--; if (s2.pierce <= 0) { s2.active = false; s2.destroy(); stones.splice(k, 1); } } else { s2.active = false; s2.destroy(); stones.splice(k, 1); } if (!boss.active) { score += 10; updateScore(); bossDefeated = true; LK.effects.flashScreen(0x00ff00, 1200); // Show "Next Chapter" if not last chapter, else show "You Win" if (chapter < 3) { // Show "Next Chapter" overlay var nextChapterTxt = new Text2('Next Chapter', { size: 200, fill: "#fff" }); nextChapterTxt.anchor.set(0.5, 0.5); nextChapterTxt.x = 1024; nextChapterTxt.y = 1200; LK.gui.center.addChild(nextChapterTxt); // Prepare for next chapter after short delay LK.setTimeout(function () { LK.gui.center.removeChild(nextChapterTxt); // Advance chapter, reset state chapter++; wave = 1; giantsToSpawn = 5; giantsSpawned = 0; spawnTimer = 60; bossActive = false; bossDefeated = false; gameActive = true; // Scale up boss for next chapter bossScale = bossScale * 2; // Cycle environment currentEnv = (currentEnv + 1) % environments.length; setEnvironment(currentEnv); // Move David back to start david.x = 1024; david.y = 2300; // Remove all stones, chickens, coins, backpacks, heart for (var i = stones.length - 1; i >= 0; i--) { stones[i].destroy(); stones.splice(i, 1); } for (var i = chickens.length - 1; i >= 0; i--) { chickens[i].destroy(); chickens.splice(i, 1); } for (var i = coins.length - 1; i >= 0; i--) { coins[i].destroy(); coins.splice(i, 1); } for (var i = backpacks.length - 1; i >= 0; i--) { backpacks[i].destroy(); backpacks.splice(i, 1); } // Remove enemy spaceship and its bullets if (enemySpaceship) { enemySpaceship.destroy(); enemySpaceship = null; enemySpaceshipActive = false; } for (var i = enemyShipBullets.length - 1; i >= 0; i--) { if (enemyShipBullets[i]) enemyShipBullets[i].destroy(); enemyShipBullets.splice(i, 1); } if (heart) { heart.destroy(); heart = null; } heartCollected = false; // Reset scroll scrollY = 0; sky.y = 0; ground.y = 2532; // Reset weapons weapon = WEAPON_LASER; weaponUnlocked = [true, false, false]; weaponLast = WEAPON_LASER; weaponTxt.setText(weaponNames[0]); // Reset HP david.hp = david.maxHp; updateHp(); // Spawn collectibles for new wave spawnCollectiblesForWave(wave - 1); }, 2200); } else { // Last chapter, keep spawning giants forever bossActive = false; bossDefeated = false; gameActive = true; // Remove all stones, chickens, coins, backpacks, heart for (var i = stones.length - 1; i >= 0; i--) { stones[i].destroy(); stones.splice(i, 1); } for (var i = chickens.length - 1; i >= 0; i--) { chickens[i].destroy(); chickens.splice(i, 1); } for (var i = coins.length - 1; i >= 0; i--) { coins[i].destroy(); coins.splice(i, 1); } for (var i = backpacks.length - 1; i >= 0; i--) { backpacks[i].destroy(); backpacks.splice(i, 1); } // Remove enemy spaceship and its bullets if (enemySpaceship) { enemySpaceship.destroy(); enemySpaceship = null; enemySpaceshipActive = false; } for (var i = enemyShipBullets.length - 1; i >= 0; i--) { if (enemyShipBullets[i]) enemyShipBullets[i].destroy(); enemyShipBullets.splice(i, 1); } if (heart) { heart.destroy(); heart = null; } heartCollected = false; // Reset scroll scrollY = 0; sky.y = 0; ground.y = 2532; // Move David back to start david.x = 1024; david.y = 2300; // Reset HP david.hp = david.maxHp; updateHp(); // Continue spawning giants forever wave++; giantsToSpawn += 3; giantsSpawned = 0; spawnTimer = 60; spawnCollectiblesForWave((wave - 1) % chickenSpawns.length); } return; } break; } } } // --- SPAWN LOGIC (vertical, from top) --- // Giants only spawn if boss is not active if (!bossActive) { // Only allow up to 5 giants at a time if (giants.length < 5 && giantsSpawned < giantsToSpawn) { spawnTimer--; if (spawnTimer <= 0) { var g = new Giant(); g.x = 400 + Math.random() * 1200; // random horizontal position g.y = -200; // spawn just above the top giants.push(g); game.addChild(g); giantsSpawned++; // --- SPAWN GOLD COIN FOR EACH GIANT --- var newCoin = new Coin(); newCoin.x = 400 + Math.random() * 1200; newCoin.y = 400 + Math.random() * 1800; coins.push(newCoin); game.addChild(newCoin); spawnTimer = 60 + Math.floor(Math.random() * 60); } } else if (giants.length === 0) { // Next wave or boss if (wave < 3) { wave++; giantsToSpawn += 3; giantsSpawned = 0; spawnTimer = 60; spawnCollectiblesForWave(wave - 1); // spawn chickens and coins for new wave } else { // Boss time: pause giants, spawn boss bossActive = true; boss = new BossGiant(); // Scale boss size and HP per chapter boss.x = 1024; // center horizontally boss.y = -300; // spawn above the top boss.scale.x = bossScale; boss.scale.y = bossScale; boss.hp = 15 * bossScale; game.addChild(boss); spawnCollectiblesForWave(wave - 1); // spawn for boss wave } } } // Giants will continuously respawn and keep coming without end // (Removed bossDefeated check and chapter2 popup, giants always respawn) };
/****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
var storage = LK.import("@upit/storage.v1");
/****
* Classes
****/
// Backpack class (for weapon upgrades)
var Backpack = Container.expand(function () {
var self = Container.call(this);
// Determine color and shape by type after instantiation
self.type = 0; // 1=laser, 2=plasma, 3=bazooka
self.collected = false;
self._bag = null;
function updateBagVisual() {
// Remove old bag if exists
if (self._bag) {
self.removeChild(self._bag);
self._bag = null;
}
var color = 0x888888;
if (self.type === 1) color = 0xff0000; // Laser: red
else if (self.type === 2) color = 0xff9800; // Plasma: orange
else if (self.type === 3) color = 0x9c27b0; // Bazooka: purple
self._bag = self.attachAsset('coin', {
anchorX: 0.5,
anchorY: 0.5,
width: 90,
height: 90,
color: color,
shape: 'box'
});
}
updateBagVisual();
self.update = function () {
// Bobbing animation
self.y += Math.sin(LK.ticks / 10 + self.x) * 0.7;
};
self.collect = function () {
if (self.collected) return;
self.collected = true;
tween(self, {
alpha: 0,
y: self.y - 60
}, {
duration: 300,
onFinish: function onFinish() {
self.destroy();
}
});
};
return self;
});
// Bazooka projectile (green, can destroy two giants)
var Bazooka = Container.expand(function () {
var self = Container.call(this);
var bazooka = self.attachAsset('slingStone', {
anchorX: 0.5,
anchorY: 0.5,
width: 60,
height: 60,
color: 0x00ff00
});
self.vx = 0;
self.vy = -28;
self.active = true;
self.damage = 2; // Bazooka deals 2 damage (kills a giant in 1 hit)
self.pierce = 2; // can hit two targets
self.update = function () {
if (!self.active) return;
self.x += self.vx;
self.y += self.vy;
};
return self;
});
// Boss Bomb projectile (thrown by boss)
var BossBomb = Container.expand(function () {
var self = Container.call(this);
var bomb = self.attachAsset('coin', {
anchorX: 0.5,
anchorY: 0.5,
width: 60,
height: 60,
color: 0x333333,
shape: 'ellipse'
});
self.vx = 0;
self.vy = 0;
self.hit = false;
self._exploded = false;
self.update = function () {
if (self._exploded) return;
self.x += self.vx;
self.y += self.vy;
self.vy += 1.2; // gravity
// Simple spin
bomb.rotation += 0.2;
};
self.explode = function () {
if (self._exploded) return;
self._exploded = true;
tween(self, {
scaleX: 2,
scaleY: 2,
alpha: 0
}, {
duration: 300,
onFinish: function onFinish() {
self.destroy();
}
});
// Optional: play explosion sound here
};
return self;
});
// Boss Giant
var BossGiant = Container.expand(function () {
var self = Container.call(this);
var body = self.attachAsset('bossBody', {
anchorX: 0.5,
anchorY: 1
});
var head = self.attachAsset('bossHead', {
anchorX: 0.5,
anchorY: 1,
y: -body.height
});
self.hp = 15;
self.speed = 2.2;
self.active = true;
self.update = function () {
if (!self.active) return;
// Move toward David (slightly faster than before)
if (david && david.y < self.y) {
// Move down (with world scrolling up)
self.y += self.speed + 1.2;
// Track David horizontally
if (Math.abs(self.x - david.x) > 10) {
if (self.x < david.x) self.x += 2.5;else self.x -= 2.5;
}
} else {
self.y += self.speed + 1.2;
}
// Keep boss inside sky image area
if (self.x < 150) self.x = 150;
if (self.x > 2048 - 150) self.x = 2048 - 150;
if (self.y < 200) self.y = 200;
if (self.y > 1800) self.y = 1800;
// --- Boss Stick (right hand) ---
// Add stick if not present
if (!self._stick) {
self._stick = self.attachAsset('davidArm', {
anchorX: 0.5,
anchorY: 0.1,
width: 30,
height: 180,
color: 0x8d5524,
x: self.width / 2 + 80,
y: -self.height + 120
});
self._stickSwingAngle = 0;
self._stickSwingDown = true;
self._stickBaseX = self._stick.x;
self._stickBaseY = self._stick.y;
}
// Animate stick swinging downward to hit David
if (typeof self._stickSwingAngle !== "number") self._stickSwingAngle = 0;
if (typeof self._stickSwingDown !== "boolean") self._stickSwingDown = true;
if (self._stickSwingDown) {
self._stickSwingAngle += 0.10 + Math.random() * 0.03;
if (self._stickSwingAngle > 1.2) self._stickSwingDown = false;
} else {
self._stickSwingAngle -= 0.08 + Math.random() * 0.02;
if (self._stickSwingAngle < -0.3) self._stickSwingDown = true;
}
self._stick.rotation = self._stickSwingAngle + 1.1;
// Keep stick attached to boss's right side
self._stick.x = self.width / 2 + 80;
self._stick.y = -self.height + 120;
// --- End Boss Stick ---
// Axe swing animation (right arm)
if (!self._axe) {
self._axe = self.attachAsset('davidArm', {
anchorX: 0.5,
anchorY: 0.1,
x: self.width / 2 + 60,
y: -self.height + 80,
color: 0x444444
});
}
if (!self._axeAngle) self._axeAngle = 0;
self._axeAngle += 0.13 + Math.random() * 0.04;
self._axe.rotation = Math.sin(self._axeAngle) * 1.2 + 1.2;
// Boss health bar (tube) above boss
if (!self._hpBarBg) {
self._hpBarBg = self.attachAsset('coin', {
anchorX: 0.5,
anchorY: 0.5,
width: 220,
height: 32,
color: 0x222222,
shape: 'box',
y: -self.height - 60
});
}
if (!self._hpBar) {
self._hpBar = self.attachAsset('coin', {
anchorX: 0,
anchorY: 0.5,
width: 210,
height: 20,
color: 0x43a047,
shape: 'box',
x: -105,
y: -self.height - 60
});
}
if (self._hpBar) {
var maxHp = self.maxHp || (self.hp > 0 ? self.hp : 1);
if (!self.maxHp) self.maxHp = self.hp;
var frac = Math.max(0, Math.min(1, self.hp / self.maxHp));
self._hpBar.width = 210 * frac;
// Color: green to red
var col = 0x43a047;
if (frac < 0.5) col = 0xffa000;
if (frac < 0.25) col = 0xd32f2f;
self._hpBar.tint = col;
}
};
self.takeDamage = function (dmg) {
self.hp -= dmg;
if (self.hp <= 0) {
self.active = false;
LK.getSound('giantVanish').play();
tween(self, {
alpha: 0
}, {
duration: 600,
onFinish: function onFinish() {
// Remove health bar assets if present
if (self._hpBarBg) {
self.removeChild(self._hpBarBg);
self._hpBarBg = null;
}
if (self._hpBar) {
self.removeChild(self._hpBar);
self._hpBar = null;
}
self.destroy();
}
});
} else {
LK.getSound('hit').play();
tween(self, {
tint: 0xff7043
}, {
duration: 180,
onFinish: function onFinish() {
tween(self, {
tint: 0xffffff
}, {
duration: 180
});
}
});
}
};
return self;
});
// Boss Whip hitbox (short-lived, for boss whip attack)
var BossWhip = Container.expand(function () {
var self = Container.call(this);
var whip = self.attachAsset('coin', {
anchorX: 0.5,
anchorY: 0.5,
width: 180,
height: 40,
color: 0x8d5524,
shape: 'box'
});
self._life = 0;
self.hit = false;
return self;
});
// Chicken class
var Chicken = Container.expand(function () {
var self = Container.call(this);
var chicken = self.attachAsset('chicken', {
anchorX: 0.5,
anchorY: 0.5
});
self.collected = false;
self.animTimer = 0;
self.update = function () {
// Simple idle animation (bobbing)
self.animTimer++;
self.y += Math.sin(self.animTimer / 10) * 1.2;
};
self.collect = function () {
if (self.collected) return;
self.collected = true;
tween(self, {
alpha: 0,
y: self.y - 60
}, {
duration: 300,
onFinish: function onFinish() {
self.destroy();
}
});
};
return self;
});
// Coin class
var Coin = Container.expand(function () {
var self = Container.call(this);
var coin = self.attachAsset('coin', {
anchorX: 0.5,
anchorY: 0.5
});
self.collected = false;
self.animTimer = 0;
self.update = function () {
// Simple spin animation
self.animTimer++;
coin.rotation += 0.15;
self.y += Math.sin(self.animTimer / 12) * 0.8;
};
self.collect = function () {
if (self.collected) return;
self.collected = true;
tween(self, {
alpha: 0,
y: self.y - 40
}, {
duration: 200,
onFinish: function onFinish() {
self.destroy();
}
});
};
return self;
});
// Spawn chickens and coins for the current wave
// David (player)
var David = Container.expand(function () {
var self = Container.call(this);
var body = self.attachAsset('davidBody', {
anchorX: 0.5,
anchorY: 1
});
// Arm removed (no davidArm asset)
self.arm = {
width: 100,
height: 100
}; // dummy object to prevent errors
self.armAngle = 0;
self.armSpeed = 0.18; // radians per frame
self.armRadius = 70;
self.slingReady = true;
self.stoneCooldown = 0;
self.hp = 5;
self.maxHp = 5;
self.regenTimer = 0;
self.update = function () {
// Spin arm
self.armAngle += self.armSpeed;
if (self.armAngle > Math.PI * 2) self.armAngle -= Math.PI * 2;
// Arm rotates in a circle
self.arm.rotation = self.armAngle;
// Stone cooldown
if (self.stoneCooldown > 0) self.stoneCooldown--;
// Health regen
if (self.hp < self.maxHp) {
self.regenTimer++;
if (self.regenTimer > 180) {
// 3 seconds
self.hp++;
self.regenTimer = 0;
}
} else {
self.regenTimer = 0;
}
};
self.throwStone = function () {
if (!self.slingReady || self.stoneCooldown > 0) return null;
self.slingReady = false;
self.stoneCooldown = 5; // fast fire rate for machine gun
LK.getSound('throw').play();
// Fire from the middle of David's body
var sx = self.x;
var sy = self.y - body.height / 2;
// Always fire a machine gun bullet straight upwards
var proj = new Stone();
proj.x = sx;
proj.y = sy;
proj.vx = 0;
proj.vy = -38; // faster than stone for machine gun
return proj;
};
self.takeDamage = function (dmg) {
self.hp -= dmg;
if (self.hp < 0) self.hp = 0;
LK.effects.flashObject(self, 0xff0000, 400);
// No weapon revert on damage in machine gun only mode
};
return self;
});
// Enemy spaceship bullet (missile-shaped, slower, 1 damage per hit)
var EnemyShipBullet = Container.expand(function () {
var self = Container.call(this);
// Use a longer, missile-like shape
var bullet = self.attachAsset('davidArm', {
anchorX: 0.5,
anchorY: 0.5,
width: 24,
height: 64,
color: 0xff2222
});
self.vx = 0;
self.vy = 0;
self.active = true;
self._life = 0;
self.lastX = self.x;
self.lastY = self.y;
self.damage = 1; // Only 1 damage per hit
self.update = function () {
self._life++;
self.x += self.vx;
self.y += self.vy;
// Rotate to face direction of movement
if (self.vx !== 0 || self.vy !== 0) {
bullet.rotation = Math.atan2(self.vy, self.vx) + Math.PI / 2;
}
// Remove if off screen
if (self.x < -100 || self.x > 2200 || self.y < -100 || self.y > 2800) {
self.active = false;
self.destroy();
}
self.lastX = self.x;
self.lastY = self.y;
};
return self;
});
// Enemy Spaceship (appears every 2 minutes, shoots at David, exits after 10s)
var EnemySpaceship = Container.expand(function () {
var self = Container.call(this);
// Use a box for the ship body (gray)
var ship = self.attachAsset('bossBody', {
anchorX: 0.5,
anchorY: 0.5,
width: 220,
height: 90,
color: 0x8888ff,
shape: 'box'
});
// Gun visual (front)
var gun = self.attachAsset('davidArm', {
anchorX: 0.5,
anchorY: 0.5,
width: 40,
height: 60,
color: 0xff2222,
x: 0,
y: 40
});
self.state = "entering"; // entering, firing, exiting
self.fireTimer = 0;
self.exitDir = 1; // 1 = right, -1 = left
self.speed = 18;
self.fireInterval = 18; // frames between shots
self.lastFireTick = 0;
self.boundaryX = 1024; // center of screen
self.targetY = 600; // y position to hover at
self.lastX = self.x;
self.lastY = self.y;
self._life = 0;
self.update = function () {
self._life++;
// State machine
if (self.state === "entering") {
// Move horizontally toward boundaryX, stop at boundary
if (self.exitDir === 1 && self.x < self.boundaryX || self.exitDir === -1 && self.x > self.boundaryX) {
self.x += self.speed * self.exitDir;
// Clamp to boundary
if (self.exitDir === 1 && self.x > self.boundaryX || self.exitDir === -1 && self.x < self.boundaryX) {
self.x = self.boundaryX;
}
}
// Move vertically to targetY
if (Math.abs(self.y - self.targetY) > 8) {
self.y += (self.targetY - self.y) * 0.18;
}
// Arrived at boundary and targetY
if (self.x === self.boundaryX && Math.abs(self.y - self.targetY) < 10) {
self.state = "firing";
self.fireTimer = 0;
}
} else if (self.state === "firing") {
// Stay at boundary, fire at David every fireInterval frames
self.fireTimer++;
if (self.fireTimer % self.fireInterval === 0) {
self.fireAtDavid();
}
// After 10 seconds (600 frames), exit
if (self.fireTimer > 600) {
self.state = "exiting";
}
} else if (self.state === "exiting") {
// Move quickly off screen in exitDir
self.x += self.speed * 2.5 * self.exitDir;
if (self.exitDir === 1 && self.x > 2200 || self.exitDir === -1 && self.x < -200) {
self.destroy();
}
}
// Defensive: update lastX/lastY for event triggers
self.lastX = self.x;
self.lastY = self.y;
};
// Fire a bullet at David's current position
self.fireAtDavid = function () {
if (!david || !gameActive) return;
var bullet = new EnemyShipBullet();
bullet.x = self.x;
bullet.y = self.y + 40;
// Aim at David's current position
var dx = david.x - self.x;
var dy = david.y - 60 - bullet.y;
var dist = Math.sqrt(dx * dx + dy * dy);
// Make missile slower than normal (e.g. 18 instead of 32)
var speed = 18;
bullet.vx = dx / dist * speed;
bullet.vy = dy / dist * speed;
enemyShipBullets.push(bullet);
game.addChild(bullet);
};
return self;
});
// Giant enemy
var Giant = Container.expand(function () {
var self = Container.call(this);
var body = self.attachAsset('giantBody', {
anchorX: 0.5,
anchorY: 1
});
self.hp = 2; // Each giant requires 2 hits to die
self.speed = 3 + Math.random() * 2;
self.active = true;
self.update = function () {
if (!self.active) return;
// Giants move downward (with world scrolling up, so add speed to y)
self.y += self.speed;
};
self.takeDamage = function (dmg) {
self.hp -= dmg;
if (self.hp <= 0) {
self.active = false;
LK.getSound('giantVanish').play();
tween(self, {
alpha: 0
}, {
duration: 300,
onFinish: function onFinish() {
self.destroy();
}
});
} else {
LK.getSound('hit').play();
tween(self, {
tint: 0xffe082
}, {
duration: 120,
onFinish: function onFinish() {
tween(self, {
tint: 0xffffff
}, {
duration: 120
});
}
});
}
};
return self;
});
// Heart class (for health pickup)
var Heart = Container.expand(function () {
var self = Container.call(this);
var heart = self.attachAsset('coin', {
anchorX: 0.5,
anchorY: 0.5,
width: 90,
height: 90,
color: 0xff69b4,
// pink
shape: 'box'
});
self.collected = false;
self.update = function () {
self.y += Math.sin(LK.ticks / 12 + self.x) * 0.8;
};
self.collect = function () {
if (self.collected) return;
self.collected = true;
tween(self, {
alpha: 0,
y: self.y - 60
}, {
duration: 300,
onFinish: function onFinish() {
self.destroy();
}
});
};
return self;
});
// Laser projectile (covers whole screen, instant)
var Laser = Container.expand(function () {
var self = Container.call(this);
var laser = self.attachAsset('slingStone', {
anchorX: 0.5,
anchorY: 1,
width: 30,
height: 400,
// Match catapult (stone) range
color: 0x9c27b0 // purple
});
self.active = true;
self.damage = 1; // Laser deals 1 damage to giants, so 2 hits are required
self.update = function () {
// Laser is instant, doesn't move, but can fade out
if (!self.active) return;
// Fade out after a few frames
if (!self._life) self._life = 0;
self._life++;
if (self._life > 10) {
self.active = false;
self.destroy();
}
};
return self;
});
// Plasma projectile (fast, yellow, destroys instantly)
var Plasma = Container.expand(function () {
var self = Container.call(this);
var plasma = self.attachAsset('slingStone', {
anchorX: 0.5,
anchorY: 0.5,
width: 44,
height: 44,
color: 0xffe600
});
self.vx = 0;
self.vy = -40;
self.active = true;
self.damage = 99; // always kills (plasma is instant kill)
self.update = function () {
if (!self.active) return;
self.x += self.vx;
self.y += self.vy;
};
return self;
});
// Stone projectile
var Stone = Container.expand(function () {
var self = Container.call(this);
var stone = self.attachAsset('slingStone', {
anchorX: 0.5,
anchorY: 0.5
});
self.vx = 0;
self.vy = 0;
self.active = true;
self.damage = 1; // Stone deals 1 damage to giants
self.update = function () {
if (!self.active) return;
if (typeof self._startY === "undefined") self._startY = self.y;
self.x += self.vx;
self.y += self.vy;
// Gravity
self.vy += 0.7;
// Remove if exceeded maximum range (e.g. 900px upwards from start)
if (self._startY - self.y > 900) {
self.active = false;
if (typeof self.destroy === "function") self.destroy();
}
};
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0x87ceeb
});
/****
* Game Code
****/
enemySpaceshipTimer = 0;
// --- PLAYER ---
var enemySpaceshipActive = false;
var enemySpaceship = null;
var enemyShipBullets = [];
var enemySpaceshipTimer = 0;
// --- ENVIRONMENT ---
// Character (David)
var sky = LK.getAsset('sky', {
x: 0,
y: 0
});
game.addChild(sky);
// Ensure sky is always at the bottom of the display list and never removed
if (typeof game._skyLock === "undefined") {
game._skyLock = true;
var originalRemoveChild = game.removeChild;
game.removeChild = function (child) {
if (child === sky) {
// Prevent sky from being removed
return;
}
return originalRemoveChild.call(this, child);
};
// Always keep sky at the bottom
var originalAddChild = game.addChild;
game.addChild = function (child) {
if (child === sky) {
// Only add sky if not already present
if (this.children && this.children[0] !== sky) {
originalAddChild.call(this, child);
// Move sky to index 0
if (this.children && this.children.length > 1) {
this.children.splice(this.children.indexOf(sky), 1);
this.children.unshift(sky);
}
}
return sky;
}
var result = originalAddChild.call(this, child);
// After adding any child, ensure sky is at index 0
if (this.children && this.children[0] !== sky && this.children.indexOf(sky) !== -1) {
this.children.splice(this.children.indexOf(sky), 1);
this.children.unshift(sky);
}
return result;
};
}
var ground = LK.getAsset('ground', {
x: 0,
y: 2532
});
ground.y = 2532; // bottom of screen
game.addChild(ground);
// --- PLAYER ---
var david = new David();
david.x = 1024; // center horizontally
david.y = 2300; // near bottom, above ground
game.addChild(david);
// --- WEAPON SYSTEM ---
// Machine Gun is the only weapon
var WEAPON_MACHINEGUN = 0;
var weaponNames = ["Machine Gun"];
var weaponColors = [0x888888];
// Start with Machine Gun only
var weapon = WEAPON_MACHINEGUN;
var weaponUnlocked = [true]; // Only Machine Gun unlocked
var weaponLast = WEAPON_MACHINEGUN;
var weaponTimeouts = [0];
// Weapon UI
var weaponTxt = new Text2('Machine Gun', {
size: 80,
fill: "#fff"
});
weaponTxt.anchor.set(0.5, 0);
LK.gui.top.addChild(weaponTxt);
weaponTxt.y = 220;
// --- BACKPACKS & HEART ---
var backpacks = [];
var heart = null;
var heartCollected = false;
// --- SCROLLING STATE ---
var scrollY = 0; // how much the world has scrolled up
var scrollSpeed = 4; // px per frame, adjust for desired speed
// --- UI ---
var score = 0;
var scoreTxt = new Text2('0', {
size: 120,
fill: "#fff"
});
scoreTxt.anchor.set(0.5, 0);
LK.gui.top.addChild(scoreTxt);
var hpTxt = new Text2('♥♥♥♥♥', {
size: 90,
fill: 0xE53935
});
hpTxt.anchor.set(0.5, 0);
LK.gui.top.addChild(hpTxt);
hpTxt.y = 120;
// --- LEADERBOARD ---
// Leaderboard UI and logic removed as per new requirements
// --- GAME STATE ---
var stones = [];
var giants = [];
var boss = null;
var chickens = [];
var coins = [];
var wave = 1;
var giantsToSpawn = 5;
var giantsSpawned = 0;
var spawnTimer = 0;
var gameActive = true;
var bossActive = false;
var bossDefeated = false;
// --- CHAPTER STATE ---
var chapter = 1;
var bossScale = 1;
// Chicken and coin spawn positions per level (max 5 coins per level)
var chickenSpawns = [
// For each wave, array of {x, y}
[{
x: 600,
y: 1800
}, {
x: 1500,
y: 800
}], [{
x: 400,
y: 1200
}, {
x: 1700,
y: 600
}, {
x: 1200,
y: 2000
}], [{
x: 900,
y: 900
}, {
x: 1800,
y: 400
}, {
x: 300,
y: 2200
}]];
var coinSpawns = [[{
x: 400,
y: 400
}, {
x: 800,
y: 600
}, {
x: 1200,
y: 800
}, {
x: 1600,
y: 1000
}, {
x: 200,
y: 2000
}], [{
x: 600,
y: 500
}, {
x: 1000,
y: 700
}, {
x: 1400,
y: 900
}, {
x: 1800,
y: 1100
}, {
x: 400,
y: 2100
}], [{
x: 800,
y: 300
}, {
x: 1200,
y: 500
}, {
x: 1600,
y: 700
}, {
x: 200,
y: 900
}, {
x: 1800,
y: 2300
}]];
// Spawn chickens and coins for the current wave
function spawnCollectiblesForWave(waveIdx) {
// Remove old
for (var i = chickens.length - 1; i >= 0; i--) {
chickens[i].destroy();
chickens.splice(i, 1);
}
for (var i = coins.length - 1; i >= 0; i--) {
coins[i].destroy();
coins.splice(i, 1);
}
// Chickens
var cSpawns = chickenSpawns[waveIdx % chickenSpawns.length];
for (var i = 0; i < cSpawns.length; i++) {
var c = new Chicken();
c.x = cSpawns[i].x;
c.y = cSpawns[i].y;
chickens.push(c);
game.addChild(c);
}
// Coins
var coinSp = coinSpawns[waveIdx % coinSpawns.length];
for (var i = 0; i < coinSp.length; i++) {
var coin = new Coin();
coin.x = coinSp[i].x;
coin.y = coinSp[i].y;
coins.push(coin);
game.addChild(coin);
}
}
// --- MUSIC ---
LK.playMusic('mainTheme');
// --- MAPS/ENVIRONMENTS ---
var environments = [{
sky: 0x87ceeb,
ground: 0x7ec850
},
// blue sky, green grass
{
sky: 0xf7e9a0,
ground: 0xe0c97f
},
// desert
{
sky: 0x9ecae1,
ground: 0x8c8c8c
} // mountain/rocky
];
var currentEnv = 0;
function setEnvironment(idx) {
sky.tint = environments[idx].sky;
ground.tint = environments[idx].ground;
}
setEnvironment(currentEnv);
// Leaderboard name entry/update function removed as per new requirements
// --- UI UPDATE ---
function updateScore() {
scoreTxt.setText(score);
}
function updateHp() {
var s = '';
for (var i = 0; i < david.hp; i++) s += '♥';
for (var i = david.hp; i < david.maxHp; i++) s += '♡';
hpTxt.setText(s);
}
updateScore();
updateHp();
// --- INPUT ---
var isTouching = false;
var lastTouchTick = 0;
// --- MACHINE GUN (AUTOMATIC FIRE) STATE ---
var autoFireInterval = 5; // frames between shots (12 shots/sec at 60fps)
var autoFireTick = 0;
var autoFireActive = false;
var autoFireTimeout = null;
game.down = function (x, y, obj) {
isTouching = true;
lastTouchTick = LK.ticks;
// Move David to pointer/touch position (centered, free movement)
if (gameActive && !bossDefeated) {
var minX = david.arm.width / 2 + 60;
var maxX = 2048 - david.arm.width / 2 - 60;
var minY = 0 + david.arm.height / 2 + 60;
var maxY = 2532;
var newX = x;
var newY = y;
if (newX < minX) newX = minX;
if (newX > maxX) newX = maxX;
if (newY < minY) newY = minY;
if (newY > maxY) newY = maxY;
david.x = newX;
david.y = newY;
}
// Start automatic fire
if (gameActive && !bossDefeated && !autoFireActive) {
autoFireActive = true;
autoFireTick = 0;
// Fire immediately
fireMachineGun();
// Set up interval for continuous fire
autoFireTimeout = LK.setInterval(fireMachineGun, autoFireInterval * 1000 / 60);
}
};
game.up = function (x, y, obj) {
isTouching = false;
// Stop automatic fire
if (autoFireActive) {
autoFireActive = false;
if (autoFireTimeout) {
LK.clearInterval(autoFireTimeout);
autoFireTimeout = null;
}
}
// Only allow throw if touch was short (tap)
if (gameActive && !bossDefeated && LK.ticks - lastTouchTick < 30) {
// Do not fire a single shot here, as auto fire already handles it
}
};
// Helper function for machine gun fire
function fireMachineGun() {
if (!gameActive || bossDefeated) return;
if (!isTouching) return;
var stone = david.throwStone();
if (stone) {
stones.push(stone);
// If the stone is a Laser, add it after all giants to ensure it's above them
if (stone instanceof Laser) {
// Always add the laser to the top of the display list so it is visible above all giants and other objects
// Remove if already present, then add to top
var idx = game.children.indexOf(stone);
if (idx !== -1) {
game.children.splice(idx, 1);
}
game.addChild(stone);
if (game.children[game.children.length - 1] !== stone) {
var idx2 = game.children.indexOf(stone);
if (idx2 !== -1) {
game.children.splice(idx2, 1);
game.children.push(stone);
}
}
stone.parent = game;
if (typeof stone.onAdded === "function") stone.onAdded();
} else {
game.addChild(stone);
}
// Arm can't throw again until next full rotation
LK.setTimeout(function () {
david.slingReady = true;
}, 400);
}
}
game.move = function (x, y, obj) {
// Move David to pointer/touch position (centered, free movement)
if (gameActive && !bossDefeated) {
var minX = david.arm.width / 2 + 60;
var maxX = 2048 - david.arm.width / 2 - 60;
var minY = 0 + david.arm.height / 2 + 60;
var maxY = 2532; // bottom of screen
var newX = x;
var newY = y;
if (newX < minX) newX = minX;
if (newX > maxX) newX = maxX;
if (newY < minY) newY = minY;
if (newY > maxY) newY = maxY;
david.x = newX;
david.y = newY;
}
};
// --- GAME LOOP ---
// --- GIANT GROUP SPAWN EVERY 5 SECONDS (3-5 GIANTS PER GROUP) ---
var giantGroupTimer = 0;
var giantGroupInterval = 5 * 60; // 5 seconds * 60 FPS
game.update = function () {
if (!gameActive) return;
// --- ENEMY SPACESHIP TIMELINE LOGIC ---
// Only spawn if not bossActive and not already present
if (!bossActive && !enemySpaceshipActive) {
enemySpaceshipTimer++;
if (enemySpaceshipTimer >= 2 * 60 * 60) {
// every 2 minutes (7200 frames)
// Spawn from same side as giants (randomly left or right)
var side = Math.random() < 0.5 ? -1 : 1;
enemySpaceship = new EnemySpaceship();
enemySpaceship.exitDir = side;
enemySpaceship.x = side === 1 ? -200 : 2248;
enemySpaceship.y = 400;
enemySpaceshipActive = true;
enemySpaceshipTimer = 0;
game.addChild(enemySpaceship);
}
}
// Update enemy spaceship if present
if (enemySpaceshipActive && enemySpaceship) {
enemySpaceship.update();
// Remove if destroyed
if (enemySpaceship.destroyed || typeof enemySpaceship.parent === "undefined" || enemySpaceship.parent === null) {
enemySpaceship = null;
enemySpaceshipActive = false;
// Remove all its bullets
for (var i = enemyShipBullets.length - 1; i >= 0; i--) {
if (enemyShipBullets[i]) {
enemyShipBullets[i].destroy();
}
}
enemyShipBullets.length = 0;
}
}
// Update enemy spaceship bullets
for (var i = enemyShipBullets.length - 1; i >= 0; i--) {
var b = enemyShipBullets[i];
if (!b || !b.active) {
enemyShipBullets.splice(i, 1);
continue;
}
b.update();
b.y -= scrollSpeed; // move up with world
// Collision with David
if (b.active && david && b.intersects(david)) {
b.active = false;
b.destroy();
enemyShipBullets.splice(i, 1);
// Always only 1 life per hit, regardless of bullet properties
david.takeDamage(1);
updateHp();
if (david.hp <= 0) {
LK.effects.flashScreen(0xff0000, 1000);
LK.showGameOver();
gameActive = false;
return;
}
}
}
// Spawn a group of 3-5 giants every 5 seconds, unless boss is active
if (!bossActive) {
giantGroupTimer++;
if (giantGroupTimer >= giantGroupInterval) {
var groupSize = 3 + Math.floor(Math.random() * 3); // 3, 4, or 5
for (var i = 0; i < groupSize; i++) {
var g = new Giant();
g.x = 400 + Math.random() * 1200;
g.y = -200 - i * 80; // stagger vertically so they don't overlap exactly
giants.push(g);
game.addChild(g);
// Also spawn a gold coin for this giant
var newCoin = new Coin();
newCoin.x = 400 + Math.random() * 1200;
newCoin.y = 400 + Math.random() * 1800;
coins.push(newCoin);
game.addChild(newCoin);
}
giantGroupTimer = 0;
}
} else {
// Reset timer if boss is active so we don't get a burst after boss
giantGroupTimer = 0;
}
// --- SCROLLING LOGIC ---
scrollY += scrollSpeed;
// Move all world elements up by scrollSpeed
sky.y = 0; // Sky remains static and always visible
ground.y = 2532 - scrollY;
david.y -= scrollSpeed;
// Move enemy spaceship and its bullets up with world
if (enemySpaceshipActive && enemySpaceship) {
enemySpaceship.y -= scrollSpeed;
}
for (var i = 0; i < enemyShipBullets.length; i++) {
if (enemyShipBullets[i]) {
enemyShipBullets[i].y -= scrollSpeed;
}
}
// Environment change per wave
if (wave - 1 < environments.length) {
setEnvironment(wave - 1);
}
// --- WEAPON/ITEM SPAWNS ---
// No weapon unlocks or backpacks for machine gun only mode
// Heart: appears at 5:00, only once, near center
// Calculate minutes based on ticks (60 ticks per second)
var minutes = Math.floor(LK.ticks / (60 * 60));
if (minutes === 5 && !heart && !heartCollected) {
heart = new Heart();
heart.x = 1024;
heart.y = 1366;
game.addChild(heart);
}
// David update
david.update();
// Stones update
for (var i = stones.length - 1; i >= 0; i--) {
var s = stones[i];
s.update();
s.y -= scrollSpeed; // move stone up with world
// Remove if off screen (top or bottom)
if (s.x > 2048 || s.y < -100 || s.x < -100 || s.y > 2732) {
s.destroy();
stones.splice(i, 1);
continue;
}
}
// Chickens update and collect
for (var i = chickens.length - 1; i >= 0; i--) {
var c = chickens[i];
c.update();
c.y -= scrollSpeed; // move up with world
if (!c.collected && c.intersects(david)) {
c.collect();
chickens.splice(i, 1);
if (david.hp < david.maxHp) {
david.hp++;
updateHp();
}
// Optional: play sound here if you have a chicken collect sound
}
}
// Backpacks update and collect
// (No backpacks in machine gun only mode)
// Heart update and collect
if (heart && !heart.collected) {
heart.update();
heart.y -= scrollSpeed;
if (heart.intersects(david)) {
heart.collect();
heartCollected = true;
if (david.hp < david.maxHp) {
david.hp++;
updateHp();
}
heart = null;
}
}
// Coins update and collect
for (var i = coins.length - 1; i >= 0; i--) {
var coin = coins[i];
coin.update();
coin.y -= scrollSpeed; // move up with world
if (!coin.collected && coin.intersects(david)) {
coin.collect();
coins.splice(i, 1);
score += 5;
updateScore();
// Optional: play sound here if you have a coin collect sound
// Respawn a new coin at a random location after collection
var newCoin = new Coin();
newCoin.x = 400 + Math.random() * 1200;
newCoin.y = 400 + Math.random() * 1800;
coins.push(newCoin);
game.addChild(newCoin);
}
}
// Giants update
for (var j = giants.length - 1; j >= 0; j--) {
var g = giants[j];
g.update();
g.y -= scrollSpeed; // move up with world
// Remove if off screen (bottom)
if (g.y < -300) {
g.destroy();
giants.splice(j, 1);
continue;
}
// Collision with David
if (g.active && g.intersects(david)) {
g.active = false;
g.destroy();
giants.splice(j, 1);
david.takeDamage(1);
updateHp();
if (david.hp <= 0) {
LK.effects.flashScreen(0xff0000, 1000);
LK.showGameOver();
gameActive = false;
return;
}
continue;
}
// Stones/projectiles hit giant
// --- Laser: only damage the closest giant in front of David, and always visible above giants ---
for (var k = stones.length - 1; k >= 0; k--) {
var s2 = stones[k];
if (s2 instanceof Laser && s2.active) {
// Find the closest active giant in front of David (y > 0, lowest y)
var closestGiant = null;
var minDist = Infinity;
for (var m = 0; m < giants.length; m++) {
var g2 = giants[m];
if (g2.active && g2.y < david.y && g2.y > -300) {
var dist = Math.abs(g2.y - david.y);
if (dist < minDist) {
minDist = dist;
closestGiant = g2;
}
}
}
if (closestGiant && closestGiant.active && s2.active && closestGiant.intersects(s2)) {
closestGiant.takeDamage(s2.damage);
s2.active = false;
s2.destroy();
stones.splice(k, 1);
if (!closestGiant.active) {
score += 1;
updateScore();
}
}
// Always keep the laser above all giants and other objects, every frame
if (s2.parent === game) {
var idx = game.children.indexOf(s2);
if (idx !== -1 && idx !== game.children.length - 1) {
game.children.splice(idx, 1);
game.children.push(s2);
}
}
continue;
}
// Non-laser projectiles
if (g.active && s2.active && g.intersects(s2)) {
g.takeDamage(s2.damage);
if (s2 instanceof Bazooka) {
s2.pierce--;
if (s2.pierce <= 0) {
s2.active = false;
s2.destroy();
stones.splice(k, 1);
}
} else {
s2.active = false;
s2.destroy();
stones.splice(k, 1);
}
if (!g.active) {
score += 1;
updateScore();
}
break;
}
}
}
// Boss update
if (bossActive && boss && boss.active) {
boss.update();
boss.y -= scrollSpeed; // move up with world
// David can shoot at boss as soon as boss appears
david.slingReady = true;
// --- BOSS BOMB THROWING LOGIC ---
// Boss throws bombs if David is far away (horizontally)
if (!boss._bombCooldown) boss._bombCooldown = 0;
boss._bombCooldown--;
var bossToDavidDist = Math.abs(boss.x - david.x);
if (bossToDavidDist > 400 && boss._bombCooldown <= 0) {
// Throw a bomb toward David
if (!boss._bombs) boss._bombs = [];
var bomb = new BossBomb();
bomb.x = boss.x;
bomb.y = boss.y - boss.height * boss.scale.y / 2;
// Calculate velocity toward David's current position
var dx = david.x - boss.x;
var dy = david.y - boss.y;
var dist = Math.sqrt(dx * dx + dy * dy);
var speed = 18 + Math.random() * 4;
bomb.vx = dx / dist * speed;
bomb.vy = dy / dist * speed * 0.7 - 6; // arc upward
boss._bombs.push(bomb);
game.addChild(bomb);
boss._bombCooldown = 90 + Math.floor(Math.random() * 60); // 1.5s cooldown
}
// Update boss bombs
if (boss._bombs) {
for (var b = boss._bombs.length - 1; b >= 0; b--) {
var bomb = boss._bombs[b];
bomb.update();
bomb.y -= scrollSpeed;
// Bomb hits David
if (!bomb.hit && bomb.intersects(david)) {
bomb.hit = true;
bomb.explode();
boss._bombs.splice(b, 1);
david.takeDamage(1);
updateHp();
if (david.hp <= 0) {
LK.effects.flashScreen(0xff0000, 1000);
LK.showGameOver();
gameActive = false;
return;
}
continue;
}
// Remove if off screen or exploded
if (bomb.y > 2732 || bomb.y < -100 || bomb._exploded) {
bomb.destroy();
boss._bombs.splice(b, 1);
}
}
}
// --- BOSS WHIP ATTACK LOGIC ---
// Boss whips if David is close horizontally
if (!boss._whipCooldown) boss._whipCooldown = 0;
boss._whipCooldown--;
if (bossToDavidDist <= 400 && boss._whipCooldown <= 0) {
// Whip attack: create a whip hitbox in front of boss
if (!boss._whipHitbox) {
boss._whipHitbox = new BossWhip();
boss._whipHitbox.x = boss.x + (boss.x < david.x ? 120 * boss.scale.x : -120 * boss.scale.x);
boss._whipHitbox.y = boss.y - boss.height * boss.scale.y / 2 + 80 * boss.scale.y;
boss._whipHitbox.scale.x = boss.scale.x;
boss._whipHitbox.scale.y = boss.scale.y;
game.addChild(boss._whipHitbox);
}
boss._whipHitbox._life = 0;
boss._whipCooldown = 60 + Math.floor(Math.random() * 30); // 1s cooldown
}
// Update whip hitbox
if (boss._whipHitbox) {
boss._whipHitbox._life++;
boss._whipHitbox.y -= scrollSpeed;
// Whip lasts for 15 frames
if (boss._whipHitbox._life > 15) {
boss._whipHitbox.destroy();
boss._whipHitbox = null;
} else if (!boss._whipHitbox.hit && boss._whipHitbox.intersects(david)) {
boss._whipHitbox.hit = true;
david.takeDamage(1);
updateHp();
if (david.hp <= 0) {
LK.effects.flashScreen(0xff0000, 1000);
LK.showGameOver();
gameActive = false;
return;
}
}
}
// Boss hits David (body collision)
if (boss.intersects(david)) {
boss.active = false;
boss.destroy();
david.takeDamage(2);
updateHp();
LK.effects.flashScreen(0xff0000, 1000);
LK.showGameOver();
gameActive = false;
return;
}
// Stones/projectiles hit boss
for (var k = stones.length - 1; k >= 0; k--) {
var s2 = stones[k];
if (boss.active && s2.active && boss.intersects(s2)) {
boss.takeDamage(s2.damage);
if (s2 instanceof Bazooka) {
s2.pierce--;
if (s2.pierce <= 0) {
s2.active = false;
s2.destroy();
stones.splice(k, 1);
}
} else {
s2.active = false;
s2.destroy();
stones.splice(k, 1);
}
if (!boss.active) {
score += 10;
updateScore();
bossDefeated = true;
LK.effects.flashScreen(0x00ff00, 1200);
// Show "Next Chapter" if not last chapter, else show "You Win"
if (chapter < 3) {
// Show "Next Chapter" overlay
var nextChapterTxt = new Text2('Next Chapter', {
size: 200,
fill: "#fff"
});
nextChapterTxt.anchor.set(0.5, 0.5);
nextChapterTxt.x = 1024;
nextChapterTxt.y = 1200;
LK.gui.center.addChild(nextChapterTxt);
// Prepare for next chapter after short delay
LK.setTimeout(function () {
LK.gui.center.removeChild(nextChapterTxt);
// Advance chapter, reset state
chapter++;
wave = 1;
giantsToSpawn = 5;
giantsSpawned = 0;
spawnTimer = 60;
bossActive = false;
bossDefeated = false;
gameActive = true;
// Scale up boss for next chapter
bossScale = bossScale * 2;
// Cycle environment
currentEnv = (currentEnv + 1) % environments.length;
setEnvironment(currentEnv);
// Move David back to start
david.x = 1024;
david.y = 2300;
// Remove all stones, chickens, coins, backpacks, heart
for (var i = stones.length - 1; i >= 0; i--) {
stones[i].destroy();
stones.splice(i, 1);
}
for (var i = chickens.length - 1; i >= 0; i--) {
chickens[i].destroy();
chickens.splice(i, 1);
}
for (var i = coins.length - 1; i >= 0; i--) {
coins[i].destroy();
coins.splice(i, 1);
}
for (var i = backpacks.length - 1; i >= 0; i--) {
backpacks[i].destroy();
backpacks.splice(i, 1);
}
// Remove enemy spaceship and its bullets
if (enemySpaceship) {
enemySpaceship.destroy();
enemySpaceship = null;
enemySpaceshipActive = false;
}
for (var i = enemyShipBullets.length - 1; i >= 0; i--) {
if (enemyShipBullets[i]) enemyShipBullets[i].destroy();
enemyShipBullets.splice(i, 1);
}
if (heart) {
heart.destroy();
heart = null;
}
heartCollected = false;
// Reset scroll
scrollY = 0;
sky.y = 0;
ground.y = 2532;
// Reset weapons
weapon = WEAPON_LASER;
weaponUnlocked = [true, false, false];
weaponLast = WEAPON_LASER;
weaponTxt.setText(weaponNames[0]);
// Reset HP
david.hp = david.maxHp;
updateHp();
// Spawn collectibles for new wave
spawnCollectiblesForWave(wave - 1);
}, 2200);
} else {
// Last chapter, keep spawning giants forever
bossActive = false;
bossDefeated = false;
gameActive = true;
// Remove all stones, chickens, coins, backpacks, heart
for (var i = stones.length - 1; i >= 0; i--) {
stones[i].destroy();
stones.splice(i, 1);
}
for (var i = chickens.length - 1; i >= 0; i--) {
chickens[i].destroy();
chickens.splice(i, 1);
}
for (var i = coins.length - 1; i >= 0; i--) {
coins[i].destroy();
coins.splice(i, 1);
}
for (var i = backpacks.length - 1; i >= 0; i--) {
backpacks[i].destroy();
backpacks.splice(i, 1);
}
// Remove enemy spaceship and its bullets
if (enemySpaceship) {
enemySpaceship.destroy();
enemySpaceship = null;
enemySpaceshipActive = false;
}
for (var i = enemyShipBullets.length - 1; i >= 0; i--) {
if (enemyShipBullets[i]) enemyShipBullets[i].destroy();
enemyShipBullets.splice(i, 1);
}
if (heart) {
heart.destroy();
heart = null;
}
heartCollected = false;
// Reset scroll
scrollY = 0;
sky.y = 0;
ground.y = 2532;
// Move David back to start
david.x = 1024;
david.y = 2300;
// Reset HP
david.hp = david.maxHp;
updateHp();
// Continue spawning giants forever
wave++;
giantsToSpawn += 3;
giantsSpawned = 0;
spawnTimer = 60;
spawnCollectiblesForWave((wave - 1) % chickenSpawns.length);
}
return;
}
break;
}
}
}
// --- SPAWN LOGIC (vertical, from top) ---
// Giants only spawn if boss is not active
if (!bossActive) {
// Only allow up to 5 giants at a time
if (giants.length < 5 && giantsSpawned < giantsToSpawn) {
spawnTimer--;
if (spawnTimer <= 0) {
var g = new Giant();
g.x = 400 + Math.random() * 1200; // random horizontal position
g.y = -200; // spawn just above the top
giants.push(g);
game.addChild(g);
giantsSpawned++;
// --- SPAWN GOLD COIN FOR EACH GIANT ---
var newCoin = new Coin();
newCoin.x = 400 + Math.random() * 1200;
newCoin.y = 400 + Math.random() * 1800;
coins.push(newCoin);
game.addChild(newCoin);
spawnTimer = 60 + Math.floor(Math.random() * 60);
}
} else if (giants.length === 0) {
// Next wave or boss
if (wave < 3) {
wave++;
giantsToSpawn += 3;
giantsSpawned = 0;
spawnTimer = 60;
spawnCollectiblesForWave(wave - 1); // spawn chickens and coins for new wave
} else {
// Boss time: pause giants, spawn boss
bossActive = true;
boss = new BossGiant();
// Scale boss size and HP per chapter
boss.x = 1024; // center horizontally
boss.y = -300; // spawn above the top
boss.scale.x = bossScale;
boss.scale.y = bossScale;
boss.hp = 15 * bossScale;
game.addChild(boss);
spawnCollectiblesForWave(wave - 1); // spawn for boss wave
}
}
}
// Giants will continuously respawn and keep coming without end
// (Removed bossDefeated check and chapter2 popup, giants always respawn)
};
Create a spaceship as the main hero. It will be in the center, with the front tip pointing upwards. There will be a firing mechanism on both sides.
Imagine a multi-legged space machine, its legs sprawling at varied, dynamic angles, giving it an organic yet mechanical feel. Encased within a transparent tube, a one-eyed green alien watches intently. Beneath the ship, vivid rocket flames burst forth, illuminating the dark void of space with fiery intensity.
The background will be a dynamic space scene, featuring only blue and dark navy colors. top to bottom, creating a lively and immersive environment."