User prompt
I don't see the health bar. Make it bigger and make it like at the top of the screen.
User prompt
Make him killable! Also, add, like, a health bar when he comes up and it goes down every time you shoot him and when it gets to the lowest, then he dies.
User prompt
When you kill the bat, you will earn 1000 coins, also make him very hard to kill.
User prompt
Make the enemy bat a lot bigger than all the rest of the enemies so it really stands out.
User prompt
Move it into the middle of the screen, the text.
User prompt
Move it to the middle.
User prompt
But make it so it's visible so the player can actually see it and not on the side.
User prompt
When the bat appears, please put text on the screen and creepy music and the text will say that is not you.
User prompt
Make bat monster only spawn sometimes
User prompt
take a maze background
User prompt
Make the enemies slower and bigger so that you can actually shoot them.
User prompt
I go back to the vertical shooter.
User prompt
Make a maze
User prompt
Make it like a maze, instead of it just shooting upwards, and like, all the monsters can just run at you at a random time, and you can't see them, until they come up, like, pretty close to you.
Code edit (1 edits merged)
Please save this source code
User prompt
Minecart Mayhem: Armed Run
Initial prompt
It's like my other game Minecart Escape, but basically you have guns and knives, and there's more than just the skeleton mob.
/**** * Plugins ****/ var tween = LK.import("@upit/tween.v1"); var storage = LK.import("@upit/storage.v1"); /**** * Classes ****/ // Bullet var Bullet = Container.expand(function () { var self = Container.call(this); var bullet = self.attachAsset('bullet', { anchorX: 0.5, anchorY: 0.5 }); self.width = bullet.width; self.height = bullet.height; self.speed = -36; // Upwards self.power = 1; // Default bullet power self.angle = 0; // For spread self.update = function () { if (self.angle !== 0) { // Move in angle for spread self.x += Math.sin(self.angle) * 12; self.y += Math.cos(self.angle) * self.speed; } else { self.y += self.speed; } }; return self; }); // Enemy base class var Enemy = Container.expand(function () { var self = Container.call(this); self.type = 'enemy'; self.hp = 1; self.speed = 8; self.scoreValue = 1; self.update = function () {}; return self; }); // Wendigo enemy (very rare, 1% spawn chance) var Wendigo = Enemy.expand(function () { var self = Enemy.call(this); // Use the 'Monster' asset for now, but tint it to distinguish var wendigo = self.attachAsset('Monster', { anchorX: 0.5, anchorY: 0.5 }); // Make Wendigo visually unique wendigo.scaleX = 3.5; wendigo.scaleY = 3.5; wendigo.tint = 0xccccff; // Pale blue/white self.width = wendigo.width * 3.5; self.height = wendigo.height * 3.5; // Wendigo stats: very high HP, very fast, high score self.hp = 40 + Math.floor(Math.random() * 20); self.speed = 12 + Math.random() * 3; self.scoreValue = 2000; // Give it a unique movement pattern (erratic zigzag) self._zigzag = Math.random() * Math.PI * 2; self.update = function () { self.y += self.speed; self.x += Math.sin(LK.ticks / 4 + self._zigzag) * 32; }; return self; }); // Skeleton enemy var Skeleton = Enemy.expand(function () { var self = Enemy.call(this); var skeleton = self.attachAsset('enemy_skeleton', { anchorX: 0.5, anchorY: 0.5 }); // Make skeleton bigger skeleton.scaleX = 1.7; skeleton.scaleY = 1.7; self.width = skeleton.width * 1.7; self.height = skeleton.height * 1.7; self.hp = 1; // Make skeleton slower self.speed = 6 + Math.random() * 2.5; self.scoreValue = 2; self.update = function () { self.y += self.speed; }; return self; }); // Nightmare Monster (unique to Nightmare Mode) var NightmareMonster = Enemy.expand(function () { var self = Enemy.call(this); // Use the 'Monster' asset, which is unique and scary var monster = self.attachAsset('Monster', { anchorX: 0.5, anchorY: 0.5 }); // Make it very large and visually imposing monster.scaleX = 3.2; monster.scaleY = 3.2; self.width = monster.width * 3.2; self.height = monster.height * 3.2; // High HP, fast, and high score value self.hp = 12 + Math.floor(Math.random() * 8); // Set NightmareMonster speed to normal enemy speed self.speed = 8 + Math.random() * 2.5; self.scoreValue = 50; // Give it a creepy wobble movement self._wobble = Math.random() * Math.PI * 2; self.update = function () { self.y += self.speed; self.x += Math.sin(LK.ticks / 8 + self._wobble) * 18; }; return self; }); // Goblin enemy var Goblin = Enemy.expand(function () { var self = Enemy.call(this); var goblin = self.attachAsset('enemy_goblin', { anchorX: 0.5, anchorY: 0.5 }); // Make goblin bigger goblin.scaleX = 1.7; goblin.scaleY = 1.7; self.width = goblin.width * 1.7; self.height = goblin.height * 1.7; self.hp = 2; // Make goblin slower self.speed = 5 + Math.random() * 2; self.scoreValue = 3; self.update = function () { self.y += self.speed; }; return self; }); // Ginger enemy var Ginger = Enemy.expand(function () { var self = Enemy.call(this); // Use goblin as base, but with different color and stats var ginger = self.attachAsset('enemy_goblin', { anchorX: 0.5, anchorY: 0.5 }); // Tint to orange/ginger color ginger.tint = 0xffa14e; ginger.scaleX = 1.7; ginger.scaleY = 1.7; self.width = ginger.width * 1.7; self.height = ginger.height * 1.7; self.hp = 3; self.speed = 7 + Math.random() * 2; self.scoreValue = 7; self.update = function () { self.y += self.speed; }; return self; }); // Bat enemy var Bat = Enemy.expand(function () { var self = Enemy.call(this); var bat = self.attachAsset('enemy_bat', { anchorX: 0.5, anchorY: 0.5 }); // Make bat much bigger so it stands out bat.scaleX = 3.5; bat.scaleY = 3.5; self.width = bat.width * 3.5; self.height = bat.height * 3.5; // Make bat very hard to kill self.hp = 30; // Make bat slower self.speed = 7 + Math.random() * 2.5; // Bat is worth 1000 coins self.scoreValue = 1000; self.update = function () { self.y += self.speed; self.x += Math.sin(LK.ticks / 12 + self._batWobble) * 8; }; self._batWobble = Math.random() * Math.PI * 2; return self; }); // Player Minecart var Minecart = Container.expand(function () { var self = Container.call(this); // Use selected player model from storage or default based on unlocks var assetId = 'minecart'; var selectedModel = storage.selectedPlayerModel || 'default'; if (selectedModel === 'chickenjockey' && typeof storage.chickenJockeyUnlocked !== 'undefined' && storage.chickenJockeyUnlocked) { assetId = 'Chickenjockey'; } else if (selectedModel === 'skeleton' && typeof skeletonPlayerUnlocked !== 'undefined' && skeletonPlayerUnlocked) { assetId = 'Skeletonmodel'; } else if (selectedModel === 'player' && typeof playerUnlocked !== 'undefined' && playerUnlocked) { assetId = 'Player'; } else if (typeof skeletonPlayerUnlocked !== 'undefined' && skeletonPlayerUnlocked && selectedModel === 'default') { assetId = 'Skeletonmodel'; } else if (typeof playerUnlocked !== 'undefined' && playerUnlocked && selectedModel === 'default') { assetId = 'Player'; } var cart = self.attachAsset(assetId, { anchorX: 0.5, anchorY: 0.5 }); self.width = cart.width; self.height = cart.height; self.weapon = 'gun'; // 'gun' or 'knife' self.weaponLevel = 1; // Upgrades increase this self.invincible = false; self.invincibleTimer = 0; self.attackCooldown = 0; self.knifeSwinging = false; self.knife = null; // Flash minecart when hit self.flash = function () { self.invincible = true; self.invincibleTimer = 60; // 1 second at 60fps tween(self, { alpha: 0.5 }, { duration: 100, easing: tween.easeOut, onFinish: function onFinish() { tween(self, { alpha: 1 }, { duration: 100 }); } }); }; // Switch weapon self.setWeapon = function (type) { self.weapon = type; }; // Upgrade weapon self.upgradeWeapon = function () { self.weaponLevel += 1; if (self.weaponLevel > 3) { self.weaponLevel = 3; } }; // Knife attack animation self.swingKnife = function () { if (self.knifeSwinging) { return; } self.knifeSwinging = true; if (!self.knife) { self.knife = self.attachAsset('knife', { anchorX: 0.1, anchorY: 0.5, x: self.width / 2 + 20, y: 0, rotation: 0 }); } self.knife.visible = true; self.knife.rotation = -0.7; tween(self.knife, { rotation: 0.7 }, { duration: 180, easing: tween.cubicOut, onFinish: function onFinish() { self.knife.visible = false; self.knifeSwinging = false; self.knife.rotation = -0.7; } }); }; return self; }); // Obstacle (rock) var Obstacle = Container.expand(function () { var self = Container.call(this); var rock = self.attachAsset('obstacle_rock', { anchorX: 0.5, anchorY: 0.5 }); self.width = rock.width; self.height = rock.height; self.speed = 14 + Math.random() * 6; self.update = function () { self.y += self.speed; }; return self; }); // Upgrade (gun or knife) var Upgrade = Container.expand(function () { var self = Container.call(this); self.kind = Math.random() < 0.5 ? 'gun' : 'knife'; var assetId = self.kind === 'gun' ? 'upgrade_gun' : 'upgrade_knife'; var upg = self.attachAsset(assetId, { anchorX: 0.5, anchorY: 0.5 }); self.width = upg.width; self.height = upg.height; self.speed = 10 + Math.random() * 4; self.update = function () { self.y += self.speed; }; return self; }); /**** * Initialize Game ****/ var game = new LK.Game({ backgroundColor: 0x181818 }); /**** * Game Code ****/ // Game constants // Minecart (player) // Player's knife // Player's bullet // Enemy: Bat // Enemy: Goblin // Enemy: Skeleton // Obstacle: Rock // Upgrade: Gun // Upgrade: Knife // Maze background image (replace with your maze image asset id) var GAME_W = 2048; var GAME_H = 2732; var TRACK_LEFT = 300; var TRACK_RIGHT = GAME_W - 300; var PLAYER_Y = GAME_H - 350; // Game state var minecart = null; var bullets = []; var enemies = []; var obstacles = []; var upgrades = []; var dragNode = null; var lastTouchX = 0; var lastTouchY = 0; var scoreTxt = null; var lastScore = 0; var visibleScore = 0; // This is the score shown on screen and used for unlocks var gameOver = false; var spawnTimer = 0; var obstacleTimer = 0; var upgradeTimer = 0; var difficulty = 1; var knifeHitEnemies = []; var lastKnifeSwingTick = -100; // Track if player has unlocked the upgraded knife by beating super hard mode var upgradedKnifeUnlocked = false; // Track if player has chosen to use upgraded knife in normal mode (set via menu) var useUpgradedKnifeNextGame = false; // Track if player has unlocked the player (250 kills in super hard mode) var playerUnlocked = false; var playerKills = 0; // Track skeleton player unlock (100 kills in normal mode - reduced for easier unlock) var skeletonPlayerUnlocked = storage.skeletonPlayerUnlocked || false; var normalModeKills = storage.normalModeKills || 0; // Track upgrades unlock (500 points in Nightmare Mode) var upgradesUnlocked = false; if (typeof storage !== "undefined" && typeof storage.upgradesUnlocked !== "undefined") { upgradesUnlocked = !!storage.upgradesUnlocked; } // Try to load from persistent storage if available if (typeof storage !== "undefined" && storage.upgradedKnifeUnlocked) { upgradedKnifeUnlocked = true; } if (typeof storage !== "undefined" && storage.playerUnlocked) { playerUnlocked = true; } if (typeof storage !== "undefined" && storage.playerKills) { playerKills = parseInt(storage.playerKills, 10) || 0; } // Always reset useUpgradedKnifeNextGame on load useUpgradedKnifeNextGame = false; // --- Main Menu Overlay --- var mainMenuOverlay = null; var mainMenuTitle = null; var normalBtn = null; var hardBtn = null; var menuActive = true; function showMainMenu() { menuActive = true; // Play Feel It by D4VD as background music for the main menu LK.playMusic('feel_it_d4vd', { fade: { start: 0, end: 0.7, duration: 1000 } }); // Overlay container mainMenuOverlay = new Container(); // Semi-transparent dark background var bg = LK.getAsset('maze_bg', { anchorX: 0, anchorY: 0, x: 0, y: 0, width: GAME_W, height: GAME_H }); bg.alpha = 0.82; mainMenuOverlay.addChild(bg); // Title mainMenuTitle = new Text2("Boneshaft 1: UPGRADED", { size: 180, fill: "#fff", font: "Impact, Arial Black, Tahoma" }); mainMenuTitle.anchor.set(0.5, 0.5); mainMenuTitle.x = GAME_W / 2; mainMenuTitle.y = 600; mainMenuOverlay.addChild(mainMenuTitle); // Normal Mode Button var normalLabel = "Normal Mode"; if (upgradedKnifeUnlocked) { normalLabel += " (Upgraded Knife!)"; } normalBtn = new Text2(normalLabel, { size: 120, fill: 0x44FF44, font: "Impact, Arial Black, Tahoma" }); normalBtn.anchor.set(0.5, 0.5); normalBtn.x = GAME_W / 2; normalBtn.y = 1100; mainMenuOverlay.addChild(normalBtn); // Super Hard Mode Button hardBtn = new Text2("Super Hard Mode", { size: 120, fill: 0xFF4444, font: "Impact, Arial Black, Tahoma" }); hardBtn.anchor.set(0.5, 0.5); hardBtn.x = GAME_W / 2; hardBtn.y = 1350; mainMenuOverlay.addChild(hardBtn); // Nightmare Mode Button nightmareBtn = new Text2("Nightmare Mode", { size: 120, fill: 0x000000, font: "Impact, Arial Black, Tahoma" }); nightmareBtn.anchor.set(0.5, 0.5); nightmareBtn.x = GAME_W / 2; // Move Nightmare Mode button a little bit further down nightmareBtn.y = 1700; mainMenuOverlay.addChild(nightmareBtn); // --- Unlocks Button and Unlocks Panel --- var unlocksBtn = new Text2("Unlocks", { size: 100, fill: 0xFFD700, font: "Impact, Arial Black, Tahoma" }); unlocksBtn.anchor.set(0.5, 0.5); unlocksBtn.x = GAME_W / 2; unlocksBtn.y = 1550; mainMenuOverlay.addChild(unlocksBtn); // Unlocks panel (hidden by default) var unlocksPanel = null; var unlocksPanelBg = null; var unlocksPanelOpen = false; function showUnlocksPanel() { if (unlocksPanelOpen) return; unlocksPanelOpen = true; unlocksPanel = new Container(); // Panel background // Always use black background for unlocks panel unlocksPanelBg = LK.getAsset('maze_bg', { anchorX: 0, anchorY: 0, x: GAME_W / 2 - 600, y: 900, width: 1200, height: 1800, // increased height for longer section color: 0x000000 // always black }); unlocksPanelBg.alpha = 0.96; unlocksPanel.addChild(unlocksPanelBg); // Title var unlocksTitle = new Text2("Unlocks", { size: 110, fill: 0xFFD700, font: "Impact, Arial Black, Tahoma" }); unlocksTitle.anchor.set(0.5, 0.5); unlocksTitle.x = GAME_W / 2; unlocksTitle.y = 1020; unlocksPanel.addChild(unlocksTitle); // Y position for first unlock var unlockY = 1150; var unlockSpacing = 200; // --- Upgraded Knife Unlock --- var unlockedKnifeText, unlockedKnifeIcon; var knifeConfirmDialog = null; if (upgradedKnifeUnlocked) { var showKnifeConfirmDialog = function showKnifeConfirmDialog() { if (knifeConfirmDialog) return; knifeConfirmDialog = new Container(); var dialogBg = LK.getAsset('maze_bg', { anchorX: 0, anchorY: 0, x: GAME_W / 2 - 400, y: 1200, width: 800, height: 400 }); dialogBg.alpha = 0.93; knifeConfirmDialog.addChild(dialogBg); var dialogText = new Text2("Use Upgraded Knife in Normal Mode?", { size: 60, fill: "#fff", font: "Impact, Arial Black, Tahoma" }); dialogText.anchor.set(0.5, 0.5); dialogText.x = GAME_W / 2; dialogText.y = 1320; knifeConfirmDialog.addChild(dialogText); var yesBtn = new Text2("Yes", { size: 90, fill: 0x44FF44, font: "Impact, Arial Black, Tahoma" }); yesBtn.anchor.set(0.5, 0.5); yesBtn.x = GAME_W / 2 - 140; yesBtn.y = 1450; knifeConfirmDialog.addChild(yesBtn); var noBtn = new Text2("No", { size: 90, fill: 0xFF4444, font: "Impact, Arial Black, Tahoma" }); noBtn.anchor.set(0.5, 0.5); noBtn.x = GAME_W / 2 + 140; noBtn.y = 1450; knifeConfirmDialog.addChild(noBtn); yesBtn.interactive = true; noBtn.interactive = true; yesBtn.down = function () { useUpgradedKnifeNextGame = true; if (knifeConfirmDialog) { knifeConfirmDialog.destroy(); knifeConfirmDialog = null; } }; noBtn.down = function () { if (knifeConfirmDialog) { knifeConfirmDialog.destroy(); knifeConfirmDialog = null; } }; yesBtn.on('down', yesBtn.down); noBtn.on('down', noBtn.down); unlocksPanel.addChild(knifeConfirmDialog); }; var unlockedKnifeDownHandler = function unlockedKnifeDownHandler() { showKnifeConfirmDialog(); }; unlockedKnifeText = new Text2("Upgraded Knife", { size: 80, fill: 0x00BFFF, font: "Impact, Arial Black, Tahoma" }); unlockedKnifeIcon = LK.getAsset('upgrade_knife', { anchorX: 0.5, anchorY: 0.5, x: GAME_W / 2 - 220, y: unlockY }); unlockedKnifeText.anchor.set(0, 0.5); unlockedKnifeText.x = GAME_W / 2 - 160; unlockedKnifeText.y = unlockY; unlocksPanel.addChild(unlockedKnifeIcon); unlocksPanel.addChild(unlockedKnifeText); unlockedKnifeText.interactive = true; unlockedKnifeIcon.interactive = true; unlockedKnifeText.down = unlockedKnifeDownHandler; unlockedKnifeIcon.down = unlockedKnifeDownHandler; unlockedKnifeText.on('down', unlockedKnifeDownHandler); unlockedKnifeIcon.on('down', unlockedKnifeDownHandler); } else { unlockedKnifeText = new Text2("??? (Beat Super Hard Mode to unlock)", { size: 70, fill: 0x00BFFF, //{3a} // bright blue font: "Impact, Arial Black, Tahoma" }); unlockedKnifeText.anchor.set(0.5, 0.5); unlockedKnifeText.x = GAME_W / 2; unlockedKnifeText.y = unlockY; unlocksPanel.addChild(unlockedKnifeText); } unlockY += unlockSpacing; // --- Player Unlock Section --- var unlockedPlayerText, unlockedPlayerIcon; var playerSectionY = unlockY; if (playerUnlocked) { unlockedPlayerText = new Text2("Player (Unlocked!)", { size: 80, fill: 0x00BFFF, font: "Impact, Arial Black, Tahoma" }); unlockedPlayerIcon = LK.getAsset('Player', { anchorX: 0.5, anchorY: 0.5, x: GAME_W / 2 - 220, y: playerSectionY }); unlockedPlayerText.anchor.set(0, 0.5); unlockedPlayerText.x = GAME_W / 2 - 160; unlockedPlayerText.y = playerSectionY; unlocksPanel.addChild(unlockedPlayerIcon); unlocksPanel.addChild(unlockedPlayerText); var playerDownHandler = function playerDownHandler() { storage.selectedPlayerModel = 'player'; }; unlockedPlayerText.interactive = true; unlockedPlayerIcon.interactive = true; unlockedPlayerText.down = playerDownHandler; unlockedPlayerIcon.down = playerDownHandler; unlockedPlayerText.on('down', playerDownHandler); unlockedPlayerIcon.on('down', playerDownHandler); } else { var killsNeeded = 50 - playerKills; if (killsNeeded < 0) killsNeeded = 0; unlockedPlayerText = new Text2("??? (Get 50 kills in Super Hard Mode to unlock)\nKills: " + playerKills + " / 50", { size: 60, fill: 0x00BFFF, //{3u} // bright blue font: "Impact, Arial Black, Tahoma" }); unlockedPlayerText.anchor.set(0.5, 0.5); unlockedPlayerText.x = GAME_W / 2; unlockedPlayerText.y = playerSectionY; unlocksPanel.addChild(unlockedPlayerText); } unlockY += unlockSpacing; // --- Skeleton Player Unlock Section --- var skeletonSectionY = unlockY; if (skeletonPlayerUnlocked) { var unlockedSkeletonText = new Text2("Skeleton Player (Unlocked!)", { size: 80, fill: 0x00BFFF, font: "Impact, Arial Black, Tahoma" }); var unlockedSkeletonIcon = LK.getAsset('Skeletonmodel', { anchorX: 0.5, anchorY: 0.5, x: GAME_W / 2 - 220, y: skeletonSectionY }); unlockedSkeletonText.anchor.set(0, 0.5); unlockedSkeletonText.x = GAME_W / 2 - 160; unlockedSkeletonText.y = skeletonSectionY; unlocksPanel.addChild(unlockedSkeletonIcon); unlocksPanel.addChild(unlockedSkeletonText); var skeletonPlayerDownHandler = function skeletonPlayerDownHandler() { storage.selectedPlayerModel = 'skeleton'; }; unlockedSkeletonText.interactive = true; unlockedSkeletonIcon.interactive = true; unlockedSkeletonText.down = skeletonPlayerDownHandler; unlockedSkeletonIcon.down = skeletonPlayerDownHandler; unlockedSkeletonText.on('down', skeletonPlayerDownHandler); unlockedSkeletonIcon.on('down', skeletonPlayerDownHandler); } else { var killsNeeded = 100 - normalModeKills; if (killsNeeded < 0) killsNeeded = 0; var unlockedSkeletonText = new Text2("??? (Get 100 kills in Normal Mode to unlock)\nKills: " + normalModeKills + " / 100", { size: 60, fill: 0x00BFFF, //{3P} // bright blue font: "Impact, Arial Black, Tahoma" }); unlockedSkeletonText.anchor.set(0.5, 0.5); unlockedSkeletonText.x = GAME_W / 2; unlockedSkeletonText.y = skeletonSectionY; unlocksPanel.addChild(unlockedSkeletonText); } unlockY += unlockSpacing; // --- Upgrades Unlock Section --- var upgradesSectionY = unlockY; var upgradesUnlockText, upgradesUnlockIcon; if (upgradesUnlocked) { upgradesUnlockText = new Text2("Upgrades (Unlocked!)", { size: 80, fill: 0xFFD700, font: "Impact, Arial Black, Tahoma" }); upgradesUnlockIcon = LK.getAsset('upgrade_gun', { anchorX: 0.5, anchorY: 0.5, x: GAME_W / 2 - 220, y: upgradesSectionY }); upgradesUnlockText.anchor.set(0, 0.5); upgradesUnlockText.x = GAME_W / 2 - 160; upgradesUnlockText.y = upgradesSectionY; unlocksPanel.addChild(upgradesUnlockIcon); unlocksPanel.addChild(upgradesUnlockText); } else if (typeof game._upgradesUnlockReady !== "undefined" && game._upgradesUnlockReady) { // Ready to unlock: show clickable unlock upgradesUnlockText = new Text2("Unlock Upgrades (500+ Nightmare points)", { size: 70, fill: 0x00BFFF, font: "Impact, Arial Black, Tahoma" }); upgradesUnlockIcon = LK.getAsset('upgrade_gun', { anchorX: 0.5, anchorY: 0.5, x: GAME_W / 2 - 220, y: upgradesSectionY }); upgradesUnlockText.anchor.set(0, 0.5); upgradesUnlockText.x = GAME_W / 2 - 160; upgradesUnlockText.y = upgradesSectionY; unlocksPanel.addChild(upgradesUnlockIcon); unlocksPanel.addChild(upgradesUnlockText); // Add interactive unlock logic upgradesUnlockText.interactive = true; upgradesUnlockIcon.interactive = true; var upgradesConfirmDialog = null; var showUpgradesConfirmDialog = function showUpgradesConfirmDialog() { if (upgradesConfirmDialog) return; upgradesConfirmDialog = new Container(); var dialogBg = LK.getAsset('maze_bg', { anchorX: 0, anchorY: 0, x: GAME_W / 2 - 400, y: 1200, width: 800, height: 400 }); dialogBg.alpha = 0.93; upgradesConfirmDialog.addChild(dialogBg); var dialogText = new Text2("Unlock Upgrades? (This cannot be undone)", { size: 60, fill: "#fff", font: "Impact, Arial Black, Tahoma" }); dialogText.anchor.set(0.5, 0.5); dialogText.x = GAME_W / 2; dialogText.y = 1320; upgradesConfirmDialog.addChild(dialogText); var yesBtn = new Text2("Yes", { size: 90, fill: 0x44FF44, font: "Impact, Arial Black, Tahoma" }); yesBtn.anchor.set(0.5, 0.5); yesBtn.x = GAME_W / 2 - 140; yesBtn.y = 1450; upgradesConfirmDialog.addChild(yesBtn); var noBtn = new Text2("No", { size: 90, fill: 0xFF4444, font: "Impact, Arial Black, Tahoma" }); noBtn.anchor.set(0.5, 0.5); noBtn.x = GAME_W / 2 + 140; noBtn.y = 1450; upgradesConfirmDialog.addChild(noBtn); yesBtn.interactive = true; noBtn.interactive = true; yesBtn.down = function () { upgradesUnlocked = true; if (typeof storage !== "undefined") { storage.upgradesUnlocked = true; } game._upgradesUnlockReady = undefined; if (upgradesConfirmDialog) { upgradesConfirmDialog.destroy(); upgradesConfirmDialog = null; } // Refresh unlocks panel to show unlocked state if (unlocksPanel) { unlocksPanel.destroy(); unlocksPanel = null; unlocksPanelOpen = false; showUnlocksPanel(); } }; noBtn.down = function () { if (upgradesConfirmDialog) { upgradesConfirmDialog.destroy(); upgradesConfirmDialog = null; } }; yesBtn.on('down', yesBtn.down); noBtn.on('down', noBtn.down); unlocksPanel.addChild(upgradesConfirmDialog); }; var upgradesUnlockDownHandler = function upgradesUnlockDownHandler() { showUpgradesConfirmDialog(); }; upgradesUnlockText.down = upgradesUnlockDownHandler; upgradesUnlockIcon.down = upgradesUnlockDownHandler; upgradesUnlockText.on('down', upgradesUnlockDownHandler); upgradesUnlockIcon.on('down', upgradesUnlockDownHandler); } else { upgradesUnlockText = new Text2("??? (To unlock Chicken Jockey avatar, please complete Nightmare Mode)", { size: 60, fill: 0x00BFFF, font: "Impact, Arial Black, Tahoma" }); upgradesUnlockText.anchor.set(0.5, 0.5); upgradesUnlockText.x = GAME_W / 2; upgradesUnlockText.y = upgradesSectionY; unlocksPanel.addChild(upgradesUnlockText); } unlockY += unlockSpacing; // --- Add more unlocks here in the future --- // Close button var closeBtn = new Text2("Close", { size: 90, fill: 0xFF4444, font: "Impact, Arial Black, Tahoma" }); closeBtn.anchor.set(0.5, 0.5); closeBtn.x = GAME_W / 2; closeBtn.y = 900 + 1800 - 80; unlocksPanel.addChild(closeBtn); closeBtn.interactive = true; closeBtn.down = function () { if (unlocksPanel) { unlocksPanel.destroy(); unlocksPanel = null; unlocksPanelOpen = false; } }; closeBtn.on('down', closeBtn.down); // Attach unlocksPanel to mainMenuOverlay mainMenuOverlay.addChild(unlocksPanel); } // Button handler for unlocksBtn unlocksBtn.interactive = true; unlocksBtn.down = function () { showUnlocksPanel(); }; unlocksBtn.on('down', unlocksBtn.down); // Add overlay to game game.addChild(mainMenuOverlay); // Button hit areas normalBtn.interactive = true; hardBtn.interactive = true; nightmareBtn.interactive = true; // Add direct down event handlers to buttons for pressable effect normalBtn.down = function (x, y, obj) { if (!mainMenuOverlay) { return; } selectMode('normal'); }; hardBtn.down = function (x, y, obj) { if (!mainMenuOverlay) { return; } selectMode('hard'); }; nightmareBtn.down = function (x, y, obj) { if (!mainMenuOverlay) { return; } selectMode('nightmare'); }; normalBtn.on('down', normalBtn.down); hardBtn.on('down', hardBtn.down); nightmareBtn.on('down', nightmareBtn.down); // Touch/click handlers mainMenuOverlay.down = function (x, y, obj) { // Defensive: If overlay is destroyed or null, do nothing if (!mainMenuOverlay) { return; } // Convert to overlay local coordinates var local = mainMenuOverlay.toLocal({ x: x, y: y }); // Check if normalBtn was pressed if (pointInText(normalBtn, local.x, local.y)) { selectMode('normal'); } else if (pointInText(hardBtn, local.x, local.y)) { selectMode('hard'); } }; // Attach to overlay mainMenuOverlay.on('down', mainMenuOverlay.down); // Also attach to game for mobile game.down = function (x, y, obj) { if (menuActive && mainMenuOverlay) { mainMenuOverlay.down(x, y, obj); return; } if (gameOver) { return; } dragNode = minecart; lastTouchX = x; lastTouchY = y; }; } // Helper: check if point is inside Text2 bounds function pointInText(txt, x, y) { var w = txt.width; var h = txt.height; var tx = txt.x - w * txt.anchor.x; var ty = txt.y - h * txt.anchor.y; return x >= tx && x <= tx + w && y >= ty && y <= ty + h; } // Remove menu and start game function selectMode(mode) { if (!mainMenuOverlay) { return; } menuActive = false; // Continue playing Feel It by D4VD during gameplay // LK.stopMusic(); // Commented out to keep music playing mainMenuOverlay.destroy(); mainMenuOverlay = null; // Set difficulty and level mode if (mode === 'normal') { // Set up original game state for normal mode difficulty = 1; game._superHardMode = false; game._nightmareMode = false; // Remove any super hard mode UI if present if (game._batHealthBarBg) { LK.gui.top.removeChild(game._batHealthBarBg); game._batHealthBarBg = null; } if (game._batHealthBar) { LK.gui.top.removeChild(game._batHealthBar); game._batHealthBar = null; } game._batEnemy = null; game._batMaxHp = null; // Place player at original starting position if (minecart) { minecart.x = GAME_W / 2; minecart.y = PLAYER_Y; // If upgraded knife is unlocked AND player chose to use it, start with knife and max level if (upgradedKnifeUnlocked && useUpgradedKnifeNextGame) { minecart.setWeapon('knife'); minecart.weaponLevel = 3; useUpgradedKnifeNextGame = false; // Reset after use } } } else if (mode === 'hard') { difficulty = 7; game._superHardMode = true; game._nightmareMode = false; } else if (mode === 'nightmare') { difficulty = 12; game._superHardMode = false; game._nightmareMode = true; } // Reset game state LK.setScore(0); if (scoreTxt) { scoreTxt.setText('0'); } if (minecart) { minecart.x = GAME_W / 2; minecart.y = PLAYER_Y; minecart.weapon = 'gun'; minecart.weaponLevel = 1; minecart.invincible = false; minecart.invincibleTimer = 0; minecart.attackCooldown = 0; minecart.knifeSwinging = false; minecart.setWeapon('gun'); } bullets = []; enemies = []; obstacles = []; upgrades = []; spawnTimer = 0; obstacleTimer = 60; upgradeTimer = 600; knifeHitEnemies = []; lastKnifeSwingTick = -100; gameOver = false; // If super hard mode, set up new level if (game._superHardMode) { // Remove all enemies, obstacles, upgrades, and spawn a new set for the new level for (var i = 0; i < enemies.length; ++i) { enemies[i].destroy(); } for (var i = 0; i < obstacles.length; ++i) { obstacles[i].destroy(); } for (var i = 0; i < upgrades.length; ++i) { upgrades[i].destroy(); } enemies = []; obstacles = []; upgrades = []; // Place player at the same starting position as normal mode minecart.x = GAME_W / 2; minecart.y = PLAYER_Y; // Optionally, change background or add new obstacles/enemy patterns // Example: spawn a wall of goblins at the top for (var gx = TRACK_LEFT + 120; gx < TRACK_RIGHT - 120; gx += 180) { var g = new Goblin(); g.x = gx; g.y = 0; enemies.push(g); game.addChild(g); } // Example: spawn a bat immediately var bat = new Bat(); bat.x = GAME_W / 2; bat.y = -100; enemies.push(bat); game.addChild(bat); // Set up bat health bar UI for super hard mode var barWidth = 1700; var barHeight = 90; var barBg = LK.getAsset('enemy_goblin', { width: barWidth, height: barHeight, color: 0x222222, anchorX: 0.5, anchorY: 0 }); barBg.x = GAME_W / 2; barBg.y = 10; var bar = LK.getAsset('enemy_goblin', { width: barWidth - 20, height: barHeight - 18, color: 0xff2222, anchorX: 0.5, anchorY: 0 }); bar.x = GAME_W / 2; bar.y = 19; LK.gui.top.addChild(barBg); LK.gui.top.addChild(bar); game._batHealthBarBg = barBg; game._batHealthBar = bar; game._batEnemy = bat; game._batMaxHp = bat.hp; } } // Add maze background image var mazeBg = LK.getAsset('maze_bg', { anchorX: 0, anchorY: 0, x: 0, y: 0, width: GAME_W, height: GAME_H }); game.addChild(mazeBg); // Score display scoreTxt = new Text2('0', { size: 120, fill: "#fff" }); scoreTxt.anchor.set(0.5, 0); LK.gui.top.addChild(scoreTxt); // Create player minecart = new Minecart(); game.addChild(minecart); minecart.x = GAME_W / 2; minecart.y = PLAYER_Y; // Vertical shooter: drag minecart only in X, fixed Y game.down = function (x, y, obj) { if (gameOver) { return; } dragNode = minecart; lastTouchX = x; lastTouchY = y; }; game.up = function (x, y, obj) { dragNode = null; }; game.move = function (x, y, obj) { if (gameOver) { return; } if (dragNode === minecart) { // Only allow movement in X, clamp to track var nx = x; if (nx < TRACK_LEFT) { nx = TRACK_LEFT; } if (nx > TRACK_RIGHT) { nx = TRACK_RIGHT; } minecart.x = nx; minecart.y = PLAYER_Y; } }; // Tap to attack (shoot or knife) game.tap = function (x, y, obj) { if (gameOver) { return; } if (minecart.weapon === 'gun') { if (minecart.attackCooldown <= 0) { fireBullet(); minecart.attackCooldown = 12 - minecart.weaponLevel * 2; // Faster with upgrades if (minecart.attackCooldown < 4) { minecart.attackCooldown = 4; } } } else if (minecart.weapon === 'knife') { if (LK.ticks - lastKnifeSwingTick > 18) { minecart.swingKnife(); lastKnifeSwingTick = LK.ticks; } } }; // Fire bullet(s) function fireBullet() { var spread = minecart.weaponLevel; var angleSpread = 0.18; // radians, about 10 degrees var basePower = 1 + Math.floor((minecart.weaponLevel - 1) / 2); // Level 1-2: 1, Level 3: 2 for (var i = 0; i < spread; ++i) { var b = new Bullet(); // Spread bullets horizontally and with angle var offset = i - (spread - 1) / 2; b.x = minecart.x + offset * 40; b.y = minecart.y - minecart.height / 2 - 10; // Angle for spread: center is 0, sides are negative/positive if (spread > 1) { b.angle = offset * angleSpread; } b.power = basePower; bullets.push(b); game.addChild(b); } } // Main update loop game.update = function () { if (menuActive) { return; } if (gameOver) { return; } // Difficulty scaling if (LK.ticks % 300 === 0 && difficulty < 10) { difficulty += 1; } // Attack cooldown if (minecart && minecart.attackCooldown > 0) { minecart.attackCooldown -= 1; } // Invincibility timer if (minecart && minecart.invincible) { minecart.invincibleTimer -= 1; if (minecart.invincibleTimer <= 0) { minecart.invincible = false; } } // Bullets update for (var i = bullets.length - 1; i >= 0; --i) { var b = bullets[i]; b.update(); if (b.y < -50) { b.destroy(); bullets.splice(i, 1); continue; } // Bullet hits enemy for (var j = enemies.length - 1; j >= 0; --j) { var e = enemies[j]; if (b.intersects(e)) { // Use bullet power for damage e.hp -= b.power || 1; // If this is the Bat, update health bar if (game._batEnemy && e === game._batEnemy && game._batHealthBar) { var percent = Math.max(0, e.hp) / game._batMaxHp; // Clamp width to minimum 0 game._batHealthBar.width = 1680 * percent; // Optionally, you can tint the bar as it gets low (not required) } b.destroy(); bullets.splice(i, 1); // Monster dies if hp <= 0 if (e.hp <= 0) { addScore(e.scoreValue); e.destroy(); enemies.splice(j, 1); // If Bat is dead, remove health bar UI if (game._batEnemy && e === game._batEnemy) { if (game._batHealthBarBg) { LK.gui.top.removeChild(game._batHealthBarBg); game._batHealthBarBg = null; } if (game._batHealthBar) { LK.gui.top.removeChild(game._batHealthBar); game._batHealthBar = null; } game._batEnemy = null; // If in super hard mode, check for score 1000 to win and unlock upgraded knife if (game._superHardMode && !upgradedKnifeUnlocked) { if (LK.getScore() >= 1000) { upgradedKnifeUnlocked = true; if (typeof storage !== "undefined") { storage.upgradedKnifeUnlocked = true; } // Show a win popup LK.showYouWin(); gameOver = true; return; } // Otherwise, keep playing until score 1000 is reached } } // --- Player unlock logic: count kills in super hard mode --- if (game._superHardMode && !playerUnlocked) { if (typeof playerKills === "undefined") playerKills = 0; playerKills++; if (playerKills >= 50) { playerUnlocked = true; if (typeof storage !== "undefined") { storage.playerUnlocked = true; } } if (typeof storage !== "undefined") { storage.playerKills = playerKills; } } // --- Skeleton player unlock logic: count kills in normal mode --- if (!game._superHardMode && !game._nightmareMode && !skeletonPlayerUnlocked) { if (typeof normalModeKills === "undefined") normalModeKills = 0; normalModeKills++; storage.normalModeKills = normalModeKills; if (normalModeKills >= 100) { skeletonPlayerUnlocked = true; storage.skeletonPlayerUnlocked = true; } } } break; } } } // Enemies update (vertical shooter: always visible, move down) for (var i = enemies.length - 1; i >= 0; --i) { var e = enemies[i]; // Track lastY for dodge detection in Nightmare Mode if (typeof e.lastY === "undefined") { e.lastY = e.y; } e.update(); // Remove if out of bounds if (e.y > GAME_H + 200) { // Nightmare Mode: Award points for dodging (enemy left screen without hitting player) if (game._nightmareMode && typeof e.dodgeScored === "undefined") { addScore(e.scoreValue); e.dodgeScored = true; } e.destroy(); enemies.splice(i, 1); continue; } // Update lastY for next frame e.lastY = e.y; // Enemy hits minecart if (minecart && minecart !== null && !minecart.invincible && e.intersects(minecart)) { // In Nightmare Mode, player doesn't die from monster hits (dodge mechanic) if (game._nightmareMode) { // Just flash to show contact, but don't kill player minecart.flash(); LK.effects.flashScreen(0xff0000, 300); continue; // Skip to next enemy, don't end game } // Check if Bat killed the player for jump scare if (game._batEnemy && e === game._batEnemy) { // Show a big jump scare: flash white, show a big FACE in the center, and play a loud sound if available LK.effects.flashScreen(0xffffff, 1200); // Create a big FACE image in the center (using the goblin face as a placeholder for a jumpscare face) var jumpscareFace = LK.getAsset('enemy_goblin', { anchorX: 0.5, anchorY: 0.5, scaleX: 2, scaleY: 2, x: GAME_W / 2, y: GAME_H / 2 }); jumpscareFace.alpha = 0; game.addChild(jumpscareFace); // Animate the face: scale up and fade in quickly, then fade out tween(jumpscareFace, { alpha: 1, scaleX: 7, scaleY: 7 }, { duration: 120, onFinish: function onFinish() { tween(jumpscareFace, { alpha: 0 }, { duration: 700, delay: 350, onFinish: function onFinish() { jumpscareFace.destroy(); } }); } }); // Optionally, play a loud sound here if you have a jumpscare sound asset } minecart.flash(); LK.effects.flashScreen(0xff0000, 600); // End game LK.showGameOver(); gameOver = true; return; } // Enemy hit by knife if (minecart.weapon === 'knife' && minecart.knife && minecart.knife.visible && !knifeHitEnemies.includes(e)) { // Knife is in swing, check collision var knifeGlobal = minecart.toGlobal(minecart.knife.position); var knifeRect = new Rectangle(knifeGlobal.x - 30, knifeGlobal.y - 12, 60, 24); var enemyRect = new Rectangle(e.x - e.width / 2, e.y - e.height / 2, e.width, e.height); if (rectsIntersect(knifeRect, enemyRect)) { e.hp -= 1; // If this is the Bat, update health bar if (game._batEnemy && e === game._batEnemy && game._batHealthBar) { var percent = Math.max(0, e.hp) / game._batMaxHp; game._batHealthBar.width = 1680 * percent; } knifeHitEnemies.push(e); if (e.hp <= 0) { addScore(e.scoreValue); e.destroy(); enemies.splice(i, 1); // If Bat is dead, remove health bar UI if (game._batEnemy && e === game._batEnemy) { if (game._batHealthBarBg) { LK.gui.top.removeChild(game._batHealthBarBg); game._batHealthBarBg = null; } if (game._batHealthBar) { LK.gui.top.removeChild(game._batHealthBar); game._batHealthBar = null; } game._batEnemy = null; } } } } } // Reset knife hit list if knife not swinging if (!minecart || !minecart.knife || !minecart.knife.visible) { knifeHitEnemies = []; } // Obstacles update for (var i = obstacles.length - 1; i >= 0; --i) { var o = obstacles[i]; o.update(); if (o.y > GAME_H + 100) { o.destroy(); obstacles.splice(i, 1); continue; } // Obstacle hits minecart if (minecart && !minecart.invincible && o.intersects(minecart)) { minecart.flash(); LK.effects.flashScreen(0xff0000, 600); LK.showGameOver(); gameOver = true; return; } } // Upgrades update for (var i = upgrades.length - 1; i >= 0; --i) { var u = upgrades[i]; u.update(); if (u.y > GAME_H + 100) { u.destroy(); upgrades.splice(i, 1); continue; } // Pickup if (u.intersects(minecart)) { if (u.kind === 'gun') { minecart.setWeapon('gun'); minecart.upgradeWeapon(); } else { minecart.setWeapon('knife'); minecart.upgradeWeapon(); } u.destroy(); upgrades.splice(i, 1); } } // Spawning enemies spawnTimer -= 1; if (spawnTimer <= 0) { spawnEnemy(); if (game._nightmareMode) { spawnTimer = Math.max(24, 60 - difficulty * 4 - Math.floor(Math.random() * 10)); } else { spawnTimer = Math.max(24, 60 - difficulty * 4 - Math.floor(Math.random() * 10)); } } // Spawning obstacles obstacleTimer -= 1; if (obstacleTimer <= 0) { if (game._nightmareMode) { if (Math.random() < 0.85) { spawnObstacle(); } obstacleTimer = 30 + Math.floor(Math.random() * 20); } else { if (Math.random() < 0.5 + difficulty * 0.04) { spawnObstacle(); } obstacleTimer = 90 + Math.floor(Math.random() * 60) - difficulty * 4; if (obstacleTimer < 30) { obstacleTimer = 30; } } } // Spawning upgrades upgradeTimer -= 1; if (upgradeTimer <= 0) { if (game._superHardMode) { // Easier: upgrades spawn more often in super hard mode if (Math.random() < 0.38) { spawnUpgrade(); } upgradeTimer = 340 + Math.floor(Math.random() * 120); } else if (game._nightmareMode) { if (Math.random() < 0.10) { spawnUpgrade(); } upgradeTimer = 900 + Math.floor(Math.random() * 400); } else { if (Math.random() < 0.18) { spawnUpgrade(); } upgradeTimer = 600 + Math.floor(Math.random() * 300); } } // Update score display and visibleScore if (LK.getScore() !== lastScore) { scoreTxt.setText(LK.getScore()); lastScore = LK.getScore(); visibleScore = LK.getScore(); } // Win condition for super hard mode: reach score 1000 if (game._superHardMode && !upgradedKnifeUnlocked && visibleScore >= 1000) { upgradedKnifeUnlocked = true; if (typeof storage !== "undefined") { storage.upgradedKnifeUnlocked = true; } LK.showYouWin(); gameOver = true; return; } // Unlock upgrades automatically when reaching 500 points in Nightmare Mode if (game._nightmareMode && !upgradesUnlocked && visibleScore >= 500) { upgradesUnlocked = true; if (typeof storage !== "undefined") { storage.upgradesUnlocked = true; } game._upgradesUnlockReady = true; } else if ((!game._nightmareMode || upgradesUnlocked || visibleScore < 500) && typeof game._upgradesUnlockReady !== "undefined") { // Reset unlock ready flag if not in correct state game._upgradesUnlockReady = undefined; } // Win condition for nightmare mode: reach score 2000 if (game._nightmareMode && LK.getScore() >= 2000) { // Unlock upgrades/chicken jockey if score >= 500 if (LK.getScore() >= 500 && !upgradesUnlocked) { upgradesUnlocked = true; if (typeof storage !== "undefined") { storage.upgradesUnlocked = true; } } LK.showYouWin(); gameOver = true; // Show chicken jockey unlock dialog if not already unlocked if (upgradesUnlocked && (typeof storage.chickenJockeyUnlocked === "undefined" || !storage.chickenJockeyUnlocked)) { if (!game._chickenJockeyDialog) { game._chickenJockeyDialog = new Container(); var dialogBg = LK.getAsset('maze_bg', { anchorX: 0, anchorY: 0, x: GAME_W / 2 - 400, y: GAME_H / 2 - 400, width: 800, height: 800 }); dialogBg.alpha = 0.95; game._chickenJockeyDialog.addChild(dialogBg); var chickenJockeyImg = LK.getAsset('Chickenjockey', { anchorX: 0.5, anchorY: 0.5, x: GAME_W / 2, y: GAME_H / 2 - 120, scaleX: 2.2, scaleY: 2.2 }); game._chickenJockeyDialog.addChild(chickenJockeyImg); var dialogText = new Text2("Do you want to become the Chicken Jockey?", { size: 70, fill: 0x00BFFF, font: "Impact, Arial Black, Tahoma" }); dialogText.anchor.set(0.5, 0.5); dialogText.x = GAME_W / 2; dialogText.y = GAME_H / 2 + 120; game._chickenJockeyDialog.addChild(dialogText); var yesBtn = new Text2("Yes", { size: 100, fill: 0x44FF44, font: "Impact, Arial Black, Tahoma" }); yesBtn.anchor.set(0.5, 0.5); yesBtn.x = GAME_W / 2 - 140; yesBtn.y = GAME_H / 2 + 260; game._chickenJockeyDialog.addChild(yesBtn); var noBtn = new Text2("No", { size: 100, fill: 0xFF4444, font: "Impact, Arial Black, Tahoma" }); noBtn.anchor.set(0.5, 0.5); noBtn.x = GAME_W / 2 + 140; noBtn.y = GAME_H / 2 + 260; game._chickenJockeyDialog.addChild(noBtn); yesBtn.interactive = true; noBtn.interactive = true; yesBtn.down = function () { // Set player model to chicken jockey storage.selectedPlayerModel = 'chickenjockey'; storage.chickenJockeyUnlocked = true; if (minecart) { // Replace minecart's asset with chicken jockey minecart.destroy(); minecart = new Minecart(); game.addChild(minecart); minecart.x = GAME_W / 2; minecart.y = PLAYER_Y; } if (game._chickenJockeyDialog) { game._chickenJockeyDialog.destroy(); game._chickenJockeyDialog = null; } }; noBtn.down = function () { storage.chickenJockeyUnlocked = true; if (game._chickenJockeyDialog) { game._chickenJockeyDialog.destroy(); game._chickenJockeyDialog = null; } }; yesBtn.on('down', yesBtn.down); noBtn.on('down', noBtn.down); game.addChild(game._chickenJockeyDialog); } } return; } }; // Spawn enemy at random X at the top (vertical shooter style) function spawnEnemy() { // If in super hard mode, spawn new enemy patterns if (game._superHardMode) { // Example: spawn a slightly slower skeleton and a goblin together (easier) var e1 = new Skeleton(); e1.speed = 10 + Math.random() * 2.5; e1.x = TRACK_LEFT + 120 + Math.random() * (TRACK_RIGHT - TRACK_LEFT - 240); e1.y = -80; enemies.push(e1); game.addChild(e1); var e2 = new Goblin(); e2.speed = 8 + Math.random() * 2; e2.x = TRACK_LEFT + 120 + Math.random() * (TRACK_RIGHT - TRACK_LEFT - 240); e2.y = -180; enemies.push(e2); game.addChild(e2); // Occasionally spawn a Bat if (Math.random() < 0.12) { var bat = new Bat(); bat.x = TRACK_LEFT + 120 + Math.random() * (TRACK_RIGHT - TRACK_LEFT - 240); bat.y = -200; enemies.push(bat); game.addChild(bat); // Set up bat health bar UI var barWidth = 1700; var barHeight = 90; var barBg = LK.getAsset('enemy_goblin', { width: barWidth, height: barHeight, color: 0x222222, anchorX: 0.5, anchorY: 0 }); barBg.x = GAME_W / 2; barBg.y = 10; var bar = LK.getAsset('enemy_goblin', { width: barWidth - 20, height: barHeight - 18, color: 0xff2222, anchorX: 0.5, anchorY: 0 }); bar.x = GAME_W / 2; bar.y = 19; LK.gui.top.addChild(barBg); LK.gui.top.addChild(bar); game._batHealthBarBg = barBg; game._batHealthBar = bar; game._batEnemy = bat; game._batMaxHp = bat.hp; } // Sometimes spawn a ginger if (Math.random() < 0.25) { var g = new Ginger(); g.x = TRACK_LEFT + 120 + Math.random() * (TRACK_RIGHT - TRACK_LEFT - 240); g.y = -120; enemies.push(g); game.addChild(g); } return; } // Nightmare Mode: spawn only NightmareMonster(s) if (game._nightmareMode) { var nMonsters = 1 + Math.floor(Math.random() * 2); // 1-2 per wave, less than before for (var i = 0; i < nMonsters; ++i) { var nm = new NightmareMonster(); nm.x = TRACK_LEFT + 120 + Math.random() * (TRACK_RIGHT - TRACK_LEFT - 240); nm.y = -200 - i * 180; enemies.push(nm); game.addChild(nm); } return; } // --- Normal mode enemy spawn --- var enemyType = Math.random(); var e = null; // Wendigo: 1% chance, must be checked first so it doesn't overlap with Bat if (enemyType < 0.01) { e = new Wendigo(); } // Bat is now extremely rare: only spawn if enemyType < 0.02 (2% chance, but not if Wendigo already spawned) else if (enemyType < 0.02) { e = new Bat(); // Show warning text and play creepy music when Bat spawns if (!game._batWarningText) { // Create warning text if not already present var batWarning = new Text2("That is not you.", { size: 120, fill: 0xFF2222, font: "Impact, Arial Black, Tahoma" }); batWarning.anchor.set(0.5, 0.5); batWarning.alpha = 0.0; game._batWarningText = batWarning; // Add to LK.gui.center for true center of screen LK.gui.center.addChild(batWarning); } // Fade in text var warningText = game._batWarningText; warningText.alpha = 0.0; tween(warningText, { alpha: 1 }, { duration: 400, onFinish: function onFinish() { // Fade out after 1.2s tween(warningText, { alpha: 0 }, { duration: 800, delay: 1200 }); } }); // Play creepy music (music asset id: 'creepy_bat') LK.playMusic('creepy_bat', { fade: { start: 0, end: 1, duration: 800 } }); // --- Bat Health Bar UI --- // Remove any previous bat health bar if (game._batHealthBarBg) { LK.gui.top.removeChild(game._batHealthBarBg); game._batHealthBarBg = null; } if (game._batHealthBar) { LK.gui.top.removeChild(game._batHealthBar); game._batHealthBar = null; } // Create background bar (much bigger, at the very top) var barWidth = 1700; var barHeight = 90; var barBg = LK.getAsset('enemy_goblin', { width: barWidth, height: barHeight, color: 0x222222, anchorX: 0.5, anchorY: 0 }); barBg.x = GAME_W / 2; barBg.y = 10; // Very top of the screen // Create health bar (red, much bigger) var bar = LK.getAsset('enemy_goblin', { width: barWidth - 20, height: barHeight - 18, color: 0xff2222, anchorX: 0.5, anchorY: 0 }); bar.x = GAME_W / 2; bar.y = 19; // Just below the background bar // Add to GUI LK.gui.top.addChild(barBg); LK.gui.top.addChild(bar); game._batHealthBarBg = barBg; game._batHealthBar = bar; // Store reference to the Bat instance for health bar updates game._batEnemy = e; game._batMaxHp = e.hp; } else if (enemyType < 0.10) { // Ginger is rare, 8% chance (between 2% and 10%) e = new Ginger(); } else if (enemyType < 0.65) { e = new Goblin(); } else { e = new Skeleton(); } // Spawn at random X at the top of the screen e.x = TRACK_LEFT + 80 + Math.random() * (TRACK_RIGHT - TRACK_LEFT - 160); e.y = -80; enemies.push(e); game.addChild(e); } // Spawn obstacle function spawnObstacle() { var o = new Obstacle(); o.x = TRACK_LEFT + 80 + Math.random() * (TRACK_RIGHT - TRACK_LEFT - 160); o.y = -60; obstacles.push(o); game.addChild(o); } // Spawn upgrade function spawnUpgrade() { var u = new Upgrade(); u.x = TRACK_LEFT + 80 + Math.random() * (TRACK_RIGHT - TRACK_LEFT - 160); u.y = -60; upgrades.push(u); game.addChild(u); } // Add score function addScore(val) { LK.setScore(LK.getScore() + val); } // Rectangle intersection helper function rectsIntersect(r1, r2) { return !(r2.x > r1.x + r1.width || r2.x + r2.width < r1.x || r2.y > r1.y + r1.height || r2.y + r2.height < r1.y); } // Touch to attack (simulate tap on mobile) game.down = function (origDown) { return function (x, y, obj) { if (origDown) { origDown(x, y, obj); } game.tap(x, y, obj); }; }(game.down); // Reset game state on game over LK.on('gameover', function () { gameOver = true; setTimeout(function () { showMainMenu(); }, 600); }); // Track run count for Bat spawn logic if (typeof game._runCount === "undefined") { game._runCount = 0; } LK.on('newgame', function () { game._runCount = (game._runCount || 0) + 1; gameOver = false; LK.setScore(0); if (scoreTxt) { scoreTxt.setText('0'); } // Remove all objects for (var i = 0; i < bullets.length; ++i) { bullets[i].destroy(); } for (var i = 0; i < enemies.length; ++i) { enemies[i].destroy(); } for (var i = 0; i < obstacles.length; ++i) { obstacles[i].destroy(); } for (var i = 0; i < upgrades.length; ++i) { upgrades[i].destroy(); } bullets = []; enemies = []; obstacles = []; upgrades = []; minecart.x = GAME_W / 2; minecart.y = PLAYER_Y; minecart.weapon = 'gun'; minecart.weaponLevel = 1; minecart.invincible = false; minecart.invincibleTimer = 0; minecart.attackCooldown = 0; minecart.knifeSwinging = false; minecart.setWeapon('gun'); difficulty = 1; spawnTimer = 0; obstacleTimer = 0; upgradeTimer = 0; knifeHitEnemies = []; lastKnifeSwingTick = -100; // Remove Bat health bar UI if present if (game._batHealthBarBg) { LK.gui.top.removeChild(game._batHealthBarBg); game._batHealthBarBg = null; } if (game._batHealthBar) { LK.gui.top.removeChild(game._batHealthBar); game._batHealthBar = null; } game._batEnemy = null; game._batMaxHp = null; }); // Initial state spawnTimer = 0; obstacleTimer = 60; upgradeTimer = 600; difficulty = 1; gameOver = false; LK.setScore(0); if (scoreTxt) { scoreTxt.setText('0'); } // Show main menu on load showMainMenu();
/****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
var storage = LK.import("@upit/storage.v1");
/****
* Classes
****/
// Bullet
var Bullet = Container.expand(function () {
var self = Container.call(this);
var bullet = self.attachAsset('bullet', {
anchorX: 0.5,
anchorY: 0.5
});
self.width = bullet.width;
self.height = bullet.height;
self.speed = -36; // Upwards
self.power = 1; // Default bullet power
self.angle = 0; // For spread
self.update = function () {
if (self.angle !== 0) {
// Move in angle for spread
self.x += Math.sin(self.angle) * 12;
self.y += Math.cos(self.angle) * self.speed;
} else {
self.y += self.speed;
}
};
return self;
});
// Enemy base class
var Enemy = Container.expand(function () {
var self = Container.call(this);
self.type = 'enemy';
self.hp = 1;
self.speed = 8;
self.scoreValue = 1;
self.update = function () {};
return self;
});
// Wendigo enemy (very rare, 1% spawn chance)
var Wendigo = Enemy.expand(function () {
var self = Enemy.call(this);
// Use the 'Monster' asset for now, but tint it to distinguish
var wendigo = self.attachAsset('Monster', {
anchorX: 0.5,
anchorY: 0.5
});
// Make Wendigo visually unique
wendigo.scaleX = 3.5;
wendigo.scaleY = 3.5;
wendigo.tint = 0xccccff; // Pale blue/white
self.width = wendigo.width * 3.5;
self.height = wendigo.height * 3.5;
// Wendigo stats: very high HP, very fast, high score
self.hp = 40 + Math.floor(Math.random() * 20);
self.speed = 12 + Math.random() * 3;
self.scoreValue = 2000;
// Give it a unique movement pattern (erratic zigzag)
self._zigzag = Math.random() * Math.PI * 2;
self.update = function () {
self.y += self.speed;
self.x += Math.sin(LK.ticks / 4 + self._zigzag) * 32;
};
return self;
});
// Skeleton enemy
var Skeleton = Enemy.expand(function () {
var self = Enemy.call(this);
var skeleton = self.attachAsset('enemy_skeleton', {
anchorX: 0.5,
anchorY: 0.5
});
// Make skeleton bigger
skeleton.scaleX = 1.7;
skeleton.scaleY = 1.7;
self.width = skeleton.width * 1.7;
self.height = skeleton.height * 1.7;
self.hp = 1;
// Make skeleton slower
self.speed = 6 + Math.random() * 2.5;
self.scoreValue = 2;
self.update = function () {
self.y += self.speed;
};
return self;
});
// Nightmare Monster (unique to Nightmare Mode)
var NightmareMonster = Enemy.expand(function () {
var self = Enemy.call(this);
// Use the 'Monster' asset, which is unique and scary
var monster = self.attachAsset('Monster', {
anchorX: 0.5,
anchorY: 0.5
});
// Make it very large and visually imposing
monster.scaleX = 3.2;
monster.scaleY = 3.2;
self.width = monster.width * 3.2;
self.height = monster.height * 3.2;
// High HP, fast, and high score value
self.hp = 12 + Math.floor(Math.random() * 8);
// Set NightmareMonster speed to normal enemy speed
self.speed = 8 + Math.random() * 2.5;
self.scoreValue = 50;
// Give it a creepy wobble movement
self._wobble = Math.random() * Math.PI * 2;
self.update = function () {
self.y += self.speed;
self.x += Math.sin(LK.ticks / 8 + self._wobble) * 18;
};
return self;
});
// Goblin enemy
var Goblin = Enemy.expand(function () {
var self = Enemy.call(this);
var goblin = self.attachAsset('enemy_goblin', {
anchorX: 0.5,
anchorY: 0.5
});
// Make goblin bigger
goblin.scaleX = 1.7;
goblin.scaleY = 1.7;
self.width = goblin.width * 1.7;
self.height = goblin.height * 1.7;
self.hp = 2;
// Make goblin slower
self.speed = 5 + Math.random() * 2;
self.scoreValue = 3;
self.update = function () {
self.y += self.speed;
};
return self;
});
// Ginger enemy
var Ginger = Enemy.expand(function () {
var self = Enemy.call(this);
// Use goblin as base, but with different color and stats
var ginger = self.attachAsset('enemy_goblin', {
anchorX: 0.5,
anchorY: 0.5
});
// Tint to orange/ginger color
ginger.tint = 0xffa14e;
ginger.scaleX = 1.7;
ginger.scaleY = 1.7;
self.width = ginger.width * 1.7;
self.height = ginger.height * 1.7;
self.hp = 3;
self.speed = 7 + Math.random() * 2;
self.scoreValue = 7;
self.update = function () {
self.y += self.speed;
};
return self;
});
// Bat enemy
var Bat = Enemy.expand(function () {
var self = Enemy.call(this);
var bat = self.attachAsset('enemy_bat', {
anchorX: 0.5,
anchorY: 0.5
});
// Make bat much bigger so it stands out
bat.scaleX = 3.5;
bat.scaleY = 3.5;
self.width = bat.width * 3.5;
self.height = bat.height * 3.5;
// Make bat very hard to kill
self.hp = 30;
// Make bat slower
self.speed = 7 + Math.random() * 2.5;
// Bat is worth 1000 coins
self.scoreValue = 1000;
self.update = function () {
self.y += self.speed;
self.x += Math.sin(LK.ticks / 12 + self._batWobble) * 8;
};
self._batWobble = Math.random() * Math.PI * 2;
return self;
});
// Player Minecart
var Minecart = Container.expand(function () {
var self = Container.call(this);
// Use selected player model from storage or default based on unlocks
var assetId = 'minecart';
var selectedModel = storage.selectedPlayerModel || 'default';
if (selectedModel === 'chickenjockey' && typeof storage.chickenJockeyUnlocked !== 'undefined' && storage.chickenJockeyUnlocked) {
assetId = 'Chickenjockey';
} else if (selectedModel === 'skeleton' && typeof skeletonPlayerUnlocked !== 'undefined' && skeletonPlayerUnlocked) {
assetId = 'Skeletonmodel';
} else if (selectedModel === 'player' && typeof playerUnlocked !== 'undefined' && playerUnlocked) {
assetId = 'Player';
} else if (typeof skeletonPlayerUnlocked !== 'undefined' && skeletonPlayerUnlocked && selectedModel === 'default') {
assetId = 'Skeletonmodel';
} else if (typeof playerUnlocked !== 'undefined' && playerUnlocked && selectedModel === 'default') {
assetId = 'Player';
}
var cart = self.attachAsset(assetId, {
anchorX: 0.5,
anchorY: 0.5
});
self.width = cart.width;
self.height = cart.height;
self.weapon = 'gun'; // 'gun' or 'knife'
self.weaponLevel = 1; // Upgrades increase this
self.invincible = false;
self.invincibleTimer = 0;
self.attackCooldown = 0;
self.knifeSwinging = false;
self.knife = null;
// Flash minecart when hit
self.flash = function () {
self.invincible = true;
self.invincibleTimer = 60; // 1 second at 60fps
tween(self, {
alpha: 0.5
}, {
duration: 100,
easing: tween.easeOut,
onFinish: function onFinish() {
tween(self, {
alpha: 1
}, {
duration: 100
});
}
});
};
// Switch weapon
self.setWeapon = function (type) {
self.weapon = type;
};
// Upgrade weapon
self.upgradeWeapon = function () {
self.weaponLevel += 1;
if (self.weaponLevel > 3) {
self.weaponLevel = 3;
}
};
// Knife attack animation
self.swingKnife = function () {
if (self.knifeSwinging) {
return;
}
self.knifeSwinging = true;
if (!self.knife) {
self.knife = self.attachAsset('knife', {
anchorX: 0.1,
anchorY: 0.5,
x: self.width / 2 + 20,
y: 0,
rotation: 0
});
}
self.knife.visible = true;
self.knife.rotation = -0.7;
tween(self.knife, {
rotation: 0.7
}, {
duration: 180,
easing: tween.cubicOut,
onFinish: function onFinish() {
self.knife.visible = false;
self.knifeSwinging = false;
self.knife.rotation = -0.7;
}
});
};
return self;
});
// Obstacle (rock)
var Obstacle = Container.expand(function () {
var self = Container.call(this);
var rock = self.attachAsset('obstacle_rock', {
anchorX: 0.5,
anchorY: 0.5
});
self.width = rock.width;
self.height = rock.height;
self.speed = 14 + Math.random() * 6;
self.update = function () {
self.y += self.speed;
};
return self;
});
// Upgrade (gun or knife)
var Upgrade = Container.expand(function () {
var self = Container.call(this);
self.kind = Math.random() < 0.5 ? 'gun' : 'knife';
var assetId = self.kind === 'gun' ? 'upgrade_gun' : 'upgrade_knife';
var upg = self.attachAsset(assetId, {
anchorX: 0.5,
anchorY: 0.5
});
self.width = upg.width;
self.height = upg.height;
self.speed = 10 + Math.random() * 4;
self.update = function () {
self.y += self.speed;
};
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0x181818
});
/****
* Game Code
****/
// Game constants
// Minecart (player)
// Player's knife
// Player's bullet
// Enemy: Bat
// Enemy: Goblin
// Enemy: Skeleton
// Obstacle: Rock
// Upgrade: Gun
// Upgrade: Knife
// Maze background image (replace with your maze image asset id)
var GAME_W = 2048;
var GAME_H = 2732;
var TRACK_LEFT = 300;
var TRACK_RIGHT = GAME_W - 300;
var PLAYER_Y = GAME_H - 350;
// Game state
var minecart = null;
var bullets = [];
var enemies = [];
var obstacles = [];
var upgrades = [];
var dragNode = null;
var lastTouchX = 0;
var lastTouchY = 0;
var scoreTxt = null;
var lastScore = 0;
var visibleScore = 0; // This is the score shown on screen and used for unlocks
var gameOver = false;
var spawnTimer = 0;
var obstacleTimer = 0;
var upgradeTimer = 0;
var difficulty = 1;
var knifeHitEnemies = [];
var lastKnifeSwingTick = -100;
// Track if player has unlocked the upgraded knife by beating super hard mode
var upgradedKnifeUnlocked = false;
// Track if player has chosen to use upgraded knife in normal mode (set via menu)
var useUpgradedKnifeNextGame = false;
// Track if player has unlocked the player (250 kills in super hard mode)
var playerUnlocked = false;
var playerKills = 0;
// Track skeleton player unlock (100 kills in normal mode - reduced for easier unlock)
var skeletonPlayerUnlocked = storage.skeletonPlayerUnlocked || false;
var normalModeKills = storage.normalModeKills || 0;
// Track upgrades unlock (500 points in Nightmare Mode)
var upgradesUnlocked = false;
if (typeof storage !== "undefined" && typeof storage.upgradesUnlocked !== "undefined") {
upgradesUnlocked = !!storage.upgradesUnlocked;
}
// Try to load from persistent storage if available
if (typeof storage !== "undefined" && storage.upgradedKnifeUnlocked) {
upgradedKnifeUnlocked = true;
}
if (typeof storage !== "undefined" && storage.playerUnlocked) {
playerUnlocked = true;
}
if (typeof storage !== "undefined" && storage.playerKills) {
playerKills = parseInt(storage.playerKills, 10) || 0;
}
// Always reset useUpgradedKnifeNextGame on load
useUpgradedKnifeNextGame = false;
// --- Main Menu Overlay ---
var mainMenuOverlay = null;
var mainMenuTitle = null;
var normalBtn = null;
var hardBtn = null;
var menuActive = true;
function showMainMenu() {
menuActive = true;
// Play Feel It by D4VD as background music for the main menu
LK.playMusic('feel_it_d4vd', {
fade: {
start: 0,
end: 0.7,
duration: 1000
}
});
// Overlay container
mainMenuOverlay = new Container();
// Semi-transparent dark background
var bg = LK.getAsset('maze_bg', {
anchorX: 0,
anchorY: 0,
x: 0,
y: 0,
width: GAME_W,
height: GAME_H
});
bg.alpha = 0.82;
mainMenuOverlay.addChild(bg);
// Title
mainMenuTitle = new Text2("Boneshaft 1: UPGRADED", {
size: 180,
fill: "#fff",
font: "Impact, Arial Black, Tahoma"
});
mainMenuTitle.anchor.set(0.5, 0.5);
mainMenuTitle.x = GAME_W / 2;
mainMenuTitle.y = 600;
mainMenuOverlay.addChild(mainMenuTitle);
// Normal Mode Button
var normalLabel = "Normal Mode";
if (upgradedKnifeUnlocked) {
normalLabel += " (Upgraded Knife!)";
}
normalBtn = new Text2(normalLabel, {
size: 120,
fill: 0x44FF44,
font: "Impact, Arial Black, Tahoma"
});
normalBtn.anchor.set(0.5, 0.5);
normalBtn.x = GAME_W / 2;
normalBtn.y = 1100;
mainMenuOverlay.addChild(normalBtn);
// Super Hard Mode Button
hardBtn = new Text2("Super Hard Mode", {
size: 120,
fill: 0xFF4444,
font: "Impact, Arial Black, Tahoma"
});
hardBtn.anchor.set(0.5, 0.5);
hardBtn.x = GAME_W / 2;
hardBtn.y = 1350;
mainMenuOverlay.addChild(hardBtn);
// Nightmare Mode Button
nightmareBtn = new Text2("Nightmare Mode", {
size: 120,
fill: 0x000000,
font: "Impact, Arial Black, Tahoma"
});
nightmareBtn.anchor.set(0.5, 0.5);
nightmareBtn.x = GAME_W / 2;
// Move Nightmare Mode button a little bit further down
nightmareBtn.y = 1700;
mainMenuOverlay.addChild(nightmareBtn);
// --- Unlocks Button and Unlocks Panel ---
var unlocksBtn = new Text2("Unlocks", {
size: 100,
fill: 0xFFD700,
font: "Impact, Arial Black, Tahoma"
});
unlocksBtn.anchor.set(0.5, 0.5);
unlocksBtn.x = GAME_W / 2;
unlocksBtn.y = 1550;
mainMenuOverlay.addChild(unlocksBtn);
// Unlocks panel (hidden by default)
var unlocksPanel = null;
var unlocksPanelBg = null;
var unlocksPanelOpen = false;
function showUnlocksPanel() {
if (unlocksPanelOpen) return;
unlocksPanelOpen = true;
unlocksPanel = new Container();
// Panel background
// Always use black background for unlocks panel
unlocksPanelBg = LK.getAsset('maze_bg', {
anchorX: 0,
anchorY: 0,
x: GAME_W / 2 - 600,
y: 900,
width: 1200,
height: 1800,
// increased height for longer section
color: 0x000000 // always black
});
unlocksPanelBg.alpha = 0.96;
unlocksPanel.addChild(unlocksPanelBg);
// Title
var unlocksTitle = new Text2("Unlocks", {
size: 110,
fill: 0xFFD700,
font: "Impact, Arial Black, Tahoma"
});
unlocksTitle.anchor.set(0.5, 0.5);
unlocksTitle.x = GAME_W / 2;
unlocksTitle.y = 1020;
unlocksPanel.addChild(unlocksTitle);
// Y position for first unlock
var unlockY = 1150;
var unlockSpacing = 200;
// --- Upgraded Knife Unlock ---
var unlockedKnifeText, unlockedKnifeIcon;
var knifeConfirmDialog = null;
if (upgradedKnifeUnlocked) {
var showKnifeConfirmDialog = function showKnifeConfirmDialog() {
if (knifeConfirmDialog) return;
knifeConfirmDialog = new Container();
var dialogBg = LK.getAsset('maze_bg', {
anchorX: 0,
anchorY: 0,
x: GAME_W / 2 - 400,
y: 1200,
width: 800,
height: 400
});
dialogBg.alpha = 0.93;
knifeConfirmDialog.addChild(dialogBg);
var dialogText = new Text2("Use Upgraded Knife in Normal Mode?", {
size: 60,
fill: "#fff",
font: "Impact, Arial Black, Tahoma"
});
dialogText.anchor.set(0.5, 0.5);
dialogText.x = GAME_W / 2;
dialogText.y = 1320;
knifeConfirmDialog.addChild(dialogText);
var yesBtn = new Text2("Yes", {
size: 90,
fill: 0x44FF44,
font: "Impact, Arial Black, Tahoma"
});
yesBtn.anchor.set(0.5, 0.5);
yesBtn.x = GAME_W / 2 - 140;
yesBtn.y = 1450;
knifeConfirmDialog.addChild(yesBtn);
var noBtn = new Text2("No", {
size: 90,
fill: 0xFF4444,
font: "Impact, Arial Black, Tahoma"
});
noBtn.anchor.set(0.5, 0.5);
noBtn.x = GAME_W / 2 + 140;
noBtn.y = 1450;
knifeConfirmDialog.addChild(noBtn);
yesBtn.interactive = true;
noBtn.interactive = true;
yesBtn.down = function () {
useUpgradedKnifeNextGame = true;
if (knifeConfirmDialog) {
knifeConfirmDialog.destroy();
knifeConfirmDialog = null;
}
};
noBtn.down = function () {
if (knifeConfirmDialog) {
knifeConfirmDialog.destroy();
knifeConfirmDialog = null;
}
};
yesBtn.on('down', yesBtn.down);
noBtn.on('down', noBtn.down);
unlocksPanel.addChild(knifeConfirmDialog);
};
var unlockedKnifeDownHandler = function unlockedKnifeDownHandler() {
showKnifeConfirmDialog();
};
unlockedKnifeText = new Text2("Upgraded Knife", {
size: 80,
fill: 0x00BFFF,
font: "Impact, Arial Black, Tahoma"
});
unlockedKnifeIcon = LK.getAsset('upgrade_knife', {
anchorX: 0.5,
anchorY: 0.5,
x: GAME_W / 2 - 220,
y: unlockY
});
unlockedKnifeText.anchor.set(0, 0.5);
unlockedKnifeText.x = GAME_W / 2 - 160;
unlockedKnifeText.y = unlockY;
unlocksPanel.addChild(unlockedKnifeIcon);
unlocksPanel.addChild(unlockedKnifeText);
unlockedKnifeText.interactive = true;
unlockedKnifeIcon.interactive = true;
unlockedKnifeText.down = unlockedKnifeDownHandler;
unlockedKnifeIcon.down = unlockedKnifeDownHandler;
unlockedKnifeText.on('down', unlockedKnifeDownHandler);
unlockedKnifeIcon.on('down', unlockedKnifeDownHandler);
} else {
unlockedKnifeText = new Text2("??? (Beat Super Hard Mode to unlock)", {
size: 70,
fill: 0x00BFFF,
//{3a} // bright blue
font: "Impact, Arial Black, Tahoma"
});
unlockedKnifeText.anchor.set(0.5, 0.5);
unlockedKnifeText.x = GAME_W / 2;
unlockedKnifeText.y = unlockY;
unlocksPanel.addChild(unlockedKnifeText);
}
unlockY += unlockSpacing;
// --- Player Unlock Section ---
var unlockedPlayerText, unlockedPlayerIcon;
var playerSectionY = unlockY;
if (playerUnlocked) {
unlockedPlayerText = new Text2("Player (Unlocked!)", {
size: 80,
fill: 0x00BFFF,
font: "Impact, Arial Black, Tahoma"
});
unlockedPlayerIcon = LK.getAsset('Player', {
anchorX: 0.5,
anchorY: 0.5,
x: GAME_W / 2 - 220,
y: playerSectionY
});
unlockedPlayerText.anchor.set(0, 0.5);
unlockedPlayerText.x = GAME_W / 2 - 160;
unlockedPlayerText.y = playerSectionY;
unlocksPanel.addChild(unlockedPlayerIcon);
unlocksPanel.addChild(unlockedPlayerText);
var playerDownHandler = function playerDownHandler() {
storage.selectedPlayerModel = 'player';
};
unlockedPlayerText.interactive = true;
unlockedPlayerIcon.interactive = true;
unlockedPlayerText.down = playerDownHandler;
unlockedPlayerIcon.down = playerDownHandler;
unlockedPlayerText.on('down', playerDownHandler);
unlockedPlayerIcon.on('down', playerDownHandler);
} else {
var killsNeeded = 50 - playerKills;
if (killsNeeded < 0) killsNeeded = 0;
unlockedPlayerText = new Text2("??? (Get 50 kills in Super Hard Mode to unlock)\nKills: " + playerKills + " / 50", {
size: 60,
fill: 0x00BFFF,
//{3u} // bright blue
font: "Impact, Arial Black, Tahoma"
});
unlockedPlayerText.anchor.set(0.5, 0.5);
unlockedPlayerText.x = GAME_W / 2;
unlockedPlayerText.y = playerSectionY;
unlocksPanel.addChild(unlockedPlayerText);
}
unlockY += unlockSpacing;
// --- Skeleton Player Unlock Section ---
var skeletonSectionY = unlockY;
if (skeletonPlayerUnlocked) {
var unlockedSkeletonText = new Text2("Skeleton Player (Unlocked!)", {
size: 80,
fill: 0x00BFFF,
font: "Impact, Arial Black, Tahoma"
});
var unlockedSkeletonIcon = LK.getAsset('Skeletonmodel', {
anchorX: 0.5,
anchorY: 0.5,
x: GAME_W / 2 - 220,
y: skeletonSectionY
});
unlockedSkeletonText.anchor.set(0, 0.5);
unlockedSkeletonText.x = GAME_W / 2 - 160;
unlockedSkeletonText.y = skeletonSectionY;
unlocksPanel.addChild(unlockedSkeletonIcon);
unlocksPanel.addChild(unlockedSkeletonText);
var skeletonPlayerDownHandler = function skeletonPlayerDownHandler() {
storage.selectedPlayerModel = 'skeleton';
};
unlockedSkeletonText.interactive = true;
unlockedSkeletonIcon.interactive = true;
unlockedSkeletonText.down = skeletonPlayerDownHandler;
unlockedSkeletonIcon.down = skeletonPlayerDownHandler;
unlockedSkeletonText.on('down', skeletonPlayerDownHandler);
unlockedSkeletonIcon.on('down', skeletonPlayerDownHandler);
} else {
var killsNeeded = 100 - normalModeKills;
if (killsNeeded < 0) killsNeeded = 0;
var unlockedSkeletonText = new Text2("??? (Get 100 kills in Normal Mode to unlock)\nKills: " + normalModeKills + " / 100", {
size: 60,
fill: 0x00BFFF,
//{3P} // bright blue
font: "Impact, Arial Black, Tahoma"
});
unlockedSkeletonText.anchor.set(0.5, 0.5);
unlockedSkeletonText.x = GAME_W / 2;
unlockedSkeletonText.y = skeletonSectionY;
unlocksPanel.addChild(unlockedSkeletonText);
}
unlockY += unlockSpacing;
// --- Upgrades Unlock Section ---
var upgradesSectionY = unlockY;
var upgradesUnlockText, upgradesUnlockIcon;
if (upgradesUnlocked) {
upgradesUnlockText = new Text2("Upgrades (Unlocked!)", {
size: 80,
fill: 0xFFD700,
font: "Impact, Arial Black, Tahoma"
});
upgradesUnlockIcon = LK.getAsset('upgrade_gun', {
anchorX: 0.5,
anchorY: 0.5,
x: GAME_W / 2 - 220,
y: upgradesSectionY
});
upgradesUnlockText.anchor.set(0, 0.5);
upgradesUnlockText.x = GAME_W / 2 - 160;
upgradesUnlockText.y = upgradesSectionY;
unlocksPanel.addChild(upgradesUnlockIcon);
unlocksPanel.addChild(upgradesUnlockText);
} else if (typeof game._upgradesUnlockReady !== "undefined" && game._upgradesUnlockReady) {
// Ready to unlock: show clickable unlock
upgradesUnlockText = new Text2("Unlock Upgrades (500+ Nightmare points)", {
size: 70,
fill: 0x00BFFF,
font: "Impact, Arial Black, Tahoma"
});
upgradesUnlockIcon = LK.getAsset('upgrade_gun', {
anchorX: 0.5,
anchorY: 0.5,
x: GAME_W / 2 - 220,
y: upgradesSectionY
});
upgradesUnlockText.anchor.set(0, 0.5);
upgradesUnlockText.x = GAME_W / 2 - 160;
upgradesUnlockText.y = upgradesSectionY;
unlocksPanel.addChild(upgradesUnlockIcon);
unlocksPanel.addChild(upgradesUnlockText);
// Add interactive unlock logic
upgradesUnlockText.interactive = true;
upgradesUnlockIcon.interactive = true;
var upgradesConfirmDialog = null;
var showUpgradesConfirmDialog = function showUpgradesConfirmDialog() {
if (upgradesConfirmDialog) return;
upgradesConfirmDialog = new Container();
var dialogBg = LK.getAsset('maze_bg', {
anchorX: 0,
anchorY: 0,
x: GAME_W / 2 - 400,
y: 1200,
width: 800,
height: 400
});
dialogBg.alpha = 0.93;
upgradesConfirmDialog.addChild(dialogBg);
var dialogText = new Text2("Unlock Upgrades? (This cannot be undone)", {
size: 60,
fill: "#fff",
font: "Impact, Arial Black, Tahoma"
});
dialogText.anchor.set(0.5, 0.5);
dialogText.x = GAME_W / 2;
dialogText.y = 1320;
upgradesConfirmDialog.addChild(dialogText);
var yesBtn = new Text2("Yes", {
size: 90,
fill: 0x44FF44,
font: "Impact, Arial Black, Tahoma"
});
yesBtn.anchor.set(0.5, 0.5);
yesBtn.x = GAME_W / 2 - 140;
yesBtn.y = 1450;
upgradesConfirmDialog.addChild(yesBtn);
var noBtn = new Text2("No", {
size: 90,
fill: 0xFF4444,
font: "Impact, Arial Black, Tahoma"
});
noBtn.anchor.set(0.5, 0.5);
noBtn.x = GAME_W / 2 + 140;
noBtn.y = 1450;
upgradesConfirmDialog.addChild(noBtn);
yesBtn.interactive = true;
noBtn.interactive = true;
yesBtn.down = function () {
upgradesUnlocked = true;
if (typeof storage !== "undefined") {
storage.upgradesUnlocked = true;
}
game._upgradesUnlockReady = undefined;
if (upgradesConfirmDialog) {
upgradesConfirmDialog.destroy();
upgradesConfirmDialog = null;
}
// Refresh unlocks panel to show unlocked state
if (unlocksPanel) {
unlocksPanel.destroy();
unlocksPanel = null;
unlocksPanelOpen = false;
showUnlocksPanel();
}
};
noBtn.down = function () {
if (upgradesConfirmDialog) {
upgradesConfirmDialog.destroy();
upgradesConfirmDialog = null;
}
};
yesBtn.on('down', yesBtn.down);
noBtn.on('down', noBtn.down);
unlocksPanel.addChild(upgradesConfirmDialog);
};
var upgradesUnlockDownHandler = function upgradesUnlockDownHandler() {
showUpgradesConfirmDialog();
};
upgradesUnlockText.down = upgradesUnlockDownHandler;
upgradesUnlockIcon.down = upgradesUnlockDownHandler;
upgradesUnlockText.on('down', upgradesUnlockDownHandler);
upgradesUnlockIcon.on('down', upgradesUnlockDownHandler);
} else {
upgradesUnlockText = new Text2("??? (To unlock Chicken Jockey avatar, please complete Nightmare Mode)", {
size: 60,
fill: 0x00BFFF,
font: "Impact, Arial Black, Tahoma"
});
upgradesUnlockText.anchor.set(0.5, 0.5);
upgradesUnlockText.x = GAME_W / 2;
upgradesUnlockText.y = upgradesSectionY;
unlocksPanel.addChild(upgradesUnlockText);
}
unlockY += unlockSpacing;
// --- Add more unlocks here in the future ---
// Close button
var closeBtn = new Text2("Close", {
size: 90,
fill: 0xFF4444,
font: "Impact, Arial Black, Tahoma"
});
closeBtn.anchor.set(0.5, 0.5);
closeBtn.x = GAME_W / 2;
closeBtn.y = 900 + 1800 - 80;
unlocksPanel.addChild(closeBtn);
closeBtn.interactive = true;
closeBtn.down = function () {
if (unlocksPanel) {
unlocksPanel.destroy();
unlocksPanel = null;
unlocksPanelOpen = false;
}
};
closeBtn.on('down', closeBtn.down);
// Attach unlocksPanel to mainMenuOverlay
mainMenuOverlay.addChild(unlocksPanel);
}
// Button handler for unlocksBtn
unlocksBtn.interactive = true;
unlocksBtn.down = function () {
showUnlocksPanel();
};
unlocksBtn.on('down', unlocksBtn.down);
// Add overlay to game
game.addChild(mainMenuOverlay);
// Button hit areas
normalBtn.interactive = true;
hardBtn.interactive = true;
nightmareBtn.interactive = true;
// Add direct down event handlers to buttons for pressable effect
normalBtn.down = function (x, y, obj) {
if (!mainMenuOverlay) {
return;
}
selectMode('normal');
};
hardBtn.down = function (x, y, obj) {
if (!mainMenuOverlay) {
return;
}
selectMode('hard');
};
nightmareBtn.down = function (x, y, obj) {
if (!mainMenuOverlay) {
return;
}
selectMode('nightmare');
};
normalBtn.on('down', normalBtn.down);
hardBtn.on('down', hardBtn.down);
nightmareBtn.on('down', nightmareBtn.down);
// Touch/click handlers
mainMenuOverlay.down = function (x, y, obj) {
// Defensive: If overlay is destroyed or null, do nothing
if (!mainMenuOverlay) {
return;
}
// Convert to overlay local coordinates
var local = mainMenuOverlay.toLocal({
x: x,
y: y
});
// Check if normalBtn was pressed
if (pointInText(normalBtn, local.x, local.y)) {
selectMode('normal');
} else if (pointInText(hardBtn, local.x, local.y)) {
selectMode('hard');
}
};
// Attach to overlay
mainMenuOverlay.on('down', mainMenuOverlay.down);
// Also attach to game for mobile
game.down = function (x, y, obj) {
if (menuActive && mainMenuOverlay) {
mainMenuOverlay.down(x, y, obj);
return;
}
if (gameOver) {
return;
}
dragNode = minecart;
lastTouchX = x;
lastTouchY = y;
};
}
// Helper: check if point is inside Text2 bounds
function pointInText(txt, x, y) {
var w = txt.width;
var h = txt.height;
var tx = txt.x - w * txt.anchor.x;
var ty = txt.y - h * txt.anchor.y;
return x >= tx && x <= tx + w && y >= ty && y <= ty + h;
}
// Remove menu and start game
function selectMode(mode) {
if (!mainMenuOverlay) {
return;
}
menuActive = false;
// Continue playing Feel It by D4VD during gameplay
// LK.stopMusic(); // Commented out to keep music playing
mainMenuOverlay.destroy();
mainMenuOverlay = null;
// Set difficulty and level mode
if (mode === 'normal') {
// Set up original game state for normal mode
difficulty = 1;
game._superHardMode = false;
game._nightmareMode = false;
// Remove any super hard mode UI if present
if (game._batHealthBarBg) {
LK.gui.top.removeChild(game._batHealthBarBg);
game._batHealthBarBg = null;
}
if (game._batHealthBar) {
LK.gui.top.removeChild(game._batHealthBar);
game._batHealthBar = null;
}
game._batEnemy = null;
game._batMaxHp = null;
// Place player at original starting position
if (minecart) {
minecart.x = GAME_W / 2;
minecart.y = PLAYER_Y;
// If upgraded knife is unlocked AND player chose to use it, start with knife and max level
if (upgradedKnifeUnlocked && useUpgradedKnifeNextGame) {
minecart.setWeapon('knife');
minecart.weaponLevel = 3;
useUpgradedKnifeNextGame = false; // Reset after use
}
}
} else if (mode === 'hard') {
difficulty = 7;
game._superHardMode = true;
game._nightmareMode = false;
} else if (mode === 'nightmare') {
difficulty = 12;
game._superHardMode = false;
game._nightmareMode = true;
}
// Reset game state
LK.setScore(0);
if (scoreTxt) {
scoreTxt.setText('0');
}
if (minecart) {
minecart.x = GAME_W / 2;
minecart.y = PLAYER_Y;
minecart.weapon = 'gun';
minecart.weaponLevel = 1;
minecart.invincible = false;
minecart.invincibleTimer = 0;
minecart.attackCooldown = 0;
minecart.knifeSwinging = false;
minecart.setWeapon('gun');
}
bullets = [];
enemies = [];
obstacles = [];
upgrades = [];
spawnTimer = 0;
obstacleTimer = 60;
upgradeTimer = 600;
knifeHitEnemies = [];
lastKnifeSwingTick = -100;
gameOver = false;
// If super hard mode, set up new level
if (game._superHardMode) {
// Remove all enemies, obstacles, upgrades, and spawn a new set for the new level
for (var i = 0; i < enemies.length; ++i) {
enemies[i].destroy();
}
for (var i = 0; i < obstacles.length; ++i) {
obstacles[i].destroy();
}
for (var i = 0; i < upgrades.length; ++i) {
upgrades[i].destroy();
}
enemies = [];
obstacles = [];
upgrades = [];
// Place player at the same starting position as normal mode
minecart.x = GAME_W / 2;
minecart.y = PLAYER_Y;
// Optionally, change background or add new obstacles/enemy patterns
// Example: spawn a wall of goblins at the top
for (var gx = TRACK_LEFT + 120; gx < TRACK_RIGHT - 120; gx += 180) {
var g = new Goblin();
g.x = gx;
g.y = 0;
enemies.push(g);
game.addChild(g);
}
// Example: spawn a bat immediately
var bat = new Bat();
bat.x = GAME_W / 2;
bat.y = -100;
enemies.push(bat);
game.addChild(bat);
// Set up bat health bar UI for super hard mode
var barWidth = 1700;
var barHeight = 90;
var barBg = LK.getAsset('enemy_goblin', {
width: barWidth,
height: barHeight,
color: 0x222222,
anchorX: 0.5,
anchorY: 0
});
barBg.x = GAME_W / 2;
barBg.y = 10;
var bar = LK.getAsset('enemy_goblin', {
width: barWidth - 20,
height: barHeight - 18,
color: 0xff2222,
anchorX: 0.5,
anchorY: 0
});
bar.x = GAME_W / 2;
bar.y = 19;
LK.gui.top.addChild(barBg);
LK.gui.top.addChild(bar);
game._batHealthBarBg = barBg;
game._batHealthBar = bar;
game._batEnemy = bat;
game._batMaxHp = bat.hp;
}
}
// Add maze background image
var mazeBg = LK.getAsset('maze_bg', {
anchorX: 0,
anchorY: 0,
x: 0,
y: 0,
width: GAME_W,
height: GAME_H
});
game.addChild(mazeBg);
// Score display
scoreTxt = new Text2('0', {
size: 120,
fill: "#fff"
});
scoreTxt.anchor.set(0.5, 0);
LK.gui.top.addChild(scoreTxt);
// Create player
minecart = new Minecart();
game.addChild(minecart);
minecart.x = GAME_W / 2;
minecart.y = PLAYER_Y;
// Vertical shooter: drag minecart only in X, fixed Y
game.down = function (x, y, obj) {
if (gameOver) {
return;
}
dragNode = minecart;
lastTouchX = x;
lastTouchY = y;
};
game.up = function (x, y, obj) {
dragNode = null;
};
game.move = function (x, y, obj) {
if (gameOver) {
return;
}
if (dragNode === minecart) {
// Only allow movement in X, clamp to track
var nx = x;
if (nx < TRACK_LEFT) {
nx = TRACK_LEFT;
}
if (nx > TRACK_RIGHT) {
nx = TRACK_RIGHT;
}
minecart.x = nx;
minecart.y = PLAYER_Y;
}
};
// Tap to attack (shoot or knife)
game.tap = function (x, y, obj) {
if (gameOver) {
return;
}
if (minecart.weapon === 'gun') {
if (minecart.attackCooldown <= 0) {
fireBullet();
minecart.attackCooldown = 12 - minecart.weaponLevel * 2; // Faster with upgrades
if (minecart.attackCooldown < 4) {
minecart.attackCooldown = 4;
}
}
} else if (minecart.weapon === 'knife') {
if (LK.ticks - lastKnifeSwingTick > 18) {
minecart.swingKnife();
lastKnifeSwingTick = LK.ticks;
}
}
};
// Fire bullet(s)
function fireBullet() {
var spread = minecart.weaponLevel;
var angleSpread = 0.18; // radians, about 10 degrees
var basePower = 1 + Math.floor((minecart.weaponLevel - 1) / 2); // Level 1-2: 1, Level 3: 2
for (var i = 0; i < spread; ++i) {
var b = new Bullet();
// Spread bullets horizontally and with angle
var offset = i - (spread - 1) / 2;
b.x = minecart.x + offset * 40;
b.y = minecart.y - minecart.height / 2 - 10;
// Angle for spread: center is 0, sides are negative/positive
if (spread > 1) {
b.angle = offset * angleSpread;
}
b.power = basePower;
bullets.push(b);
game.addChild(b);
}
}
// Main update loop
game.update = function () {
if (menuActive) {
return;
}
if (gameOver) {
return;
}
// Difficulty scaling
if (LK.ticks % 300 === 0 && difficulty < 10) {
difficulty += 1;
}
// Attack cooldown
if (minecart && minecart.attackCooldown > 0) {
minecart.attackCooldown -= 1;
}
// Invincibility timer
if (minecart && minecart.invincible) {
minecart.invincibleTimer -= 1;
if (minecart.invincibleTimer <= 0) {
minecart.invincible = false;
}
}
// Bullets update
for (var i = bullets.length - 1; i >= 0; --i) {
var b = bullets[i];
b.update();
if (b.y < -50) {
b.destroy();
bullets.splice(i, 1);
continue;
}
// Bullet hits enemy
for (var j = enemies.length - 1; j >= 0; --j) {
var e = enemies[j];
if (b.intersects(e)) {
// Use bullet power for damage
e.hp -= b.power || 1;
// If this is the Bat, update health bar
if (game._batEnemy && e === game._batEnemy && game._batHealthBar) {
var percent = Math.max(0, e.hp) / game._batMaxHp;
// Clamp width to minimum 0
game._batHealthBar.width = 1680 * percent;
// Optionally, you can tint the bar as it gets low (not required)
}
b.destroy();
bullets.splice(i, 1);
// Monster dies if hp <= 0
if (e.hp <= 0) {
addScore(e.scoreValue);
e.destroy();
enemies.splice(j, 1);
// If Bat is dead, remove health bar UI
if (game._batEnemy && e === game._batEnemy) {
if (game._batHealthBarBg) {
LK.gui.top.removeChild(game._batHealthBarBg);
game._batHealthBarBg = null;
}
if (game._batHealthBar) {
LK.gui.top.removeChild(game._batHealthBar);
game._batHealthBar = null;
}
game._batEnemy = null;
// If in super hard mode, check for score 1000 to win and unlock upgraded knife
if (game._superHardMode && !upgradedKnifeUnlocked) {
if (LK.getScore() >= 1000) {
upgradedKnifeUnlocked = true;
if (typeof storage !== "undefined") {
storage.upgradedKnifeUnlocked = true;
}
// Show a win popup
LK.showYouWin();
gameOver = true;
return;
}
// Otherwise, keep playing until score 1000 is reached
}
}
// --- Player unlock logic: count kills in super hard mode ---
if (game._superHardMode && !playerUnlocked) {
if (typeof playerKills === "undefined") playerKills = 0;
playerKills++;
if (playerKills >= 50) {
playerUnlocked = true;
if (typeof storage !== "undefined") {
storage.playerUnlocked = true;
}
}
if (typeof storage !== "undefined") {
storage.playerKills = playerKills;
}
}
// --- Skeleton player unlock logic: count kills in normal mode ---
if (!game._superHardMode && !game._nightmareMode && !skeletonPlayerUnlocked) {
if (typeof normalModeKills === "undefined") normalModeKills = 0;
normalModeKills++;
storage.normalModeKills = normalModeKills;
if (normalModeKills >= 100) {
skeletonPlayerUnlocked = true;
storage.skeletonPlayerUnlocked = true;
}
}
}
break;
}
}
}
// Enemies update (vertical shooter: always visible, move down)
for (var i = enemies.length - 1; i >= 0; --i) {
var e = enemies[i];
// Track lastY for dodge detection in Nightmare Mode
if (typeof e.lastY === "undefined") {
e.lastY = e.y;
}
e.update();
// Remove if out of bounds
if (e.y > GAME_H + 200) {
// Nightmare Mode: Award points for dodging (enemy left screen without hitting player)
if (game._nightmareMode && typeof e.dodgeScored === "undefined") {
addScore(e.scoreValue);
e.dodgeScored = true;
}
e.destroy();
enemies.splice(i, 1);
continue;
}
// Update lastY for next frame
e.lastY = e.y;
// Enemy hits minecart
if (minecart && minecart !== null && !minecart.invincible && e.intersects(minecart)) {
// In Nightmare Mode, player doesn't die from monster hits (dodge mechanic)
if (game._nightmareMode) {
// Just flash to show contact, but don't kill player
minecart.flash();
LK.effects.flashScreen(0xff0000, 300);
continue; // Skip to next enemy, don't end game
}
// Check if Bat killed the player for jump scare
if (game._batEnemy && e === game._batEnemy) {
// Show a big jump scare: flash white, show a big FACE in the center, and play a loud sound if available
LK.effects.flashScreen(0xffffff, 1200);
// Create a big FACE image in the center (using the goblin face as a placeholder for a jumpscare face)
var jumpscareFace = LK.getAsset('enemy_goblin', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 2,
scaleY: 2,
x: GAME_W / 2,
y: GAME_H / 2
});
jumpscareFace.alpha = 0;
game.addChild(jumpscareFace);
// Animate the face: scale up and fade in quickly, then fade out
tween(jumpscareFace, {
alpha: 1,
scaleX: 7,
scaleY: 7
}, {
duration: 120,
onFinish: function onFinish() {
tween(jumpscareFace, {
alpha: 0
}, {
duration: 700,
delay: 350,
onFinish: function onFinish() {
jumpscareFace.destroy();
}
});
}
});
// Optionally, play a loud sound here if you have a jumpscare sound asset
}
minecart.flash();
LK.effects.flashScreen(0xff0000, 600);
// End game
LK.showGameOver();
gameOver = true;
return;
}
// Enemy hit by knife
if (minecart.weapon === 'knife' && minecart.knife && minecart.knife.visible && !knifeHitEnemies.includes(e)) {
// Knife is in swing, check collision
var knifeGlobal = minecart.toGlobal(minecart.knife.position);
var knifeRect = new Rectangle(knifeGlobal.x - 30, knifeGlobal.y - 12, 60, 24);
var enemyRect = new Rectangle(e.x - e.width / 2, e.y - e.height / 2, e.width, e.height);
if (rectsIntersect(knifeRect, enemyRect)) {
e.hp -= 1;
// If this is the Bat, update health bar
if (game._batEnemy && e === game._batEnemy && game._batHealthBar) {
var percent = Math.max(0, e.hp) / game._batMaxHp;
game._batHealthBar.width = 1680 * percent;
}
knifeHitEnemies.push(e);
if (e.hp <= 0) {
addScore(e.scoreValue);
e.destroy();
enemies.splice(i, 1);
// If Bat is dead, remove health bar UI
if (game._batEnemy && e === game._batEnemy) {
if (game._batHealthBarBg) {
LK.gui.top.removeChild(game._batHealthBarBg);
game._batHealthBarBg = null;
}
if (game._batHealthBar) {
LK.gui.top.removeChild(game._batHealthBar);
game._batHealthBar = null;
}
game._batEnemy = null;
}
}
}
}
}
// Reset knife hit list if knife not swinging
if (!minecart || !minecart.knife || !minecart.knife.visible) {
knifeHitEnemies = [];
}
// Obstacles update
for (var i = obstacles.length - 1; i >= 0; --i) {
var o = obstacles[i];
o.update();
if (o.y > GAME_H + 100) {
o.destroy();
obstacles.splice(i, 1);
continue;
}
// Obstacle hits minecart
if (minecart && !minecart.invincible && o.intersects(minecart)) {
minecart.flash();
LK.effects.flashScreen(0xff0000, 600);
LK.showGameOver();
gameOver = true;
return;
}
}
// Upgrades update
for (var i = upgrades.length - 1; i >= 0; --i) {
var u = upgrades[i];
u.update();
if (u.y > GAME_H + 100) {
u.destroy();
upgrades.splice(i, 1);
continue;
}
// Pickup
if (u.intersects(minecart)) {
if (u.kind === 'gun') {
minecart.setWeapon('gun');
minecart.upgradeWeapon();
} else {
minecart.setWeapon('knife');
minecart.upgradeWeapon();
}
u.destroy();
upgrades.splice(i, 1);
}
}
// Spawning enemies
spawnTimer -= 1;
if (spawnTimer <= 0) {
spawnEnemy();
if (game._nightmareMode) {
spawnTimer = Math.max(24, 60 - difficulty * 4 - Math.floor(Math.random() * 10));
} else {
spawnTimer = Math.max(24, 60 - difficulty * 4 - Math.floor(Math.random() * 10));
}
}
// Spawning obstacles
obstacleTimer -= 1;
if (obstacleTimer <= 0) {
if (game._nightmareMode) {
if (Math.random() < 0.85) {
spawnObstacle();
}
obstacleTimer = 30 + Math.floor(Math.random() * 20);
} else {
if (Math.random() < 0.5 + difficulty * 0.04) {
spawnObstacle();
}
obstacleTimer = 90 + Math.floor(Math.random() * 60) - difficulty * 4;
if (obstacleTimer < 30) {
obstacleTimer = 30;
}
}
}
// Spawning upgrades
upgradeTimer -= 1;
if (upgradeTimer <= 0) {
if (game._superHardMode) {
// Easier: upgrades spawn more often in super hard mode
if (Math.random() < 0.38) {
spawnUpgrade();
}
upgradeTimer = 340 + Math.floor(Math.random() * 120);
} else if (game._nightmareMode) {
if (Math.random() < 0.10) {
spawnUpgrade();
}
upgradeTimer = 900 + Math.floor(Math.random() * 400);
} else {
if (Math.random() < 0.18) {
spawnUpgrade();
}
upgradeTimer = 600 + Math.floor(Math.random() * 300);
}
}
// Update score display and visibleScore
if (LK.getScore() !== lastScore) {
scoreTxt.setText(LK.getScore());
lastScore = LK.getScore();
visibleScore = LK.getScore();
}
// Win condition for super hard mode: reach score 1000
if (game._superHardMode && !upgradedKnifeUnlocked && visibleScore >= 1000) {
upgradedKnifeUnlocked = true;
if (typeof storage !== "undefined") {
storage.upgradedKnifeUnlocked = true;
}
LK.showYouWin();
gameOver = true;
return;
}
// Unlock upgrades automatically when reaching 500 points in Nightmare Mode
if (game._nightmareMode && !upgradesUnlocked && visibleScore >= 500) {
upgradesUnlocked = true;
if (typeof storage !== "undefined") {
storage.upgradesUnlocked = true;
}
game._upgradesUnlockReady = true;
} else if ((!game._nightmareMode || upgradesUnlocked || visibleScore < 500) && typeof game._upgradesUnlockReady !== "undefined") {
// Reset unlock ready flag if not in correct state
game._upgradesUnlockReady = undefined;
}
// Win condition for nightmare mode: reach score 2000
if (game._nightmareMode && LK.getScore() >= 2000) {
// Unlock upgrades/chicken jockey if score >= 500
if (LK.getScore() >= 500 && !upgradesUnlocked) {
upgradesUnlocked = true;
if (typeof storage !== "undefined") {
storage.upgradesUnlocked = true;
}
}
LK.showYouWin();
gameOver = true;
// Show chicken jockey unlock dialog if not already unlocked
if (upgradesUnlocked && (typeof storage.chickenJockeyUnlocked === "undefined" || !storage.chickenJockeyUnlocked)) {
if (!game._chickenJockeyDialog) {
game._chickenJockeyDialog = new Container();
var dialogBg = LK.getAsset('maze_bg', {
anchorX: 0,
anchorY: 0,
x: GAME_W / 2 - 400,
y: GAME_H / 2 - 400,
width: 800,
height: 800
});
dialogBg.alpha = 0.95;
game._chickenJockeyDialog.addChild(dialogBg);
var chickenJockeyImg = LK.getAsset('Chickenjockey', {
anchorX: 0.5,
anchorY: 0.5,
x: GAME_W / 2,
y: GAME_H / 2 - 120,
scaleX: 2.2,
scaleY: 2.2
});
game._chickenJockeyDialog.addChild(chickenJockeyImg);
var dialogText = new Text2("Do you want to become the Chicken Jockey?", {
size: 70,
fill: 0x00BFFF,
font: "Impact, Arial Black, Tahoma"
});
dialogText.anchor.set(0.5, 0.5);
dialogText.x = GAME_W / 2;
dialogText.y = GAME_H / 2 + 120;
game._chickenJockeyDialog.addChild(dialogText);
var yesBtn = new Text2("Yes", {
size: 100,
fill: 0x44FF44,
font: "Impact, Arial Black, Tahoma"
});
yesBtn.anchor.set(0.5, 0.5);
yesBtn.x = GAME_W / 2 - 140;
yesBtn.y = GAME_H / 2 + 260;
game._chickenJockeyDialog.addChild(yesBtn);
var noBtn = new Text2("No", {
size: 100,
fill: 0xFF4444,
font: "Impact, Arial Black, Tahoma"
});
noBtn.anchor.set(0.5, 0.5);
noBtn.x = GAME_W / 2 + 140;
noBtn.y = GAME_H / 2 + 260;
game._chickenJockeyDialog.addChild(noBtn);
yesBtn.interactive = true;
noBtn.interactive = true;
yesBtn.down = function () {
// Set player model to chicken jockey
storage.selectedPlayerModel = 'chickenjockey';
storage.chickenJockeyUnlocked = true;
if (minecart) {
// Replace minecart's asset with chicken jockey
minecart.destroy();
minecart = new Minecart();
game.addChild(minecart);
minecart.x = GAME_W / 2;
minecart.y = PLAYER_Y;
}
if (game._chickenJockeyDialog) {
game._chickenJockeyDialog.destroy();
game._chickenJockeyDialog = null;
}
};
noBtn.down = function () {
storage.chickenJockeyUnlocked = true;
if (game._chickenJockeyDialog) {
game._chickenJockeyDialog.destroy();
game._chickenJockeyDialog = null;
}
};
yesBtn.on('down', yesBtn.down);
noBtn.on('down', noBtn.down);
game.addChild(game._chickenJockeyDialog);
}
}
return;
}
};
// Spawn enemy at random X at the top (vertical shooter style)
function spawnEnemy() {
// If in super hard mode, spawn new enemy patterns
if (game._superHardMode) {
// Example: spawn a slightly slower skeleton and a goblin together (easier)
var e1 = new Skeleton();
e1.speed = 10 + Math.random() * 2.5;
e1.x = TRACK_LEFT + 120 + Math.random() * (TRACK_RIGHT - TRACK_LEFT - 240);
e1.y = -80;
enemies.push(e1);
game.addChild(e1);
var e2 = new Goblin();
e2.speed = 8 + Math.random() * 2;
e2.x = TRACK_LEFT + 120 + Math.random() * (TRACK_RIGHT - TRACK_LEFT - 240);
e2.y = -180;
enemies.push(e2);
game.addChild(e2);
// Occasionally spawn a Bat
if (Math.random() < 0.12) {
var bat = new Bat();
bat.x = TRACK_LEFT + 120 + Math.random() * (TRACK_RIGHT - TRACK_LEFT - 240);
bat.y = -200;
enemies.push(bat);
game.addChild(bat);
// Set up bat health bar UI
var barWidth = 1700;
var barHeight = 90;
var barBg = LK.getAsset('enemy_goblin', {
width: barWidth,
height: barHeight,
color: 0x222222,
anchorX: 0.5,
anchorY: 0
});
barBg.x = GAME_W / 2;
barBg.y = 10;
var bar = LK.getAsset('enemy_goblin', {
width: barWidth - 20,
height: barHeight - 18,
color: 0xff2222,
anchorX: 0.5,
anchorY: 0
});
bar.x = GAME_W / 2;
bar.y = 19;
LK.gui.top.addChild(barBg);
LK.gui.top.addChild(bar);
game._batHealthBarBg = barBg;
game._batHealthBar = bar;
game._batEnemy = bat;
game._batMaxHp = bat.hp;
}
// Sometimes spawn a ginger
if (Math.random() < 0.25) {
var g = new Ginger();
g.x = TRACK_LEFT + 120 + Math.random() * (TRACK_RIGHT - TRACK_LEFT - 240);
g.y = -120;
enemies.push(g);
game.addChild(g);
}
return;
}
// Nightmare Mode: spawn only NightmareMonster(s)
if (game._nightmareMode) {
var nMonsters = 1 + Math.floor(Math.random() * 2); // 1-2 per wave, less than before
for (var i = 0; i < nMonsters; ++i) {
var nm = new NightmareMonster();
nm.x = TRACK_LEFT + 120 + Math.random() * (TRACK_RIGHT - TRACK_LEFT - 240);
nm.y = -200 - i * 180;
enemies.push(nm);
game.addChild(nm);
}
return;
}
// --- Normal mode enemy spawn ---
var enemyType = Math.random();
var e = null;
// Wendigo: 1% chance, must be checked first so it doesn't overlap with Bat
if (enemyType < 0.01) {
e = new Wendigo();
}
// Bat is now extremely rare: only spawn if enemyType < 0.02 (2% chance, but not if Wendigo already spawned)
else if (enemyType < 0.02) {
e = new Bat();
// Show warning text and play creepy music when Bat spawns
if (!game._batWarningText) {
// Create warning text if not already present
var batWarning = new Text2("That is not you.", {
size: 120,
fill: 0xFF2222,
font: "Impact, Arial Black, Tahoma"
});
batWarning.anchor.set(0.5, 0.5);
batWarning.alpha = 0.0;
game._batWarningText = batWarning;
// Add to LK.gui.center for true center of screen
LK.gui.center.addChild(batWarning);
}
// Fade in text
var warningText = game._batWarningText;
warningText.alpha = 0.0;
tween(warningText, {
alpha: 1
}, {
duration: 400,
onFinish: function onFinish() {
// Fade out after 1.2s
tween(warningText, {
alpha: 0
}, {
duration: 800,
delay: 1200
});
}
});
// Play creepy music (music asset id: 'creepy_bat')
LK.playMusic('creepy_bat', {
fade: {
start: 0,
end: 1,
duration: 800
}
});
// --- Bat Health Bar UI ---
// Remove any previous bat health bar
if (game._batHealthBarBg) {
LK.gui.top.removeChild(game._batHealthBarBg);
game._batHealthBarBg = null;
}
if (game._batHealthBar) {
LK.gui.top.removeChild(game._batHealthBar);
game._batHealthBar = null;
}
// Create background bar (much bigger, at the very top)
var barWidth = 1700;
var barHeight = 90;
var barBg = LK.getAsset('enemy_goblin', {
width: barWidth,
height: barHeight,
color: 0x222222,
anchorX: 0.5,
anchorY: 0
});
barBg.x = GAME_W / 2;
barBg.y = 10; // Very top of the screen
// Create health bar (red, much bigger)
var bar = LK.getAsset('enemy_goblin', {
width: barWidth - 20,
height: barHeight - 18,
color: 0xff2222,
anchorX: 0.5,
anchorY: 0
});
bar.x = GAME_W / 2;
bar.y = 19; // Just below the background bar
// Add to GUI
LK.gui.top.addChild(barBg);
LK.gui.top.addChild(bar);
game._batHealthBarBg = barBg;
game._batHealthBar = bar;
// Store reference to the Bat instance for health bar updates
game._batEnemy = e;
game._batMaxHp = e.hp;
} else if (enemyType < 0.10) {
// Ginger is rare, 8% chance (between 2% and 10%)
e = new Ginger();
} else if (enemyType < 0.65) {
e = new Goblin();
} else {
e = new Skeleton();
}
// Spawn at random X at the top of the screen
e.x = TRACK_LEFT + 80 + Math.random() * (TRACK_RIGHT - TRACK_LEFT - 160);
e.y = -80;
enemies.push(e);
game.addChild(e);
}
// Spawn obstacle
function spawnObstacle() {
var o = new Obstacle();
o.x = TRACK_LEFT + 80 + Math.random() * (TRACK_RIGHT - TRACK_LEFT - 160);
o.y = -60;
obstacles.push(o);
game.addChild(o);
}
// Spawn upgrade
function spawnUpgrade() {
var u = new Upgrade();
u.x = TRACK_LEFT + 80 + Math.random() * (TRACK_RIGHT - TRACK_LEFT - 160);
u.y = -60;
upgrades.push(u);
game.addChild(u);
}
// Add score
function addScore(val) {
LK.setScore(LK.getScore() + val);
}
// Rectangle intersection helper
function rectsIntersect(r1, r2) {
return !(r2.x > r1.x + r1.width || r2.x + r2.width < r1.x || r2.y > r1.y + r1.height || r2.y + r2.height < r1.y);
}
// Touch to attack (simulate tap on mobile)
game.down = function (origDown) {
return function (x, y, obj) {
if (origDown) {
origDown(x, y, obj);
}
game.tap(x, y, obj);
};
}(game.down);
// Reset game state on game over
LK.on('gameover', function () {
gameOver = true;
setTimeout(function () {
showMainMenu();
}, 600);
});
// Track run count for Bat spawn logic
if (typeof game._runCount === "undefined") {
game._runCount = 0;
}
LK.on('newgame', function () {
game._runCount = (game._runCount || 0) + 1;
gameOver = false;
LK.setScore(0);
if (scoreTxt) {
scoreTxt.setText('0');
}
// Remove all objects
for (var i = 0; i < bullets.length; ++i) {
bullets[i].destroy();
}
for (var i = 0; i < enemies.length; ++i) {
enemies[i].destroy();
}
for (var i = 0; i < obstacles.length; ++i) {
obstacles[i].destroy();
}
for (var i = 0; i < upgrades.length; ++i) {
upgrades[i].destroy();
}
bullets = [];
enemies = [];
obstacles = [];
upgrades = [];
minecart.x = GAME_W / 2;
minecart.y = PLAYER_Y;
minecart.weapon = 'gun';
minecart.weaponLevel = 1;
minecart.invincible = false;
minecart.invincibleTimer = 0;
minecart.attackCooldown = 0;
minecart.knifeSwinging = false;
minecart.setWeapon('gun');
difficulty = 1;
spawnTimer = 0;
obstacleTimer = 0;
upgradeTimer = 0;
knifeHitEnemies = [];
lastKnifeSwingTick = -100;
// Remove Bat health bar UI if present
if (game._batHealthBarBg) {
LK.gui.top.removeChild(game._batHealthBarBg);
game._batHealthBarBg = null;
}
if (game._batHealthBar) {
LK.gui.top.removeChild(game._batHealthBar);
game._batHealthBar = null;
}
game._batEnemy = null;
game._batMaxHp = null;
});
// Initial state
spawnTimer = 0;
obstacleTimer = 60;
upgradeTimer = 600;
difficulty = 1;
gameOver = false;
LK.setScore(0);
if (scoreTxt) {
scoreTxt.setText('0');
}
// Show main menu on load
showMainMenu();
Fullscreen modern App Store landscape banner, 16:9, high definition, for a game titled "Minecart Mayhem: Armed Run" and with the description "Survive a wild minecart ride by battling multiple enemy types with guns and knives, dodging obstacles, and collecting upgrades for a high-score chase.". No text on banner!
A skeleton with 3 legs and a creepy face and red eyes. No background. Transparent background. Blank background. No shadows. 2d. In-Game asset. flat
A silver bullet. No background. Transparent background. Blank background. No shadows. 2d. In-Game asset. flat
a maze . No background. Transparent background. Blank background. No shadows. 2d. In-Game asset. flat
Man with bag on head and red eyes. No background. Transparent background. Blank background. No shadows. 2d. In-Game asset. flat
A man with a bag over his head and red eyes. No background. Transparent background. Blank background. No shadows. 2d. In-Game asset. flat
Knife. No background. Transparent background. Blank background. No shadows. 2d. In-Game asset. flat
Rock. No background. Transparent background. Blank background. No shadows. 2d. In-Game asset. flat
Zombie. No background. Transparent background. Blank background. No shadows. 2d. In-Game asset. flat
A knife with purple and golden around it. No background. Transparent background. Blank background. No shadows. 2d. In-Game asset. flat
Man with bag on head with cross on it. No background. Transparent background. Blank background. No shadows. 2d. In-Game asset. flat
Skeleton player model. No background. Transparent background. Blank background. No shadows. 2d. In-Game asset. flat
A black monster with a creepy white grin. No background. Transparent background. Blank background. No shadows. 2d. In-Game asset. flat
Blocky zombie riding blocky chicken. No background. Transparent background. Blank background. No shadows. 2d. In-Game asset. flat