/**** * Plugins ****/ var tween = LK.import("@upit/tween.v1"); /**** * Classes ****/ // Attack slash (enemy attack) var AttackSlash = Container.expand(function () { var self = Container.call(this); var slash = self.attachAsset('attack_slash', { anchorX: 0.5, anchorY: 0.5 }); self.speed = 22 + Math.random() * 8; self.direction = Math.random() < 0.5 ? 1 : -1; self.active = true; self.update = function () { self.x += self.speed * self.direction; if (self.x < 200 || self.x > 1848) { self.active = false; } }; return self; }); // Boss: Asgore var BossAsgore = Container.expand(function () { var self = Container.call(this); var monster = self.attachAsset('boss_asgorepng', { anchorX: 0.5, anchorY: 0.5 }); self.hp = 30; self.maxHp = 30; self.name = "Asgore"; self.state = "neutral"; self.dialogue = ["Asgore stands tall, trident in hand.", "Asgore's eyes are full of sorrow.", "Asgore readies his attack.", "Asgore hesitates for a moment."]; self.dialogueIndex = 0; self.getDialogue = function () { if (self.state === "spared") return self.name + " looks relieved."; if (self.state === "angry") return self.name + " is furious!"; return self.dialogue[self.dialogueIndex % self.dialogue.length]; }; self.nextDialogue = function () { self.dialogueIndex++; }; self.update = function () { // Only move during battle phases if (typeof gameState !== "undefined" && (gameState === STATE.BATTLE_INTRO || gameState === STATE.BATTLE_MENU || gameState === STATE.BATTLE_ACT || gameState === STATE.BATTLE_ATTACK || gameState === STATE.BATTLE_DODGE)) { // Only move if this is the current monster and Asgore is visible if (typeof monster !== "undefined" && monster === self && self.visible) { // Initialize movement state if not present if (typeof self.moveDir === "undefined") self.moveDir = 1; if (typeof self.moveTimer === "undefined") self.moveTimer = 0; if (typeof self.movePause === "undefined") self.movePause = 0; if (typeof self.lastX === "undefined") self.lastX = self.x; // Only move if not paused if (self.movePause > 0) { self.movePause--; } else { // Move left/right var speed = 7; self.x += self.moveDir * speed; // Clamp to arena bounds (leave some margin) var minX = 400, maxX = 2048 - 400; if (self.x < minX) { self.x = minX; self.moveDir = 1; self.movePause = 30 + Math.floor(Math.random() * 30); // pause at edge } else if (self.x > maxX) { self.x = maxX; self.moveDir = -1; self.movePause = 30 + Math.floor(Math.random() * 30); } // Occasionally change direction in the middle self.moveTimer++; if (self.moveTimer > 60 + Math.random() * 60) { if (Math.random() < 0.4) { self.moveDir *= -1; self.movePause = 10 + Math.floor(Math.random() * 20); } self.moveTimer = 0; } } self.lastX = self.x; } } }; return self; }); // 2. Sans // Only one Sans should exist in the monster list // Boss: Papyrus var BossPapyrus = Container.expand(function () { var self = Container.call(this); var monster = self.attachAsset('boss_papyruspng', { anchorX: 0.5, anchorY: 0.5 }); self.hp = 22; self.maxHp = 22; self.name = "Papyrus"; self.state = "neutral"; self.dialogue = ["Papyrus bounds in! \"NYEH HEH HEH!\"", "Papyrus poses dramatically.", "Papyrus is thinking about spaghetti.", "Papyrus wants to be your friend."]; self.dialogueIndex = 0; self.getDialogue = function () { if (self.state === "spared") return self.name + " looks relieved."; if (self.state === "angry") return self.name + " is furious!"; return self.dialogue[self.dialogueIndex % self.dialogue.length]; }; self.nextDialogue = function () { self.dialogueIndex++; }; self.update = function () {}; return self; }); // Boss: Sans var BossSans = Container.expand(function () { var self = Container.call(this); var monster = self.attachAsset('boss_sanspng', { anchorX: 0.5, anchorY: 0.5 }); self.hp = 20; self.maxHp = 20; self.name = "Sans"; self.state = "neutral"; self.dialogue = ["Sans appears with a grin. \"hey kiddo.\"", "Sans shrugs. \"you look like you've seen a ghost.\"", "Sans winks. \"get dunked on? nah, just kidding.\"", "Sans tells a pun. \"what do you call a lazy skeleton? bone-idle.\""]; self.dialogueIndex = 0; self.getDialogue = function () { if (self.state === "spared") return self.name + " looks relieved."; if (self.state === "angry") return self.name + " is furious!"; return self.dialogue[self.dialogueIndex % self.dialogue.length]; }; self.nextDialogue = function () { self.dialogueIndex++; }; self.update = function () {}; return self; }); // Dialogue box var DialogueBox = Container.expand(function () { var self = Container.call(this); self.bg = self.attachAsset('dialogue_box', { anchorX: 0.5, anchorY: 0.5 }); self.txt = new Text2('', { size: 54, fill: "#fff" }); self.txt.anchor.set(0.5, 0.5); self.txt.x = 0; self.txt.y = 0; self.addChild(self.txt); self.setText = function (t) { self.txt.setText(t); }; return self; }); // Hero soul in battle (the heart you move to dodge attacks) var Heart = Container.expand(function () { var self = Container.call(this); var heart = self.attachAsset('heart', { anchorX: 0.5, anchorY: 0.5 }); self.radius = 30; self.speed = 18; self.update = function () { // Movement handled in game.move }; return self; }); // Button for menu var MenuButton = Container.expand(function () { var self = Container.call(this); self.bg = self.attachAsset('btn_bg', { anchorX: 0.5, anchorY: 0.5 }); self.hl = self.attachAsset('btn_hl', { anchorX: 0.5, anchorY: 0.5 }); self.hl.visible = false; self.txt = new Text2('', { size: 60, fill: "#fff" }); self.txt.anchor.set(0.5, 0.5); self.addChild(self.txt); self.setText = function (t) { self.txt.setText(t); }; self.setHighlight = function (v) { self.hl.visible = v; }; return self; }); // Monster class (for battle) var Monster = Container.expand(function () { var self = Container.call(this); // Attach a generic monster asset (non-boss) var monster = self.attachAsset('monster1', { anchorX: 0.5, anchorY: 0.5 }); self.hp = 12; self.maxHp = 12; self.name = "Monster"; self.state = "neutral"; // can be "neutral", "spared", "angry" self.dialogue = ["A monster appears.", "The monster seems nervous.", "The monster is watching you closely.", "The monster croaks softly."]; self.dialogueIndex = 0; self.getDialogue = function () { if (self.state === "spared") return self.name + " looks relieved."; if (self.state === "angry") return self.name + " is furious!"; return self.dialogue[self.dialogueIndex % self.dialogue.length]; }; self.nextDialogue = function () { self.dialogueIndex++; }; self.update = function () { // No movement for monster }; return self; }); /**** * Initialize Game ****/ var game = new LK.Game({ backgroundColor: 0x181824 }); /**** * Game Code ****/ // Add round joystick assets // Mercy yellow // Dialogue box // Button highlight // Button backgrounds // Heart (player soul in battle) // Attack indicator (red slash) // Enemy monster (first encounter, "Froggit" inspired, but original) // Main character (the "child") // --- Game State --- var STATE = { STORY: 0, BATTLE_INTRO: 1, BATTLE_MENU: 2, BATTLE_ACT: 3, BATTLE_ATTACK: 4, BATTLE_DODGE: 5, BATTLE_RESULT: 6, ENDING: 7, BOSS_SELECT: 8 }; var gameState = STATE.STORY; // --- Main variables --- var hero, heart, dialogueBox; var monsters = []; var currentMonsterIndex = 0; var monster = null; var menuButtons = []; var selectedMenu = 0; var actButtons = []; var selectedAct = 0; var mercyAvailable = false; var playerHP = 50; var playerMaxHP = 50; // --- Boss Select Menu --- var bossSelectButtons = []; var bossSelectTitle = null; function showBossSelectMenu() { // Hide all other menus hideAllMenus(); // Hide dialogue box if (dialogueBox) dialogueBox.visible = false; // Hide skip tutorial showSkipTutorial(false); // Hide joystick showJoystick(false); // Hide result text if (resultText) resultText.visible = false; // Hide monster if (monster) monster.visible = false; // Hide heart if (heart) heart.visible = false; // Hide monster HP bar monsterHpBarBg.visible = false; monsterHpBar.visible = false; monsterHpText.visible = false; // Remove old boss select buttons for (var i = 0; i < bossSelectButtons.length; i++) { if (bossSelectButtons[i] && typeof bossSelectButtons[i].destroy === "function") bossSelectButtons[i].destroy(); } bossSelectButtons = []; // Remove old title if (bossSelectTitle && typeof bossSelectTitle.destroy === "function") bossSelectTitle.destroy(); bossSelectTitle = new Text2("Select Boss", { size: 100, fill: "#fff", padding: 24, shadow: { color: 0x000000, blur: 16, distance: 0, angle: 0, alpha: 0.7 } }); bossSelectTitle.anchor.set(0.5, 0.5); bossSelectTitle.x = 2048 / 2; bossSelectTitle.y = 320; game.addChild(bossSelectTitle); // Show a button for each boss/monster var btnW = 420, btnH = 170, btnSpacing = 40; var startY = 500; for (var i = 0; i < monsters.length; i++) { var btn = new MenuButton(); btn.x = 2048 / 2; btn.y = startY + i * (btnH + btnSpacing); btn.bg.width = btnW; btn.bg.height = btnH; btn.hl.width = btnW + 16; btn.hl.height = btnH + 16; btn.bg.alpha = 0.93; btn.bg.tint = 0x222244; btn.hl.alpha = 0.22; if (btn.bg && typeof btn.bg.filters !== "undefined") { btn.bg.filters = [{ type: "shadow", color: 0x000000, blur: 16, distance: 0, angle: 0, alpha: 0.5 }]; } if (btn.txt && btn.txt.style) { btn.txt.style.size = 60; btn.txt.style.fill = "#fff"; } // Show HP for each boss var hpStr = typeof monsters[i].hp !== "undefined" && typeof monsters[i].maxHp !== "undefined" ? " (" + monsters[i].hp + "/" + monsters[i].maxHp + " HP)" : ""; btn.setText(monsters[i].name + hpStr); btn.visible = true; // Add tap highlight feedback (function (b, idx) { b.down = function () { b.setHighlight(true); }; b.up = function () { b.setHighlight(false); }; b.tap = function () { // Jump to this boss for (var j = 0; j < bossSelectButtons.length; j++) bossSelectButtons[j].visible = false; if (bossSelectTitle) bossSelectTitle.visible = false; currentMonsterIndex = idx; monster.visible = false; monster = monsters[currentMonsterIndex]; monster.visible = false; monster.actCount = 0; monster.mercyAvailable = false; game.addChild(monster); updateMonsterHpBar(); // Start battle with this boss gameState = STATE.BATTLE_INTRO; hero.visible = false; monster.visible = true; showDialogue(monster.getDialogue()); LK.setTimeout(function () { gameState = STATE.BATTLE_MENU; showBattleMenu(); }, 1200); }; })(btn, i); bossSelectButtons.push(btn); game.addChild(btn); } // Add a "Cancel" button to return to story/battle var cancelBtn = new MenuButton(); cancelBtn.x = 2048 / 2; cancelBtn.y = startY + monsters.length * (btnH + btnSpacing) + 60; cancelBtn.bg.width = btnW; cancelBtn.bg.height = btnH; cancelBtn.hl.width = btnW + 16; cancelBtn.hl.height = btnH + 16; cancelBtn.bg.alpha = 0.93; cancelBtn.bg.tint = 0x222244; cancelBtn.hl.alpha = 0.22; if (cancelBtn.bg && typeof cancelBtn.bg.filters !== "undefined") { cancelBtn.bg.filters = [{ type: "shadow", color: 0x000000, blur: 16, distance: 0, angle: 0, alpha: 0.5 }]; } if (cancelBtn.txt && cancelBtn.txt.style) { cancelBtn.txt.style.size = 60; cancelBtn.txt.style.fill = "#fff"; } cancelBtn.setText("Cancel"); cancelBtn.visible = true; cancelBtn.down = function () { cancelBtn.setHighlight(true); }; cancelBtn.up = function () { cancelBtn.setHighlight(false); }; cancelBtn.tap = function () { // Hide boss select menu, return to previous state for (var j = 0; j < bossSelectButtons.length; j++) bossSelectButtons[j].visible = false; if (bossSelectTitle) bossSelectTitle.visible = false; cancelBtn.visible = false; // Resume previous state if (gameState === STATE.BOSS_SELECT) { gameState = STATE.STORY; nextStory(); } }; bossSelectButtons.push(cancelBtn); game.addChild(cancelBtn); } // --- Boss Select Button (top right) --- var bossSelectBtn = new MenuButton(); bossSelectBtn.setText("Boss Select"); bossSelectBtn.x = 2048 - 350; bossSelectBtn.y = 360; bossSelectBtn.bg.width = 420; bossSelectBtn.bg.height = 150; bossSelectBtn.hl.width = 420 + 16; bossSelectBtn.hl.height = 150 + 16; bossSelectBtn.bg.alpha = 0.93; bossSelectBtn.bg.tint = 0x222244; bossSelectBtn.hl.alpha = 0.22; if (bossSelectBtn.bg && typeof bossSelectBtn.bg.filters !== "undefined") { bossSelectBtn.bg.filters = [{ type: "shadow", color: 0x000000, blur: 16, distance: 0, angle: 0, alpha: 0.5 }]; } if (bossSelectBtn.txt && bossSelectBtn.txt.style) { bossSelectBtn.txt.style.size = 60; bossSelectBtn.txt.style.fill = "#fff"; } bossSelectBtn.visible = true; bossSelectBtn.setHighlight(false); bossSelectBtn.down = function () { bossSelectBtn.setHighlight(true); }; bossSelectBtn.up = function () { bossSelectBtn.setHighlight(false); }; bossSelectBtn.tap = function () { gameState = STATE.BOSS_SELECT; showBossSelectMenu(); }; game.addChild(bossSelectBtn); // Add more items to the inventory! var playerInventory = [{ name: "Candy", heal: 10, qty: 1 }, { name: "Donut", heal: 15, qty: 1 }, { name: "Pie", heal: 20, qty: 1 }, { name: "Soda", heal: 8, qty: 2 }, { name: "Bandage", heal: 5, qty: 3 }, { name: "egegokalp", type: "special", effect: "sezaryen_yumurtasi", qty: 1 }]; var sezaryenYumurtasi = 0; // Track extra sezaryen yumurtası var selectedItem = 0; // Support up to 5 item buttons for more items var itemButtons = []; var mercyButton; var attackSlashes = []; var attackTimer = 0; var attackDuration = 90; var battleResultText = ''; var storyStep = 0; var storyTexts = ["You wake up in a dark, unfamiliar place.", "A soft croak echoes in the distance...", "A strange creature approaches!", "You survived the first battle. But the journey continues...", "A new monster blocks your path!", "You brace yourself for another fight.", // --- Asgore pre-battle sequence --- "You find Asgore watering the Golden Flowers in the Throne Room.", "He turns around and greets you warmly.", "Asgore realizes you are a human. He looks surprised, but not angry.", "\"Ah... It seems we finally meet. I am Asgore, king of the Underground.\"", "He gently leads you toward the barrier.", "\"If you have anything left to do, now is the time. I do not mind waiting.\"", "You sense his sadness. He admits, \"I am not ready either.\"", "He stands before the barrier, trident in hand.", "Asgore: \"It was nice knowing you.\"", "He draws his trident and, with a heavy heart, destroys the MERCY button.", "There is no turning back now. FIGHT is the only way forward."]; // Add more monsters for a longer game function createMonsters() { // 1. Sans (now first monster) var m1 = new BossSans(); m1.x = 2048 / 2; m1.y = 900; m1.mercyAvailable = false; // 2. Sans // Only one Sans should exist in the monster list var m2 = new BossSans(); m2.x = 2048 / 2; m2.y = 900; m2.mercyAvailable = false; // 3. Papyrus var m3 = new BossPapyrus(); m3.x = 2048 / 2; m3.y = 900; m3.mercyAvailable = false; // 4. Asgore var m4 = new BossAsgore(); m4.x = 2048 / 2; m4.y = 900; m4.mercyAvailable = false; // 5. Turtloid var m5 = new Monster(); m5.x = 2048 / 2; m5.y = 900; m5.hp = 30; m5.maxHp = 30; m5.name = "Turtloid"; m5.state = "neutral"; m5.dialogue = ["Turtloid withdraws into its shell.", "Turtloid peeks out cautiously.", "Turtloid spins slowly.", "Turtloid seems unhurried."]; m5.dialogueIndex = 0; m5.mercyAvailable = false; // 6. Mothmire var m6 = new Monster(); m6.x = 2048 / 2; m6.y = 900; m6.hp = 16; m6.maxHp = 16; m6.name = "Mothmire"; m6.state = "neutral"; m6.dialogue = ["Mothmire flutters its wings.", "Mothmire is drawn to the light.", "Mothmire hovers silently.", "Mothmire's dust sparkles."]; m6.dialogueIndex = 0; m6.mercyAvailable = false; // 7. Spindle var m7 = new Monster(); m7.x = 2048 / 2; m7.y = 900; m7.hp = 20; m7.maxHp = 20; m7.name = "Spindle"; m7.state = "neutral"; m7.dialogue = ["Spindle weaves a web.", "Spindle's eyes glint.", "Spindle scuttles around.", "Spindle is alert."]; m7.dialogueIndex = 0; m7.mercyAvailable = false; // 8. Barkbark var m8 = new Monster(); m8.x = 2048 / 2; m8.y = 900; m8.hp = 24; m8.maxHp = 24; m8.name = "Barkbark"; m8.state = "neutral"; m8.dialogue = ["Barkbark wags its tail.", "Barkbark barks excitedly.", "Barkbark circles you.", "Barkbark pants happily."]; m8.dialogueIndex = 0; m8.mercyAvailable = false; // 9. Glimmerbug var m9 = new Monster(); m9.x = 2048 / 2; m9.y = 900; m9.hp = 15; m9.maxHp = 15; m9.name = "Glimmerbug"; m9.state = "neutral"; m9.dialogue = ["Glimmerbug glows softly.", "Glimmerbug zips around.", "Glimmerbug leaves a trail of light.", "Glimmerbug hums a tune."]; m9.dialogueIndex = 0; m9.mercyAvailable = false; // 10. Thornet var m10 = new Monster(); m10.x = 2048 / 2; m10.y = 900; m10.hp = 28; m10.maxHp = 28; m10.name = "Thornet"; m10.state = "neutral"; m10.dialogue = ["Thornet buzzes menacingly.", "Thornet sharpens its stinger.", "Thornet hovers above.", "Thornet's wings vibrate."]; m10.dialogueIndex = 0; m10.mercyAvailable = false; // 11. Shadewisp var m11 = new Monster(); m11.x = 2048 / 2; m11.y = 900; m11.hp = 35; m11.maxHp = 35; m11.name = "Shadewisp"; m11.state = "neutral"; m11.dialogue = ["Shadewisp flickers in and out.", "Shadewisp's form is hard to see.", "Shadewisp whispers your name.", "Shadewisp chills the air."]; m11.dialogueIndex = 0; m11.mercyAvailable = false; // 12. Queen Lily var m12 = new Monster(); m12.x = 2048 / 2; m12.y = 900; m12.hp = 40; m12.maxHp = 40; m12.name = "Queen Lily"; m12.state = "neutral"; m12.dialogue = ["Queen Lily gazes at you regally.", "Queen Lily's petals shimmer.", "Queen Lily radiates calm.", "Queen Lily stands tall."]; m12.dialogueIndex = 0; m12.mercyAvailable = false; m1.actCount = 0; m1.mercyAvailable = false; m2.actCount = 0; m2.mercyAvailable = false; m3.actCount = 0; m3.mercyAvailable = false; m4.actCount = 0; m4.mercyAvailable = false; m5.actCount = 0; m5.mercyAvailable = false; m6.actCount = 0; m6.mercyAvailable = false; m7.actCount = 0; m7.mercyAvailable = false; m8.actCount = 0; m8.mercyAvailable = false; m9.actCount = 0; m9.mercyAvailable = false; m10.actCount = 0; m10.mercyAvailable = false; m11.actCount = 0; m11.mercyAvailable = false; m12.actCount = 0; m12.mercyAvailable = false; monsters = [m1, m3, m4, m5, m6, m7, m8, m9, m10, m11, m12]; // Only one Sans in the monster list } createMonsters(); // Ensure the first monster is Sans, not Froggle monster = monsters[0]; // --- Utility functions --- function showDialogue(text) { dialogueBox.setText(text); dialogueBox.visible = true; } function hideDialogue() { dialogueBox.visible = false; } function updateHPText() { hpText.setText("HP: " + playerHP + "/" + playerMaxHP); } function clamp(val, min, max) { return val < min ? min : val > max ? max : val; } // --- UI Elements --- var hpText = new Text2("HP: 50/50", { size: 64, fill: 0xFF6666, padding: 10, shadow: { color: 0x000000, blur: 8, distance: 0, angle: 0, alpha: 0.7 } }); hpText.anchor.set(0, 0); LK.gui.top.addChild(hpText); hpText.x = 120; hpText.y = 20; // Dialogue box dialogueBox = new DialogueBox(); dialogueBox.x = 2048 / 2; dialogueBox.y = 2732 - 220; if (dialogueBox.bg && typeof dialogueBox.bg.filters !== "undefined") { dialogueBox.bg.filters = [{ type: "shadow", color: 0x000000, blur: 24, distance: 0, angle: 0, alpha: 0.7 }]; dialogueBox.bg.alpha = 0.98; } if (dialogueBox.txt && dialogueBox.txt.style) { dialogueBox.txt.style.size = 60; dialogueBox.txt.style.fill = "#fff"; dialogueBox.txt.style.padding = 24; } game.addChild(dialogueBox); // --- Skip Tutorial Button --- var skipTutorialBtn = new MenuButton(); skipTutorialBtn.setText("Skip Tutorial"); skipTutorialBtn.x = 2048 - 350; skipTutorialBtn.y = 180; skipTutorialBtn.visible = false; skipTutorialBtn.setHighlight(false); game.addChild(skipTutorialBtn); function showSkipTutorial(show) { skipTutorialBtn.visible = !!show; } // --- Main character (not visible in battle) --- hero = LK.getAsset('hero', { anchorX: 0.5, anchorY: 0.5, x: 2048 / 2, y: 2732 / 2 + 400 }); game.addChild(hero); // --- Monster (battle only) --- // Start with Sans as the first monster monster = monsters[0]; monster.x = 2048 / 2; monster.y = 900; monster.visible = false; game.addChild(monster); // --- Monster Health Bar --- var monsterHpBarBg = LK.getAsset('dialogue_box', { anchorX: 0.5, anchorY: 0.5, width: 1100, // much bigger width height: 90, // much bigger height x: 2048 / 2, y: 220 //{27} // moved up }); monsterHpBarBg.tint = 0x222222; monsterHpBarBg.alpha = 0.7; monsterHpBarBg.visible = false; game.addChild(monsterHpBarBg); var monsterHpBar = LK.getAsset('dialogue_box', { anchorX: 0.5, anchorY: 0.5, width: 1050, // much bigger width height: 60, // much bigger height x: 2048 / 2, y: 220 //{2f} // moved up }); monsterHpBar.tint = 0x00e676; monsterHpBar.alpha = 0.95; monsterHpBar.visible = false; game.addChild(monsterHpBar); var monsterHpText = new Text2('', { size: 44, fill: "#fff", padding: 12, shadow: { color: 0x000000, blur: 8, distance: 0, angle: 0, alpha: 0.7 } }); monsterHpText.anchor.set(0.5, 0.5); monsterHpText.x = 2048 / 2; monsterHpText.y = 220; // moved up monsterHpText.visible = false; game.addChild(monsterHpText); function updateMonsterHpBar() { if (!monster || typeof monster.hp === "undefined" || typeof monster.maxHp === "undefined") { monsterHpBarBg.visible = false; monsterHpBar.visible = false; monsterHpText.visible = false; // Hide hearts if present if (window.sansHpHearts && window.sansHpHearts.length) { for (var i = 0; i < window.sansHpHearts.length; i++) { window.sansHpHearts[i].visible = false; } } return; } // Only show during battle if (gameState === STATE.BATTLE_INTRO || gameState === STATE.BATTLE_MENU || gameState === STATE.BATTLE_ACT || gameState === STATE.BATTLE_ATTACK || gameState === STATE.BATTLE_DODGE) { // Show hearts for every boss, not just Sans monsterHpBarBg.visible = false; monsterHpBar.visible = false; monsterHpText.visible = false; // Always show 20 hearts for every boss, not maxHp hearts var hp = Math.max(0, Math.min(monster.hp, 20)); var maxHearts = 20; var heartSize = 70; var spacing = 18; var totalWidth = maxHearts * heartSize + (maxHearts - 1) * spacing; var startX = 2048 / 2 - totalWidth / 2 + heartSize / 2; var y = 220; if (!window.sansHpHearts) window.sansHpHearts = []; // Create hearts if not enough for (var i = 0; i < maxHearts; i++) { if (!window.sansHpHearts[i]) { var h = LK.getAsset('heart', { anchorX: 0.5, anchorY: 0.5, width: heartSize, height: heartSize, x: startX + i * (heartSize + spacing), y: y }); h.visible = false; game.addChild(h); window.sansHpHearts[i] = h; } // Always update position in case of resize window.sansHpHearts[i].x = startX + i * (heartSize + spacing); window.sansHpHearts[i].y = y; window.sansHpHearts[i].width = heartSize; window.sansHpHearts[i].height = heartSize; window.sansHpHearts[i].visible = i < hp; window.sansHpHearts[i].alpha = i < hp ? 1 : 0.25; } // Hide extra hearts if more than 20 were created for (var i = maxHearts; window.sansHpHearts && i < window.sansHpHearts.length; i++) { window.sansHpHearts[i].visible = false; } } else { monsterHpBarBg.visible = false; monsterHpBar.visible = false; monsterHpText.visible = false; // Hide hearts if present if (window.sansHpHearts && window.sansHpHearts.length) { for (var i = 0; i < window.sansHpHearts.length; i++) { window.sansHpHearts[i].visible = false; } } } } // --- Heart (player soul in battle) --- heart = new Heart(); heart.x = 2048 / 2; heart.y = 1800; // Place heart in the center of the even larger arena at start of battle heart.visible = false; game.addChild(heart); // --- Menu buttons (Fight, Act, Item, Mercy) --- // Redesigned: Consistent size, spacing, padding, and shadow for a clean, readable UI var menuNames = ["Fight", "Act", "Item", "Mercy"]; var menuBtnWidth = 380; var menuBtnHeight = 170; var menuBtnSpacing = 60; var menuBtnY = 2732 - 340; var menuBtnStartX = (2048 - (menuNames.length * menuBtnWidth + (menuNames.length - 1) * menuBtnSpacing)) / 2 + menuBtnWidth / 2; for (var i = 0; i < 4; i++) { var btn = new MenuButton(); btn.x = menuBtnStartX + i * (menuBtnWidth + menuBtnSpacing); btn.y = menuBtnY; btn.bg.width = menuBtnWidth; btn.bg.height = menuBtnHeight; btn.hl.width = menuBtnWidth + 16; btn.hl.height = menuBtnHeight + 16; btn.bg.alpha = 0.93; btn.bg.tint = 0x222244; btn.hl.alpha = 0.22; if (btn.bg && typeof btn.bg.filters !== "undefined") { btn.bg.filters = [{ type: "shadow", color: 0x000000, blur: 16, distance: 0, angle: 0, alpha: 0.5 }]; } if (btn.txt && btn.txt.style) { btn.txt.style.size = 64; btn.txt.style.fill = "#fff"; } btn.visible = false; // Add simple tap feedback: highlight on press, unhighlight on release (function (b) { b.down = function () { b.setHighlight(true); }; b.up = function () { b.setHighlight(false); }; })(btn); btn.setText(menuNames[i]); menuButtons.push(btn); game.addChild(btn); } menuButtons[0].setHighlight(true); // --- Act buttons (contextual) --- // Redesigned: Consistent size, spacing, padding, and shadow for a clean, readable UI var actNames = ["Check", "Compliment", "Threaten", "Cesur"]; var actBtnWidth = 380; var actBtnHeight = 170; var actBtnSpacing = 60; var actBtnY = 2732 - 340; var actBtnStartX = (2048 - (actNames.length * actBtnWidth + (actNames.length - 1) * actBtnSpacing)) / 2 + actBtnWidth / 2; for (var i = 0; i < 4; i++) { var btn = new MenuButton(); btn.x = actBtnStartX + i * (actBtnWidth + actBtnSpacing); btn.y = actBtnY; btn.bg.width = actBtnWidth; btn.bg.height = actBtnHeight; btn.hl.width = actBtnWidth + 16; btn.hl.height = actBtnHeight + 16; btn.bg.alpha = 0.93; btn.bg.tint = 0x222244; btn.hl.alpha = 0.22; if (btn.bg && typeof btn.bg.filters !== "undefined") { btn.bg.filters = [{ type: "shadow", color: 0x000000, blur: 16, distance: 0, angle: 0, alpha: 0.5 }]; } if (btn.txt && btn.txt.style) { btn.txt.style.size = 64; btn.txt.style.fill = "#fff"; } btn.visible = false; // Add tap highlight feedback (function (b) { b.down = function () { b.setHighlight(true); }; b.up = function () { b.setHighlight(false); }; })(btn); btn.setText(actNames[i]); actButtons.push(btn); game.addChild(btn); } actButtons[0].setHighlight(true); // --- Item buttons (contextual) --- // Redesigned: Consistent size, spacing, padding, and shadow for a clean, readable UI // Support up to 5 item buttons for more items var itemBtnWidth = 380; var itemBtnHeight = 170; var itemBtnSpacingX = 60; var itemBtnSpacingY = 40; var itemBtnCols = 3; var itemBtnRows = 2; var itemBtnStartX = (2048 - (itemBtnCols * itemBtnWidth + (itemBtnCols - 1) * itemBtnSpacingX)) / 2 + itemBtnWidth / 2; var itemBtnStartY = 2732 - 340 - (itemBtnRows - 1) * (itemBtnHeight + itemBtnSpacingY) / 2; for (var i = 0; i < 5; i++) { var btn = new MenuButton(); var col = i % itemBtnCols; var row = Math.floor(i / itemBtnCols); btn.x = itemBtnStartX + col * (itemBtnWidth + itemBtnSpacingX); btn.y = itemBtnStartY + row * (itemBtnHeight + itemBtnSpacingY); btn.bg.width = itemBtnWidth; btn.bg.height = itemBtnHeight; btn.hl.width = itemBtnWidth + 16; btn.hl.height = itemBtnHeight + 16; btn.bg.alpha = 0.93; btn.bg.tint = 0x222244; btn.hl.alpha = 0.22; if (btn.bg && typeof btn.bg.filters !== "undefined") { btn.bg.filters = [{ type: "shadow", color: 0x000000, blur: 16, distance: 0, angle: 0, alpha: 0.5 }]; } if (btn.txt && btn.txt.style) { btn.txt.style.size = 60; btn.txt.style.fill = "#fff"; } btn.setText(''); btn.visible = false; // Add tap highlight feedback (function (b) { b.down = function () { b.setHighlight(true); }; b.up = function () { b.setHighlight(false); }; })(btn); itemButtons.push(btn); game.addChild(btn); } itemButtons[0].setHighlight(true); // --- Mercy button (yellow if available) --- mercyButton = menuButtons[3]; // --- Battle result text --- var resultText = new Text2('', { size: 90, fill: "#fff", padding: 24, shadow: { color: 0x000000, blur: 16, distance: 0, angle: 0, alpha: 0.7 } }); resultText.anchor.set(0.5, 0.5); resultText.x = 2048 / 2; resultText.y = 2732 / 2; resultText.visible = false; game.addChild(resultText); // --- Story flow --- function nextStory() { if (storyStep < storyTexts.length) { showDialogue(storyTexts[storyStep]); storyStep++; // Show skip tutorial button only during story phase, before battle starts if (gameState === STATE.STORY && storyStep < storyTexts.length) { showSkipTutorial(true); } else { showSkipTutorial(false); } // If we just finished the first story arc, and there are more monsters, prep next monster if (storyStep === 3 && currentMonsterIndex === 0) { // After first monster, show next monster LK.setTimeout(function () { currentMonsterIndex = 1; monster.visible = false; monster = monsters[currentMonsterIndex]; monster.visible = false; monster.actCount = 0; monster.mercyAvailable = false; game.addChild(monster); updateMonsterHpBar(); }, 1200); } if (storyStep === 5 && currentMonsterIndex === 1) { LK.setTimeout(function () { currentMonsterIndex = 2; monster.visible = false; monster = monsters[currentMonsterIndex]; monster.visible = false; monster.actCount = 0; monster.mercyAvailable = false; game.addChild(monster); }, 1200); } // --- Asgore pre-battle sequence: destroy MERCY button --- if (storyStep === 17 && currentMonsterIndex === 2 && monster && monster.name === "Asgore") { // Show the MERCY button being destroyed LK.setTimeout(function () { // Visually "break" the mercy button: hide yellow asset, show a crack or just hide if (mercyButton.mercyAsset) mercyButton.mercyAsset.visible = false; mercyButton.bg.visible = false; mercyButton.setText("MERCY"); // Defensive: Only set fill and alpha if style exists and property is settable if (mercyButton && mercyButton.txt && mercyButton.txt.style) { try { if (typeof mercyButton.txt.style.fill !== "undefined" || typeof mercyButton.txt.style.fill === "string") { mercyButton.txt.style.fill = "#ff2222"; //{8e} // RED after destroyed } } catch (e) {} try { if (typeof mercyButton.txt.style.alpha !== "undefined" || typeof mercyButton.txt.style.alpha === "number") { mercyButton.txt.style.alpha = 0.5; } } catch (e) {} } else if (mercyButton && mercyButton.txt && typeof mercyButton.txt.setStyle === "function") { // Fallback: use setStyle if available try { mercyButton.txt.setStyle({ fill: 0xFF2222, //{8q} // RED after destroyed alpha: 0.5 }); } catch (e) {} } mercyButton.setHighlight(false); mercyButton.disabled = true; mercyButton.destroyedByAsgore = true; // <--- Mark as destroyed for all future menus // Add a visual shake effect to the button text for drama if (typeof tween === "function" && mercyButton.txt) { try { var origX = mercyButton.txt.x; tween(mercyButton.txt).to({ x: origX + 30 }, 80).yoyo(true).repeat(3).start(); } catch (e) {} } // Show a dialogue for dramatic effect dialogueBox.setText("Asgore destroyed the MERCY button.\nThere is no turning back now."); }, 800); } } else { showSkipTutorial(false); // Start battle with current monster gameState = STATE.BATTLE_INTRO; hero.visible = false; monster.visible = true; showDialogue(monster.getDialogue()); LK.setTimeout(function () { gameState = STATE.BATTLE_MENU; showBattleMenu(); }, 1200); } } nextStory(); // --- Battle menu logic --- function showBattleMenu() { for (var i = 0; i < menuButtons.length; i++) { menuButtons[i].visible = true; menuButtons[i].setHighlight(i === selectedMenu); } for (var i = 0; i < actButtons.length; i++) actButtons[i].visible = false; for (var i = 0; i < itemButtons.length; i++) itemButtons[i].visible = false; dialogueBox.visible = true; dialogueBox.setText(monster.name + ": " + monster.getDialogue()); // If Asgore, disable MERCY button if (monster && monster.name === "Asgore" || mercyButton && mercyButton.destroyedByAsgore) { mercyButton.bg.visible = false; if (mercyButton.mercyAsset) mercyButton.mercyAsset.visible = false; mercyButton.setText("MERCY"); // Defensive: Only set fill and alpha if style exists and property is settable if (mercyButton && mercyButton.txt && mercyButton.txt.style) { try { if (typeof mercyButton.txt.style.fill !== "undefined" || typeof mercyButton.txt.style.fill === "string") { mercyButton.txt.style.fill = "#ff2222"; //{92} // RED after destroyed } } catch (e) {} try { if (typeof mercyButton.txt.style.alpha !== "undefined" || typeof mercyButton.txt.style.alpha === "number") { mercyButton.txt.style.alpha = 0.5; } } catch (e) {} } else if (mercyButton && mercyButton.txt && typeof mercyButton.txt.setStyle === "function") { // Fallback: use setStyle if available try { mercyButton.txt.setStyle({ fill: 0xFF2222, //{9e} // RED after destroyed alpha: 0.5 }); } catch (e) {} } mercyButton.setHighlight(false); mercyButton.disabled = true; } else { var mercyState = monster.mercyAvailable === true; mercyButton.bg.visible = !mercyState; if (mercyState) { mercyButton.bg.visible = false; if (!mercyButton.mercyAsset) { mercyButton.mercyAsset = mercyButton.attachAsset('mercy_yellow', { anchorX: 0.5, anchorY: 0.5 }); } mercyButton.mercyAsset.visible = true; } else if (mercyButton.mercyAsset) { mercyButton.mercyAsset.visible = false; } } } // --- Act menu logic --- function showActMenu() { for (var i = 0; i < menuButtons.length; i++) menuButtons[i].visible = false; for (var i = 0; i < actButtons.length; i++) { actButtons[i].visible = true; actButtons[i].setHighlight(i === selectedAct); } for (var i = 0; i < itemButtons.length; i++) itemButtons[i].visible = false; dialogueBox.visible = true; dialogueBox.setText("What will you do?"); } // --- Item menu logic --- function showItemMenu() { for (var i = 0; i < menuButtons.length; i++) menuButtons[i].visible = false; for (var i = 0; i < actButtons.length; i++) actButtons[i].visible = false; // Always show egegokalp next to Bandage in the item menu // Find Bandage and egegokalp indices in inventory var bandageIdx = -1, egegokalpIdx = -1; for (var i = 0; i < playerInventory.length; i++) { if (playerInventory[i].name === "Bandage") bandageIdx = i; if (playerInventory[i].name === "egegokalp") egegokalpIdx = i; } // Build a display order: all items up to Bandage, then Bandage, then egegokalp (if present), then the rest var displayOrder = []; for (var i = 0; i < playerInventory.length; i++) { if (i === bandageIdx) { displayOrder.push(i); if (egegokalpIdx !== -1) displayOrder.push(egegokalpIdx); } else if (i !== egegokalpIdx) { displayOrder.push(i); } } for (var i = 0; i < itemButtons.length; i++) { var invIdx = displayOrder[i]; var item = playerInventory[invIdx]; if (item && item.qty > 0) { itemButtons[i].setText(item.name + " (" + item.qty + ")"); itemButtons[i].visible = true; itemButtons[i].setHighlight(invIdx === selectedItem); } else { itemButtons[i].visible = false; } } // Add a "Leave Item Box" button below the item buttons if (!window.leaveItemBoxBtn) { window.leaveItemBoxBtn = new MenuButton(); window.leaveItemBoxBtn.setText("Leave Item Box"); window.leaveItemBoxBtn.x = 2048 / 2; window.leaveItemBoxBtn.y = 2732 - 120; window.leaveItemBoxBtn.bg.width = 420; window.leaveItemBoxBtn.bg.height = 150; window.leaveItemBoxBtn.hl.width = 420 + 16; window.leaveItemBoxBtn.hl.height = 150 + 16; window.leaveItemBoxBtn.bg.alpha = 0.93; window.leaveItemBoxBtn.bg.tint = 0x222244; window.leaveItemBoxBtn.hl.alpha = 0.22; if (window.leaveItemBoxBtn.bg && typeof window.leaveItemBoxBtn.bg.filters !== "undefined") { window.leaveItemBoxBtn.bg.filters = [{ type: "shadow", color: 0x000000, blur: 16, distance: 0, angle: 0, alpha: 0.5 }]; } if (window.leaveItemBoxBtn.txt && window.leaveItemBoxBtn.txt.style) { window.leaveItemBoxBtn.txt.style.size = 60; window.leaveItemBoxBtn.txt.style.fill = "#fff"; } window.leaveItemBoxBtn.visible = false; window.leaveItemBoxBtn.setHighlight(false); // Add tap highlight feedback window.leaveItemBoxBtn.down = function () { window.leaveItemBoxBtn.setHighlight(true); }; window.leaveItemBoxBtn.up = function () { window.leaveItemBoxBtn.setHighlight(false); }; game.addChild(window.leaveItemBoxBtn); } window.leaveItemBoxBtn.visible = true; window.leaveItemBoxBtn.setHighlight(false); dialogueBox.visible = true; dialogueBox.setText("Use which item?"); } // --- Hide all menus --- function hideAllMenus() { for (var i = 0; i < menuButtons.length; i++) { menuButtons[i].visible = false; } for (var i = 0; i < actButtons.length; i++) actButtons[i].visible = false; for (var i = 0; i < itemButtons.length; i++) itemButtons[i].visible = false; if (window.leaveItemBoxBtn) window.leaveItemBoxBtn.visible = false; } // --- Handle menu selection (touch) --- function handleMenuTouch(x, y) { if (gameState === STATE.BATTLE_MENU) { for (var i = 0; i < menuButtons.length; i++) { var btn = menuButtons[i]; if (btn.visible && x > btn.x - 200 && x < btn.x + 200 && y > btn.y - 60 && y < btn.y + 60) { selectedMenu = i; for (var j = 0; j < menuButtons.length; j++) menuButtons[j].setHighlight(j === i); selectMenuOption(i); return; } } } else if (gameState === STATE.BATTLE_ACT) { for (var i = 0; i < actButtons.length; i++) { var btn = actButtons[i]; if (btn.visible && x > btn.x - 200 && x < btn.x + 200 && y > btn.y - 60 && y < btn.y + 60) { selectedAct = i; for (var j = 0; j < actButtons.length; j++) actButtons[j].setHighlight(j === i); selectActOption(i); return; } } } else if (gameState === STATE.BATTLE_ATTACK) { // No touch input } else if (gameState === STATE.BATTLE_DODGE) { // Heart movement handled in move } else if (gameState === STATE.BATTLE_RESULT) { // Tap to continue resultText.visible = false; if (monster.hp <= 0 || monster.state === "spared") { // If there are more monsters, continue to next battle if (currentMonsterIndex < monsters.length - 1) { currentMonsterIndex++; monster.visible = false; monster = monsters[currentMonsterIndex]; monster.visible = false; monster.actCount = 0; monster.mercyAvailable = false; game.addChild(monster); updateMonsterHpBar(); // Show next story segment before next battle storyStep++; nextStory(); } else { gameState = STATE.ENDING; showEnding(); } } else { gameState = STATE.BATTLE_MENU; showBattleMenu(); } } else if (gameState === STATE.BATTLE_INTRO) { // skip intro } else if (gameState === STATE.STORY) { nextStory(); } else if (gameState === STATE.BATTLE_ITEM) { // Check leave item box button first if (window.leaveItemBoxBtn && window.leaveItemBoxBtn.visible && x > window.leaveItemBoxBtn.x - 200 && x < window.leaveItemBoxBtn.x + 200 && y > window.leaveItemBoxBtn.y - 60 && y < window.leaveItemBoxBtn.y + 60) { // Hide item menu, return to battle menu window.leaveItemBoxBtn.visible = false; hideAllMenus(); gameState = STATE.BATTLE_MENU; showBattleMenu(); return; } for (var i = 0; i < itemButtons.length; i++) { var btn = itemButtons[i]; if (btn.visible && x > btn.x - 200 && x < btn.x + 200 && y > btn.y - 60 && y < btn.y + 60) { selectedItem = i; for (var j = 0; j < itemButtons.length; j++) itemButtons[j].setHighlight(j === i); selectItemOption(i); return; } } // If tap is not on any button, do nothing } } // --- Menu option selection --- function selectMenuOption(idx) { if (idx === 0) { // Fight hideAllMenus(); dialogueBox.setText("You attack!"); gameState = STATE.BATTLE_ATTACK; LK.setTimeout(function () { // Special: Asgore takes massive damage at low HP, but survives with 1 HP and kneels if (monster && monster.name === "Asgore") { if (monster.hp > 8) { monster.hp -= 5; if (monster.hp < 8) monster.hp = 8; updateMonsterHpBar(); dialogueBox.setText("You hit Asgore for 5 damage!"); monster.state = "angry"; monster.nextDialogue(); LK.setTimeout(function () { startEnemyAttack(); }, 900); } else if (monster.hp > 1) { // Final blow: deal huge damage, but leave at 1 HP monster.hp = 1; updateMonsterHpBar(); dialogueBox.setText("You strike with all your might!\nAsgore kneels, defeated."); monster.state = "neutral"; monster.nextDialogue(); // Show final choice after a pause LK.setTimeout(function () { // Show FIGHT and a partially repaired MERCY button gameState = STATE.BATTLE_MENU; // Re-enable MERCY for final choice mercyButton.bg.visible = false; if (!mercyButton.mercyAsset) { mercyButton.mercyAsset = mercyButton.attachAsset('mercy_yellow', { anchorX: 0.5, anchorY: 0.5 }); } mercyButton.mercyAsset.visible = true; mercyButton.setText("MERCY"); mercyButton.txt.style.fill = "#ffff00"; mercyButton.txt.style.alpha = 1; mercyButton.setHighlight(false); mercyButton.disabled = false; monster.mercyAvailable = true; dialogueBox.setText("Asgore: \"Take my SOUL and leave this place...\"\nWill you FIGHT or show MERCY?"); showBattleMenu(); }, 1200); } else { // Already at 1 HP, just show final choice updateMonsterHpBar(); dialogueBox.setText("Asgore: \"Take my SOUL and leave this place...\"\nWill you FIGHT or show MERCY?"); gameState = STATE.BATTLE_MENU; mercyButton.bg.visible = false; if (!mercyButton.mercyAsset) { mercyButton.mercyAsset = mercyButton.attachAsset('mercy_yellow', { anchorX: 0.5, anchorY: 0.5 }); } mercyButton.mercyAsset.visible = true; mercyButton.setText("MERCY"); mercyButton.txt.style.fill = "#ffff00"; mercyButton.txt.style.alpha = 1; mercyButton.setHighlight(false); mercyButton.disabled = false; monster.mercyAvailable = true; showBattleMenu(); } return; } // Simple attack: always hit for 5 monster.hp -= 5; if (monster.hp < 0) monster.hp = 0; updateMonsterHpBar(); dialogueBox.setText("You hit " + monster.name + " for 5 damage!"); monster.state = "angry"; monster.nextDialogue(); LK.setTimeout(function () { if (monster.hp <= 0) { battleResultText = "You defeated " + monster.name + "!"; resultText.setText(battleResultText); resultText.visible = true; gameState = STATE.BATTLE_RESULT; } else { startEnemyAttack(); } }, 900); }, 600); } else if (idx === 1) { // Act hideAllMenus(); gameState = STATE.BATTLE_ACT; showActMenu(); } else if (idx === 2) { // Item hideAllMenus(); gameState = STATE.BATTLE_ITEM; showItemMenu(); } else if (idx === 3) { // Mercy if (monster && monster.name === "Asgore" || mercyButton && mercyButton.destroyedByAsgore) { // Show destroyed message dialogueBox.setText("The MERCY button is destroyed.\nYou have no choice but to FIGHT."); LK.setTimeout(function () { showBattleMenu(); }, 1200); return; } if (monster.mercyAvailable === true) { hideAllMenus(); // 1 in 3 chance to show a funny dialogue var funnyMercyLines = ["You spare " + monster.name + ".\n" + monster.name + " gives you a confused thumbs up.", "You spare " + monster.name + ".\n" + monster.name + " wonders if this is a dating sim.", "You spare " + monster.name + ".\n" + monster.name + " is now legally your roommate.", "You spare " + monster.name + ".\n" + monster.name + " croaks in relief... or is it laughter?", "You spare " + monster.name + ".\n" + monster.name + " offers you a coupon for free flies.", "You spare " + monster.name + ".\n" + monster.name + " does a little dance.", "You spare " + monster.name + ".\n" + monster.name + " is writing about this on its blog.", "You spare " + monster.name + ".\n" + monster.name + " is now following you on RibbitBook.", "You spare " + monster.name + ".\n" + monster.name + " will remember this.", "You spare " + monster.name + ".\n" + monster.name + " is already planning your birthday party."]; var showFunny = Math.random() < 0.33; if (showFunny) { var idx = Math.floor(Math.random() * funnyMercyLines.length); dialogueBox.setText(funnyMercyLines[idx]); } else { dialogueBox.setText("You spare " + monster.name + "."); } monster.state = "spared"; LK.setTimeout(function () { battleResultText = "You showed mercy to " + monster.name + "!"; resultText.setText(battleResultText); resultText.visible = true; gameState = STATE.BATTLE_RESULT; }, 900); } else { dialogueBox.setText("You tried to spare, but " + monster.name + " isn't ready."); LK.setTimeout(function () { showBattleMenu(); }, 900); } } } // --- Act option selection --- function selectActOption(idx) { hideAllMenus(); // Always restore menu input after Act phase, regardless of which Act was chosen function restoreMenu() { gameState = STATE.BATTLE_MENU; showBattleMenu(); for (var i = 0; i < menuButtons.length; i++) { menuButtons[i].visible = true; } } if (idx === 0) { // Check dialogueBox.setText(monster.name + " - ATK 4 DEF 2\nA nervous little monster."); LK.setTimeout(restoreMenu, 1200); } else if (idx === 1) { // Compliment dialogueBox.setText("You compliment " + monster.name + ".\n" + monster.name + " blushes."); monster.state = "neutral"; // --- Mercy Progression: require 5-6 successful Acts before mercy is available --- if (typeof monster.actCount === "undefined") monster.actCount = 0; monster.actCount++; if (monster.actCount >= 5 + Math.floor(Math.random() * 2)) { monster.mercyAvailable = true; } else { monster.mercyAvailable = false; } LK.setTimeout(restoreMenu, 1200); } else if (idx === 2) { // Threaten dialogueBox.setText("You threaten " + monster.name + ".\n" + monster.name + " looks scared."); monster.state = "angry"; if (typeof monster.actCount === "undefined") monster.actCount = 0; monster.actCount++; if (monster.actCount >= 5 + Math.floor(Math.random() * 2)) { monster.mercyAvailable = true; } else { monster.mercyAvailable = false; } LK.setTimeout(restoreMenu, 1200); } else if (idx === 3) { // Cesur dialogueBox.setText("Sezaryen yumurtaları"); monster.state = "neutral"; if (typeof monster.actCount === "undefined") monster.actCount = 0; monster.actCount++; if (monster.actCount >= 5 + Math.floor(Math.random() * 2)) { monster.mercyAvailable = true; } else { monster.mercyAvailable = false; } LK.setTimeout(restoreMenu, 1200); } } // --- Item option selection --- function selectItemOption(idx) { // Use the same displayOrder as in showItemMenu to map button index to inventory index var bandageIdx = -1, egegokalpIdx = -1; for (var i = 0; i < playerInventory.length; i++) { if (playerInventory[i].name === "Bandage") bandageIdx = i; if (playerInventory[i].name === "egegokalp") egegokalpIdx = i; } var displayOrder = []; for (var i = 0; i < playerInventory.length; i++) { if (i === bandageIdx) { displayOrder.push(i); if (egegokalpIdx !== -1) displayOrder.push(egegokalpIdx); } else if (i !== egegokalpIdx) { displayOrder.push(i); } } var invIdx = displayOrder[idx]; var item = playerInventory[invIdx]; if (item && item.qty > 0) { if (item.name === "egegokalp") { sezaryenYumurtasi += 10; item.qty--; dialogueBox.setText("You used egegokalp!\nKazandın: 10 sezaryen yumurtası!\nToplam: " + sezaryenYumurtasi); } else { playerHP += item.heal; if (playerHP > playerMaxHP) playerHP = playerMaxHP; item.qty--; updateHPText(); dialogueBox.setText("You used " + item.name + ".\nRecovered " + item.heal + " HP!"); } LK.setTimeout(function () { // Always restore menu input after Item phase, just like Act gameState = STATE.BATTLE_MENU; showBattleMenu(); for (var i = 0; i < menuButtons.length; i++) { menuButtons[i].visible = true; } }, 1200); } } // --- Enemy attack phase --- function startEnemyAttack() { hideAllMenus(); // Show unique, attentive attack dialogue for each monster var attackDialogue = ""; switch (monster.name) { case "Sans": attackDialogue = "Sans gives you a lazy grin. \"let's see if you can dodge this, kid.\""; break; case "Papyrus": attackDialogue = "Papyrus: \"NYEH HEH HEH! Behold, my special attack!\""; break; case "Asgore": attackDialogue = "Asgore raises his trident. Flames swirl around you!"; break; case "Croaknight": attackDialogue = "Croaknight channels Undyne's spirit! Blue and yellow spears fly from all sides..."; break; case "Turtloid": attackDialogue = "Turtloid spins in its shell, barreling toward you with surprising speed!"; break; case "Mothmire": attackDialogue = "Mothmire flutters above, scattering a cloud of dazzling, stinging dust!"; break; case "Spindle": attackDialogue = "Spindle weaves a sticky web and flings it at you!"; break; case "Barkbark": attackDialogue = "Barkbark barks wildly and dashes in circles, trying to trip you!"; break; case "Glimmerbug": attackDialogue = "Glimmerbug zips around, leaving a blinding trail of sparkling light!"; break; case "Thornet": attackDialogue = "Thornet buzzes furiously, diving at you with its venomous stinger!"; break; case "Shadewisp": attackDialogue = "Shadewisp flickers and vanishes, then lashes out from the shadows!"; break; case "Queen Lily": attackDialogue = "Queen Lily summons a swirling storm of razor-sharp petals your way!"; break; default: attackDialogue = monster.name + " attacks fiercely!"; break; } dialogueBox.setText(attackDialogue); heart.visible = true; heart.x = 2048 / 2; heart.y = 1800; attackSlashes = []; attackTimer = 0; // Unique special attacks for each boss if (monster.name === "Sans") { // SANS: Fast bone waves, gaster blasters, gravity jumps attackDuration = 420; // Clean up helpers if (typeof sansBones !== "undefined" && sansBones && sansBones.length) { for (var i = 0; i < sansBones.length; i++) { if (sansBones[i] && typeof sansBones[i].destroy === "function") sansBones[i].destroy(); } } var sansBones = []; if (typeof gasterBlasters !== "undefined" && gasterBlasters && gasterBlasters.length) { for (var i = 0; i < gasterBlasters.length; i++) { if (gasterBlasters[i] && typeof gasterBlasters[i].destroy === "function") gasterBlasters[i].destroy(); } } var gasterBlasters = []; if (typeof gasterBlasterBeams !== "undefined" && gasterBlasterBeams && gasterBlasterBeams.length) { for (var i = 0; i < gasterBlasterBeams.length; i++) { if (gasterBlasterBeams[i] && typeof gasterBlasterBeams[i].destroy === "function") gasterBlasterBeams[i].destroy(); } } var gasterBlasterBeams = []; // 1. Fast bone waves from left and right for (var w = 0; w < 3; w++) { LK.setTimeout(function (waveIdx) { for (var i = 0; i < 6; i++) { var bone = new AttackSlash(); bone.y = 1200 + i * 180; bone.x = waveIdx % 2 === 0 ? 200 : 2048 - 200; bone.direction = bone.x < 1024 ? 1 : -1; bone.speed = 32 + Math.random() * 6; bone.update = function () { this.x += this.speed * this.direction; if (this.x < 0 || this.x > 2048) this.active = false; }; attackSlashes.push(bone); game.addChild(bone); sansBones.push(bone); } }, w * 220, w); } // 2. Gaster Blasters: fire from top/bottom, then diagonals var spawnGasterBlaster = function spawnGasterBlaster(x, y, angle, beamLength, beamWidth, delay, duration) { var blaster = LK.getAsset('blaster', { anchorX: 0.5, anchorY: 0.5, width: 120, height: 120, x: x, y: y }); blaster.tint = 0x00ffff; blaster.alpha = 0.95; game.addChild(blaster); gasterBlasters.push(blaster); var beam = LK.getAsset('dialogue_box', { anchorX: 0.0, anchorY: 0.5, width: beamLength, height: beamWidth, x: x, y: y }); beam.tint = 0x00ffff; beam.alpha = 0.0; beam.rotation = angle; game.addChild(beam); gasterBlasterBeams.push(beam); LK.setTimeout(function () { beam.alpha = 0.95; LK.setTimeout(function () { beam.alpha = 0.0; if (typeof beam.destroy === "function") beam.destroy(); }, duration); }, delay); LK.setTimeout(function () { if (typeof blaster.destroy === "function") blaster.destroy(); }, delay + duration); }; LK.setTimeout(function () { // Top and bottom spawnGasterBlaster(400, 1100, Math.PI / 2, 900, 80, 100, 400); spawnGasterBlaster(1648, 2550, -Math.PI / 2, 900, 80, 100, 400); }, 700); LK.setTimeout(function () { // Diagonals spawnGasterBlaster(200, 1200, Math.PI / 4, 1200, 80, 0, 400); spawnGasterBlaster(2048 - 200, 1200, 3 * Math.PI / 4, 1200, 80, 0, 400); spawnGasterBlaster(200, 2550, -Math.PI / 4, 1200, 80, 0, 400); spawnGasterBlaster(2048 - 200, 2550, -(3 * Math.PI) / 4, 1200, 80, 0, 400); }, 1200); // 3. Gravity jump: heart is pulled up, then down LK.setTimeout(function () { if (typeof heart !== "undefined" && heart !== null && typeof tween === "function") { try { tween(heart).to({ y: 1200 }, 300).yoyo(true).repeat(1).start(); } catch (e) { heart.y = 1200; } } }, 1800); } else if (monster.name === "Papyrus") { // PAPYRUS: Slow warning bones, blue attacks attackDuration = 320; // 1. Warning lines (visual only) for (var i = 0; i < 2; i++) { LK.setTimeout(function (j) { var warn = LK.getAsset('dialogue_box', { anchorX: 0.5, anchorY: 0.5, width: 2048, height: 30, x: 2048 / 2, y: 1200 + j * 800 }); warn.tint = 0x00aaff; warn.alpha = 0.5; game.addChild(warn); LK.setTimeout(function () { if (typeof warn.destroy === "function") warn.destroy(); }, 400); }, i * 400, i); } // 2. Slow bones from top and bottom for (var w = 0; w < 2; w++) { LK.setTimeout(function (waveIdx) { for (var i = 0; i < 4; i++) { var bone = new AttackSlash(); bone.x = 400 + i * 400; bone.y = waveIdx === 0 ? 1050 : 2550; bone.direction = waveIdx === 0 ? 1 : -1; bone.speed = 12 + Math.random() * 4; bone.update = function () { this.y += this.speed * this.direction; if (this.y < 1000 || this.y > 2600) this.active = false; }; attackSlashes.push(bone); game.addChild(bone); } }, w * 300, w); } // 3. Blue bone (hurts only if moving) LK.setTimeout(function () { var blueBone = new AttackSlash(); blueBone.x = 2048 - 200; blueBone.y = 1800; blueBone.direction = -1; blueBone.speed = 10; blueBone.spearType = "blue"; blueBone.update = function () { this.x += this.speed * this.direction; if (this.x < 200) this.active = false; }; attackSlashes.push(blueBone); game.addChild(blueBone); }, 1200); // 4. Final warning line and bone LK.setTimeout(function () { var warn = LK.getAsset('dialogue_box', { anchorX: 0.5, anchorY: 0.5, width: 2048, height: 30, x: 2048 / 2, y: 1800 }); warn.tint = 0x00aaff; warn.alpha = 0.5; game.addChild(warn); LK.setTimeout(function () { if (typeof warn.destroy === "function") warn.destroy(); }, 400); var bone = new AttackSlash(); bone.x = 200; bone.y = 1800; bone.direction = 1; bone.speed = 18; bone.update = function () { this.x += this.speed * this.direction; if (this.x > 2048 - 200) this.active = false; }; attackSlashes.push(bone); game.addChild(bone); }, 1800); } else if (monster.name === "Asgore") { // ASGORE: Fire trident waves, symmetrical blasts attackDuration = 220; // 1. Five tridents fall from above, fast, less spacing for (var i = 0; i < 5; i++) { LK.setTimeout(function (j) { var trident = new AttackSlash(); trident.x = 600 + j * 220; trident.y = 1050; trident.direction = 1; trident.speed = 28 + Math.random() * 8; trident.update = function () { this.y += this.speed * this.direction; if (this.y > 2550) this.active = false; }; attackSlashes.push(trident); game.addChild(trident); }, i * 100, i); } // 2. Symmetrical cross slashes (left/right, same y, repeated) for (var k = 0; k < 3; k++) { (function (crossIdx) { LK.setTimeout(function () { var yCross = 1300 + crossIdx * 350 + Math.random() * 100; var left = new AttackSlash(); left.x = 200; left.y = yCross; left.direction = 1; left.speed = 34; left.update = function () { this.x += this.speed * this.direction; if (this.x > 2048) this.active = false; }; attackSlashes.push(left); game.addChild(left); var right = new AttackSlash(); right.x = 2048 - 200; right.y = yCross; right.direction = -1; right.speed = 34; right.update = function () { this.x += this.speed * this.direction; if (this.x < 0) this.active = false; }; attackSlashes.push(right); game.addChild(right); }, 600 + crossIdx * 180); })(k); } // 3. Final flame wave (centered, fast) LK.setTimeout(function () { for (var i = 0; i < 3; i++) { var flame = new AttackSlash(); flame.x = 900 + i * 120; flame.y = 1050; flame.direction = 1; flame.speed = 36 + Math.random() * 6; flame.update = function () { this.y += this.speed * this.direction; if (this.y > 2550) this.active = false; }; attackSlashes.push(flame); game.addChild(flame); } }, 1200); } else { // Default: fallback to generic pattern attackDuration = 90 + Math.floor(Math.random() * 30); } gameState = STATE.BATTLE_DODGE; showJoystick(true); } // --- Endings --- function showEnding() { hideAllMenus(); heart.visible = false; monster.visible = false; dialogueBox.visible = false; monsterHpBarBg.visible = false; monsterHpBar.visible = false; monsterHpText.visible = false; if (monster && monster.name === "Asgore") { if (monster.state === "spared") { resultText.setText("You spared Asgore.\nHe smiles, tears in his eyes.\nA gentle path lies ahead."); } else if (monster.hp <= 0) { resultText.setText("You defeated Asgore.\nHe kneels, defeated.\nBut at what cost?"); } else { resultText.setText("The story continues..."); } } else { if (monster.state === "spared") { resultText.setText("You made a friend: " + monster.name + ".\nA gentle path lies ahead."); } else if (monster.hp <= 0) { resultText.setText("You defeated " + monster.name + ".\nBut at what cost?"); } else { resultText.setText("The story continues..."); } } resultText.visible = true; LK.setTimeout(function () { LK.showYouWin(); }, 1800); } // --- Input handling --- // Virtual joystick for heart movement (touch-optimized) // Ava: This joystick appears during enemy attack phase and is fully touch/mouse compatible. var joystick = { active: false, baseX: 0, baseY: 0, stickX: 0, stickY: 0, radius: 120, stickRadius: 60, visible: false, baseAsset: null, stickAsset: null }; // Create joystick assets (will be positioned below the heart dynamically) joystick.baseAsset = LK.getAsset('joystick_base', { anchorX: 0.5, anchorY: 0.5, x: 0, // will be set dynamically y: 0, // will be set dynamically width: joystick.radius * 2, height: joystick.radius * 2 }); joystick.baseAsset.alpha = 0.25; joystick.baseAsset.visible = false; game.addChild(joystick.baseAsset); // Use a simple circle for joystick stick instead of a heart to avoid duplicate heart visuals joystick.stickAsset = LK.getAsset('joystick_stick', { anchorX: 0.5, anchorY: 0.5, x: 0, // will be set dynamically y: 0, // will be set dynamically width: joystick.stickRadius * 2, height: joystick.stickRadius * 2 }); joystick.stickAsset.alpha = 0.7; joystick.stickAsset.visible = false; game.addChild(joystick.stickAsset); // Add a "MOVE" button styled like the menu buttons, and make it moveable joystick.moveButton = new MenuButton(); joystick.moveButton.setText("MOVE"); joystick.moveButton.x = 0; // will be set dynamically joystick.moveButton.y = 0; // will be set dynamically joystick.moveButton.visible = false; joystick.moveButton.setHighlight(false); game.addChild(joystick.moveButton); // --- Show joystick at start of game (story phase) --- function showJoystick(show) { joystick.visible = show; joystick.baseAsset.visible = show; joystick.stickAsset.visible = show; joystick.moveButton.visible = false; if (show) { // Place joystick below the heart if heart is visible, otherwise below hero (start of game) var centerX, baseY; if (heart && heart.visible) { centerX = heart.x; baseY = heart.y + heart.height / 2; } else if (hero && hero.visible) { centerX = hero.x; baseY = hero.y + hero.height / 2; } else { centerX = 2048 / 2; baseY = 2732 / 2 + 400 + 60; // fallback } var joystickOffset = 120 + joystick.radius; var joyX = centerX; var joyY = baseY + joystickOffset; // Adjust joystick Y placement for new arena var minJoyY = Math.max(baseY + 100, 1050 + 100); var maxJoyY = Math.min(2732 - joystick.radius - 40, 2550 + 100); if (joyY > maxJoyY) joyY = maxJoyY; if (joyY < minJoyY) joyY = minJoyY; joystick.baseAsset.x = joyX; joystick.baseAsset.y = joyY; joystick.stickAsset.x = joyX; joystick.stickAsset.y = joyY; joystick.baseX = joyX; joystick.baseY = joyY; joystick.stickX = joyX; joystick.stickY = joyY; } else { joystick.active = false; joystick.moveButton.visible = false; } } // Show joystick at the start of the game (story phase) showJoystick(true); // Returns true if (x, y) is inside joystick base function isInJoystick(x, y) { var dx = x - joystick.baseAsset.x; var dy = y - joystick.baseAsset.y; return dx * dx + dy * dy <= joystick.radius * joystick.radius; } var draggingHeart = false; game.down = function (x, y, obj) { // Boss select button (top right) if (bossSelectBtn && bossSelectBtn.visible && x > bossSelectBtn.x - 200 && x < bossSelectBtn.x + 200 && y > bossSelectBtn.y - 60 && y < bossSelectBtn.y + 60) { bossSelectBtn.setHighlight(true); bossSelectBtn.tap(); return; } // Boss select menu if (gameState === STATE.BOSS_SELECT && bossSelectButtons && bossSelectButtons.length) { for (var i = 0; i < bossSelectButtons.length; i++) { var btn = bossSelectButtons[i]; if (btn.visible && x > btn.x - 200 && x < btn.x + 200 && y > btn.y - 60 && y < btn.y + 60) { btn.setHighlight(true); if (typeof btn.tap === "function") btn.tap(); return; } } } if (gameState === STATE.BATTLE_DODGE) { // If touch is inside joystick, activate joystick if (isInJoystick(x, y)) { joystick.active = true; joystick.baseX = joystick.baseAsset.x; joystick.baseY = joystick.baseAsset.y; joystick.stickX = x; joystick.stickY = y; joystick.stickAsset.x = x; joystick.stickAsset.y = y; } } else if (gameState === STATE.STORY && skipTutorialBtn.visible && x > skipTutorialBtn.x - 200 && x < skipTutorialBtn.x + 200 && y > skipTutorialBtn.y - 60 && y < skipTutorialBtn.y + 60) { // Skip tutorial button pressed // Fast-forward story to battle storyStep = storyTexts.length; showSkipTutorial(false); nextStory(); return; } else if (gameState === STATE.BATTLE_MENU || gameState === STATE.BATTLE_ACT || gameState === STATE.BATTLE_ITEM || gameState === STATE.BATTLE_RESULT || gameState === STATE.BATTLE_INTRO || gameState === STATE.STORY) { handleMenuTouch(x, y); } }; game.up = function (x, y, obj) { joystick.active = false; joystick.stickAsset.x = joystick.baseAsset.x; joystick.stickAsset.y = joystick.baseAsset.y; lastTouchY = null; }; // Unified move handler for joystick and menu navigation (NO mouse/drag/hover/Enter) game.move = function (x, y, obj) { if (gameState === STATE.BATTLE_DODGE) { // Joystick movement only if (joystick.active) { var dx = x - joystick.baseX; var dy = y - joystick.baseY; var dist = Math.sqrt(dx * dx + dy * dy); if (dist > joystick.radius) { dx = dx * joystick.radius / dist; dy = dy * joystick.radius / dist; } joystick.stickX = joystick.baseX + dx; joystick.stickY = joystick.baseY + dy; joystick.stickAsset.x = joystick.stickX; joystick.stickAsset.y = joystick.stickY; // Move heart // Make joystick move the heart even faster by increasing the multiplier var moveSpeed = heart.speed * 2.3; // Increased from 1.7x to 2.3x for even faster movement var moveX = dx / joystick.radius; var moveY = dy / joystick.radius; // Even further expanded arena for maximum space to dodge var minX = 40, maxX = 2048 - 40, minY = 1050, maxY = 2550; heart.x = clamp(heart.x + moveX * moveSpeed, minX, maxX); heart.y = clamp(heart.y + moveY * moveSpeed, minY, maxY); } } }; // --- Main update loop --- game.update = function () { // Update boss select menu HP text if visible if (gameState === STATE.BOSS_SELECT && bossSelectButtons && bossSelectButtons.length) { for (var i = 0; i < monsters.length; i++) { var btn = bossSelectButtons[i]; if (btn && btn.txt) { var hpStr = typeof monsters[i].hp !== "undefined" && typeof monsters[i].maxHp !== "undefined" ? " (" + monsters[i].hp + "/" + monsters[i].maxHp + " HP)" : ""; btn.setText(monsters[i].name + hpStr); } } } // Update monster health bar every frame updateMonsterHpBar(); // Always ensure monster is visible during battle phases if (gameState === STATE.BATTLE_INTRO || gameState === STATE.BATTLE_MENU || gameState === STATE.BATTLE_ACT || gameState === STATE.BATTLE_ATTACK || gameState === STATE.BATTLE_DODGE) { if (monster && !monster.visible) monster.visible = true; } // Heart update if (gameState === STATE.BATTLE_DODGE) { // Always lock joystick directly below the heart, centered, fixed in place var heartCenterX = heart.x; var heartBottomY = heart.y + heart.height / 2; // Place joystick at 20% from the bottom of the screen, centered horizontally var joyX = heartCenterX; var joyY = heartBottomY + 120 + joystick.radius; // Clamp so joystick never goes off screen, but always stays below the heart var minJoyY = Math.max(heart.y + 100, 1050 + 100); var maxJoyY = Math.min(2732 - joystick.radius - 40, 2550 + 100); if (joyY > maxJoyY) joyY = maxJoyY; if (joyY < minJoyY) joyY = minJoyY; joystick.baseAsset.x = joyX; joystick.baseAsset.y = joyY; if (!joystick.active) { joystick.stickAsset.x = joyX; joystick.stickAsset.y = joyY; joystick.stickX = joyX; joystick.stickY = joyY; } joystick.baseX = joyX; joystick.baseY = joyY; // Spawn slashes if (attackTimer % 24 === 0 && attackSlashes.length < 5) { var slash = new AttackSlash(); // Expanded vertical range for slashes to match even larger arena slash.y = 1050 + Math.random() * 1500; slash.x = Math.random() < 0.5 ? 2048 - 200 : 200; slash.direction = slash.x < 1024 ? 1 : -1; attackSlashes.push(slash); game.addChild(slash); } // Update slashes for (var i = attackSlashes.length - 1; i >= 0; i--) { var s = attackSlashes[i]; s.update(); // Collision with heart var dx = s.x - heart.x, dy = s.y - heart.y; if (s.active && dx * dx + dy * dy < 60 * 60) { // UNDYNESQUE: If Croaknight and spearType is blue/yellow, apply special rules if (monster && monster.name === "Croaknight" && typeof s.spearType !== "undefined") { // Blue: only hurts if heart is moving // Yellow: only hurts if heart is NOT moving var isMoving = false; if (typeof joystick !== "undefined" && joystick.active) { var moveDx = joystick.stickX - joystick.baseX; var moveDy = joystick.stickY - joystick.baseY; isMoving = Math.abs(moveDx) > 6 || Math.abs(moveDy) > 6; } if (s.spearType === "blue" && isMoving || s.spearType === "yellow" && !isMoving) { s.active = false; playerHP -= 4; updateHPText(); LK.effects.flashObject(heart, 0xff0000, 400); if (playerHP <= 0) { playerHP = 0; updateHPText(); LK.effects.flashScreen(0xff0000, 1200); LK.showGameOver(); return; } } // If not the right condition, don't hurt, but still destroy spear s.active = false; } else { s.active = false; playerHP -= 4; updateHPText(); LK.effects.flashObject(heart, 0xff0000, 400); if (playerHP <= 0) { playerHP = 0; updateHPText(); LK.effects.flashScreen(0xff0000, 1200); LK.showGameOver(); return; } } } if (!s.active) { s.destroy(); attackSlashes.splice(i, 1); } } attackTimer++; if (attackTimer > attackDuration) { // End attack phase for (var i = 0; i < attackSlashes.length; i++) attackSlashes[i].destroy(); attackSlashes = []; heart.visible = false; showJoystick(false); gameState = STATE.BATTLE_MENU; showBattleMenu(); } } }; // --- Touch navigation for menu (swipe up/down to change selection) --- var lastTouchY = null;
/****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
/****
* Classes
****/
// Attack slash (enemy attack)
var AttackSlash = Container.expand(function () {
var self = Container.call(this);
var slash = self.attachAsset('attack_slash', {
anchorX: 0.5,
anchorY: 0.5
});
self.speed = 22 + Math.random() * 8;
self.direction = Math.random() < 0.5 ? 1 : -1;
self.active = true;
self.update = function () {
self.x += self.speed * self.direction;
if (self.x < 200 || self.x > 1848) {
self.active = false;
}
};
return self;
});
// Boss: Asgore
var BossAsgore = Container.expand(function () {
var self = Container.call(this);
var monster = self.attachAsset('boss_asgorepng', {
anchorX: 0.5,
anchorY: 0.5
});
self.hp = 30;
self.maxHp = 30;
self.name = "Asgore";
self.state = "neutral";
self.dialogue = ["Asgore stands tall, trident in hand.", "Asgore's eyes are full of sorrow.", "Asgore readies his attack.", "Asgore hesitates for a moment."];
self.dialogueIndex = 0;
self.getDialogue = function () {
if (self.state === "spared") return self.name + " looks relieved.";
if (self.state === "angry") return self.name + " is furious!";
return self.dialogue[self.dialogueIndex % self.dialogue.length];
};
self.nextDialogue = function () {
self.dialogueIndex++;
};
self.update = function () {
// Only move during battle phases
if (typeof gameState !== "undefined" && (gameState === STATE.BATTLE_INTRO || gameState === STATE.BATTLE_MENU || gameState === STATE.BATTLE_ACT || gameState === STATE.BATTLE_ATTACK || gameState === STATE.BATTLE_DODGE)) {
// Only move if this is the current monster and Asgore is visible
if (typeof monster !== "undefined" && monster === self && self.visible) {
// Initialize movement state if not present
if (typeof self.moveDir === "undefined") self.moveDir = 1;
if (typeof self.moveTimer === "undefined") self.moveTimer = 0;
if (typeof self.movePause === "undefined") self.movePause = 0;
if (typeof self.lastX === "undefined") self.lastX = self.x;
// Only move if not paused
if (self.movePause > 0) {
self.movePause--;
} else {
// Move left/right
var speed = 7;
self.x += self.moveDir * speed;
// Clamp to arena bounds (leave some margin)
var minX = 400,
maxX = 2048 - 400;
if (self.x < minX) {
self.x = minX;
self.moveDir = 1;
self.movePause = 30 + Math.floor(Math.random() * 30); // pause at edge
} else if (self.x > maxX) {
self.x = maxX;
self.moveDir = -1;
self.movePause = 30 + Math.floor(Math.random() * 30);
}
// Occasionally change direction in the middle
self.moveTimer++;
if (self.moveTimer > 60 + Math.random() * 60) {
if (Math.random() < 0.4) {
self.moveDir *= -1;
self.movePause = 10 + Math.floor(Math.random() * 20);
}
self.moveTimer = 0;
}
}
self.lastX = self.x;
}
}
};
return self;
});
// 2. Sans
// Only one Sans should exist in the monster list
// Boss: Papyrus
var BossPapyrus = Container.expand(function () {
var self = Container.call(this);
var monster = self.attachAsset('boss_papyruspng', {
anchorX: 0.5,
anchorY: 0.5
});
self.hp = 22;
self.maxHp = 22;
self.name = "Papyrus";
self.state = "neutral";
self.dialogue = ["Papyrus bounds in! \"NYEH HEH HEH!\"", "Papyrus poses dramatically.", "Papyrus is thinking about spaghetti.", "Papyrus wants to be your friend."];
self.dialogueIndex = 0;
self.getDialogue = function () {
if (self.state === "spared") return self.name + " looks relieved.";
if (self.state === "angry") return self.name + " is furious!";
return self.dialogue[self.dialogueIndex % self.dialogue.length];
};
self.nextDialogue = function () {
self.dialogueIndex++;
};
self.update = function () {};
return self;
});
// Boss: Sans
var BossSans = Container.expand(function () {
var self = Container.call(this);
var monster = self.attachAsset('boss_sanspng', {
anchorX: 0.5,
anchorY: 0.5
});
self.hp = 20;
self.maxHp = 20;
self.name = "Sans";
self.state = "neutral";
self.dialogue = ["Sans appears with a grin. \"hey kiddo.\"", "Sans shrugs. \"you look like you've seen a ghost.\"", "Sans winks. \"get dunked on? nah, just kidding.\"", "Sans tells a pun. \"what do you call a lazy skeleton? bone-idle.\""];
self.dialogueIndex = 0;
self.getDialogue = function () {
if (self.state === "spared") return self.name + " looks relieved.";
if (self.state === "angry") return self.name + " is furious!";
return self.dialogue[self.dialogueIndex % self.dialogue.length];
};
self.nextDialogue = function () {
self.dialogueIndex++;
};
self.update = function () {};
return self;
});
// Dialogue box
var DialogueBox = Container.expand(function () {
var self = Container.call(this);
self.bg = self.attachAsset('dialogue_box', {
anchorX: 0.5,
anchorY: 0.5
});
self.txt = new Text2('', {
size: 54,
fill: "#fff"
});
self.txt.anchor.set(0.5, 0.5);
self.txt.x = 0;
self.txt.y = 0;
self.addChild(self.txt);
self.setText = function (t) {
self.txt.setText(t);
};
return self;
});
// Hero soul in battle (the heart you move to dodge attacks)
var Heart = Container.expand(function () {
var self = Container.call(this);
var heart = self.attachAsset('heart', {
anchorX: 0.5,
anchorY: 0.5
});
self.radius = 30;
self.speed = 18;
self.update = function () {
// Movement handled in game.move
};
return self;
});
// Button for menu
var MenuButton = Container.expand(function () {
var self = Container.call(this);
self.bg = self.attachAsset('btn_bg', {
anchorX: 0.5,
anchorY: 0.5
});
self.hl = self.attachAsset('btn_hl', {
anchorX: 0.5,
anchorY: 0.5
});
self.hl.visible = false;
self.txt = new Text2('', {
size: 60,
fill: "#fff"
});
self.txt.anchor.set(0.5, 0.5);
self.addChild(self.txt);
self.setText = function (t) {
self.txt.setText(t);
};
self.setHighlight = function (v) {
self.hl.visible = v;
};
return self;
});
// Monster class (for battle)
var Monster = Container.expand(function () {
var self = Container.call(this);
// Attach a generic monster asset (non-boss)
var monster = self.attachAsset('monster1', {
anchorX: 0.5,
anchorY: 0.5
});
self.hp = 12;
self.maxHp = 12;
self.name = "Monster";
self.state = "neutral"; // can be "neutral", "spared", "angry"
self.dialogue = ["A monster appears.", "The monster seems nervous.", "The monster is watching you closely.", "The monster croaks softly."];
self.dialogueIndex = 0;
self.getDialogue = function () {
if (self.state === "spared") return self.name + " looks relieved.";
if (self.state === "angry") return self.name + " is furious!";
return self.dialogue[self.dialogueIndex % self.dialogue.length];
};
self.nextDialogue = function () {
self.dialogueIndex++;
};
self.update = function () {
// No movement for monster
};
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0x181824
});
/****
* Game Code
****/
// Add round joystick assets
// Mercy yellow
// Dialogue box
// Button highlight
// Button backgrounds
// Heart (player soul in battle)
// Attack indicator (red slash)
// Enemy monster (first encounter, "Froggit" inspired, but original)
// Main character (the "child")
// --- Game State ---
var STATE = {
STORY: 0,
BATTLE_INTRO: 1,
BATTLE_MENU: 2,
BATTLE_ACT: 3,
BATTLE_ATTACK: 4,
BATTLE_DODGE: 5,
BATTLE_RESULT: 6,
ENDING: 7,
BOSS_SELECT: 8
};
var gameState = STATE.STORY;
// --- Main variables ---
var hero, heart, dialogueBox;
var monsters = [];
var currentMonsterIndex = 0;
var monster = null;
var menuButtons = [];
var selectedMenu = 0;
var actButtons = [];
var selectedAct = 0;
var mercyAvailable = false;
var playerHP = 50;
var playerMaxHP = 50;
// --- Boss Select Menu ---
var bossSelectButtons = [];
var bossSelectTitle = null;
function showBossSelectMenu() {
// Hide all other menus
hideAllMenus();
// Hide dialogue box
if (dialogueBox) dialogueBox.visible = false;
// Hide skip tutorial
showSkipTutorial(false);
// Hide joystick
showJoystick(false);
// Hide result text
if (resultText) resultText.visible = false;
// Hide monster
if (monster) monster.visible = false;
// Hide heart
if (heart) heart.visible = false;
// Hide monster HP bar
monsterHpBarBg.visible = false;
monsterHpBar.visible = false;
monsterHpText.visible = false;
// Remove old boss select buttons
for (var i = 0; i < bossSelectButtons.length; i++) {
if (bossSelectButtons[i] && typeof bossSelectButtons[i].destroy === "function") bossSelectButtons[i].destroy();
}
bossSelectButtons = [];
// Remove old title
if (bossSelectTitle && typeof bossSelectTitle.destroy === "function") bossSelectTitle.destroy();
bossSelectTitle = new Text2("Select Boss", {
size: 100,
fill: "#fff",
padding: 24,
shadow: {
color: 0x000000,
blur: 16,
distance: 0,
angle: 0,
alpha: 0.7
}
});
bossSelectTitle.anchor.set(0.5, 0.5);
bossSelectTitle.x = 2048 / 2;
bossSelectTitle.y = 320;
game.addChild(bossSelectTitle);
// Show a button for each boss/monster
var btnW = 420,
btnH = 170,
btnSpacing = 40;
var startY = 500;
for (var i = 0; i < monsters.length; i++) {
var btn = new MenuButton();
btn.x = 2048 / 2;
btn.y = startY + i * (btnH + btnSpacing);
btn.bg.width = btnW;
btn.bg.height = btnH;
btn.hl.width = btnW + 16;
btn.hl.height = btnH + 16;
btn.bg.alpha = 0.93;
btn.bg.tint = 0x222244;
btn.hl.alpha = 0.22;
if (btn.bg && typeof btn.bg.filters !== "undefined") {
btn.bg.filters = [{
type: "shadow",
color: 0x000000,
blur: 16,
distance: 0,
angle: 0,
alpha: 0.5
}];
}
if (btn.txt && btn.txt.style) {
btn.txt.style.size = 60;
btn.txt.style.fill = "#fff";
}
// Show HP for each boss
var hpStr = typeof monsters[i].hp !== "undefined" && typeof monsters[i].maxHp !== "undefined" ? " (" + monsters[i].hp + "/" + monsters[i].maxHp + " HP)" : "";
btn.setText(monsters[i].name + hpStr);
btn.visible = true;
// Add tap highlight feedback
(function (b, idx) {
b.down = function () {
b.setHighlight(true);
};
b.up = function () {
b.setHighlight(false);
};
b.tap = function () {
// Jump to this boss
for (var j = 0; j < bossSelectButtons.length; j++) bossSelectButtons[j].visible = false;
if (bossSelectTitle) bossSelectTitle.visible = false;
currentMonsterIndex = idx;
monster.visible = false;
monster = monsters[currentMonsterIndex];
monster.visible = false;
monster.actCount = 0;
monster.mercyAvailable = false;
game.addChild(monster);
updateMonsterHpBar();
// Start battle with this boss
gameState = STATE.BATTLE_INTRO;
hero.visible = false;
monster.visible = true;
showDialogue(monster.getDialogue());
LK.setTimeout(function () {
gameState = STATE.BATTLE_MENU;
showBattleMenu();
}, 1200);
};
})(btn, i);
bossSelectButtons.push(btn);
game.addChild(btn);
}
// Add a "Cancel" button to return to story/battle
var cancelBtn = new MenuButton();
cancelBtn.x = 2048 / 2;
cancelBtn.y = startY + monsters.length * (btnH + btnSpacing) + 60;
cancelBtn.bg.width = btnW;
cancelBtn.bg.height = btnH;
cancelBtn.hl.width = btnW + 16;
cancelBtn.hl.height = btnH + 16;
cancelBtn.bg.alpha = 0.93;
cancelBtn.bg.tint = 0x222244;
cancelBtn.hl.alpha = 0.22;
if (cancelBtn.bg && typeof cancelBtn.bg.filters !== "undefined") {
cancelBtn.bg.filters = [{
type: "shadow",
color: 0x000000,
blur: 16,
distance: 0,
angle: 0,
alpha: 0.5
}];
}
if (cancelBtn.txt && cancelBtn.txt.style) {
cancelBtn.txt.style.size = 60;
cancelBtn.txt.style.fill = "#fff";
}
cancelBtn.setText("Cancel");
cancelBtn.visible = true;
cancelBtn.down = function () {
cancelBtn.setHighlight(true);
};
cancelBtn.up = function () {
cancelBtn.setHighlight(false);
};
cancelBtn.tap = function () {
// Hide boss select menu, return to previous state
for (var j = 0; j < bossSelectButtons.length; j++) bossSelectButtons[j].visible = false;
if (bossSelectTitle) bossSelectTitle.visible = false;
cancelBtn.visible = false;
// Resume previous state
if (gameState === STATE.BOSS_SELECT) {
gameState = STATE.STORY;
nextStory();
}
};
bossSelectButtons.push(cancelBtn);
game.addChild(cancelBtn);
}
// --- Boss Select Button (top right) ---
var bossSelectBtn = new MenuButton();
bossSelectBtn.setText("Boss Select");
bossSelectBtn.x = 2048 - 350;
bossSelectBtn.y = 360;
bossSelectBtn.bg.width = 420;
bossSelectBtn.bg.height = 150;
bossSelectBtn.hl.width = 420 + 16;
bossSelectBtn.hl.height = 150 + 16;
bossSelectBtn.bg.alpha = 0.93;
bossSelectBtn.bg.tint = 0x222244;
bossSelectBtn.hl.alpha = 0.22;
if (bossSelectBtn.bg && typeof bossSelectBtn.bg.filters !== "undefined") {
bossSelectBtn.bg.filters = [{
type: "shadow",
color: 0x000000,
blur: 16,
distance: 0,
angle: 0,
alpha: 0.5
}];
}
if (bossSelectBtn.txt && bossSelectBtn.txt.style) {
bossSelectBtn.txt.style.size = 60;
bossSelectBtn.txt.style.fill = "#fff";
}
bossSelectBtn.visible = true;
bossSelectBtn.setHighlight(false);
bossSelectBtn.down = function () {
bossSelectBtn.setHighlight(true);
};
bossSelectBtn.up = function () {
bossSelectBtn.setHighlight(false);
};
bossSelectBtn.tap = function () {
gameState = STATE.BOSS_SELECT;
showBossSelectMenu();
};
game.addChild(bossSelectBtn);
// Add more items to the inventory!
var playerInventory = [{
name: "Candy",
heal: 10,
qty: 1
}, {
name: "Donut",
heal: 15,
qty: 1
}, {
name: "Pie",
heal: 20,
qty: 1
}, {
name: "Soda",
heal: 8,
qty: 2
}, {
name: "Bandage",
heal: 5,
qty: 3
}, {
name: "egegokalp",
type: "special",
effect: "sezaryen_yumurtasi",
qty: 1
}];
var sezaryenYumurtasi = 0; // Track extra sezaryen yumurtası
var selectedItem = 0;
// Support up to 5 item buttons for more items
var itemButtons = [];
var mercyButton;
var attackSlashes = [];
var attackTimer = 0;
var attackDuration = 90;
var battleResultText = '';
var storyStep = 0;
var storyTexts = ["You wake up in a dark, unfamiliar place.", "A soft croak echoes in the distance...", "A strange creature approaches!", "You survived the first battle. But the journey continues...", "A new monster blocks your path!", "You brace yourself for another fight.",
// --- Asgore pre-battle sequence ---
"You find Asgore watering the Golden Flowers in the Throne Room.", "He turns around and greets you warmly.", "Asgore realizes you are a human. He looks surprised, but not angry.", "\"Ah... It seems we finally meet. I am Asgore, king of the Underground.\"", "He gently leads you toward the barrier.", "\"If you have anything left to do, now is the time. I do not mind waiting.\"", "You sense his sadness. He admits, \"I am not ready either.\"", "He stands before the barrier, trident in hand.", "Asgore: \"It was nice knowing you.\"", "He draws his trident and, with a heavy heart, destroys the MERCY button.", "There is no turning back now. FIGHT is the only way forward."];
// Add more monsters for a longer game
function createMonsters() {
// 1. Sans (now first monster)
var m1 = new BossSans();
m1.x = 2048 / 2;
m1.y = 900;
m1.mercyAvailable = false;
// 2. Sans
// Only one Sans should exist in the monster list
var m2 = new BossSans();
m2.x = 2048 / 2;
m2.y = 900;
m2.mercyAvailable = false;
// 3. Papyrus
var m3 = new BossPapyrus();
m3.x = 2048 / 2;
m3.y = 900;
m3.mercyAvailable = false;
// 4. Asgore
var m4 = new BossAsgore();
m4.x = 2048 / 2;
m4.y = 900;
m4.mercyAvailable = false;
// 5. Turtloid
var m5 = new Monster();
m5.x = 2048 / 2;
m5.y = 900;
m5.hp = 30;
m5.maxHp = 30;
m5.name = "Turtloid";
m5.state = "neutral";
m5.dialogue = ["Turtloid withdraws into its shell.", "Turtloid peeks out cautiously.", "Turtloid spins slowly.", "Turtloid seems unhurried."];
m5.dialogueIndex = 0;
m5.mercyAvailable = false;
// 6. Mothmire
var m6 = new Monster();
m6.x = 2048 / 2;
m6.y = 900;
m6.hp = 16;
m6.maxHp = 16;
m6.name = "Mothmire";
m6.state = "neutral";
m6.dialogue = ["Mothmire flutters its wings.", "Mothmire is drawn to the light.", "Mothmire hovers silently.", "Mothmire's dust sparkles."];
m6.dialogueIndex = 0;
m6.mercyAvailable = false;
// 7. Spindle
var m7 = new Monster();
m7.x = 2048 / 2;
m7.y = 900;
m7.hp = 20;
m7.maxHp = 20;
m7.name = "Spindle";
m7.state = "neutral";
m7.dialogue = ["Spindle weaves a web.", "Spindle's eyes glint.", "Spindle scuttles around.", "Spindle is alert."];
m7.dialogueIndex = 0;
m7.mercyAvailable = false;
// 8. Barkbark
var m8 = new Monster();
m8.x = 2048 / 2;
m8.y = 900;
m8.hp = 24;
m8.maxHp = 24;
m8.name = "Barkbark";
m8.state = "neutral";
m8.dialogue = ["Barkbark wags its tail.", "Barkbark barks excitedly.", "Barkbark circles you.", "Barkbark pants happily."];
m8.dialogueIndex = 0;
m8.mercyAvailable = false;
// 9. Glimmerbug
var m9 = new Monster();
m9.x = 2048 / 2;
m9.y = 900;
m9.hp = 15;
m9.maxHp = 15;
m9.name = "Glimmerbug";
m9.state = "neutral";
m9.dialogue = ["Glimmerbug glows softly.", "Glimmerbug zips around.", "Glimmerbug leaves a trail of light.", "Glimmerbug hums a tune."];
m9.dialogueIndex = 0;
m9.mercyAvailable = false;
// 10. Thornet
var m10 = new Monster();
m10.x = 2048 / 2;
m10.y = 900;
m10.hp = 28;
m10.maxHp = 28;
m10.name = "Thornet";
m10.state = "neutral";
m10.dialogue = ["Thornet buzzes menacingly.", "Thornet sharpens its stinger.", "Thornet hovers above.", "Thornet's wings vibrate."];
m10.dialogueIndex = 0;
m10.mercyAvailable = false;
// 11. Shadewisp
var m11 = new Monster();
m11.x = 2048 / 2;
m11.y = 900;
m11.hp = 35;
m11.maxHp = 35;
m11.name = "Shadewisp";
m11.state = "neutral";
m11.dialogue = ["Shadewisp flickers in and out.", "Shadewisp's form is hard to see.", "Shadewisp whispers your name.", "Shadewisp chills the air."];
m11.dialogueIndex = 0;
m11.mercyAvailable = false;
// 12. Queen Lily
var m12 = new Monster();
m12.x = 2048 / 2;
m12.y = 900;
m12.hp = 40;
m12.maxHp = 40;
m12.name = "Queen Lily";
m12.state = "neutral";
m12.dialogue = ["Queen Lily gazes at you regally.", "Queen Lily's petals shimmer.", "Queen Lily radiates calm.", "Queen Lily stands tall."];
m12.dialogueIndex = 0;
m12.mercyAvailable = false;
m1.actCount = 0;
m1.mercyAvailable = false;
m2.actCount = 0;
m2.mercyAvailable = false;
m3.actCount = 0;
m3.mercyAvailable = false;
m4.actCount = 0;
m4.mercyAvailable = false;
m5.actCount = 0;
m5.mercyAvailable = false;
m6.actCount = 0;
m6.mercyAvailable = false;
m7.actCount = 0;
m7.mercyAvailable = false;
m8.actCount = 0;
m8.mercyAvailable = false;
m9.actCount = 0;
m9.mercyAvailable = false;
m10.actCount = 0;
m10.mercyAvailable = false;
m11.actCount = 0;
m11.mercyAvailable = false;
m12.actCount = 0;
m12.mercyAvailable = false;
monsters = [m1, m3, m4, m5, m6, m7, m8, m9, m10, m11, m12];
// Only one Sans in the monster list
}
createMonsters();
// Ensure the first monster is Sans, not Froggle
monster = monsters[0];
// --- Utility functions ---
function showDialogue(text) {
dialogueBox.setText(text);
dialogueBox.visible = true;
}
function hideDialogue() {
dialogueBox.visible = false;
}
function updateHPText() {
hpText.setText("HP: " + playerHP + "/" + playerMaxHP);
}
function clamp(val, min, max) {
return val < min ? min : val > max ? max : val;
}
// --- UI Elements ---
var hpText = new Text2("HP: 50/50", {
size: 64,
fill: 0xFF6666,
padding: 10,
shadow: {
color: 0x000000,
blur: 8,
distance: 0,
angle: 0,
alpha: 0.7
}
});
hpText.anchor.set(0, 0);
LK.gui.top.addChild(hpText);
hpText.x = 120;
hpText.y = 20;
// Dialogue box
dialogueBox = new DialogueBox();
dialogueBox.x = 2048 / 2;
dialogueBox.y = 2732 - 220;
if (dialogueBox.bg && typeof dialogueBox.bg.filters !== "undefined") {
dialogueBox.bg.filters = [{
type: "shadow",
color: 0x000000,
blur: 24,
distance: 0,
angle: 0,
alpha: 0.7
}];
dialogueBox.bg.alpha = 0.98;
}
if (dialogueBox.txt && dialogueBox.txt.style) {
dialogueBox.txt.style.size = 60;
dialogueBox.txt.style.fill = "#fff";
dialogueBox.txt.style.padding = 24;
}
game.addChild(dialogueBox);
// --- Skip Tutorial Button ---
var skipTutorialBtn = new MenuButton();
skipTutorialBtn.setText("Skip Tutorial");
skipTutorialBtn.x = 2048 - 350;
skipTutorialBtn.y = 180;
skipTutorialBtn.visible = false;
skipTutorialBtn.setHighlight(false);
game.addChild(skipTutorialBtn);
function showSkipTutorial(show) {
skipTutorialBtn.visible = !!show;
}
// --- Main character (not visible in battle) ---
hero = LK.getAsset('hero', {
anchorX: 0.5,
anchorY: 0.5,
x: 2048 / 2,
y: 2732 / 2 + 400
});
game.addChild(hero);
// --- Monster (battle only) ---
// Start with Sans as the first monster
monster = monsters[0];
monster.x = 2048 / 2;
monster.y = 900;
monster.visible = false;
game.addChild(monster);
// --- Monster Health Bar ---
var monsterHpBarBg = LK.getAsset('dialogue_box', {
anchorX: 0.5,
anchorY: 0.5,
width: 1100,
// much bigger width
height: 90,
// much bigger height
x: 2048 / 2,
y: 220 //{27} // moved up
});
monsterHpBarBg.tint = 0x222222;
monsterHpBarBg.alpha = 0.7;
monsterHpBarBg.visible = false;
game.addChild(monsterHpBarBg);
var monsterHpBar = LK.getAsset('dialogue_box', {
anchorX: 0.5,
anchorY: 0.5,
width: 1050,
// much bigger width
height: 60,
// much bigger height
x: 2048 / 2,
y: 220 //{2f} // moved up
});
monsterHpBar.tint = 0x00e676;
monsterHpBar.alpha = 0.95;
monsterHpBar.visible = false;
game.addChild(monsterHpBar);
var monsterHpText = new Text2('', {
size: 44,
fill: "#fff",
padding: 12,
shadow: {
color: 0x000000,
blur: 8,
distance: 0,
angle: 0,
alpha: 0.7
}
});
monsterHpText.anchor.set(0.5, 0.5);
monsterHpText.x = 2048 / 2;
monsterHpText.y = 220; // moved up
monsterHpText.visible = false;
game.addChild(monsterHpText);
function updateMonsterHpBar() {
if (!monster || typeof monster.hp === "undefined" || typeof monster.maxHp === "undefined") {
monsterHpBarBg.visible = false;
monsterHpBar.visible = false;
monsterHpText.visible = false;
// Hide hearts if present
if (window.sansHpHearts && window.sansHpHearts.length) {
for (var i = 0; i < window.sansHpHearts.length; i++) {
window.sansHpHearts[i].visible = false;
}
}
return;
}
// Only show during battle
if (gameState === STATE.BATTLE_INTRO || gameState === STATE.BATTLE_MENU || gameState === STATE.BATTLE_ACT || gameState === STATE.BATTLE_ATTACK || gameState === STATE.BATTLE_DODGE) {
// Show hearts for every boss, not just Sans
monsterHpBarBg.visible = false;
monsterHpBar.visible = false;
monsterHpText.visible = false;
// Always show 20 hearts for every boss, not maxHp hearts
var hp = Math.max(0, Math.min(monster.hp, 20));
var maxHearts = 20;
var heartSize = 70;
var spacing = 18;
var totalWidth = maxHearts * heartSize + (maxHearts - 1) * spacing;
var startX = 2048 / 2 - totalWidth / 2 + heartSize / 2;
var y = 220;
if (!window.sansHpHearts) window.sansHpHearts = [];
// Create hearts if not enough
for (var i = 0; i < maxHearts; i++) {
if (!window.sansHpHearts[i]) {
var h = LK.getAsset('heart', {
anchorX: 0.5,
anchorY: 0.5,
width: heartSize,
height: heartSize,
x: startX + i * (heartSize + spacing),
y: y
});
h.visible = false;
game.addChild(h);
window.sansHpHearts[i] = h;
}
// Always update position in case of resize
window.sansHpHearts[i].x = startX + i * (heartSize + spacing);
window.sansHpHearts[i].y = y;
window.sansHpHearts[i].width = heartSize;
window.sansHpHearts[i].height = heartSize;
window.sansHpHearts[i].visible = i < hp;
window.sansHpHearts[i].alpha = i < hp ? 1 : 0.25;
}
// Hide extra hearts if more than 20 were created
for (var i = maxHearts; window.sansHpHearts && i < window.sansHpHearts.length; i++) {
window.sansHpHearts[i].visible = false;
}
} else {
monsterHpBarBg.visible = false;
monsterHpBar.visible = false;
monsterHpText.visible = false;
// Hide hearts if present
if (window.sansHpHearts && window.sansHpHearts.length) {
for (var i = 0; i < window.sansHpHearts.length; i++) {
window.sansHpHearts[i].visible = false;
}
}
}
}
// --- Heart (player soul in battle) ---
heart = new Heart();
heart.x = 2048 / 2;
heart.y = 1800;
// Place heart in the center of the even larger arena at start of battle
heart.visible = false;
game.addChild(heart);
// --- Menu buttons (Fight, Act, Item, Mercy) ---
// Redesigned: Consistent size, spacing, padding, and shadow for a clean, readable UI
var menuNames = ["Fight", "Act", "Item", "Mercy"];
var menuBtnWidth = 380;
var menuBtnHeight = 170;
var menuBtnSpacing = 60;
var menuBtnY = 2732 - 340;
var menuBtnStartX = (2048 - (menuNames.length * menuBtnWidth + (menuNames.length - 1) * menuBtnSpacing)) / 2 + menuBtnWidth / 2;
for (var i = 0; i < 4; i++) {
var btn = new MenuButton();
btn.x = menuBtnStartX + i * (menuBtnWidth + menuBtnSpacing);
btn.y = menuBtnY;
btn.bg.width = menuBtnWidth;
btn.bg.height = menuBtnHeight;
btn.hl.width = menuBtnWidth + 16;
btn.hl.height = menuBtnHeight + 16;
btn.bg.alpha = 0.93;
btn.bg.tint = 0x222244;
btn.hl.alpha = 0.22;
if (btn.bg && typeof btn.bg.filters !== "undefined") {
btn.bg.filters = [{
type: "shadow",
color: 0x000000,
blur: 16,
distance: 0,
angle: 0,
alpha: 0.5
}];
}
if (btn.txt && btn.txt.style) {
btn.txt.style.size = 64;
btn.txt.style.fill = "#fff";
}
btn.visible = false;
// Add simple tap feedback: highlight on press, unhighlight on release
(function (b) {
b.down = function () {
b.setHighlight(true);
};
b.up = function () {
b.setHighlight(false);
};
})(btn);
btn.setText(menuNames[i]);
menuButtons.push(btn);
game.addChild(btn);
}
menuButtons[0].setHighlight(true);
// --- Act buttons (contextual) ---
// Redesigned: Consistent size, spacing, padding, and shadow for a clean, readable UI
var actNames = ["Check", "Compliment", "Threaten", "Cesur"];
var actBtnWidth = 380;
var actBtnHeight = 170;
var actBtnSpacing = 60;
var actBtnY = 2732 - 340;
var actBtnStartX = (2048 - (actNames.length * actBtnWidth + (actNames.length - 1) * actBtnSpacing)) / 2 + actBtnWidth / 2;
for (var i = 0; i < 4; i++) {
var btn = new MenuButton();
btn.x = actBtnStartX + i * (actBtnWidth + actBtnSpacing);
btn.y = actBtnY;
btn.bg.width = actBtnWidth;
btn.bg.height = actBtnHeight;
btn.hl.width = actBtnWidth + 16;
btn.hl.height = actBtnHeight + 16;
btn.bg.alpha = 0.93;
btn.bg.tint = 0x222244;
btn.hl.alpha = 0.22;
if (btn.bg && typeof btn.bg.filters !== "undefined") {
btn.bg.filters = [{
type: "shadow",
color: 0x000000,
blur: 16,
distance: 0,
angle: 0,
alpha: 0.5
}];
}
if (btn.txt && btn.txt.style) {
btn.txt.style.size = 64;
btn.txt.style.fill = "#fff";
}
btn.visible = false;
// Add tap highlight feedback
(function (b) {
b.down = function () {
b.setHighlight(true);
};
b.up = function () {
b.setHighlight(false);
};
})(btn);
btn.setText(actNames[i]);
actButtons.push(btn);
game.addChild(btn);
}
actButtons[0].setHighlight(true);
// --- Item buttons (contextual) ---
// Redesigned: Consistent size, spacing, padding, and shadow for a clean, readable UI
// Support up to 5 item buttons for more items
var itemBtnWidth = 380;
var itemBtnHeight = 170;
var itemBtnSpacingX = 60;
var itemBtnSpacingY = 40;
var itemBtnCols = 3;
var itemBtnRows = 2;
var itemBtnStartX = (2048 - (itemBtnCols * itemBtnWidth + (itemBtnCols - 1) * itemBtnSpacingX)) / 2 + itemBtnWidth / 2;
var itemBtnStartY = 2732 - 340 - (itemBtnRows - 1) * (itemBtnHeight + itemBtnSpacingY) / 2;
for (var i = 0; i < 5; i++) {
var btn = new MenuButton();
var col = i % itemBtnCols;
var row = Math.floor(i / itemBtnCols);
btn.x = itemBtnStartX + col * (itemBtnWidth + itemBtnSpacingX);
btn.y = itemBtnStartY + row * (itemBtnHeight + itemBtnSpacingY);
btn.bg.width = itemBtnWidth;
btn.bg.height = itemBtnHeight;
btn.hl.width = itemBtnWidth + 16;
btn.hl.height = itemBtnHeight + 16;
btn.bg.alpha = 0.93;
btn.bg.tint = 0x222244;
btn.hl.alpha = 0.22;
if (btn.bg && typeof btn.bg.filters !== "undefined") {
btn.bg.filters = [{
type: "shadow",
color: 0x000000,
blur: 16,
distance: 0,
angle: 0,
alpha: 0.5
}];
}
if (btn.txt && btn.txt.style) {
btn.txt.style.size = 60;
btn.txt.style.fill = "#fff";
}
btn.setText('');
btn.visible = false;
// Add tap highlight feedback
(function (b) {
b.down = function () {
b.setHighlight(true);
};
b.up = function () {
b.setHighlight(false);
};
})(btn);
itemButtons.push(btn);
game.addChild(btn);
}
itemButtons[0].setHighlight(true);
// --- Mercy button (yellow if available) ---
mercyButton = menuButtons[3];
// --- Battle result text ---
var resultText = new Text2('', {
size: 90,
fill: "#fff",
padding: 24,
shadow: {
color: 0x000000,
blur: 16,
distance: 0,
angle: 0,
alpha: 0.7
}
});
resultText.anchor.set(0.5, 0.5);
resultText.x = 2048 / 2;
resultText.y = 2732 / 2;
resultText.visible = false;
game.addChild(resultText);
// --- Story flow ---
function nextStory() {
if (storyStep < storyTexts.length) {
showDialogue(storyTexts[storyStep]);
storyStep++;
// Show skip tutorial button only during story phase, before battle starts
if (gameState === STATE.STORY && storyStep < storyTexts.length) {
showSkipTutorial(true);
} else {
showSkipTutorial(false);
}
// If we just finished the first story arc, and there are more monsters, prep next monster
if (storyStep === 3 && currentMonsterIndex === 0) {
// After first monster, show next monster
LK.setTimeout(function () {
currentMonsterIndex = 1;
monster.visible = false;
monster = monsters[currentMonsterIndex];
monster.visible = false;
monster.actCount = 0;
monster.mercyAvailable = false;
game.addChild(monster);
updateMonsterHpBar();
}, 1200);
}
if (storyStep === 5 && currentMonsterIndex === 1) {
LK.setTimeout(function () {
currentMonsterIndex = 2;
monster.visible = false;
monster = monsters[currentMonsterIndex];
monster.visible = false;
monster.actCount = 0;
monster.mercyAvailable = false;
game.addChild(monster);
}, 1200);
}
// --- Asgore pre-battle sequence: destroy MERCY button ---
if (storyStep === 17 && currentMonsterIndex === 2 && monster && monster.name === "Asgore") {
// Show the MERCY button being destroyed
LK.setTimeout(function () {
// Visually "break" the mercy button: hide yellow asset, show a crack or just hide
if (mercyButton.mercyAsset) mercyButton.mercyAsset.visible = false;
mercyButton.bg.visible = false;
mercyButton.setText("MERCY");
// Defensive: Only set fill and alpha if style exists and property is settable
if (mercyButton && mercyButton.txt && mercyButton.txt.style) {
try {
if (typeof mercyButton.txt.style.fill !== "undefined" || typeof mercyButton.txt.style.fill === "string") {
mercyButton.txt.style.fill = "#ff2222"; //{8e} // RED after destroyed
}
} catch (e) {}
try {
if (typeof mercyButton.txt.style.alpha !== "undefined" || typeof mercyButton.txt.style.alpha === "number") {
mercyButton.txt.style.alpha = 0.5;
}
} catch (e) {}
} else if (mercyButton && mercyButton.txt && typeof mercyButton.txt.setStyle === "function") {
// Fallback: use setStyle if available
try {
mercyButton.txt.setStyle({
fill: 0xFF2222,
//{8q} // RED after destroyed
alpha: 0.5
});
} catch (e) {}
}
mercyButton.setHighlight(false);
mercyButton.disabled = true;
mercyButton.destroyedByAsgore = true; // <--- Mark as destroyed for all future menus
// Add a visual shake effect to the button text for drama
if (typeof tween === "function" && mercyButton.txt) {
try {
var origX = mercyButton.txt.x;
tween(mercyButton.txt).to({
x: origX + 30
}, 80).yoyo(true).repeat(3).start();
} catch (e) {}
}
// Show a dialogue for dramatic effect
dialogueBox.setText("Asgore destroyed the MERCY button.\nThere is no turning back now.");
}, 800);
}
} else {
showSkipTutorial(false);
// Start battle with current monster
gameState = STATE.BATTLE_INTRO;
hero.visible = false;
monster.visible = true;
showDialogue(monster.getDialogue());
LK.setTimeout(function () {
gameState = STATE.BATTLE_MENU;
showBattleMenu();
}, 1200);
}
}
nextStory();
// --- Battle menu logic ---
function showBattleMenu() {
for (var i = 0; i < menuButtons.length; i++) {
menuButtons[i].visible = true;
menuButtons[i].setHighlight(i === selectedMenu);
}
for (var i = 0; i < actButtons.length; i++) actButtons[i].visible = false;
for (var i = 0; i < itemButtons.length; i++) itemButtons[i].visible = false;
dialogueBox.visible = true;
dialogueBox.setText(monster.name + ": " + monster.getDialogue());
// If Asgore, disable MERCY button
if (monster && monster.name === "Asgore" || mercyButton && mercyButton.destroyedByAsgore) {
mercyButton.bg.visible = false;
if (mercyButton.mercyAsset) mercyButton.mercyAsset.visible = false;
mercyButton.setText("MERCY");
// Defensive: Only set fill and alpha if style exists and property is settable
if (mercyButton && mercyButton.txt && mercyButton.txt.style) {
try {
if (typeof mercyButton.txt.style.fill !== "undefined" || typeof mercyButton.txt.style.fill === "string") {
mercyButton.txt.style.fill = "#ff2222"; //{92} // RED after destroyed
}
} catch (e) {}
try {
if (typeof mercyButton.txt.style.alpha !== "undefined" || typeof mercyButton.txt.style.alpha === "number") {
mercyButton.txt.style.alpha = 0.5;
}
} catch (e) {}
} else if (mercyButton && mercyButton.txt && typeof mercyButton.txt.setStyle === "function") {
// Fallback: use setStyle if available
try {
mercyButton.txt.setStyle({
fill: 0xFF2222,
//{9e} // RED after destroyed
alpha: 0.5
});
} catch (e) {}
}
mercyButton.setHighlight(false);
mercyButton.disabled = true;
} else {
var mercyState = monster.mercyAvailable === true;
mercyButton.bg.visible = !mercyState;
if (mercyState) {
mercyButton.bg.visible = false;
if (!mercyButton.mercyAsset) {
mercyButton.mercyAsset = mercyButton.attachAsset('mercy_yellow', {
anchorX: 0.5,
anchorY: 0.5
});
}
mercyButton.mercyAsset.visible = true;
} else if (mercyButton.mercyAsset) {
mercyButton.mercyAsset.visible = false;
}
}
}
// --- Act menu logic ---
function showActMenu() {
for (var i = 0; i < menuButtons.length; i++) menuButtons[i].visible = false;
for (var i = 0; i < actButtons.length; i++) {
actButtons[i].visible = true;
actButtons[i].setHighlight(i === selectedAct);
}
for (var i = 0; i < itemButtons.length; i++) itemButtons[i].visible = false;
dialogueBox.visible = true;
dialogueBox.setText("What will you do?");
}
// --- Item menu logic ---
function showItemMenu() {
for (var i = 0; i < menuButtons.length; i++) menuButtons[i].visible = false;
for (var i = 0; i < actButtons.length; i++) actButtons[i].visible = false;
// Always show egegokalp next to Bandage in the item menu
// Find Bandage and egegokalp indices in inventory
var bandageIdx = -1,
egegokalpIdx = -1;
for (var i = 0; i < playerInventory.length; i++) {
if (playerInventory[i].name === "Bandage") bandageIdx = i;
if (playerInventory[i].name === "egegokalp") egegokalpIdx = i;
}
// Build a display order: all items up to Bandage, then Bandage, then egegokalp (if present), then the rest
var displayOrder = [];
for (var i = 0; i < playerInventory.length; i++) {
if (i === bandageIdx) {
displayOrder.push(i);
if (egegokalpIdx !== -1) displayOrder.push(egegokalpIdx);
} else if (i !== egegokalpIdx) {
displayOrder.push(i);
}
}
for (var i = 0; i < itemButtons.length; i++) {
var invIdx = displayOrder[i];
var item = playerInventory[invIdx];
if (item && item.qty > 0) {
itemButtons[i].setText(item.name + " (" + item.qty + ")");
itemButtons[i].visible = true;
itemButtons[i].setHighlight(invIdx === selectedItem);
} else {
itemButtons[i].visible = false;
}
}
// Add a "Leave Item Box" button below the item buttons
if (!window.leaveItemBoxBtn) {
window.leaveItemBoxBtn = new MenuButton();
window.leaveItemBoxBtn.setText("Leave Item Box");
window.leaveItemBoxBtn.x = 2048 / 2;
window.leaveItemBoxBtn.y = 2732 - 120;
window.leaveItemBoxBtn.bg.width = 420;
window.leaveItemBoxBtn.bg.height = 150;
window.leaveItemBoxBtn.hl.width = 420 + 16;
window.leaveItemBoxBtn.hl.height = 150 + 16;
window.leaveItemBoxBtn.bg.alpha = 0.93;
window.leaveItemBoxBtn.bg.tint = 0x222244;
window.leaveItemBoxBtn.hl.alpha = 0.22;
if (window.leaveItemBoxBtn.bg && typeof window.leaveItemBoxBtn.bg.filters !== "undefined") {
window.leaveItemBoxBtn.bg.filters = [{
type: "shadow",
color: 0x000000,
blur: 16,
distance: 0,
angle: 0,
alpha: 0.5
}];
}
if (window.leaveItemBoxBtn.txt && window.leaveItemBoxBtn.txt.style) {
window.leaveItemBoxBtn.txt.style.size = 60;
window.leaveItemBoxBtn.txt.style.fill = "#fff";
}
window.leaveItemBoxBtn.visible = false;
window.leaveItemBoxBtn.setHighlight(false);
// Add tap highlight feedback
window.leaveItemBoxBtn.down = function () {
window.leaveItemBoxBtn.setHighlight(true);
};
window.leaveItemBoxBtn.up = function () {
window.leaveItemBoxBtn.setHighlight(false);
};
game.addChild(window.leaveItemBoxBtn);
}
window.leaveItemBoxBtn.visible = true;
window.leaveItemBoxBtn.setHighlight(false);
dialogueBox.visible = true;
dialogueBox.setText("Use which item?");
}
// --- Hide all menus ---
function hideAllMenus() {
for (var i = 0; i < menuButtons.length; i++) {
menuButtons[i].visible = false;
}
for (var i = 0; i < actButtons.length; i++) actButtons[i].visible = false;
for (var i = 0; i < itemButtons.length; i++) itemButtons[i].visible = false;
if (window.leaveItemBoxBtn) window.leaveItemBoxBtn.visible = false;
}
// --- Handle menu selection (touch) ---
function handleMenuTouch(x, y) {
if (gameState === STATE.BATTLE_MENU) {
for (var i = 0; i < menuButtons.length; i++) {
var btn = menuButtons[i];
if (btn.visible && x > btn.x - 200 && x < btn.x + 200 && y > btn.y - 60 && y < btn.y + 60) {
selectedMenu = i;
for (var j = 0; j < menuButtons.length; j++) menuButtons[j].setHighlight(j === i);
selectMenuOption(i);
return;
}
}
} else if (gameState === STATE.BATTLE_ACT) {
for (var i = 0; i < actButtons.length; i++) {
var btn = actButtons[i];
if (btn.visible && x > btn.x - 200 && x < btn.x + 200 && y > btn.y - 60 && y < btn.y + 60) {
selectedAct = i;
for (var j = 0; j < actButtons.length; j++) actButtons[j].setHighlight(j === i);
selectActOption(i);
return;
}
}
} else if (gameState === STATE.BATTLE_ATTACK) {
// No touch input
} else if (gameState === STATE.BATTLE_DODGE) {
// Heart movement handled in move
} else if (gameState === STATE.BATTLE_RESULT) {
// Tap to continue
resultText.visible = false;
if (monster.hp <= 0 || monster.state === "spared") {
// If there are more monsters, continue to next battle
if (currentMonsterIndex < monsters.length - 1) {
currentMonsterIndex++;
monster.visible = false;
monster = monsters[currentMonsterIndex];
monster.visible = false;
monster.actCount = 0;
monster.mercyAvailable = false;
game.addChild(monster);
updateMonsterHpBar();
// Show next story segment before next battle
storyStep++;
nextStory();
} else {
gameState = STATE.ENDING;
showEnding();
}
} else {
gameState = STATE.BATTLE_MENU;
showBattleMenu();
}
} else if (gameState === STATE.BATTLE_INTRO) {
// skip intro
} else if (gameState === STATE.STORY) {
nextStory();
} else if (gameState === STATE.BATTLE_ITEM) {
// Check leave item box button first
if (window.leaveItemBoxBtn && window.leaveItemBoxBtn.visible && x > window.leaveItemBoxBtn.x - 200 && x < window.leaveItemBoxBtn.x + 200 && y > window.leaveItemBoxBtn.y - 60 && y < window.leaveItemBoxBtn.y + 60) {
// Hide item menu, return to battle menu
window.leaveItemBoxBtn.visible = false;
hideAllMenus();
gameState = STATE.BATTLE_MENU;
showBattleMenu();
return;
}
for (var i = 0; i < itemButtons.length; i++) {
var btn = itemButtons[i];
if (btn.visible && x > btn.x - 200 && x < btn.x + 200 && y > btn.y - 60 && y < btn.y + 60) {
selectedItem = i;
for (var j = 0; j < itemButtons.length; j++) itemButtons[j].setHighlight(j === i);
selectItemOption(i);
return;
}
}
// If tap is not on any button, do nothing
}
}
// --- Menu option selection ---
function selectMenuOption(idx) {
if (idx === 0) {
// Fight
hideAllMenus();
dialogueBox.setText("You attack!");
gameState = STATE.BATTLE_ATTACK;
LK.setTimeout(function () {
// Special: Asgore takes massive damage at low HP, but survives with 1 HP and kneels
if (monster && monster.name === "Asgore") {
if (monster.hp > 8) {
monster.hp -= 5;
if (monster.hp < 8) monster.hp = 8;
updateMonsterHpBar();
dialogueBox.setText("You hit Asgore for 5 damage!");
monster.state = "angry";
monster.nextDialogue();
LK.setTimeout(function () {
startEnemyAttack();
}, 900);
} else if (monster.hp > 1) {
// Final blow: deal huge damage, but leave at 1 HP
monster.hp = 1;
updateMonsterHpBar();
dialogueBox.setText("You strike with all your might!\nAsgore kneels, defeated.");
monster.state = "neutral";
monster.nextDialogue();
// Show final choice after a pause
LK.setTimeout(function () {
// Show FIGHT and a partially repaired MERCY button
gameState = STATE.BATTLE_MENU;
// Re-enable MERCY for final choice
mercyButton.bg.visible = false;
if (!mercyButton.mercyAsset) {
mercyButton.mercyAsset = mercyButton.attachAsset('mercy_yellow', {
anchorX: 0.5,
anchorY: 0.5
});
}
mercyButton.mercyAsset.visible = true;
mercyButton.setText("MERCY");
mercyButton.txt.style.fill = "#ffff00";
mercyButton.txt.style.alpha = 1;
mercyButton.setHighlight(false);
mercyButton.disabled = false;
monster.mercyAvailable = true;
dialogueBox.setText("Asgore: \"Take my SOUL and leave this place...\"\nWill you FIGHT or show MERCY?");
showBattleMenu();
}, 1200);
} else {
// Already at 1 HP, just show final choice
updateMonsterHpBar();
dialogueBox.setText("Asgore: \"Take my SOUL and leave this place...\"\nWill you FIGHT or show MERCY?");
gameState = STATE.BATTLE_MENU;
mercyButton.bg.visible = false;
if (!mercyButton.mercyAsset) {
mercyButton.mercyAsset = mercyButton.attachAsset('mercy_yellow', {
anchorX: 0.5,
anchorY: 0.5
});
}
mercyButton.mercyAsset.visible = true;
mercyButton.setText("MERCY");
mercyButton.txt.style.fill = "#ffff00";
mercyButton.txt.style.alpha = 1;
mercyButton.setHighlight(false);
mercyButton.disabled = false;
monster.mercyAvailable = true;
showBattleMenu();
}
return;
}
// Simple attack: always hit for 5
monster.hp -= 5;
if (monster.hp < 0) monster.hp = 0;
updateMonsterHpBar();
dialogueBox.setText("You hit " + monster.name + " for 5 damage!");
monster.state = "angry";
monster.nextDialogue();
LK.setTimeout(function () {
if (monster.hp <= 0) {
battleResultText = "You defeated " + monster.name + "!";
resultText.setText(battleResultText);
resultText.visible = true;
gameState = STATE.BATTLE_RESULT;
} else {
startEnemyAttack();
}
}, 900);
}, 600);
} else if (idx === 1) {
// Act
hideAllMenus();
gameState = STATE.BATTLE_ACT;
showActMenu();
} else if (idx === 2) {
// Item
hideAllMenus();
gameState = STATE.BATTLE_ITEM;
showItemMenu();
} else if (idx === 3) {
// Mercy
if (monster && monster.name === "Asgore" || mercyButton && mercyButton.destroyedByAsgore) {
// Show destroyed message
dialogueBox.setText("The MERCY button is destroyed.\nYou have no choice but to FIGHT.");
LK.setTimeout(function () {
showBattleMenu();
}, 1200);
return;
}
if (monster.mercyAvailable === true) {
hideAllMenus();
// 1 in 3 chance to show a funny dialogue
var funnyMercyLines = ["You spare " + monster.name + ".\n" + monster.name + " gives you a confused thumbs up.", "You spare " + monster.name + ".\n" + monster.name + " wonders if this is a dating sim.", "You spare " + monster.name + ".\n" + monster.name + " is now legally your roommate.", "You spare " + monster.name + ".\n" + monster.name + " croaks in relief... or is it laughter?", "You spare " + monster.name + ".\n" + monster.name + " offers you a coupon for free flies.", "You spare " + monster.name + ".\n" + monster.name + " does a little dance.", "You spare " + monster.name + ".\n" + monster.name + " is writing about this on its blog.", "You spare " + monster.name + ".\n" + monster.name + " is now following you on RibbitBook.", "You spare " + monster.name + ".\n" + monster.name + " will remember this.", "You spare " + monster.name + ".\n" + monster.name + " is already planning your birthday party."];
var showFunny = Math.random() < 0.33;
if (showFunny) {
var idx = Math.floor(Math.random() * funnyMercyLines.length);
dialogueBox.setText(funnyMercyLines[idx]);
} else {
dialogueBox.setText("You spare " + monster.name + ".");
}
monster.state = "spared";
LK.setTimeout(function () {
battleResultText = "You showed mercy to " + monster.name + "!";
resultText.setText(battleResultText);
resultText.visible = true;
gameState = STATE.BATTLE_RESULT;
}, 900);
} else {
dialogueBox.setText("You tried to spare, but " + monster.name + " isn't ready.");
LK.setTimeout(function () {
showBattleMenu();
}, 900);
}
}
}
// --- Act option selection ---
function selectActOption(idx) {
hideAllMenus();
// Always restore menu input after Act phase, regardless of which Act was chosen
function restoreMenu() {
gameState = STATE.BATTLE_MENU;
showBattleMenu();
for (var i = 0; i < menuButtons.length; i++) {
menuButtons[i].visible = true;
}
}
if (idx === 0) {
// Check
dialogueBox.setText(monster.name + " - ATK 4 DEF 2\nA nervous little monster.");
LK.setTimeout(restoreMenu, 1200);
} else if (idx === 1) {
// Compliment
dialogueBox.setText("You compliment " + monster.name + ".\n" + monster.name + " blushes.");
monster.state = "neutral";
// --- Mercy Progression: require 5-6 successful Acts before mercy is available ---
if (typeof monster.actCount === "undefined") monster.actCount = 0;
monster.actCount++;
if (monster.actCount >= 5 + Math.floor(Math.random() * 2)) {
monster.mercyAvailable = true;
} else {
monster.mercyAvailable = false;
}
LK.setTimeout(restoreMenu, 1200);
} else if (idx === 2) {
// Threaten
dialogueBox.setText("You threaten " + monster.name + ".\n" + monster.name + " looks scared.");
monster.state = "angry";
if (typeof monster.actCount === "undefined") monster.actCount = 0;
monster.actCount++;
if (monster.actCount >= 5 + Math.floor(Math.random() * 2)) {
monster.mercyAvailable = true;
} else {
monster.mercyAvailable = false;
}
LK.setTimeout(restoreMenu, 1200);
} else if (idx === 3) {
// Cesur
dialogueBox.setText("Sezaryen yumurtaları");
monster.state = "neutral";
if (typeof monster.actCount === "undefined") monster.actCount = 0;
monster.actCount++;
if (monster.actCount >= 5 + Math.floor(Math.random() * 2)) {
monster.mercyAvailable = true;
} else {
monster.mercyAvailable = false;
}
LK.setTimeout(restoreMenu, 1200);
}
}
// --- Item option selection ---
function selectItemOption(idx) {
// Use the same displayOrder as in showItemMenu to map button index to inventory index
var bandageIdx = -1,
egegokalpIdx = -1;
for (var i = 0; i < playerInventory.length; i++) {
if (playerInventory[i].name === "Bandage") bandageIdx = i;
if (playerInventory[i].name === "egegokalp") egegokalpIdx = i;
}
var displayOrder = [];
for (var i = 0; i < playerInventory.length; i++) {
if (i === bandageIdx) {
displayOrder.push(i);
if (egegokalpIdx !== -1) displayOrder.push(egegokalpIdx);
} else if (i !== egegokalpIdx) {
displayOrder.push(i);
}
}
var invIdx = displayOrder[idx];
var item = playerInventory[invIdx];
if (item && item.qty > 0) {
if (item.name === "egegokalp") {
sezaryenYumurtasi += 10;
item.qty--;
dialogueBox.setText("You used egegokalp!\nKazandın: 10 sezaryen yumurtası!\nToplam: " + sezaryenYumurtasi);
} else {
playerHP += item.heal;
if (playerHP > playerMaxHP) playerHP = playerMaxHP;
item.qty--;
updateHPText();
dialogueBox.setText("You used " + item.name + ".\nRecovered " + item.heal + " HP!");
}
LK.setTimeout(function () {
// Always restore menu input after Item phase, just like Act
gameState = STATE.BATTLE_MENU;
showBattleMenu();
for (var i = 0; i < menuButtons.length; i++) {
menuButtons[i].visible = true;
}
}, 1200);
}
}
// --- Enemy attack phase ---
function startEnemyAttack() {
hideAllMenus();
// Show unique, attentive attack dialogue for each monster
var attackDialogue = "";
switch (monster.name) {
case "Sans":
attackDialogue = "Sans gives you a lazy grin. \"let's see if you can dodge this, kid.\"";
break;
case "Papyrus":
attackDialogue = "Papyrus: \"NYEH HEH HEH! Behold, my special attack!\"";
break;
case "Asgore":
attackDialogue = "Asgore raises his trident. Flames swirl around you!";
break;
case "Croaknight":
attackDialogue = "Croaknight channels Undyne's spirit! Blue and yellow spears fly from all sides...";
break;
case "Turtloid":
attackDialogue = "Turtloid spins in its shell, barreling toward you with surprising speed!";
break;
case "Mothmire":
attackDialogue = "Mothmire flutters above, scattering a cloud of dazzling, stinging dust!";
break;
case "Spindle":
attackDialogue = "Spindle weaves a sticky web and flings it at you!";
break;
case "Barkbark":
attackDialogue = "Barkbark barks wildly and dashes in circles, trying to trip you!";
break;
case "Glimmerbug":
attackDialogue = "Glimmerbug zips around, leaving a blinding trail of sparkling light!";
break;
case "Thornet":
attackDialogue = "Thornet buzzes furiously, diving at you with its venomous stinger!";
break;
case "Shadewisp":
attackDialogue = "Shadewisp flickers and vanishes, then lashes out from the shadows!";
break;
case "Queen Lily":
attackDialogue = "Queen Lily summons a swirling storm of razor-sharp petals your way!";
break;
default:
attackDialogue = monster.name + " attacks fiercely!";
break;
}
dialogueBox.setText(attackDialogue);
heart.visible = true;
heart.x = 2048 / 2;
heart.y = 1800;
attackSlashes = [];
attackTimer = 0;
// Unique special attacks for each boss
if (monster.name === "Sans") {
// SANS: Fast bone waves, gaster blasters, gravity jumps
attackDuration = 420;
// Clean up helpers
if (typeof sansBones !== "undefined" && sansBones && sansBones.length) {
for (var i = 0; i < sansBones.length; i++) {
if (sansBones[i] && typeof sansBones[i].destroy === "function") sansBones[i].destroy();
}
}
var sansBones = [];
if (typeof gasterBlasters !== "undefined" && gasterBlasters && gasterBlasters.length) {
for (var i = 0; i < gasterBlasters.length; i++) {
if (gasterBlasters[i] && typeof gasterBlasters[i].destroy === "function") gasterBlasters[i].destroy();
}
}
var gasterBlasters = [];
if (typeof gasterBlasterBeams !== "undefined" && gasterBlasterBeams && gasterBlasterBeams.length) {
for (var i = 0; i < gasterBlasterBeams.length; i++) {
if (gasterBlasterBeams[i] && typeof gasterBlasterBeams[i].destroy === "function") gasterBlasterBeams[i].destroy();
}
}
var gasterBlasterBeams = [];
// 1. Fast bone waves from left and right
for (var w = 0; w < 3; w++) {
LK.setTimeout(function (waveIdx) {
for (var i = 0; i < 6; i++) {
var bone = new AttackSlash();
bone.y = 1200 + i * 180;
bone.x = waveIdx % 2 === 0 ? 200 : 2048 - 200;
bone.direction = bone.x < 1024 ? 1 : -1;
bone.speed = 32 + Math.random() * 6;
bone.update = function () {
this.x += this.speed * this.direction;
if (this.x < 0 || this.x > 2048) this.active = false;
};
attackSlashes.push(bone);
game.addChild(bone);
sansBones.push(bone);
}
}, w * 220, w);
}
// 2. Gaster Blasters: fire from top/bottom, then diagonals
var spawnGasterBlaster = function spawnGasterBlaster(x, y, angle, beamLength, beamWidth, delay, duration) {
var blaster = LK.getAsset('blaster', {
anchorX: 0.5,
anchorY: 0.5,
width: 120,
height: 120,
x: x,
y: y
});
blaster.tint = 0x00ffff;
blaster.alpha = 0.95;
game.addChild(blaster);
gasterBlasters.push(blaster);
var beam = LK.getAsset('dialogue_box', {
anchorX: 0.0,
anchorY: 0.5,
width: beamLength,
height: beamWidth,
x: x,
y: y
});
beam.tint = 0x00ffff;
beam.alpha = 0.0;
beam.rotation = angle;
game.addChild(beam);
gasterBlasterBeams.push(beam);
LK.setTimeout(function () {
beam.alpha = 0.95;
LK.setTimeout(function () {
beam.alpha = 0.0;
if (typeof beam.destroy === "function") beam.destroy();
}, duration);
}, delay);
LK.setTimeout(function () {
if (typeof blaster.destroy === "function") blaster.destroy();
}, delay + duration);
};
LK.setTimeout(function () {
// Top and bottom
spawnGasterBlaster(400, 1100, Math.PI / 2, 900, 80, 100, 400);
spawnGasterBlaster(1648, 2550, -Math.PI / 2, 900, 80, 100, 400);
}, 700);
LK.setTimeout(function () {
// Diagonals
spawnGasterBlaster(200, 1200, Math.PI / 4, 1200, 80, 0, 400);
spawnGasterBlaster(2048 - 200, 1200, 3 * Math.PI / 4, 1200, 80, 0, 400);
spawnGasterBlaster(200, 2550, -Math.PI / 4, 1200, 80, 0, 400);
spawnGasterBlaster(2048 - 200, 2550, -(3 * Math.PI) / 4, 1200, 80, 0, 400);
}, 1200);
// 3. Gravity jump: heart is pulled up, then down
LK.setTimeout(function () {
if (typeof heart !== "undefined" && heart !== null && typeof tween === "function") {
try {
tween(heart).to({
y: 1200
}, 300).yoyo(true).repeat(1).start();
} catch (e) {
heart.y = 1200;
}
}
}, 1800);
} else if (monster.name === "Papyrus") {
// PAPYRUS: Slow warning bones, blue attacks
attackDuration = 320;
// 1. Warning lines (visual only)
for (var i = 0; i < 2; i++) {
LK.setTimeout(function (j) {
var warn = LK.getAsset('dialogue_box', {
anchorX: 0.5,
anchorY: 0.5,
width: 2048,
height: 30,
x: 2048 / 2,
y: 1200 + j * 800
});
warn.tint = 0x00aaff;
warn.alpha = 0.5;
game.addChild(warn);
LK.setTimeout(function () {
if (typeof warn.destroy === "function") warn.destroy();
}, 400);
}, i * 400, i);
}
// 2. Slow bones from top and bottom
for (var w = 0; w < 2; w++) {
LK.setTimeout(function (waveIdx) {
for (var i = 0; i < 4; i++) {
var bone = new AttackSlash();
bone.x = 400 + i * 400;
bone.y = waveIdx === 0 ? 1050 : 2550;
bone.direction = waveIdx === 0 ? 1 : -1;
bone.speed = 12 + Math.random() * 4;
bone.update = function () {
this.y += this.speed * this.direction;
if (this.y < 1000 || this.y > 2600) this.active = false;
};
attackSlashes.push(bone);
game.addChild(bone);
}
}, w * 300, w);
}
// 3. Blue bone (hurts only if moving)
LK.setTimeout(function () {
var blueBone = new AttackSlash();
blueBone.x = 2048 - 200;
blueBone.y = 1800;
blueBone.direction = -1;
blueBone.speed = 10;
blueBone.spearType = "blue";
blueBone.update = function () {
this.x += this.speed * this.direction;
if (this.x < 200) this.active = false;
};
attackSlashes.push(blueBone);
game.addChild(blueBone);
}, 1200);
// 4. Final warning line and bone
LK.setTimeout(function () {
var warn = LK.getAsset('dialogue_box', {
anchorX: 0.5,
anchorY: 0.5,
width: 2048,
height: 30,
x: 2048 / 2,
y: 1800
});
warn.tint = 0x00aaff;
warn.alpha = 0.5;
game.addChild(warn);
LK.setTimeout(function () {
if (typeof warn.destroy === "function") warn.destroy();
}, 400);
var bone = new AttackSlash();
bone.x = 200;
bone.y = 1800;
bone.direction = 1;
bone.speed = 18;
bone.update = function () {
this.x += this.speed * this.direction;
if (this.x > 2048 - 200) this.active = false;
};
attackSlashes.push(bone);
game.addChild(bone);
}, 1800);
} else if (monster.name === "Asgore") {
// ASGORE: Fire trident waves, symmetrical blasts
attackDuration = 220;
// 1. Five tridents fall from above, fast, less spacing
for (var i = 0; i < 5; i++) {
LK.setTimeout(function (j) {
var trident = new AttackSlash();
trident.x = 600 + j * 220;
trident.y = 1050;
trident.direction = 1;
trident.speed = 28 + Math.random() * 8;
trident.update = function () {
this.y += this.speed * this.direction;
if (this.y > 2550) this.active = false;
};
attackSlashes.push(trident);
game.addChild(trident);
}, i * 100, i);
}
// 2. Symmetrical cross slashes (left/right, same y, repeated)
for (var k = 0; k < 3; k++) {
(function (crossIdx) {
LK.setTimeout(function () {
var yCross = 1300 + crossIdx * 350 + Math.random() * 100;
var left = new AttackSlash();
left.x = 200;
left.y = yCross;
left.direction = 1;
left.speed = 34;
left.update = function () {
this.x += this.speed * this.direction;
if (this.x > 2048) this.active = false;
};
attackSlashes.push(left);
game.addChild(left);
var right = new AttackSlash();
right.x = 2048 - 200;
right.y = yCross;
right.direction = -1;
right.speed = 34;
right.update = function () {
this.x += this.speed * this.direction;
if (this.x < 0) this.active = false;
};
attackSlashes.push(right);
game.addChild(right);
}, 600 + crossIdx * 180);
})(k);
}
// 3. Final flame wave (centered, fast)
LK.setTimeout(function () {
for (var i = 0; i < 3; i++) {
var flame = new AttackSlash();
flame.x = 900 + i * 120;
flame.y = 1050;
flame.direction = 1;
flame.speed = 36 + Math.random() * 6;
flame.update = function () {
this.y += this.speed * this.direction;
if (this.y > 2550) this.active = false;
};
attackSlashes.push(flame);
game.addChild(flame);
}
}, 1200);
} else {
// Default: fallback to generic pattern
attackDuration = 90 + Math.floor(Math.random() * 30);
}
gameState = STATE.BATTLE_DODGE;
showJoystick(true);
}
// --- Endings ---
function showEnding() {
hideAllMenus();
heart.visible = false;
monster.visible = false;
dialogueBox.visible = false;
monsterHpBarBg.visible = false;
monsterHpBar.visible = false;
monsterHpText.visible = false;
if (monster && monster.name === "Asgore") {
if (monster.state === "spared") {
resultText.setText("You spared Asgore.\nHe smiles, tears in his eyes.\nA gentle path lies ahead.");
} else if (monster.hp <= 0) {
resultText.setText("You defeated Asgore.\nHe kneels, defeated.\nBut at what cost?");
} else {
resultText.setText("The story continues...");
}
} else {
if (monster.state === "spared") {
resultText.setText("You made a friend: " + monster.name + ".\nA gentle path lies ahead.");
} else if (monster.hp <= 0) {
resultText.setText("You defeated " + monster.name + ".\nBut at what cost?");
} else {
resultText.setText("The story continues...");
}
}
resultText.visible = true;
LK.setTimeout(function () {
LK.showYouWin();
}, 1800);
}
// --- Input handling ---
// Virtual joystick for heart movement (touch-optimized)
// Ava: This joystick appears during enemy attack phase and is fully touch/mouse compatible.
var joystick = {
active: false,
baseX: 0,
baseY: 0,
stickX: 0,
stickY: 0,
radius: 120,
stickRadius: 60,
visible: false,
baseAsset: null,
stickAsset: null
};
// Create joystick assets (will be positioned below the heart dynamically)
joystick.baseAsset = LK.getAsset('joystick_base', {
anchorX: 0.5,
anchorY: 0.5,
x: 0,
// will be set dynamically
y: 0,
// will be set dynamically
width: joystick.radius * 2,
height: joystick.radius * 2
});
joystick.baseAsset.alpha = 0.25;
joystick.baseAsset.visible = false;
game.addChild(joystick.baseAsset);
// Use a simple circle for joystick stick instead of a heart to avoid duplicate heart visuals
joystick.stickAsset = LK.getAsset('joystick_stick', {
anchorX: 0.5,
anchorY: 0.5,
x: 0,
// will be set dynamically
y: 0,
// will be set dynamically
width: joystick.stickRadius * 2,
height: joystick.stickRadius * 2
});
joystick.stickAsset.alpha = 0.7;
joystick.stickAsset.visible = false;
game.addChild(joystick.stickAsset);
// Add a "MOVE" button styled like the menu buttons, and make it moveable
joystick.moveButton = new MenuButton();
joystick.moveButton.setText("MOVE");
joystick.moveButton.x = 0; // will be set dynamically
joystick.moveButton.y = 0; // will be set dynamically
joystick.moveButton.visible = false;
joystick.moveButton.setHighlight(false);
game.addChild(joystick.moveButton);
// --- Show joystick at start of game (story phase) ---
function showJoystick(show) {
joystick.visible = show;
joystick.baseAsset.visible = show;
joystick.stickAsset.visible = show;
joystick.moveButton.visible = false;
if (show) {
// Place joystick below the heart if heart is visible, otherwise below hero (start of game)
var centerX, baseY;
if (heart && heart.visible) {
centerX = heart.x;
baseY = heart.y + heart.height / 2;
} else if (hero && hero.visible) {
centerX = hero.x;
baseY = hero.y + hero.height / 2;
} else {
centerX = 2048 / 2;
baseY = 2732 / 2 + 400 + 60; // fallback
}
var joystickOffset = 120 + joystick.radius;
var joyX = centerX;
var joyY = baseY + joystickOffset;
// Adjust joystick Y placement for new arena
var minJoyY = Math.max(baseY + 100, 1050 + 100);
var maxJoyY = Math.min(2732 - joystick.radius - 40, 2550 + 100);
if (joyY > maxJoyY) joyY = maxJoyY;
if (joyY < minJoyY) joyY = minJoyY;
joystick.baseAsset.x = joyX;
joystick.baseAsset.y = joyY;
joystick.stickAsset.x = joyX;
joystick.stickAsset.y = joyY;
joystick.baseX = joyX;
joystick.baseY = joyY;
joystick.stickX = joyX;
joystick.stickY = joyY;
} else {
joystick.active = false;
joystick.moveButton.visible = false;
}
}
// Show joystick at the start of the game (story phase)
showJoystick(true);
// Returns true if (x, y) is inside joystick base
function isInJoystick(x, y) {
var dx = x - joystick.baseAsset.x;
var dy = y - joystick.baseAsset.y;
return dx * dx + dy * dy <= joystick.radius * joystick.radius;
}
var draggingHeart = false;
game.down = function (x, y, obj) {
// Boss select button (top right)
if (bossSelectBtn && bossSelectBtn.visible && x > bossSelectBtn.x - 200 && x < bossSelectBtn.x + 200 && y > bossSelectBtn.y - 60 && y < bossSelectBtn.y + 60) {
bossSelectBtn.setHighlight(true);
bossSelectBtn.tap();
return;
}
// Boss select menu
if (gameState === STATE.BOSS_SELECT && bossSelectButtons && bossSelectButtons.length) {
for (var i = 0; i < bossSelectButtons.length; i++) {
var btn = bossSelectButtons[i];
if (btn.visible && x > btn.x - 200 && x < btn.x + 200 && y > btn.y - 60 && y < btn.y + 60) {
btn.setHighlight(true);
if (typeof btn.tap === "function") btn.tap();
return;
}
}
}
if (gameState === STATE.BATTLE_DODGE) {
// If touch is inside joystick, activate joystick
if (isInJoystick(x, y)) {
joystick.active = true;
joystick.baseX = joystick.baseAsset.x;
joystick.baseY = joystick.baseAsset.y;
joystick.stickX = x;
joystick.stickY = y;
joystick.stickAsset.x = x;
joystick.stickAsset.y = y;
}
} else if (gameState === STATE.STORY && skipTutorialBtn.visible && x > skipTutorialBtn.x - 200 && x < skipTutorialBtn.x + 200 && y > skipTutorialBtn.y - 60 && y < skipTutorialBtn.y + 60) {
// Skip tutorial button pressed
// Fast-forward story to battle
storyStep = storyTexts.length;
showSkipTutorial(false);
nextStory();
return;
} else if (gameState === STATE.BATTLE_MENU || gameState === STATE.BATTLE_ACT || gameState === STATE.BATTLE_ITEM || gameState === STATE.BATTLE_RESULT || gameState === STATE.BATTLE_INTRO || gameState === STATE.STORY) {
handleMenuTouch(x, y);
}
};
game.up = function (x, y, obj) {
joystick.active = false;
joystick.stickAsset.x = joystick.baseAsset.x;
joystick.stickAsset.y = joystick.baseAsset.y;
lastTouchY = null;
};
// Unified move handler for joystick and menu navigation (NO mouse/drag/hover/Enter)
game.move = function (x, y, obj) {
if (gameState === STATE.BATTLE_DODGE) {
// Joystick movement only
if (joystick.active) {
var dx = x - joystick.baseX;
var dy = y - joystick.baseY;
var dist = Math.sqrt(dx * dx + dy * dy);
if (dist > joystick.radius) {
dx = dx * joystick.radius / dist;
dy = dy * joystick.radius / dist;
}
joystick.stickX = joystick.baseX + dx;
joystick.stickY = joystick.baseY + dy;
joystick.stickAsset.x = joystick.stickX;
joystick.stickAsset.y = joystick.stickY;
// Move heart
// Make joystick move the heart even faster by increasing the multiplier
var moveSpeed = heart.speed * 2.3; // Increased from 1.7x to 2.3x for even faster movement
var moveX = dx / joystick.radius;
var moveY = dy / joystick.radius;
// Even further expanded arena for maximum space to dodge
var minX = 40,
maxX = 2048 - 40,
minY = 1050,
maxY = 2550;
heart.x = clamp(heart.x + moveX * moveSpeed, minX, maxX);
heart.y = clamp(heart.y + moveY * moveSpeed, minY, maxY);
}
}
};
// --- Main update loop ---
game.update = function () {
// Update boss select menu HP text if visible
if (gameState === STATE.BOSS_SELECT && bossSelectButtons && bossSelectButtons.length) {
for (var i = 0; i < monsters.length; i++) {
var btn = bossSelectButtons[i];
if (btn && btn.txt) {
var hpStr = typeof monsters[i].hp !== "undefined" && typeof monsters[i].maxHp !== "undefined" ? " (" + monsters[i].hp + "/" + monsters[i].maxHp + " HP)" : "";
btn.setText(monsters[i].name + hpStr);
}
}
}
// Update monster health bar every frame
updateMonsterHpBar();
// Always ensure monster is visible during battle phases
if (gameState === STATE.BATTLE_INTRO || gameState === STATE.BATTLE_MENU || gameState === STATE.BATTLE_ACT || gameState === STATE.BATTLE_ATTACK || gameState === STATE.BATTLE_DODGE) {
if (monster && !monster.visible) monster.visible = true;
}
// Heart update
if (gameState === STATE.BATTLE_DODGE) {
// Always lock joystick directly below the heart, centered, fixed in place
var heartCenterX = heart.x;
var heartBottomY = heart.y + heart.height / 2;
// Place joystick at 20% from the bottom of the screen, centered horizontally
var joyX = heartCenterX;
var joyY = heartBottomY + 120 + joystick.radius;
// Clamp so joystick never goes off screen, but always stays below the heart
var minJoyY = Math.max(heart.y + 100, 1050 + 100);
var maxJoyY = Math.min(2732 - joystick.radius - 40, 2550 + 100);
if (joyY > maxJoyY) joyY = maxJoyY;
if (joyY < minJoyY) joyY = minJoyY;
joystick.baseAsset.x = joyX;
joystick.baseAsset.y = joyY;
if (!joystick.active) {
joystick.stickAsset.x = joyX;
joystick.stickAsset.y = joyY;
joystick.stickX = joyX;
joystick.stickY = joyY;
}
joystick.baseX = joyX;
joystick.baseY = joyY;
// Spawn slashes
if (attackTimer % 24 === 0 && attackSlashes.length < 5) {
var slash = new AttackSlash();
// Expanded vertical range for slashes to match even larger arena
slash.y = 1050 + Math.random() * 1500;
slash.x = Math.random() < 0.5 ? 2048 - 200 : 200;
slash.direction = slash.x < 1024 ? 1 : -1;
attackSlashes.push(slash);
game.addChild(slash);
}
// Update slashes
for (var i = attackSlashes.length - 1; i >= 0; i--) {
var s = attackSlashes[i];
s.update();
// Collision with heart
var dx = s.x - heart.x,
dy = s.y - heart.y;
if (s.active && dx * dx + dy * dy < 60 * 60) {
// UNDYNESQUE: If Croaknight and spearType is blue/yellow, apply special rules
if (monster && monster.name === "Croaknight" && typeof s.spearType !== "undefined") {
// Blue: only hurts if heart is moving
// Yellow: only hurts if heart is NOT moving
var isMoving = false;
if (typeof joystick !== "undefined" && joystick.active) {
var moveDx = joystick.stickX - joystick.baseX;
var moveDy = joystick.stickY - joystick.baseY;
isMoving = Math.abs(moveDx) > 6 || Math.abs(moveDy) > 6;
}
if (s.spearType === "blue" && isMoving || s.spearType === "yellow" && !isMoving) {
s.active = false;
playerHP -= 4;
updateHPText();
LK.effects.flashObject(heart, 0xff0000, 400);
if (playerHP <= 0) {
playerHP = 0;
updateHPText();
LK.effects.flashScreen(0xff0000, 1200);
LK.showGameOver();
return;
}
}
// If not the right condition, don't hurt, but still destroy spear
s.active = false;
} else {
s.active = false;
playerHP -= 4;
updateHPText();
LK.effects.flashObject(heart, 0xff0000, 400);
if (playerHP <= 0) {
playerHP = 0;
updateHPText();
LK.effects.flashScreen(0xff0000, 1200);
LK.showGameOver();
return;
}
}
}
if (!s.active) {
s.destroy();
attackSlashes.splice(i, 1);
}
}
attackTimer++;
if (attackTimer > attackDuration) {
// End attack phase
for (var i = 0; i < attackSlashes.length; i++) attackSlashes[i].destroy();
attackSlashes = [];
heart.visible = false;
showJoystick(false);
gameState = STATE.BATTLE_MENU;
showBattleMenu();
}
}
};
// --- Touch navigation for menu (swipe up/down to change selection) ---
var lastTouchY = null;
make chara from underatle. In-Game asset. 2d. High contrast. No shadows
make undertale heart. In-Game asset. 2d. High contrast. No shadows
make it undertale mercy button. In-Game asset. 2d. High contrast. No shadows
make sans bones. In-Game asset. 2d. High contrast. No shadows
make bones like undartale sans but just one bone. In-Game asset. 2d. High contrast. No shadows
black rectangle. In-Game asset. 2d. High contrast. No shadows
make funny thing. In-Game asset. 2d. High contrast. No shadows
asgore undeertale. In-Game asset. 2d. High contrast. No shadows
black rectangle. In-Game asset. 2d. High contrast. No shadows