/**** * Plugins ****/ var tween = LK.import("@upit/tween.v1"); /**** * Classes ****/ // BattleScreen: Handles the turn-based combat var BattleScreen = Container.expand(function () { var self = Container.call(this); self.visible = false; // Initially hidden self.alpha = 0; // Set alpha to 0 for fade-in animation // Background overlay self.overlay = self.attachAsset('emptyTileCover', { width: 2048, height: 2732, alpha: 0.8 }); // Monster display self.monsterDisplay = self.attachAsset('monsterTileMonster', { anchorX: 0.5, anchorY: 0.5, x: 2048 - 300, // Position near top-right corner within the battle area y: 2732 * 0.2, // Position near top-right corner within the battle area, moved higher scaleX: 4, // Increased scale scaleY: 4 // Increased scale }); // Monster stats text self.monsterStatText = new Text2('', { size: 60, fill: "#fff", font: "Impact" // Using a pixel-style font }); self.monsterStatText.anchor.set(0.5, 0); // Anchor to top-center self.monsterStatText.x = self.monsterDisplay.x; self.monsterStatText.y = self.monsterDisplay.y + self.monsterDisplay.height * self.monsterDisplay.scaleY / 2 + 60; // Positioned closer below monster image self.addChild(self.monsterStatText); // Player stats text (in battle) self.playerBattleStatText = new Text2('', { size: 60, fill: "#fff", font: "Impact" // Using a pixel-style font }); self.playerDisplay = self.attachAsset('character', { anchorX: 0.5, anchorY: 0.5, x: 300, // Position near bottom-left corner within the battle area y: 2732 * 0.8 - 350, // Position near bottom-left corner within the battle area, moved higher scaleX: 4, // Increased scale scaleY: 4 // Increased scale }); self.playerBattleStatText.anchor.set(0.5, 0); // Anchor to top-center // Set stat text position to where hero will appear self.playerBattleStatText.x = self.playerDisplay.x; // Relative to player display X self.playerBattleStatText.y = self.playerDisplay.y + self.playerDisplay.height * self.playerDisplay.scaleY / 2 + 60; // Positioned closer below player, accounting for new scale self.addChild(self.playerBattleStatText); // Action button (Attack) self.attackBtn = new Text2('ATTACK', { size: 80, fill: "#fff", font: "Impact" // Using a pixel-style font }); self.attackBtn.anchor.set(0.5, 0.5); // Action button (Items) - Initial declaration for width calculation // The actual self.itemsBtn will be (re)declared later. This corresponds to original first itemsBtn {J} var tempItemsBtn = new Text2('ITEMS', { size: 80, fill: "#fff", font: "Impact" // Using a pixel-style font }); // Action button (Run) - Initial declaration for width calculation // The actual self.runBtn will be (re)declared later. This corresponds to original first runBtn {D} var tempRunBtn = new Text2('RUN', { size: 80, fill: "#fff", font: "Impact" // Using a pixel-style font }); // Calculate total width using the three distinct buttons' initial declarations var totalButtonWidth = self.attackBtn.width + tempItemsBtn.width + tempRunBtn.width + 120; // Widths + 2*60px spacing var startX = (2048 - totalButtonWidth) / 2; // --- Position buttons in order: Attack, Items, Run --- var commonButtonY = 2732 - 300; // Common Y position for all buttons // 1. ATTACK Button (leftmost) // self.attackBtn is already defined and its anchor is set. self.attackBtn.x = startX + self.attackBtn.width / 2; self.attackBtn.y = commonButtonY; self.addChild(self.attackBtn); self.attackBtn.down = function () { if (!self.visible) return; self.playerTurn(); }; // 2. ITEMS Button (center) // Action button (Items)//{Z} // This (re)declaration corresponds to original second itemsBtn {Z} self.itemsBtn = new Text2('ITEMS', { size: 80, fill: "#fff", font: "Impact" // Using a pixel-style font }); self.itemsBtn.anchor.set(0.5, 0.5); self.itemsBtn.x = startX + self.attackBtn.width + 60 + self.itemsBtn.width / 2; self.itemsBtn.y = commonButtonY; self.addChild(self.itemsBtn); self.itemsBtn.down = function () { if (!self.visible || player.inventory.potionCount <= 0) return; // Only allow if visible and player has potions self.showBattleItemsMenu(); }; // 3. RUN Button (rightmost) // Action button (Run)//{R} // This (re)declaration corresponds to original second runBtn {R} self.runBtn = new Text2('RUN', { size: 80, fill: "#fff", font: "Impact" // Using a pixel-style font }); self.runBtn.anchor.set(0.5, 0.5); self.runBtn.x = startX + self.attackBtn.width + 60 + self.itemsBtn.width + 60 + self.runBtn.width / 2; self.runBtn.y = commonButtonY; self.addChild(self.runBtn); self.runBtn.down = function () { if (!self.visible) return; self.tryToRun(); }; self.currentMonster = null; // Reference to the monster tile being fought // Battle Items Menu self.battleItemsMenu = new Container(); self.battleItemsMenu.visible = false; self.battleItemsMenuOverlay = self.battleItemsMenu.attachAsset('emptyTileCover', { width: 800, height: 500, alpha: 0.9, anchorX: 0.5, anchorY: 0.5, x: 2048 / 2, y: 2732 / 2 }); self.battleItemsMenu.titleText = new Text2('Use Item', { size: 70, fill: 0x7DD3FC, // Light Blue font: "Impact" // Using a pixel-style font }); self.battleItemsMenu.titleText.anchor.set(0.5, 0.5); self.battleItemsMenu.titleText.x = 2048 / 2; self.battleItemsMenu.titleText.y = 2732 / 2 - 150; self.battleItemsMenu.addChild(self.battleItemsMenu.titleText); self.battleItemsMenu.potionCountText = new Text2('', { size: 60, fill: 0xA3E635, // Green font: "Impact" // Using a pixel-style font }); self.battleItemsMenu.potionCountText.anchor.set(0, 0.5); self.battleItemsMenu.potionCountText.x = 2048 / 2 - 120; self.battleItemsMenu.potionCountText.y = 2732 / 2 - 50; self.battleItemsMenu.addChild(self.battleItemsMenu.potionCountText); // DRINK button for potion, placed next to quantity self.battleItemsMenu.usePotionBtn = new Text2('DRINK', { size: 60, fill: 0xA3E635, font: "Impact" }); self.battleItemsMenu.usePotionBtn.anchor.set(0, 0.5); self.battleItemsMenu.usePotionBtn.x = self.battleItemsMenu.potionCountText.x + 350; self.battleItemsMenu.usePotionBtn.y = self.battleItemsMenu.potionCountText.y; self.battleItemsMenu.addChild(self.battleItemsMenu.usePotionBtn); self.battleItemsMenu.usePotionBtn.down = function () { if (player.inventory.potionCount > 0 && player.hp < player.maxHp) { player.inventory.potionCount--; player.hp += 20; // Heal amount if (player.hp > player.maxHp) player.hp = player.maxHp; self.updateBattleStats(); // Update battle stats display self.hideBattleItemsMenu(); self.showConfirmationText('Used a Life Potion!', 0xA3E635); // Show confirmation message playerInventoryScreen.updateInventoryDisplay(); // Update inventory screen if open showGameGUIWithTransition(); } }; self.battleItemsMenu.closeBtn = new Text2('Close', { size: 60, fill: "#fff", font: "Impact" // Using a pixel-style font }); self.battleItemsMenu.closeBtn.anchor.set(0.5, 0.5); self.battleItemsMenu.closeBtn.x = 2048 / 2; self.battleItemsMenu.closeBtn.y = 2732 / 2 + 150; self.battleItemsMenu.addChild(self.battleItemsMenu.closeBtn); self.battleItemsMenu.closeBtn.down = function () { self.hideBattleItemsMenu(); }; self.addChild(self.battleItemsMenu); self._confirmationText = null; // Text for temporary messages (e.g., item used) self.showConfirmationText = function (message, color) { if (self._confirmationText && typeof self._confirmationText.destroy === 'function') { self._confirmationText.destroy(); } self._confirmationText = new Text2(message, { size: 60, fill: color, font: "Impact" // Using a pixel-style font }); self._confirmationText.anchor.set(0.5, 0.5); self._confirmationText.x = 2048 / 2; self._confirmationText.y = 2732 / 2 + 300; // Position below action buttons self.addChild(self._confirmationText); tween(self._confirmationText, { alpha: 0 }, { duration: 1000, delay: 800, onFinish: function onFinish() { self._confirmationText.destroy(); self._confirmationText = null; } }); }; // Start battle self.startBattle = function (monsterTile) { self.currentMonster = monsterTile; // Set monster image in battle screen based on monster type if (monsterTile instanceof PoisonMonsterTile) { // Remove old asset if present if (self.monsterDisplay) { self.removeChild(self.monsterDisplay); } self.monsterDisplay = self.attachAsset('poisonMonsterTileMonster', { anchorX: 0.5, anchorY: 0.5, x: 2048 - 300, y: 2732 * 0.2, scaleX: 4, scaleY: 4 }); // Reposition monsterStatText below the new monsterDisplay self.monsterStatText.x = self.monsterDisplay.x; self.monsterStatText.y = self.monsterDisplay.y + self.monsterDisplay.height * self.monsterDisplay.scaleY / 2 + 60; self.addChild(self.monsterDisplay); } else { // Remove old asset if present if (self.monsterDisplay) { self.removeChild(self.monsterDisplay); } self.monsterDisplay = self.attachAsset('monsterTileMonster', { anchorX: 0.5, anchorY: 0.5, x: 2048 - 300, y: 2732 * 0.2, scaleX: 4, scaleY: 4 }); // Reposition monsterStatText below the new monsterDisplay self.monsterStatText.x = self.monsterDisplay.x; self.monsterStatText.y = self.monsterDisplay.y + self.monsterDisplay.height * self.monsterDisplay.scaleY / 2 + 60; self.addChild(self.monsterDisplay); } self.visible = true; self.alpha = 0; // Play encounter music when a player fights a monster in the battle screen LK.playMusic('encounter'); game.addChild(self); // Add battle screen to game updateGUI(); // Ensure main GUI elements are hidden // Hide hero/monster and attack button for intro animation self.monsterDisplay.visible = false; self.playerDisplay.visible = false; self.monsterStatText.visible = false; self.playerBattleStatText.visible = false; self.attackBtn.visible = false; self.runBtn.visible = false; // Initially hidden self.itemsBtn.visible = false; // Hide items button // Fade in battle screen tween(self, { alpha: 1 }, { duration: 300, easing: tween.easeOut, onFinish: function onFinish() { // Show "You encountered a monster!" message var encounterText = new Text2('You encountered a monster!', { size: 110, fill: 0xFFE066, font: "Impact" // Using a pixel-style font }); encounterText.anchor.set(0.5, 0.5); encounterText.x = 2048 / 2; encounterText.y = 2732 / 2 - 200; self.addChild(encounterText); tween(encounterText, { alpha: 0 }, { duration: 900, delay: 900, easing: tween.easeOut, onFinish: function onFinish() { encounterText.destroy(); // Prepare hero/monster offscreen for slide-in // Save original positions var heroTargetX = 300; var heroTargetY = 2732 * 0.8 - 350; // Adjusted Y position var monsterTargetX = 2048 - 300; var monsterTargetY = 2732 * 0.2; // Adjusted Y position self.playerDisplay.x = -self.playerDisplay.width; // Offscreen left self.playerDisplay.y = heroTargetY; self.monsterDisplay.x = 2048 + self.monsterDisplay.width; // Offscreen right self.monsterDisplay.y = monsterTargetY; self.playerDisplay.visible = true; self.monsterDisplay.visible = true; self.monsterStatText.visible = false; self.playerBattleStatText.visible = false; // Slide in hero and monster simultaneously var slideInCount = 0; function afterSlideIn() { slideInCount++; if (slideInCount === 2) { // Show stats and decide who attacks first var firstAttacker = "monster"; // Always monster attacks first self.monsterStatText.visible = true; self.playerBattleStatText.visible = true; self.updateBattleStats(); // Randomize who attacks first: 0 = player, 1 = monster var firstAttacker = Math.random() < 0.5 ? "player" : "monster"; var firstText = firstAttacker === "monster" ? "Monster strikes first!" : "You start!"; var firstTextColor = firstAttacker === "monster" ? 0xff0000 : 0xFFE066; var whoFirstText = new Text2(firstText, { size: 110, fill: firstTextColor, font: "Impact" // Using a pixel-style font }); whoFirstText.anchor.set(0.5, 0.5); whoFirstText.x = 2048 / 2; whoFirstText.y = 2732 / 2; self.addChild(whoFirstText); tween(whoFirstText, { alpha: 0 }, { duration: 900, delay: 900, easing: tween.easeOut, onFinish: function onFinish() { whoFirstText.destroy(); // Show attack button and start the correct turn after a short delay if (firstAttacker === "monster") { // Hide player actions until after monster strikes self.attackBtn.visible = false; self.runBtn.visible = false; self.itemsBtn.visible = false; LK.setTimeout(self.monsterTurn, 500); } else { self.attackBtn.visible = true; self.runBtn.visible = true; // Show run button after intro self.itemsBtn.visible = true; // Show items button after intro } // If player starts, do nothing: player must press attackBtn } }); } } tween(self.playerDisplay, { x: heroTargetX }, { duration: 600, easing: tween.cubicOut, onFinish: afterSlideIn }); tween(self.monsterDisplay, { x: monsterTargetX }, { duration: 600, easing: tween.cubicOut, onFinish: afterSlideIn }); } }); } }); }; // End battle self.endBattle = function (win) { self.visible = false; self.alpha = 0; // Reset alpha self.attackBtn.visible = false; self.runBtn.visible = false; // Hide run button self.itemsBtn.visible = false; // Hide items button self.hideBattleItemsMenu(); // Hide item menu if (self._confirmationText) { // Hide any confirmation text self._confirmationText.destroy(); self._confirmationText = null; } if (win) { // expGained and player.gainExp(expGained) are now handled in playerTurn's onFinish monster death animation self.currentMonster.defeat(); monstersLeft--; showGameGUIWithTransition(); if (monstersLeft <= 0) { // If not last level, advance to next level if (currentLevel < LEVELS.length - 1) { // Show level end screen levelEndScreen.startLevelEndScreen(); } else { LK.showYouWin(); gameOver = true; } } else {} // Player lost, game over is handled in handleTileDown } self.currentMonster = null; self.parent.removeChild(self); // Remove battle screen from game // Play gametitle music when battle screen ends LK.playMusic('gametitle'); }; // Update battle stats display self.updateBattleStats = function () { if (self.currentMonster) { self.monsterStatText.setText('HP:' + self.currentMonster.hp + '/' + self.currentMonster.maxHp + ' DMG:' + self.currentMonster.damage); self.playerBattleStatText.setText('HP:' + player.hp + '/' + player.maxHp + ' DMG:' + player.damage); } // Grey out and disable ITEMS button if player has no potions if (player.inventory.potionCount <= 0) { self.itemsBtn.alpha = 0.5; self.itemsBtn.interactive = false; } else { self.itemsBtn.alpha = 1; self.itemsBtn.interactive = true; } }; // Player's turn self.playerTurn = function () { if (!self.currentMonster) return; // Animate player attack: slide playerDisplay forward diagonally, then back, then apply damage var originalX = self.playerDisplay.x; var originalY = self.playerDisplay.y; var attackX = self.monsterDisplay.x - self.monsterDisplay.width * self.monsterDisplay.scaleX / 2 - 60; // Stop a bit before monster var attackY = self.monsterDisplay.y + self.monsterDisplay.height * self.monsterDisplay.scaleY / 2 - 60; // Move up towards monster self.attackBtn.visible = false; // Hide attack button during animation self.runBtn.visible = false; // Hide run button during animation self.itemsBtn.visible = false; // Hide items button during animation // Show "Player Attacks!" text var playerAttackText = new Text2('Player Attacks!', { size: 90, fill: 0xffe066, font: "Impact" // Using a pixel-style font }); playerAttackText.anchor.set(0.5, 0.5); playerAttackText.x = 2048 / 2; playerAttackText.y = self.monsterStatText.y - 100; self.addChild(playerAttackText); function doAttack() { // Damage and flash self.currentMonster.hp -= player.damage; LK.effects.flashObject(self.monsterDisplay, 0xffe066, 300); // Show animated -X text above monster in battle if (self.currentMonster && self.monsterDisplay) { var monsterDmgText = new Text2('-' + player.damage, { size: 100, fill: 0xff0000, font: "Impact" // Using a pixel-style font }); monsterDmgText.anchor.set(0.5, 1); // Position above monster sprite monsterDmgText.x = self.monsterDisplay.x; monsterDmgText.y = self.monsterDisplay.y - self.monsterDisplay.height / 2 - 20; self.addChild(monsterDmgText); // Animate: pop up and fade out tween(monsterDmgText, { y: monsterDmgText.y - 80, alpha: 0 }, { duration: 700, easing: tween.cubicOut, onFinish: function onFinish() { monsterDmgText.destroy(); } }); } self.updateBattleStats(); // Fade out attack text tween(playerAttackText, { alpha: 0 }, { duration: 600, easing: tween.easeOut, onFinish: function onFinish() { playerAttackText.destroy(); } }); // If monster dies, animate monster death if (self.currentMonster.hp <= 0) { // Play victory sound when player defeats monster LK.getSound('victory').play(); // Monster death animation: fade out and scale down tween(self.monsterDisplay, { alpha: 0, scaleX: 0.1, scaleY: 0.1 }, { duration: 600, easing: tween.cubicIn, onFinish: function onFinish() { var expGained = self.currentMonster.exp; var lootMessages = []; // Award EXP player.gainExp(expGained); // Coin Drop var coinsDropped = Math.floor(self.currentMonster.exp * (2 + Math.random() * 4)); // 2-5 coins per EXP point if (coinsDropped > 0) { player.gold += coinsDropped; lootMessages.push(coinsDropped + ' Coins'); } // Gear Drop (Rare) var gearDropChance = 0.1; // 10% chance for now, we can adjust this later! if (Math.random() < gearDropChance) { if (chestScreen && chestScreen._rewardTypes && chestScreen._rewardTypes.length > 2 && chestScreen._lootQualities && chestScreen._lootQualities.length > 0) { var potentialGearRewards = chestScreen._rewardTypes.slice(2); // Exclude potion and coins if (potentialGearRewards.length > 0) { var gearReward = potentialGearRewards[Math.floor(Math.random() * potentialGearRewards.length)]; var gearQuality = chestScreen._lootQualities[Math.floor(Math.random() * chestScreen._lootQualities.length)]; player.equipGear(gearReward.type, gearQuality); // This also calls player.updateEquippedStats() var gearLabel = gearQuality.label + ' ' + gearReward.label; lootMessages.push(gearLabel); // playerStatsScreen and playerInventoryScreen will be updated via updateGUI() when battle ends } } } // Construct Victory Message var victoryMessage = 'Victory!\nGained ' + expGained + ' EXP!'; if (lootMessages.length > 0) { victoryMessage += '\n\nLoot:\n' + lootMessages.join('\n'); } // Show "Victory!" message before closing battle screen var victoryText = new Text2(victoryMessage, { size: 110, fill: 0xA3E635, align: 'center', // Ensure multi-line text is centered font: "Impact" // Using a pixel-style font }); victoryText.anchor.set(0.5, 0.5); victoryText.x = 2048 / 2; victoryText.y = 2732 / 2; self.addChild(victoryText); victoryText.alpha = 0; // Fade in, hold, then fade out tween(victoryText, { alpha: 1 }, { duration: 300, easing: tween.easeOut, onFinish: function onFinish() { tween(victoryText, { alpha: 0 }, { duration: 500, delay: 1000, // Increased delay to read loot easing: tween.easeIn, onFinish: function onFinish() { victoryText.destroy(); self.endBattle(true); // Player wins // Reset monsterDisplay for next battle self.monsterDisplay.alpha = 1; self.monsterDisplay.scaleX = 4; // Adjusted to new scale self.monsterDisplay.scaleY = 4; // Adjusted to new scale } }); } }); } }); } else { // Monster survives, monster's turn after a delay LK.setTimeout(self.monsterTurn, 500); self.attackBtn.visible = true; self.runBtn.visible = true; // Show run button again self.itemsBtn.visible = true; // Show items button again } } // Animate player forward diagonally, then back, then do attack // Play slash sound when player attacks LK.getSound('slash').play(); tween(self.playerDisplay, { x: attackX, y: attackY }, { duration: 180, easing: tween.cubicOut, onFinish: function onFinish() { tween(self.playerDisplay, { x: originalX, y: originalY }, { duration: 180, easing: tween.cubicIn, onFinish: doAttack }); } }); }; // Monster's turn self.monsterTurn = function () { if (!self.currentMonster) return; // Animate monster attack: slide monsterDisplay forward diagonally, then back, then apply damage var originalX = self.monsterDisplay.x; var originalY = self.monsterDisplay.y; var attackX = self.playerDisplay.x + self.playerDisplay.width * self.playerDisplay.scaleX / 2 + 60; // Stop a bit before player var attackY = self.playerDisplay.y - self.playerDisplay.height * self.playerDisplay.scaleY / 2 + 60; // Move down towards player // Play monster1 sound when monster attacks LK.getSound('monster1').play(); self.attackBtn.visible = false; // Hide attack button during animation self.runBtn.visible = false; // Hide run button during animation self.itemsBtn.visible = false; // Hide items button during animation // Show "Monster Attacks!" text var monsterAttackText = new Text2('Monster Attacks!', { size: 90, fill: 0xff0000, font: "Impact" // Using a pixel-style font }); monsterAttackText.anchor.set(0.5, 0.5); monsterAttackText.x = 2048 / 2; monsterAttackText.y = self.playerBattleStatText.y + 100; self.addChild(monsterAttackText); function doMonsterAttack() { player.takeDamage(self.currentMonster.damage); // Flash monster and player LK.effects.flashObject(self.monsterDisplay, 0xff0000, 300); // Flash monster indicating it attacked LK.effects.flashObject(self.playerDisplay, 0xff0000, 300); // Flash player sprite LK.effects.flashObject(self.playerBattleStatText, 0xff0000, 300); // Flash player stats indicating damage // Show animated -X text above player in battle (damage taken) or show "Blocked!" text if (player && self.playerDisplay) { var damageTaken = Math.max(0, self.currentMonster.damage - player.defense); var playerDmgText; var textColor; if (damageTaken > 0) { playerDmgText = new Text2('-' + damageTaken, { size: 100, fill: 0xff0000, // Red for damage font: "Impact" // Using a pixel-style font }); textColor = 0xff0000; } else { playerDmgText = new Text2('Blocked!', { size: 90, fill: 0x7DD3FC, // Light blue for blocked font: "Impact" // Using a pixel-style font }); textColor = 0x7DD3FC; } playerDmgText.anchor.set(0.5, 1); // Position above player sprite playerDmgText.x = self.playerDisplay.x; playerDmgText.y = self.playerDisplay.y - self.playerDisplay.height / 2 - 20; self.addChild(playerDmgText); // Animate: pop up and fade out tween(playerDmgText, { y: playerDmgText.y - 80, alpha: 0 }, { duration: 700, easing: tween.cubicOut, onFinish: function onFinish() { playerDmgText.destroy(); } }); } // Fade out attack text tween(monsterAttackText, { alpha: 0 }, { duration: 600, easing: tween.easeOut, onFinish: function onFinish() { monsterAttackText.destroy(); // Show attack button again if player is still alive and battle is ongoing if (player.hp > 0 && self.currentMonster && !gameOver) { self.attackBtn.visible = true; self.runBtn.visible = true; // Show run button again self.itemsBtn.visible = true; // Show items button again } } }); self.updateBattleStats(); if (player.hp <= 0) { LK.effects.flashScreen(0xff0000, 1000); LK.showGameOver(); gameOver = true; revealAllMonsters(); self.endBattle(false); // Player loses } } // Animate monster forward diagonally, then back, then do attack tween(self.monsterDisplay, { x: attackX, y: attackY }, { duration: 180, easing: tween.cubicOut, onFinish: function onFinish() { tween(self.monsterDisplay, { x: originalX, y: originalY }, { duration: 180, easing: tween.cubicIn, onFinish: doMonsterAttack }); } }); }; self.tryToRun = function () { if (!self.currentMonster) return; self.attackBtn.visible = false; // Hide attack and run buttons self.runBtn.visible = false; self.itemsBtn.visible = false; // Hide items button self.itemsBtn.visible = false; // Hide items button var runSuccess = true; // Calculate chance to fail run based on monster level vs player level if (self.currentMonster.exp > player.level) { // Use monster EXP as a rough proxy for level/difficulty var failChance = Math.min(0.8, (self.currentMonster.exp - player.level) * 0.1); // 10% fail chance per monster level difference, max 80% if (Math.random() < failChance) { runSuccess = false; } } var runMessageText; var runMessageColor; if (runSuccess) { runMessageText = new Text2('You successfully ran away!', { size: 90, fill: 0xA3E635, // Green font: "Impact" // Using a pixel-style font }); } else { runMessageText = new Text2('Failed to run!', { size: 90, fill: 0xFF0000, // Red font: "Impact" // Using a pixel-style font }); } runMessageText.anchor.set(0.5, 0.5); runMessageText.x = 2048 / 2; runMessageText.y = 2732 / 2; self.addChild(runMessageText); tween(runMessageText, { alpha: 0 }, { duration: 800, delay: 600, onFinish: function onFinish() { runMessageText.destroy(); if (runSuccess) { LK.getSound('steps').play(); // Play steps sound on successful run // Reset monster HP and mark as not defeated self.currentMonster.hp = self.currentMonster.maxHp; self.currentMonster.defeated = false; // Important: Reset defeated state // Make sure monster remains visible but cover is restored self.currentMonster.cover.alpha = 1; // Restore cover to original state self.currentMonster.monster.alpha = 1; // Ensure monster image remains visible self.currentMonster.revealed = false; // Set revealed to false // End battle and return to grid self.endBattle(false); // Pass false, as it's not a win. This sets battleScreen.visible to false. showGameGUIWithTransition(); } else { // Running failed, monster attacks LK.setTimeout(self.monsterTurn, 300); // Monster attacks immediately after message } } }); }; // Show the battle items menu self.showBattleItemsMenu = function () { self.battleItemsMenu.visible = true; self.attackBtn.visible = false; // Hide action buttons self.runBtn.visible = false; self.itemsBtn.visible = false; // Update potion count display self.battleItemsMenu.potionCountText.setText('Life Potions: ' + player.inventory.potionCount); // Disable DRINK button if player is full HP or no potions if (player.inventory.potionCount <= 0 || player.hp >= player.maxHp) { self.battleItemsMenu.usePotionBtn.alpha = 0.5; self.battleItemsMenu.usePotionBtn.interactive = false; } else { self.battleItemsMenu.usePotionBtn.alpha = 1; self.battleItemsMenu.usePotionBtn.interactive = true; } // Also update ITEMS button state in battle screen if (player.inventory.potionCount <= 0) { self.itemsBtn.alpha = 0.5; self.itemsBtn.interactive = false; } else { self.itemsBtn.alpha = 1; self.itemsBtn.interactive = true; } }; // Hide the battle items menu self.hideBattleItemsMenu = function () { self.battleItemsMenu.visible = false; // Show action buttons again if player is alive and battle is ongoing if (player.hp > 0 && self.currentMonster && !gameOver) { self.attackBtn.visible = true; self.runBtn.visible = true; self.itemsBtn.visible = true; // Update ITEMS button state if (player.inventory.potionCount <= 0) { self.itemsBtn.alpha = 0.5; self.itemsBtn.interactive = false; } else { self.itemsBtn.alpha = 1; self.itemsBtn.interactive = true; } } }; return self; }); // ChestScreen: Handles the chest found popup var ChestScreen = Container.expand(function () { var self = Container.call(this); self.visible = false; self.alpha = 0; // Background overlay self.overlay = self.attachAsset('emptyTileCover', { width: 2048, height: 2732, alpha: 0.8 }); // Chest display (center) - use chest image asset self.chestDisplay = self.attachAsset('chest', { anchorX: 0.5, anchorY: 0.5, x: 2048 / 2, y: 2732 / 2 - 100, scaleX: 4, // Increased scale scaleY: 4 // Increased scale }); // "You found a chest!" text self.chestText = new Text2('You found a chest!', { size: 120, // Increased size fill: 0xFFD700, font: "Impact" // Using a pixel-style font }); self.chestText.anchor.set(0.5, 0.5); self.chestText.x = 2048 / 2; self.chestText.y = 2732 * 0.25; // Adjusted Y position to be higher self.addChild(self.chestText); // Player display (bottom, like battle screen) self.playerDisplay = self.attachAsset('character', { anchorX: 0.5, anchorY: 0.5, x: 2048 / 2, y: 2732 * 0.8, scaleX: 4, scaleY: 4 }); // --- Double-tap to open chest logic --- self._lastTapTime = 0; self._tapCount = 0; self._rewardPopup = null; // Chest reward types self._rewardTypes = [{ type: "potion", label: "Life Potion", color: 0xA3E635 }, { type: "coins", label: "Coins", color: 0xFFD700 }, { type: "shield", label: "Shield", color: 0x7DD3FC }, { type: "sword", label: "Sword", color: 0xF87171 }, { type: "helmet", label: "Helmet", color: 0xFACC15 }, { type: "armour", label: "Armour", color: 0xA78BFA }]; // Loot qualities self._allLootQualities = [{ label: "Wooden", modifier: 1, color: 0x8B4513, // SaddleBrown minLevel: 1 }, { label: "Bronze", modifier: 2, color: 0xCD7F32, // Bronze minLevel: 2 }, { label: "Silver", modifier: 3, color: 0xC0C0C0, // Silver minLevel: 3 }]; // This will be set on chest open, based on player.level self._lootQualities = self._allLootQualities; // Helper to show reward popup self._showRewardPopup = function (reward) { if (self._rewardPopup) { self._rewardPopup.destroy(); self._rewardPopup = null; } var popup = new Container(); // Overlay var overlay = LK.getAsset('emptyTileBg', { width: 1200, height: 600, alpha: 0.95, anchorX: 0.5, anchorY: 0.5, x: 2048 / 2, y: 2732 / 2 }); popup.addChild(overlay); // Reward text var rewardText = new Text2('You found: ' + reward.label + '!', { size: 90, fill: "#fff", font: "Impact" // Using a pixel-style font }); rewardText.anchor.set(0.5, 0.5); rewardText.x = 2048 / 2; rewardText.y = 2732 / 2 - 50; popup.addChild(rewardText); // "Close" button var okBtn = new Text2('Close', { size: 70, fill: "#fff", font: "Impact" // Using a pixel-style font }); okBtn.anchor.set(0.5, 0.5); okBtn.x = 2048 / 2; okBtn.y = 2732 / 2 + 80; okBtn.down = function () { LK.getSound('tap').play(); popup.destroy(); self._rewardPopup = null; self.endChest(); }; popup.addChild(okBtn); self._rewardPopup = popup; game.addChild(popup); }; // Double-tap handler for chest self.chestDisplay.down = function (x, y, obj) { var now = Date.now(); if (now - self._lastTapTime < 400) { self._tapCount++; } else { self._tapCount = 1; } self._lastTapTime = now; if (self._tapCount === 2 && self.visible && !self._rewardPopup) { LK.getSound('openchest').play(); // Open chest if (self.currentChest && !self.currentChest.opened) { self.currentChest.open(); // Pick a random reward var reward = self._rewardTypes[Math.floor(Math.random() * self._rewardTypes.length)]; var quality = null; if (reward.type !== "potion" && reward.type !== "coins") { // For gear, pick a random quality from those unlocked by player level var unlockedQualities = []; for (var i = 0; i < self._allLootQualities.length; i++) { if (player.level >= self._allLootQualities[i].minLevel) { unlockedQualities.push(self._allLootQualities[i]); } } // Fallback: always at least wooden if (unlockedQualities.length === 0) unlockedQualities.push(self._allLootQualities[0]); quality = unlockedQualities[Math.floor(Math.random() * unlockedQualities.length)]; } // Apply reward effect (for now, just heal for potion, add coins, or do nothing for gear) if (reward.type === "potion") { player.inventory.potionCount++; // Add potion to inventory self._showRewardPopup(reward); playerInventoryScreen.updateInventoryDisplay(); // Update inventory screen if open } else if (reward.type === "coins") { player.gold += 10 + Math.floor(Math.random() * 10); // Add 10-20 coins self._showRewardPopup(reward); playerInventoryScreen.updateInventoryDisplay(); // Update inventory screen if open } else if (quality) { // Add gear to player inventory with quality modifier player.equipGear(reward.type, quality); var lootLabel = quality.label + ' ' + reward.label; self._showRewardPopup({ label: lootLabel, color: quality.color }); playerStatsScreen.updateStatsDisplay(); // Update stats screen if open playerInventoryScreen.updateInventoryDisplay(); // Update inventory screen if open } } self._tapCount = 0; } }; // Start chest screen self.startChest = function (chestTile) { self.currentChest = chestTile; // Set available loot qualities for this chest based on player level var unlockedQualities = []; for (var i = 0; i < self._allLootQualities.length; i++) { if (player.level >= self._allLootQualities[i].minLevel) { unlockedQualities.push(self._allLootQualities[i]); } } // Fallback: always at least wooden if (unlockedQualities.length === 0) unlockedQualities.push(self._allLootQualities[0]); self._lootQualities = unlockedQualities; self.visible = true; self.alpha = 0; game.addChild(self); updateGUI(); // Hide main GUI // Fade in tween(self, { alpha: 1 }, { duration: 300, easing: tween.easeOut }); }; // End chest screen self.endChest = function () { self.visible = false; self.alpha = 0; self.currentChest = null; if (self._rewardPopup) { self._rewardPopup.destroy(); self._rewardPopup = null; } if (self.parent) self.parent.removeChild(self); showGameGUIWithTransition(); }; return self; }); // ChestTile: Represents a chest hidden under a tile var ChestTile = Container.expand(function () { var self = Container.call(this); self.revealed = false; self.opened = false; self.outline = self.attachAsset('tileOutline', { // Attach black outline anchorX: 0.5, anchorY: 0.5 }); self.cover = self.attachAsset('emptyTileCover', { anchorX: 0.5, anchorY: 0.5 }); // Use a chest image asset self.chest = self.attachAsset('chest', { anchorX: 0.5, anchorY: 0.5, alpha: 0 // Hidden until revealed }); // Reveal the chest self.reveal = function () { if (self.revealed) return; self.revealed = true; self.cover.alpha = 0.2; self.chest.alpha = 1; }; // Mark as opened self.open = function () { self.opened = true; LK.getSound('openchest').play(); self.cover.alpha = 0.1; // Grey out cover self.chest.alpha = 0.5; // Grey out chest image // Optionally animate chest opening here }; return self; }); // EmptyTile: Represents a safe tile (no monster) var EmptyTile = Container.expand(function () { var self = Container.call(this); self.revealed = false; self.adjacentMonsterDamage = 0; // Change to store damage sum self.outline = self.attachAsset('tileOutline', { // Attach black outline anchorX: 0.5, anchorY: 0.5 }); self.cover = self.attachAsset('emptyTileCover', { anchorX: 0.5, anchorY: 0.5 }); self.bg = self.attachAsset('emptyTileBg', { anchorX: 0.5, anchorY: 0.5, alpha: 0 // Hidden until revealed }); self.adjText = new Text2('', { size: 48, fill: "#fff", font: "Impact" // Using a pixel-style font }); self.adjText.anchor.set(0.5, 0.5); self.adjText.alpha = 0; self.addChild(self.adjText); self.reveal = function () { if (self.revealed) return; self.revealed = true; self.cover.alpha = 0.2; self.bg.alpha = 1; if (self.adjacentMonsterDamage > 0) { // Check damage sum self.adjText.setText(self.adjacentMonsterDamage + ''); // Display damage sum self.adjText.alpha = 1; } }; return self; }); // LevelEndScreen: Appears after clearing a level, offering Next Level and Shop options var LevelEndScreen = Container.expand(function () { var self = Container.call(this); self.visible = false; self.alpha = 0; // Background overlay self.overlay = self.attachAsset('emptyTileCover', { width: 2048, height: 2732, alpha: 0.9 }); // Main panel background self.panel = self.attachAsset('emptyTileBg', { width: 1200, height: 1000, anchorX: 0.5, anchorY: 0.5, x: 2048 / 2, y: 2732 / 2 }); // Title self.titleText = new Text2('Level Complete!', { size: 110, fill: 0xA3E635, font: "Impact" // Using a pixel-style font }); self.titleText.anchor.set(0.5, 0.5); self.titleText.x = 2048 / 2; self.titleText.y = self.panel.y - self.panel.height / 2 + 120; self.addChild(self.titleText); // Next Level Button self.nextLevelBtn = new Text2('NEXT LEVEL', { size: 80, fill: "#fff", font: "Impact" // Using a pixel-style font }); self.nextLevelBtn.anchor.set(0.5, 0.5); self.nextLevelBtn.x = 2048 / 2; self.nextLevelBtn.y = self.panel.y + 100; self.nextLevelBtn.down = function () { if (!self.visible) return; LK.getSound('tap').play(); self.endLevelEndScreen(); currentLevel++; MONSTER_COUNT = LEVELS[currentLevel].monsters; generateBoard(); updateGUI(); levelText.setText('LVL: ' + (currentLevel + 1)); gameOver = false; }; self.addChild(self.nextLevelBtn); // Shop Button self.shopBtn = new Text2('SHOP', { size: 80, fill: "#fff", font: "Impact" // Using a pixel-style font }); self.shopBtn.anchor.set(0.5, 0.5); self.shopBtn.x = 2048 / 2; self.shopBtn.y = self.panel.y + 300; self.shopBtn.down = function () { if (!self.visible) return; LK.getSound('tap').play(); self.endLevelEndScreen(); shopScreen.startShopScreen(); }; self.addChild(self.shopBtn); // Start the level end screen self.startLevelEndScreen = function () { self.visible = true; self.alpha = 0; game.addChild(self); updateGUI(); // Hide main GUI // Fade in tween(self, { alpha: 1 }, { duration: 300, easing: tween.easeOut }); }; // End the level end screen self.endLevelEndScreen = function () { self.visible = false; self.alpha = 0; if (self.parent) self.parent.removeChild(self); showGameGUIWithTransition(); }; return self; }); var MainScreen = Container.expand(function () { var self = Container.call(this); self.visible = false; // Initially hidden self.alpha = 0; // Set alpha to 0 for fade-in animation // Background overlay // self.overlay = self.attachAsset('emptyTileCover', { // width: 2048, // height: 2732, // alpha: 0.9 // }); self.overlay = new Container(); // Replace the overlay with an empty container // Main panel background // self.panel = self.attachAsset('emptyTileBg', { // width: 1200, // height: 1000, // anchorX: 0.5, // anchorY: 0.5, // x: 2048 / 2, // y: 2732 / 2 // }); self.panel = new Container(); // Replace the panel with an empty container // Title // Title - now using image asset self.titleImage = self.attachAsset('gameTitle', { // Using the new gameTitle image asset anchorX: 0.5, // Anchor to center anchorY: 0.5, // Anchor to center x: 2048 / 2, // Center horizontally y: 2732 * 0.3 // Position the game title image higher on the screen }); // Remove the old text title // self.titleText.destroy(); // Assuming the original text is added somewhere before this block // Re-add the title image to ensure it's a child of MainScreen. self.addChild(self.titleImage); // Add the title image to the screen container // Update the reference if needed // self.titleText = self.titleImage; // If any other code references self.titleText, this line can replace it // Animate the title image up and down (gentle float) (function animateTitleFloat() { var startY = self.titleImage.y; var floatDistance = 40; tween(self.titleImage, { y: startY + floatDistance }, { duration: 1200, easing: tween.easeInOut, onFinish: function onFinish() { tween(self.titleImage, { y: startY }, { duration: 1200, easing: tween.easeInOut, onFinish: animateTitleFloat }); } }); })(); // Start Game Button with background and animation self.startGameBtnBg = self.attachAsset('emptyTileBg', { anchorX: 0.5, anchorY: 0.5, x: 2048 / 2, y: 2732 * 0.7 + 100, width: 500, height: 140, alpha: 0.85 }); self.startGameBtn = new Text2('ENTER', { size: 80, fill: "#fff", font: "Impact" }); self.startGameBtn.anchor.set(0.5, 0.5); self.startGameBtn.x = 2048 / 2; self.startGameBtn.y = 2732 * 0.7 + 100; // Position start button lower // Animate the button background (pulse) // tween(self.startGameBtnBg, { // scaleX: 1.1, // scaleY: 1.1 // }, { // duration: 700, // easing: tween.easeInOut, // onFinish: function onFinish() { // tween(self.startGameBtnBg, { // scaleX: 1, // scaleY: 1 // }, { // duration: 700, // easing: tween.easeInOut, // onFinish: function pulseAgain() { // // Loop the pulse // tween(self.startGameBtnBg, { // scaleX: 1.1, // scaleY: 1.1 // }, { // duration: 700, // easing: tween.easeInOut, // onFinish: function onFinish() { // tween(self.startGameBtnBg, { // scaleX: 1, // scaleY: 1 // }, { // duration: 700, // easing: tween.easeInOut, // onFinish: pulseAgain // }); // } // }); // } // }); // } // }); // Animate the button text (gentle scale pulse) // tween(self.startGameBtn, { // scaleX: 1.08, // scaleY: 1.08 // }, { // duration: 700, // easing: tween.easeInOut, // onFinish: function onFinish() { // tween(self.startGameBtn, { // scaleX: 1, // scaleY: 1 // }, { // duration: 700, // easing: tween.easeInOut, // onFinish: function pulseTextAgain() { // tween(self.startGameBtn, { // scaleX: 1.08, // scaleY: 1.08 // }, { // duration: 700, // easing: tween.easeInOut, // onFinish: function onFinish() { // tween(self.startGameBtn, { // scaleX: 1, // scaleY: 1 // }, { // duration: 700, // easing: tween.easeInOut, // onFinish: pulseTextAgain // }); // } // }); // } // }); // } // }); self.startGameBtn.down = function () { if (!self.visible) return; // Play steps sound when player touches start game LK.getSound('steps').play(); self.endMainScreen(); // Start the game logic here (e.g., generateBoard) startGame(); // Call a new function to start the game }; self.addChild(self.startGameBtnBg); self.addChild(self.startGameBtn); // (Reset Progress Button removed) // Start the main screen self.startMainScreen = function () { self.visible = true; self.alpha = 0; game.addChild(self); // Play gametitle music when main screen starts LK.playMusic('gametitle'); // Fade in tween(self, { alpha: 1 }, { duration: 300, easing: tween.easeOut }); }; // End the main screen self.endMainScreen = function () { self.visible = false; self.alpha = 0; if (self.parent) self.parent.removeChild(self); }; return self; }); // MonsterTile: Represents a monster hidden under a tile var MonsterTile = Container.expand(function () { var self = Container.call(this); // Monster stats self.hp = 1; self.maxHp = 1; self.damage = 1; self.exp = 1; self.revealed = false; self.defeated = false; // Visuals self.outline = self.attachAsset('tileOutline', { // Attach black outline anchorX: 0.5, anchorY: 0.5 }); self.cover = self.attachAsset('monsterTileCover', { anchorX: 0.5, anchorY: 0.5 }); self.monster = self.attachAsset('monsterTileMonster', { anchorX: 0.5, anchorY: 0.5, alpha: 0 // Hidden until revealed }); // Show monster stats as text (hidden until revealed) self.statText = new Text2('', { size: 48, fill: "#fff", font: "Impact" // Using a pixel-style font }); self.statText.anchor.set(0.5, 0.5); self.statText.alpha = 0; self.addChild(self.statText); // Reveal the monster self.reveal = function () { if (self.revealed) return; self.revealed = true; self.cover.alpha = 0.2; self.monster.alpha = 1; self.statText.setText(''); self.statText.alpha = 0; }; // Mark as defeated self.defeat = function () { self.defeated = true; self.monster.alpha = 0.3; self.cover.alpha = 0.1; self.statText.setText(''); self.statText.alpha = 0; // Update adjacent empty tiles' numbers since this monster is no longer generating damage if (typeof grid !== "undefined") { // Find this monster's position in the grid for (var row = 0; row < grid.length; row++) { for (var col = 0; col < grid[row].length; col++) { if (grid[row][col] === self) { var adj = getAdjacent(row, col); for (var i = 0; i < adj.length; i++) { var nr = adj[i][0], nc = adj[i][1]; var t = grid[nr][nc]; if (t instanceof EmptyTile) { // Recalculate adjacentMonsterDamage for this empty tile var adj2 = getAdjacent(nr, nc); var damageSum = 0; for (var j = 0; j < adj2.length; j++) { var ar = adj2[j][0], ac = adj2[j][1]; var adjTile = grid[ar][ac]; if (adjTile instanceof MonsterTile && !adjTile.defeated) { damageSum += adjTile.damage; } } t.adjacentMonsterDamage = damageSum; // If revealed, update the displayed number if (t.revealed) { if (damageSum > 0) { t.adjText.setText(damageSum + ''); t.adjText.alpha = 1; } else { t.adjText.setText(''); t.adjText.alpha = 0; } } } } // Only need to update once for the found monster row = grid.length; // break outer loop break; } } } } }; return self; }); // PoisonMonsterTile: A monster that can poison the player var PoisonMonsterTile = MonsterTile.expand(function () { var self = MonsterTile.call(this); // Override monster asset to use poison monster image if (self.monster) { self.removeChild(self.monster); } self.monster = self.attachAsset('poisonMonsterTileMonster', { anchorX: 0.5, anchorY: 0.5, alpha: 0 // Hidden until revealed }); // Add stat text again if needed if (!self.statText) { self.statText = new Text2('', { size: 48, fill: "#fff", font: "Impact" }); self.statText.anchor.set(0.5, 0.5); self.statText.alpha = 0; self.addChild(self.statText); } // These will be set during board generation based on level stats self.poisonChance = 0.3; // 30% chance to poison self.poisonDamage = 1; // -1 HP per turn when poisoned // Override the doMonsterAttack function to add poison effect var originalDoMonsterAttack = self.doMonsterAttack; // Keep reference to original self.doMonsterAttack = function () { originalDoMonsterAttack(); // Call the original monster attack logic // Check if player was hit and apply poison effect var damageTaken = Math.max(0, self.damage - player.defense); if (damageTaken > 0 && Math.random() < self.poisonChance) { if (!player.isPoisoned) { player.applyPoison(1); // Always poison for -1 per turn battleScreen.showConfirmationText('You have been poisoned!', 0xA3E635); // Green message LK.getSound('poison').play(); // Play poison sound } else { // Already poisoned, do nothing (could extend duration in future) } } }; // Override monster's turn to call the new doMonsterAttack logic var originalMonsterTurn = self.monsterTurn; // Keep reference to original self.monsterTurn = function () { if (!self.currentMonster) return; // Animate monster attack: slide monsterDisplay forward diagonally, then back var originalX = self.monsterDisplay.x; var originalY = self.monsterDisplay.y; var attackX = self.playerDisplay.x + self.playerDisplay.width * self.playerDisplay.scaleX / 2 + 60; // Stop a bit before player var attackY = self.playerDisplay.y - self.playerDisplay.height * self.playerDisplay.scaleY / 2 + 60; // Move down towards player LK.getSound('monster1').play(); // Play monster1 sound when monster attacks self.attackBtn.visible = false; // Hide attack button during animation self.runBtn.visible = false; // Hide run button during animation self.itemsBtn.visible = false; // Hide items button during animation // Show "Monster Attacks!" text var monsterAttackText = new Text2('Monster Attacks!', { size: 90, fill: 0xff0000, font: "Impact" // Using a pixel-style font }); monsterAttackText.anchor.set(0.5, 0.5); monsterAttackText.x = 2048 / 2; monsterAttackText.y = self.playerBattleStatText.y + 100; self.addChild(monsterAttackText); // Animate monster forward diagonally, then back, then do attack (calling the new doMonsterAttack) tween(self.monsterDisplay, { x: attackX, y: attackY }, { duration: 180, easing: tween.cubicOut, onFinish: function onFinish() { tween(self.monsterDisplay, { x: originalX, y: originalY }, { duration: 180, easing: tween.cubicIn, onFinish: function onFinish() { // Now call the potentially overridden doMonsterAttack self.doMonsterAttack(); // Fade out attack text tween(monsterAttackText, { alpha: 0 }, { duration: 600, easing: tween.easeOut, onFinish: function onFinish() { monsterAttackText.destroy(); // Show attack button again if player is still alive and battle is ongoing if (player.hp > 0 && self.currentMonster && !gameOver) { self.attackBtn.visible = true; self.runBtn.visible = true; // Show run button again self.itemsBtn.visible = true; // Show items button again } } }); self.updateBattleStats(); if (player.hp <= 0) { LK.effects.flashScreen(0xff0000, 1000); LK.showGameOver(); gameOver = true; revealAllMonsters(); self.endBattle(false); // Player loses } } }); } }); }; return self; }); // Add event listener to MonsterTile for clicking // Player: Holds player stats and methods var Player = Container.expand(function () { var self = Container.call(this); // Player stats self.level = 1; self.exp = 0; self.expToLevel = 3; // Start with 3 exp needed for level up self.maxHp = 5; self.hp = self.maxHp; self.damage = 1; // Start with 1 damage self.defense = 0; // Initial defense is 0 self.skillPoints = 0; // Points to spend on upgrades self.gold = 0; // Player's currency self.inventory = { // Player inventory potionCount: 0, antidoteCount: 0, // Add antidote count sword: null, // Equipped items shield: null, helmet: null, armour: null }; self.baseDamage = 1; // Base damage before gear self.baseDefense = 0; // Base defense before gear // Gain experience self.gainExp = function (amount) { self.exp += amount; while (self.exp >= self.expToLevel) { self.exp -= self.expToLevel; self.levelUp(); } }; // Level up player self.levelUp = function () { self.level++; self.skillPoints++; self.expToLevel = Math.ceil(self.expToLevel * 1.2); // Increase EXP needed for next level, ensuring it consistently grows // Increase base stats on level up // self.maxHp += 5; // HP is no longer gained automatically on level up // self.damage += 1; // Example stat increase // self.hp = self.maxHp; // Do not heal to full HP on level up self.updateEquippedStats(); // Update stats after base increase }; // Take damage self.takeDamage = function (amount) { var damageTaken = Math.max(0, amount - self.defense); // Damage reduced by defense self.hp -= damageTaken; if (self.hp < 0) self.hp = 0; }; // Spend skill points self.spendSkillPoint = function (stat) { if (self.skillPoints <= 0) return; self.skillPoints--; if (stat === "hp") { self.maxHp += 5; // Gain 5 HP per point self.hp = self.maxHp; // Heal to full } else if (stat === "damage") { self.baseDamage += 2; // Increase base damage } else if (stat === "defense") { self.baseDefense += 1; // Increase base defense } self.updateEquippedStats(); // Update stats after spending points }; // Equip gear self.equipGear = function (type, quality) { self.inventory[type] = { type: type, label: quality.label, modifier: quality.modifier, color: quality.color }; self.updateEquippedStats(); // Update stats after equipping }; // Update stats based on equipped gear self.updateEquippedStats = function () { self.damage = self.baseDamage; self.defense = self.baseDefense; if (self.inventory.sword) self.damage += self.inventory.sword.modifier; if (self.inventory.shield) self.defense += self.inventory.shield.modifier; if (self.inventory.helmet) self.defense += Math.floor(self.inventory.helmet.modifier / 2); // Helmet gives half defense if (self.inventory.armour) self.defense += self.inventory.armour.modifier; // Armour gives full defense // Ensure current HP doesn't exceed new max HP after stat changes if (self.hp > self.maxHp) self.hp = self.maxHp; }; // Poison state self.isPoisoned = false; self.poisonDamagePerTurn = 0; self._poisonTimer = null; // Apply poison effect self.applyPoison = function (damage) { self.isPoisoned = true; self.poisonDamagePerTurn = 1; // Always -1 per turn // Show green message when poisoned if (battleScreen && battleScreen.visible) { battleScreen.showConfirmationText('You have been poisoned!', 0xA3E635); } // Start a timer for poison effect if (self._poisonTimer) { LK.clearInterval(self._poisonTimer); // Clear existing timer if any } // Apply poison damage at the start of each turn (simulated by timer) self._poisonTimer = LK.setInterval(function () { if (self.hp > 0) { self.takeDamage(self.poisonDamagePerTurn); // Show damage text for poison if (battleScreen.visible && battleScreen.playerDisplay) { var poisonDmgText = new Text2('-' + self.poisonDamagePerTurn, { size: 70, fill: 0x800080, // Purple for poison font: "Impact" // Using a pixel-style font }); poisonDmgText.anchor.set(0.5, 1); // Position above player sprite poisonDmgText.x = battleScreen.playerDisplay.x; poisonDmgText.y = battleScreen.playerDisplay.y - battleScreen.playerDisplay.height / 2 - 50; battleScreen.addChild(poisonDmgText); LK.getSound('poison').play(); // Play poison sound // Animate: pop up and fade out tween(poisonDmgText, { y: poisonDmgText.y - 60, alpha: 0 }, { duration: 600, easing: tween.cubicOut, onFinish: function onFinish() { poisonDmgText.destroy(); } }); } battleScreen.updateBattleStats(); // Update battle stats display updateGUI(); // Update main GUI HP display if (self.hp <= 0) { // Player died from poison LK.effects.flashScreen(0xff0000, 1000); LK.showGameOver(); gameOver = true; revealAllMonsters(); battleScreen.endBattle(false); // Player loses } } else { self.curePoison(); // Player is already dead } }, 1000); // Apply poison damage every 1 second (simulating turn-based) // Note: This is a simplified turn-based simulation using a timer. // In a true turn-based system, poison would apply at the start of the player's turn. }; // Cure poison effect self.curePoison = function () { self.isPoisoned = false; self.poisonDamagePerTurn = 0; if (self._poisonTimer) { LK.clearInterval(self._poisonTimer); self._poisonTimer = null; } }; return self; }); // PlayerInventoryScreen: Displays player's inventory and allows using potions var PlayerInventoryScreen = Container.expand(function () { var self = Container.call(this); self.visible = false; // Initially hidden self.alpha = 0; // Set alpha to 0 for fade-in animation // Background overlay self.overlay = self.attachAsset('emptyTileCover', { width: 2048, height: 2732, alpha: 0.9 }); // Main panel background self.panel = self.attachAsset('emptyTileBg', { width: 1600, // Increased width for grid layout height: 1800, // Increased height for more items anchorX: 0.5, anchorY: 0.5, x: 2048 / 2, y: 2732 / 2 }); var panelLeftEdge = self.panel.x - self.panel.width / 2; var contentStartX = panelLeftEdge + 80; var valueColumnX = contentStartX + 350; var buttonColumnX = valueColumnX + 200; var itemSpacingY = 70; // Title self.titleText = new Text2('INVENTORY', { size: 110, fill: 0x7DD3FC, // Light Blue font: "Impact" // Using a pixel-style font }); self.titleText.anchor.set(0.5, 0.5); self.titleText.x = 2048 / 2; self.titleText.y = self.panel.y - self.panel.height / 2 + 120; self.addChild(self.titleText); // Gold display self.goldText = new Text2('', { size: 60, fill: 0xFFD700, // Gold font: "Impact" // Using a pixel-style font }); self.goldText.anchor.set(0, 0.5); self.goldText.x = contentStartX; self.goldText.y = self.panel.y - self.panel.height / 2 + 220; self.addChild(self.goldText); // --- Consumables Section --- self.consumablesTitle = new Text2('Consumables:', { size: 70, fill: 0x7DD3FC, font: "Impact" // Using a pixel-style font }); self.consumablesTitle.anchor.set(0, 0.5); self.consumablesTitle.x = contentStartX; self.consumablesTitle.y = self.goldText.y + 100; self.addChild(self.consumablesTitle); // Potions var potionY = self.consumablesTitle.y + itemSpacingY; self.potionLabelText = new Text2('Life Potions:', { size: 60, fill: "#fff", font: "Impact" }); self.potionLabelText.anchor.set(0, 0.5); self.potionLabelText.x = contentStartX + 40; // Indent self.potionLabelText.y = potionY; self.addChild(self.potionLabelText); self.potionValueText = new Text2('', { size: 60, fill: 0xA3E635, font: "Impact" }); self.potionValueText.anchor.set(0, 0.5); self.potionValueText.x = valueColumnX; self.potionValueText.y = potionY; self.addChild(self.potionValueText); self.usePotionBtn = new Text2('DRINK', { size: 60, fill: 0xA3E635, font: "Impact" }); self.usePotionBtn.anchor.set(0, 0.5); self.usePotionBtn.x = buttonColumnX; self.usePotionBtn.y = potionY; self.usePotionBtn.visible = false; // Initially hidden self.usePotionBtn.down = function () { if (!self.visible || player.inventory.potionCount <= 0 || player.hp >= player.maxHp) return; player.inventory.potionCount--; player.hp += 20; // Heal amount if (player.hp > player.maxHp) player.hp = player.maxHp; self.updateInventoryDisplay(); playerStatsScreen.updateStatsDisplay(); // Update stats screen if open updateGUI(); // Update main GUI HP display }; self.addChild(self.usePotionBtn); // Antidotes var antidoteY = potionY + itemSpacingY; self.antidoteLabelText = new Text2('Antidotes:', { size: 60, fill: 0xA78BFA, // Light purple for better readability font: "Impact" }); self.antidoteLabelText.anchor.set(0, 0.5); self.antidoteLabelText.x = contentStartX + 40; // Indent self.antidoteLabelText.y = antidoteY; self.addChild(self.antidoteLabelText); self.antidoteValueText = new Text2('', { size: 60, fill: 0x800080, font: "Impact" }); self.antidoteValueText.anchor.set(0, 0.5); self.antidoteValueText.x = valueColumnX; self.antidoteValueText.y = antidoteY; self.addChild(self.antidoteValueText); self.useAntidoteBtn = new Text2('DRINK', { size: 60, fill: 0x800080, font: "Impact" }); self.useAntidoteBtn.anchor.set(0, 0.5); self.useAntidoteBtn.x = buttonColumnX; self.useAntidoteBtn.y = antidoteY; self.useAntidoteBtn.visible = false; // Initially hidden self.useAntidoteBtn.down = function () { if (!self.visible || player.inventory.antidoteCount <= 0 || !player.isPoisoned) return; player.inventory.antidoteCount--; player.curePoison(); self.updateInventoryDisplay(); updateGUI(); // Update main GUI HP display (poison effect removed) }; self.addChild(self.useAntidoteBtn); // --- Equipped Gear Section --- self.gearTitle = new Text2('Equipped Gear:', { size: 70, fill: 0xFFE066, font: "Impact" // Using a pixel-style font }); self.gearTitle.anchor.set(0, 0.5); self.gearTitle.x = contentStartX; self.gearTitle.y = antidoteY + itemSpacingY + 40; // Position below consumables self.addChild(self.gearTitle); var gearStartY = self.gearTitle.y + itemSpacingY; var gearLabelColumnX = contentStartX + 40; // Indent var gearItemColumnX = valueColumnX - 80; // Align with consumable values, adjust for wider text self.swordSlotLabel = new Text2('Sword:', { size: 60, fill: "#fff", font: "Impact" }); self.swordSlotLabel.anchor.set(0, 0.5); self.swordSlotLabel.x = gearLabelColumnX; self.swordSlotLabel.y = gearStartY; self.addChild(self.swordSlotLabel); self.swordItemText = new Text2('-', { size: 55, fill: "#fff", font: "Impact" }); // Slightly smaller for item details self.swordItemText.anchor.set(0, 0.5); self.swordItemText.x = gearItemColumnX; self.swordItemText.y = gearStartY; self.addChild(self.swordItemText); self.shieldSlotLabel = new Text2('Shield:', { size: 60, fill: "#fff", font: "Impact" }); self.shieldSlotLabel.anchor.set(0, 0.5); self.shieldSlotLabel.x = gearLabelColumnX; self.shieldSlotLabel.y = gearStartY + itemSpacingY; self.addChild(self.shieldSlotLabel); self.shieldItemText = new Text2('-', { size: 55, fill: "#fff", font: "Impact" }); self.shieldItemText.anchor.set(0, 0.5); self.shieldItemText.x = gearItemColumnX; self.shieldItemText.y = gearStartY + itemSpacingY; self.addChild(self.shieldItemText); self.helmetSlotLabel = new Text2('Helmet:', { size: 60, fill: "#fff", font: "Impact" }); self.helmetSlotLabel.anchor.set(0, 0.5); self.helmetSlotLabel.x = gearLabelColumnX; self.helmetSlotLabel.y = gearStartY + itemSpacingY * 2; self.addChild(self.helmetSlotLabel); self.helmetItemText = new Text2('-', { size: 55, fill: "#fff", font: "Impact" }); self.helmetItemText.anchor.set(0, 0.5); self.helmetItemText.x = gearItemColumnX; self.helmetItemText.y = gearStartY + itemSpacingY * 2; self.addChild(self.helmetItemText); self.armourSlotLabel = new Text2('Armour:', { size: 60, fill: "#fff", font: "Impact" }); self.armourSlotLabel.anchor.set(0, 0.5); self.armourSlotLabel.x = gearLabelColumnX; self.armourSlotLabel.y = gearStartY + itemSpacingY * 3; self.addChild(self.armourSlotLabel); self.armourItemText = new Text2('-', { size: 55, fill: "#fff", font: "Impact" }); self.armourItemText.anchor.set(0, 0.5); self.armourItemText.x = gearItemColumnX; self.armourItemText.y = gearStartY + itemSpacingY * 3; self.addChild(self.armourItemText); // Close button self.closeBtn = new Text2('CLOSE', { size: 70, fill: "#fff", font: "Impact" // Using a pixel-style font }); self.closeBtn.anchor.set(0.5, 0.5); self.closeBtn.x = 2048 / 2; self.closeBtn.y = self.panel.y + self.panel.height / 2 - 80; self.closeBtn.down = function () { if (!self.visible) return; self.endInventoryScreen(); }; self.addChild(self.closeBtn); // Update the displayed inventory self.updateInventoryDisplay = function () { // Animate gold text if value changed if (typeof self._lastGold !== "undefined" && self._lastGold !== player.gold) { tween(self.goldText, { scaleX: 1.25, scaleY: 1.25 }, { duration: 120, easing: tween.cubicOut, onFinish: function onFinish() { tween(self.goldText, { scaleX: 1, scaleY: 1 }, { duration: 120, easing: tween.cubicIn }); } }); } self.goldText.setText('Coins: ' + player.gold); // Update Potion count and animate if changed if (typeof self._lastPotionCount !== "undefined" && self._lastPotionCount !== player.inventory.potionCount) { tween(self.potionValueText, { scaleX: 1.25, scaleY: 1.25 }, { duration: 120, easing: tween.cubicOut, onFinish: function onFinish() { tween(self.potionValueText, { scaleX: 1, scaleY: 1 }, { duration: 120, easing: tween.cubicIn }); } }); } self.potionValueText.setText('' + player.inventory.potionCount); // Update Antidote count and animate if changed if (typeof self._lastAntidoteCount !== "undefined" && self._lastAntidoteCount !== player.inventory.antidoteCount) { tween(self.antidoteValueText, { scaleX: 1.25, scaleY: 1.25 }, { duration: 120, easing: tween.cubicOut, onFinish: function onFinish() { tween(self.antidoteValueText, { scaleX: 1, scaleY: 1 }, { duration: 120, easing: tween.cubicIn }); } }); } self.antidoteValueText.setText('' + player.inventory.antidoteCount); // Update Equipped Gear Display var gearMap = { sword: self.swordItemText, shield: self.shieldItemText, helmet: self.helmetItemText, armour: self.armourItemText }; var gearTypes = ['sword', 'shield', 'helmet', 'armour']; for (var i = 0; i < gearTypes.length; i++) { var type = gearTypes[i]; var item = player.inventory[type]; var itemTextObj = gearMap[type]; if (item) { var attributeText = ''; if (type === 'sword') { attributeText = '(+' + item.modifier + ' DMG)'; } else if (type === 'shield' || type === 'helmet' || type === 'armour') { var defenseBonus = 0; if (type === 'shield') defenseBonus = item.modifier; if (type === 'helmet') defenseBonus = Math.floor(item.modifier / 2); if (type === 'armour') defenseBonus = item.modifier; attributeText = '(+' + defenseBonus + ' DEF)'; } itemTextObj.setText(item.label + ' ' + attributeText); if (itemTextObj.style) { itemTextObj.style.fill = item.color || "#fff"; // Set color based on item quality } } else { itemTextObj.setText('-'); if (itemTextObj.style) { itemTextObj.style.fill = "#fff"; // Default color for empty slot } } } // Store last values for next update self._lastGold = player.gold; self._lastPotionCount = player.inventory.potionCount; self._lastAntidoteCount = player.inventory.antidoteCount; // Store last antidote count // Show/hide DRINK button for potion if (player.inventory.potionCount > 0 && player.hp < player.maxHp) { self.usePotionBtn.visible = true; self.usePotionBtn.alpha = 1; self.usePotionBtn.interactive = true; } else { self.usePotionBtn.visible = true; //{iL} // Keep visible but disabled for layout consistency self.usePotionBtn.alpha = 0.5; self.usePotionBtn.interactive = false; } // Show/hide DRINK button for antidote if (player.inventory.antidoteCount > 0 && player.isPoisoned) { self.useAntidoteBtn.visible = true; self.useAntidoteBtn.alpha = 1; self.useAntidoteBtn.interactive = true; } else { self.useAntidoteBtn.visible = true; //{iP} // Keep visible but disabled self.useAntidoteBtn.alpha = 0.5; self.useAntidoteBtn.interactive = false; } }; // Start the inventory screen self.startInventoryScreen = function () { self.visible = true; self.alpha = 0; game.addChild(self); LK.getSound('tap').play(); updateGUI(); // Hide main GUI self.updateInventoryDisplay(); // Update inventory when opening // Fade in tween(self, { alpha: 1 }, { duration: 300, easing: tween.easeOut }); }; // End the inventory screen self.endInventoryScreen = function () { self.visible = false; self.alpha = 0; if (self.parent) self.parent.removeChild(self); LK.getSound('tap').play(); showGameGUIWithTransition(); }; return self; }); // PlayerStatsScreen: Displays and allows spending skill points var PlayerStatsScreen = Container.expand(function () { var self = Container.call(this); self.visible = false; self.alpha = 0; // Background overlay self.overlay = self.attachAsset('emptyTileCover', { width: 2048, height: 2732, alpha: 0.9 }); // Main panel background self.panel = self.attachAsset('emptyTileBg', { width: 1200, height: 1600, // Adjusted height anchorX: 0.5, anchorY: 0.5, x: 2048 / 2, y: 2732 / 2 }); // Title self.titleText = new Text2('Player Stats', { size: 110, fill: 0xFFE066, font: "Impact" // Using a pixel-style font }); self.titleText.anchor.set(0.5, 0.5); self.titleText.x = 2048 / 2; self.titleText.y = self.panel.y - self.panel.height / 2 + 120; self.addChild(self.titleText); var panelLeftEdge = self.panel.x - self.panel.width / 2; var labelColumnX = panelLeftEdge + 80; var valueColumnX = labelColumnX + 300; // For stat values var buttonColumnX = self.panel.x + self.panel.width / 2 - 250; // For upgrade buttons, pushed more to the right var itemSpacingY = 80; var currentY = self.panel.y - self.panel.height / 2 + 250; // --- Player Info Section --- // Level self.levelLabelText = new Text2('Level:', { size: 60, fill: "#fff", font: "Impact" }); self.levelLabelText.anchor.set(0, 0.5); self.levelLabelText.x = labelColumnX; self.levelLabelText.y = currentY; self.addChild(self.levelLabelText); self.levelValueText = new Text2('', { size: 60, fill: 0xA3E635, font: "Impact" }); self.levelValueText.anchor.set(0, 0.5); self.levelValueText.x = valueColumnX; self.levelValueText.y = currentY; self.addChild(self.levelValueText); currentY += itemSpacingY; // EXP self.expLabelText = new Text2('EXP:', { size: 60, fill: "#fff", font: "Impact" }); self.expLabelText.anchor.set(0, 0.5); self.expLabelText.x = labelColumnX; self.expLabelText.y = currentY; self.addChild(self.expLabelText); self.expValueText = new Text2('', { size: 60, fill: 0xFFE066, font: "Impact" }); self.expValueText.anchor.set(0, 0.5); self.expValueText.x = valueColumnX; self.expValueText.y = currentY; self.addChild(self.expValueText); currentY += itemSpacingY * 1.5; // Extra space before stats // --- Core Stats Section --- // HP self.hpLabelText = new Text2('HP:', { size: 60, fill: "#fff", font: "Impact" }); self.hpLabelText.anchor.set(0, 0.5); self.hpLabelText.x = labelColumnX; self.hpLabelText.y = currentY; self.addChild(self.hpLabelText); self.hpValueText = new Text2('', { size: 60, fill: 0xA3E635, font: "Impact" }); self.hpValueText.anchor.set(0, 0.5); self.hpValueText.x = valueColumnX; self.hpValueText.y = currentY; self.addChild(self.hpValueText); currentY += itemSpacingY; // DMG self.dmgLabelText = new Text2('Damage:', { size: 60, fill: "#fff", font: "Impact" }); self.dmgLabelText.anchor.set(0, 0.5); self.dmgLabelText.x = labelColumnX; self.dmgLabelText.y = currentY; self.addChild(self.dmgLabelText); self.dmgValueText = new Text2('', { size: 60, fill: 0xF87171, font: "Impact" }); self.dmgValueText.anchor.set(0, 0.5); self.dmgValueText.x = valueColumnX; self.dmgValueText.y = currentY; self.addChild(self.dmgValueText); currentY += itemSpacingY; // DEF self.defLabelText = new Text2('Defense:', { size: 60, fill: "#fff", font: "Impact" }); self.defLabelText.anchor.set(0, 0.5); self.defLabelText.x = labelColumnX; self.defLabelText.y = currentY; self.addChild(self.defLabelText); self.defValueText = new Text2('', { size: 60, fill: 0x7DD3FC, font: "Impact" }); self.defValueText.anchor.set(0, 0.5); self.defValueText.x = valueColumnX; self.defValueText.y = currentY; self.addChild(self.defValueText); currentY += itemSpacingY * 1.5; // Extra space // --- Skill Points & Upgrades Section --- self.skillPointsLabelText = new Text2('Skill Points:', { size: 60, fill: "#fff", font: "Impact" }); self.skillPointsLabelText.anchor.set(0, 0.5); self.skillPointsLabelText.x = labelColumnX; self.skillPointsLabelText.y = currentY; self.addChild(self.skillPointsLabelText); self.skillPointsValueText = new Text2('', { size: 60, fill: 0xFFE066, font: "Impact" }); self.skillPointsValueText.anchor.set(0, 0.5); self.skillPointsValueText.x = valueColumnX; self.skillPointsValueText.y = currentY; self.addChild(self.skillPointsValueText); currentY += itemSpacingY; // Upgrade Buttons self.upgradeBtns = []; var upgradeOptions = [{ label: "+HP & Heal", stat: "hp", color: 0xA3E635 }, { label: "+Damage", // Shortened for consistency stat: "damage", color: 0xF87171 }, { label: "+Defense", // Shortened stat: "defense", color: 0x7DD3FC }]; var upgradeButtonYStart = currentY; for (var i = 0; i < upgradeOptions.length; i++) { var option = upgradeOptions[i]; // Upgrade Label (optional, if we want "Upgrade HP:" next to button) // var upgradeLabel = new Text2('Upgrade ' + option.stat.toUpperCase() + ':', { size: 55, fill: "#fff", font: "Impact" }); // upgradeLabel.anchor.set(0, 0.5); // upgradeLabel.x = labelColumnX; // upgradeLabel.y = upgradeButtonYStart + i * itemSpacingY; // self.addChild(upgradeLabel); var btn = new Text2(option.label, { size: 60, fill: option.color, font: "Impact" // Using a pixel-style font }); btn.anchor.set(0.5, 0.5); // Anchor to its center btn.x = valueColumnX + 150; // Position next to the stat value btn.y = upgradeButtonYStart + i * (itemSpacingY + 10); // Adjusted Y position for buttons btn.optionStat = option.stat; btn.visible = false; btn.down = function () { if (!self.visible || player.skillPoints <= 0) return; player.spendSkillPoint(this.optionStat); self.updateStatsDisplay(); updateGUI(); if (player.skillPoints <= 0) { self.hideUpgradeButtons(); } }; self.addChild(btn); self.upgradeBtns.push(btn); } // Close button self.closeBtn = new Text2('CLOSE', { size: 70, fill: "#fff", font: "Impact" // Using a pixel-style font }); self.closeBtn.anchor.set(0.5, 0.5); self.closeBtn.x = 2048 / 2; self.closeBtn.y = self.panel.y + self.panel.height / 2 - 80; self.closeBtn.down = function () { if (!self.visible) return; self.endStatsScreen(); }; self.addChild(self.closeBtn); // Update the displayed stats self.updateStatsDisplay = function () { // Animate stat value text if changed function animateStatChange(textObject, newValue, oldValue) { if (typeof oldValue !== "undefined" && oldValue !== newValue) { tween(textObject, { scaleX: 1.25, scaleY: 1.25 }, { duration: 120, easing: tween.cubicOut, onFinish: function onFinish() { tween(textObject, { scaleX: 1, scaleY: 1 }, { duration: 120, easing: tween.cubicIn }); } }); } } animateStatChange(self.levelValueText, player.level, self._lastLevel); self.levelValueText.setText('' + player.level); var expString = player.exp + '/' + player.expToLevel; animateStatChange(self.expValueText, expString, self._lastExpString); self.expValueText.setText(expString); var hpString = player.hp + '/' + player.maxHp; animateStatChange(self.hpValueText, hpString, self._lastHpString); self.hpValueText.setText(hpString); animateStatChange(self.dmgValueText, player.damage, self._lastDmg); self.dmgValueText.setText('' + player.damage); animateStatChange(self.defValueText, player.defense, self._lastDef); self.defValueText.setText('' + player.defense); animateStatChange(self.skillPointsValueText, player.skillPoints, self._lastSkillPoints); self.skillPointsValueText.setText('' + player.skillPoints); // Store last values for next update self._lastLevel = player.level; self._lastExpString = expString; self._lastHpString = hpString; self._lastDmg = player.damage; self._lastDef = player.defense; self._lastSkillPoints = player.skillPoints; if (player.skillPoints > 0) { self.showUpgradeButtons(); } else { self.hideUpgradeButtons(); } }; // Show upgrade buttons self.showUpgradeButtons = function () { for (var i = 0; i < self.upgradeBtns.length; i++) { self.upgradeBtns[i].visible = true; } }; // Hide upgrade buttons self.hideUpgradeButtons = function () { for (var i = 0; i < self.upgradeBtns.length; i++) { self.upgradeBtns[i].visible = false; } }; // Start the stats screen self.startStatsScreen = function () { self.visible = true; self.alpha = 0; game.addChild(self); LK.getSound('tap').play(); updateGUI(); // Hide main GUI self.updateStatsDisplay(); // Update stats when opening // Fade in tween(self, { alpha: 1 }, { duration: 300, easing: tween.easeOut }); }; // End the stats screen self.endStatsScreen = function () { self.visible = false; self.alpha = 0; if (self.parent) self.parent.removeChild(self); LK.getSound('tap').play(); showGameGUIWithTransition(); }; return self; }); // ShopScreen: Allows buying and selling items var ShopScreen = Container.expand(function () { var self = Container.call(this); self.visible = false; self.alpha = 0; // Background overlay self.overlay = self.attachAsset('emptyTileCover', { width: 2048, height: 2732, alpha: 0.9 }); // Main panel background self.panel = self.attachAsset('emptyTileBg', { width: 1400, height: 2000, anchorX: 0.5, anchorY: 0.5, x: 2048 / 2, y: 2732 / 2 }); // Title self.titleText = new Text2('Shop', { size: 110, fill: 0xADD8E6, // LightBlue font: "Impact" // Using a pixel-style font }); self.titleText.anchor.set(0.5, 0.5); self.titleText.x = 2048 / 2; self.titleText.y = self.panel.y - self.panel.height / 2 + 120; self.addChild(self.titleText); // Coins display self.coinsText = new Text2('Coins: ' + player.gold, { size: 60, fill: 0xFFD700, font: "Impact" // Using a pixel-style font }); self.coinsText.anchor.set(0, 0.5); self.coinsText.x = self.panel.x - self.panel.width / 2 + 80; self.coinsText.y = self.panel.y - self.panel.height / 2 + 220; self.addChild(self.coinsText); // Items for Sale (expanded: quality depends on level) self.itemsForSale = []; // Always sell potions and antidotes self.itemsForSale.push({ type: "potion", label: "Life Potion", color: 0xA3E635, cost: 10 }); self.itemsForSale.push({ type: "antidote", label: "Antidote Potion", color: 0x800080, cost: 15 }); // Determine available gear qualities for this shop based on currentLevel var availableQualities = []; if (chestScreen && chestScreen._allLootQualities) { for (var i = 0; i < chestScreen._allLootQualities.length; i++) { if (currentLevel + 1 >= chestScreen._allLootQualities[i].minLevel) { availableQualities.push(chestScreen._allLootQualities[i]); } } } // Lower level shops only sell lower quality gear var maxQualityIndex = Math.min(currentLevel, availableQualities.length - 1); if (maxQualityIndex >= 0) { // Only offer up to maxQualityIndex quality for (var q = 0; q <= maxQualityIndex; q++) { var quality = availableQualities[q]; // For each gear type var gearTypes = [{ type: "sword", label: "Sword" }, { type: "shield", label: "Shield" }, { type: "helmet", label: "Helmet" }, { type: "armour", label: "Armour" }]; for (var g = 0; g < gearTypes.length; g++) { var gear = gearTypes[g]; // Cost scales with quality modifier var cost = 20 + quality.modifier * 15 + currentLevel * 5; self.itemsForSale.push({ type: gear.type, label: quality.label + " " + gear.label, color: quality.color, cost: cost, quality: quality }); } } } self.saleButtons = []; var startY = self.coinsText.y + 100; for (var i = 0; i < self.itemsForSale.length; i++) { var item = self.itemsForSale[i]; var buyBtn = new Text2('BUY ' + item.label + ' (' + item.cost + ' Coins)', { size: 60, fill: item.color, font: "Impact" }); buyBtn.anchor.set(0, 0.5); buyBtn.x = self.panel.x - self.panel.width / 2 + 80; buyBtn.y = startY + i * 100; buyBtn.item = item; // Add stat benefit text next to the buy button var statBenefitText = new Text2('', { size: 45, // Smaller size fill: item.color, font: "Impact" }); statBenefitText.anchor.set(0, 0.5); statBenefitText.x = buyBtn.x + buyBtn.width + 20; // Position to the right of the button statBenefitText.y = buyBtn.y; self.addChild(statBenefitText); buyBtn.statBenefitText = statBenefitText; // Store reference to the text // Set the stat benefit text based on item type if (item.type === "potion") { statBenefitText.setText("(Heals 20 HP)"); } else if (item.type === "antidote") { statBenefitText.setText("(Cures Poison)"); } else if (item.quality) { var benefit = ''; if (item.type === 'sword') { benefit = '+' + item.quality.modifier + ' DMG'; } else if (item.type === 'shield') { benefit = '+' + item.quality.modifier + ' DEF'; } else if (item.type === 'helmet') { benefit = '+' + Math.floor(item.quality.modifier / 2) + ' DEF'; } else if (item.type === 'armour') { benefit = '+' + item.quality.modifier + ' DEF'; } statBenefitText.setText("(" + benefit + ")"); } buyBtn.down = function () { if (!self.visible) return; var itemToBuy = this.item; if (player.gold >= itemToBuy.cost) { player.gold -= itemToBuy.cost; if (itemToBuy.type === "potion") { player.inventory.potionCount++; } else if (itemToBuy.type === "antidote") { player.inventory.antidoteCount = (player.inventory.antidoteCount || 0) + 1; } else if (itemToBuy.quality) { // Gear purchase: equip or replace player.equipGear(itemToBuy.type, itemToBuy.quality); playerStatsScreen.updateStatsDisplay(); } self.updateShopDisplay(); playerInventoryScreen.updateInventoryDisplay(); self.showConfirmationText('Bought ' + itemToBuy.label + '!', itemToBuy.color); } else { self.showConfirmationText('Not enough coins!', 0xFF0000); } }; self.addChild(buyBtn); self.saleButtons.push(buyBtn); } // Items to Sell (simplified for now: gear and potions) self.sellButtons = []; var sellTitle = new Text2('Sell Items:', { size: 70, fill: 0xFFE066, // Yellow font: "Impact" // Using a pixel-style font }); sellTitle.anchor.set(0, 0.5); sellTitle.x = self.panel.x - self.panel.width / 2 + 80; sellTitle.y = startY + self.itemsForSale.length * 100 + 80; self.addChild(sellTitle); self.updateSellButtons = function () { // Clear existing sell buttons for (var i = 0; i < self.sellButtons.length; i++) { self.sellButtons[i].destroy(); } self.sellButtons = []; var currentSellY = sellTitle.y + 80; // Sell gear var gearTypes = ['sword', 'shield', 'helmet', 'armour']; for (var i = 0; i < gearTypes.length; i++) { var gearType = gearTypes[i]; var item = player.inventory[gearType]; if (item) { var sellPrice = item.modifier * 5; // Example sell price var sellBtn = new Text2('SELL ' + item.label + ' ' + gearType.charAt(0).toUpperCase() + gearType.slice(1) + ' (' + sellPrice + ' Coins)', { size: 50, fill: item.color, font: "Impact" // Using a pixel-style font }); sellBtn.anchor.set(0, 0.5); sellBtn.x = self.panel.x - self.panel.width / 2 + 80; sellBtn.y = currentSellY; sellBtn.itemType = gearType; sellBtn.sellPrice = sellPrice; sellBtn.down = function () { if (!self.visible) return; var type = this.itemType; var price = this.sellPrice; player.gold += price; player.inventory[type] = null; // Remove item from inventory player.updateEquippedStats(); // Update player stats self.updateShopDisplay(); playerInventoryScreen.updateInventoryDisplay(); // Update inventory screen if open playerStatsScreen.updateStatsDisplay(); // Update stats screen if open self.updateSellButtons(); // Refresh sell buttons self.showConfirmationText('Sold ' + type.charAt(0).toUpperCase() + type.slice(1) + ' for ' + price + ' coins!', 0xFFD700); }; self.addChild(sellBtn); self.sellButtons.push(sellBtn); currentSellY += 60; } } // Sell potions if (player.inventory.potionCount > 0) { var potionSellPrice = 10; // Example sell price for potion var sellPotionBtn = new Text2('SELL Life Potion (' + potionSellPrice + ' Coins)', { size: 50, fill: 0xA3E635, font: "Impact" // Using a pixel-style font }); sellPotionBtn.anchor.set(0, 0.5); sellPotionBtn.x = self.panel.x - self.panel.width / 2 + 80; sellPotionBtn.y = currentSellY; sellPotionBtn.sellPrice = potionSellPrice; sellPotionBtn.down = function () { if (!self.visible || player.inventory.potionCount <= 0) return; var price = this.sellPrice; player.gold += price; player.inventory.potionCount--; // Remove potion self.updateShopDisplay(); playerInventoryScreen.updateInventoryDisplay(); // Update inventory screen if open self.updateSellButtons(); // Refresh sell buttons self.showConfirmationText('Sold a Life Potion for ' + price + ' coins!', 0xFFD700); }; self.addChild(sellPotionBtn); self.sellButtons.push(sellPotionBtn); currentSellY += 60; } // Sell antidotes if (player.inventory.antidoteCount > 0) { var antidoteSellPrice = 10; // Example sell price for antidote var sellAntidoteBtn = new Text2('SELL Antidote Potion (' + antidoteSellPrice + ' Coins)', { size: 50, fill: 0x800080, // Purple font: "Impact" // Using a pixel-style font }); sellAntidoteBtn.anchor.set(0, 0.5); sellAntidoteBtn.x = self.panel.x - self.panel.width / 2 + 80; sellAntidoteBtn.y = currentSellY; sellAntidoteBtn.sellPrice = antidoteSellPrice; sellAntidoteBtn.down = function () { if (!self.visible || player.inventory.antidoteCount <= 0) return; var price = this.sellPrice; player.gold += price; player.inventory.antidoteCount--; self.updateShopDisplay(); playerInventoryScreen.updateInventoryDisplay(); // Update inventory screen if open self.updateSellButtons(); // Refresh sell buttons self.showConfirmationText('Sold an Antidote Potion for ' + price + ' coins!', 0xFFD700); }; self.addChild(sellAntidoteBtn); self.sellButtons.push(sellAntidoteBtn); currentSellY += 60; } }; // Confirmation text display self._confirmationText = null; self.showConfirmationText = function (message, color) { if (self._confirmationText && typeof self._confirmationText.destroy === 'function') { self._confirmationText.destroy(); } self._confirmationText = new Text2(message, { size: 60, fill: color, font: "Impact" // Using a pixel-style font }); self._confirmationText.anchor.set(0.5, 0.5); self._confirmationText.x = 2048 / 2; self._confirmationText.y = self.panel.y + self.panel.height / 2 - 150; self.addChild(self._confirmationText); tween(self._confirmationText, { alpha: 0 }, { duration: 1000, delay: 800, onFinish: function onFinish() { if (self._confirmationText && typeof self._confirmationText.destroy === 'function') { self._confirmationText.destroy(); } self._confirmationText = null; } }); }; // Close button self.closeBtn = new Text2('CLOSE', { size: 70, fill: "#fff", font: "Impact" // Using a pixel-style font }); self.closeBtn.anchor.set(0.5, 0.5); self.closeBtn.x = 2048 / 2; self.closeBtn.y = self.panel.y + self.panel.height / 2 - 80; self.closeBtn.down = function () { if (!self.visible) return; self.endShopScreen(); }; self.addChild(self.closeBtn); // Update the displayed shop info self.updateShopDisplay = function () { // Animate coins text if value changed if (typeof self._lastGold !== "undefined" && self._lastGold !== player.gold) { tween(self.coinsText, { scaleX: 1.25, scaleY: 1.25 }, { duration: 120, easing: tween.cubicOut, onFinish: function onFinish() { tween(self.coinsText, { scaleX: 1, scaleY: 1 }, { duration: 120, easing: tween.cubicIn }); } }); } self.coinsText.setText('Coins: ' + player.gold); self._lastGold = player.gold; self.updateSellButtons(); // Refresh sell buttons }; // Start the shop screen self.startShopScreen = function () { self.visible = true; self.alpha = 0; game.addChild(self); self.updateShopDisplay(); // Update shop info when opening // Fade in tween(self, { alpha: 1 }, { duration: 300, easing: tween.easeOut }); }; // End the shop screen self.endShopScreen = function () { self.visible = false; self.alpha = 0; if (self._confirmationText && typeof self._confirmationText.destroy === 'function') { self._confirmationText.destroy(); self._confirmationText = null; } if (self.parent) self.parent.removeChild(self); if (monstersLeft <= 0 && currentLevel < LEVELS.length - 1) { levelEndScreen.startLevelEndScreen(); } else { showGameGUIWithTransition(); } }; return self; }); /**** * Initialize Game ****/ var game = new LK.Game({ backgroundColor: 0x22223a }); /**** * Game Code ****/ // New shape for black outline // --- Game Constants --- // Add event listener to MonsterTile for clicking // New asset for game title image MonsterTile.prototype.down = function (x, y, obj) { if (gameOver) return; // Prevent interaction if any overlay screen is visible if (mainScreen.visible || battleScreen.visible || battleScreen.battleItemsMenu.visible || chestScreen.visible || playerStatsScreen.visible || playerInventoryScreen.visible || levelEndScreen.visible || shopScreen.visible) return; // If the monster is defeated, do nothing if (this.defeated) return; // If the monster is not defeated (either initially or after running away), a click should initiate a battle. // Ensure it's revealed before starting the battle. // The reveal() method has an internal check (if (self.revealed) return;) // so it's safe to call even if already revealed. this.reveal(); battleScreen.startBattle(this); }; function _slicedToArray(r, e) { return _arrayWithHoles(r) || _iterableToArrayLimit(r, e) || _unsupportedIterableToArray(r, e) || _nonIterableRest(); } function _nonIterableRest() { throw new TypeError("Invalid attempt to destructure non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); } function _unsupportedIterableToArray(r, a) { if (r) { if ("string" == typeof r) return _arrayLikeToArray(r, a); var t = {}.toString.call(r).slice(8, -1); return "Object" === t && r.constructor && (t = r.constructor.name), "Map" === t || "Set" === t ? Array.from(r) : "Arguments" === t || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(t) ? _arrayLikeToArray(r, a) : void 0; } } function _arrayLikeToArray(r, a) { (null == a || a > r.length) && (a = r.length); for (var e = 0, n = Array(a); e < a; e++) n[e] = r[e]; return n; } function _iterableToArrayLimit(r, l) { var t = null == r ? null : "undefined" != typeof Symbol && r[Symbol.iterator] || r["@@iterator"]; if (null != t) { var e, n, i, u, a = [], f = !0, o = !1; try { if (i = (t = t.call(r)).next, 0 === l) { if (Object(t) !== t) return; f = !1; } else for (; !(f = (e = i.call(t)).done) && (a.push(e.value), a.length !== l); f = !0); } catch (r) { o = !0, n = r; } finally { try { if (!f && null != t["return"] && (u = t["return"](), Object(u) !== u)) return; } finally { if (o) throw n; } } return a; } } function _arrayWithHoles(r) { if (Array.isArray(r)) return r; } var GRID_COLS = 8; var GRID_ROWS = 8; var TILE_SIZE = 180; var GRID_OFFSET_X = Math.floor((2048 - GRID_COLS * TILE_SIZE) / 2); var GRID_OFFSET_Y = Math.floor(2732 * 0.2 + (2732 * 0.6 - GRID_ROWS * TILE_SIZE) / 2); // Center the grid vertically within the middle 60% // --- Level System --- var LEVELS = [{ monsters: 3, // Adjusted for level 1 monsterStats: { minHp: 1, maxHp: 2, minDmg: 1, maxDmg: 1, minExp: 1, maxExp: 1 }, // Level 1: 3 normal monsters, no poison monster monsterTypes: [MonsterTile, MonsterTile, MonsterTile] // Adjusted for level 1 }, { monsters: 5, monsterStats: { minHp: 3, maxHp: 5, minDmg: 2, maxDmg: 3, minExp: 2, maxExp: 3 }, monsterTypes: [MonsterTile, PoisonMonsterTile] // Level 2 can have basic or poison monsters }, { monsters: 7, monsterStats: { minHp: 5, maxHp: 8, minDmg: 3, maxDmg: 5, minExp: 3, maxExp: 4 }, monsterTypes: [MonsterTile, PoisonMonsterTile] // Level 3 can also have both monster types }]; var currentLevel = 0; var MONSTER_COUNT = LEVELS[currentLevel].monsters; // --- Asset Initialization --- // --- Game State --- var player = new Player(); player.damage = 1; player.baseDamage = 1; player.exp = 0; var grid = []; // 2D array [row][col] var tileObjs = []; // Flat array of all tile objects for easy iteration var monstersLeft = MONSTER_COUNT; var revealedTiles = 0; var totalSafeTiles = GRID_COLS * GRID_ROWS - MONSTER_COUNT; var gameOver = false; var mainScreen = new MainScreen(); // Initialize main screen var background = game.addChild(LK.getAsset('background', { anchorX: 0.5, anchorY: 0.5, x: 2048 / 2, y: 2732 / 2 })); var battleScreen = new BattleScreen(); var chestScreen = new ChestScreen(); var playerStatsScreen = new PlayerStatsScreen(); // Initialize stats screen var playerInventoryScreen = new PlayerInventoryScreen(); // Initialize inventory screen var levelEndScreen = new LevelEndScreen(); // Initialize level end screen var shopScreen = new ShopScreen(); // Initialize shop screen // --- GUI Elements --- var hpText = new Text2('', { size: 60, fill: 0xFF6666, font: "Impact" // Using a pixel-style font }); hpText.anchor.set(0.5, 0); // Anchor to top-center LK.gui.top.addChild(hpText); var expText = new Text2('', { size: 60, fill: 0xFFE066, font: "Impact" // Using a pixel-style font }); expText.anchor.set(0.5, 0); // Anchor to top-center LK.gui.top.addChild(expText); var levelText = new Text2('', { size: 60, fill: 0xA3E635, font: "Impact" // Using a pixel-style font }); levelText.anchor.set(0, 0.5); // Anchor to left-middle LK.gui.top.addChild(levelText); // var monstersLeftText = new Text2('', { // size: 40, // fill: "#fff" // }); // monstersLeftText.anchor.set(0, 0.5); // Anchor to left-middle // LK.gui.top.addChild(monstersLeftText); var statsBtn = new Text2('STATS', { size: 70, fill: "#fff", font: "Impact" // Using a pixel-style font }); statsBtn.anchor.set(0.5, 1); // Anchor to center-bottom statsBtn.x = -100; // Position to the left of the right edge (adjusted for safe area) statsBtn.y = -40; // Position near the bottom edge statsBtn.visible = false; // Set initial visibility to false statsBtn.down = function () { if (gameOver || battleScreen.visible || chestScreen.visible || playerStatsScreen.visible || playerInventoryScreen.visible) return; // Don't open if another screen is active playerStatsScreen.startStatsScreen(); }; LK.gui.bottom.addChild(statsBtn); var inventoryBtn = new Text2('INVENTORY', { size: 70, fill: "#fff", font: "Impact" // Using a pixel-style font }); inventoryBtn.anchor.set(0.5, 1); // Anchor to center-bottom inventoryBtn.x = -100 - statsBtn.width - 40; // Position to the left of the right edge (adjusted for safe area and other button) inventoryBtn.y = -40; // Position near the bottom edge inventoryBtn.visible = false; // Set initial visibility to false inventoryBtn.down = function () { if (gameOver || battleScreen.visible || chestScreen.visible || playerStatsScreen.visible || playerInventoryScreen.visible) return; // Don't open if another screen is active playerInventoryScreen.startInventoryScreen(); }; LK.gui.bottom.addChild(inventoryBtn); var levelUpBtn = new Text2('LEVEL UP', { size: 70, fill: "#fff", font: "Impact" // Using a pixel-style font }); levelUpBtn.anchor.set(0.5, 0.5); levelUpBtn.alpha = 0.7; levelUpBtn.visible = false; levelUpBtn.down = function () { if (!this.visible) return; // Old level up choices removed }; LK.gui.top.addChild(levelUpBtn); var managedGameGuiElements = [hpText, expText, levelText, statsBtn, inventoryBtn]; // Old level up choice buttons and logic removed // --- Helper Functions --- // Helper function to show main GUI elements with a fade-in transition function showGameGUIWithTransition() { updateGUI(); // Set visibility and content first for (var i = 0; i < managedGameGuiElements.length; i++) { var el = managedGameGuiElements[i]; // Tween alpha to 1. statsBtn's specific animation in game.update will take over if active. tween(el, { alpha: 1 }, { duration: 300 }); } } function updateGUI() { var showMainGui = !mainScreen.visible && !battleScreen.visible && !(chestScreen && chestScreen.visible) && !(playerStatsScreen && playerStatsScreen.visible) && !(playerInventoryScreen && playerInventoryScreen.visible) && !(levelEndScreen && levelEndScreen.visible) && !(shopScreen && shopScreen.visible); for (var i = 0; i < managedGameGuiElements.length; i++) { var el = managedGameGuiElements[i]; if (showMainGui) { el.visible = true; // Alpha is primarily handled by transitions or default to 1 after transition. // Specific alpha animations (like statsBtn) are handled in game.update. } else { el.visible = false; el.alpha = 0; // Ensure they are fully transparent when hidden } } // Ensure levelUpBtn is also hidden if main GUI is hidden or if it's simply not used levelUpBtn.visible = false; // Specific content updates if GUI is shown if (showMainGui) { hpText.setText('HP: ' + player.hp + '/' + player.maxHp); hpText.x = 450; // Position HP more to the right hpText.y = 50; // Adjusted vertical position var canLevelUp = player.exp >= player.expToLevel; var plusStr = canLevelUp ? " [color=#FFE066]+[/color]" : ""; expText.setText('EXP: ' + player.exp + '/' + player.expToLevel + plusStr); expText.x = 450; // Position EXP more to the right expText.y = 120; // Adjusted vertical position below HP var lvlPlusStr = canLevelUp ? " [color=#FFE066]+[/color]" : ""; levelText.setText('LEVEL: ' + player.level + lvlPlusStr); levelText.x = 0; // Position LVL in the center of the top gui levelText.y = 80; // Move level text a little lower levelText.anchor.set(0.5, 0.5); // monstersLeftText logic remains commented out as in original // monstersLeftText.setText('Monsters: ' + monstersLeft); // monstersLeftText.x = levelText.x + levelText.width + 40; // monstersLeftText.y = 60; // Position and style statsBtn and inventoryBtn // Their visibility is already handled by the loop above. var totalBtnWidth = statsBtn.width + inventoryBtn.width + 40; var centerX = 0; // LK.gui.bottom center is x=0, these are added to LK.gui.bottom statsBtn.x = centerX + totalBtnWidth / 2 - statsBtn.width / 2; statsBtn.y = -40; // Position near the bottom edge inventoryBtn.x = centerX - totalBtnWidth / 2 + inventoryBtn.width / 2; inventoryBtn.y = -40; // Position near the bottom edge if (player.skillPoints > 0) { statsBtn.setText('STATS +'); if (statsBtn.style) statsBtn.style.fill = 0xFFE066; // Yellow color if (typeof statsBtn.tint !== "undefined") statsBtn.tint = 0xFFE066; // Set yellow tint // Alpha for statsBtn (pulsing animation) is handled in game.update } else { statsBtn.setText('STATS'); if (statsBtn.style) statsBtn.style.fill = "#fff"; // Default white color if (typeof statsBtn.tint !== "undefined") statsBtn.tint = 0xFFFFFF; // Reset to white tint // Alpha for statsBtn (solid) is handled in game.update or by transition } } else { // If GUI is hidden, and battle screen is the one visible, update its stats if (battleScreen.visible) { battleScreen.updateBattleStats(); } } } // Returns true if (row, col) is inside grid function inBounds(row, col) { return row >= 0 && row < GRID_ROWS && col >= 0 && col < GRID_COLS; } // Get all adjacent tile positions function getAdjacent(row, col) { var adj = []; for (var dr = -1; dr <= 1; dr++) { for (var dc = -1; dc <= 1; dc++) { if (dr === 0 && dc === 0) continue; var nr = row + dr, nc = col + dc; if (inBounds(nr, nc)) adj.push([nr, nc]); } } return adj; } // Reveal empty tiles recursively (flood fill) function revealEmptyTiles(row, col) { var tile = grid[row][col]; if (tile.revealed) return; tile.reveal(); revealedTiles++; if (tile.adjacentMonsterDamage === 0) { var adj = getAdjacent(row, col); for (var i = 0; i < adj.length; i++) { var _adj$i = _slicedToArray(adj[i], 2), nr = _adj$i[0], nc = _adj$i[1]; var t = grid[nr][nc]; if (t instanceof EmptyTile && !t.revealed) { revealEmptyTiles(nr, nc); } } } } // Reveal all monsters (on game over) function revealAllMonsters() { for (var i = 0; i < tileObjs.length; i++) { var t = tileObjs[i]; if (t instanceof MonsterTile && !t.revealed) { t.reveal(); } } } // --- Board Generation --- function generateBoard() { // Clear previous for (var i = 0; i < tileObjs.length; i++) { tileObjs[i].destroy(); } grid = []; tileObjs = []; monstersLeft = LEVELS[currentLevel].monsters; revealedTiles = 0; totalSafeTiles = GRID_COLS * GRID_ROWS - LEVELS[currentLevel].monsters; gameOver = false; player.curePoison(); // Cure poison at the start of a new level // Place monsters var monsterPositions = []; while (monsterPositions.length < LEVELS[currentLevel].monsters) { var r = Math.floor(Math.random() * GRID_ROWS); var c = Math.floor(Math.random() * GRID_COLS); // Prevent monsters from being placed at (0,0) where the chest is always placed if (r === 0 && c === 0) continue; var found = false; for (var i = 0; i < monsterPositions.length; i++) { if (monsterPositions[i][0] === r && monsterPositions[i][1] === c) { found = true; break; } } if (!found) monsterPositions.push([r, c]); } // --- Randomize chest position --- var chestRow = -1, chestCol = -1; while (true) { var tryRow = Math.floor(Math.random() * GRID_ROWS); var tryCol = Math.floor(Math.random() * GRID_COLS); // Chest cannot be placed on a monster var isMonsterCell = false; for (var i = 0; i < monsterPositions.length; i++) { if (monsterPositions[i][0] === tryRow && monsterPositions[i][1] === tryCol) { isMonsterCell = true; break; } } if (!isMonsterCell) { chestRow = tryRow; chestCol = tryCol; break; } } // Build grid var stats = LEVELS[currentLevel].monsterStats; for (var row = 0; row < GRID_ROWS; row++) { grid[row] = []; for (var col = 0; col < GRID_COLS; col++) { var isMonster = false; for (var i = 0; i < monsterPositions.length; i++) { if (monsterPositions[i][0] === row && monsterPositions[i][1] === col) { isMonster = true; break; } } var tile; // Place chest at randomized position if (row === chestRow && col === chestCol) { tile = new ChestTile(); } else if (isMonster) { // Choose a monster type for this monster position var monsterTypesForLevel = LEVELS[currentLevel].monsterTypes; var MonsterClass; if (currentLevel === 0 && monsterTypesForLevel.length === 4) { // For level 1, assign the monster type based on the monster index MonsterClass = monsterTypesForLevel[monsterPositions.findIndex(function (pos) { return pos[0] === row && pos[1] === col; })]; // Fallback to MonsterTile if not found (should not happen) if (!MonsterClass) MonsterClass = MonsterTile; } else { // For other levels, pick randomly MonsterClass = monsterTypesForLevel[Math.floor(Math.random() * monsterTypesForLevel.length)]; } tile = new MonsterClass(); // Use the selected monster class // Level-based monster stats tile.maxHp = tile.hp = stats.minHp + Math.floor(Math.random() * (stats.maxHp - stats.minHp + 1)); tile.damage = stats.minDmg + Math.floor(Math.random() * (stats.maxDmg - stats.minDmg + 1)); tile.exp = stats.minExp + Math.floor(Math.random() * (stats.maxExp - stats.minExp + 1)); } else { tile = new EmptyTile(); } tile.x = GRID_OFFSET_X + col * TILE_SIZE + TILE_SIZE / 2; tile.y = GRID_OFFSET_Y + row * TILE_SIZE + TILE_SIZE / 2; game.addChild(tile); grid[row][col] = tile; tileObjs.push(tile); } } // Set adjacent monster counts for empty tiles for (var row = 0; row < GRID_ROWS; row++) { for (var col = 0; col < GRID_COLS; col++) { var tile = grid[row][col]; if (tile instanceof EmptyTile) { var adj = getAdjacent(row, col); var damageSum = 0; // Variable to store the sum of damage for (var i = 0; i < adj.length; i++) { var _adj$i2 = _slicedToArray(adj[i], 2), nr = _adj$i2[0], nc = _adj$i2[1]; if (grid[nr][nc] instanceof MonsterTile) { damageSum += grid[nr][nc].damage; // Add monster's damage to the sum } } tile.adjacentMonsterDamage = damageSum; // Store the damage sum } } } } // --- Game Logic --- function handleTileDown(x, y, obj) { if (gameOver) return; if (battleScreen.visible || battleScreen.battleItemsMenu.visible || levelEndScreen.visible || shopScreen.visible || chestScreen.visible || playerStatsScreen.visible || playerInventoryScreen.visible) return; // Don't allow tile interaction if battle screen, battle item menu, level end, shop, chest, stats, or inventory screen is up // Find which tile was pressed for (var i = 0; i < tileObjs.length; i++) { var tile = tileObjs[i]; if (tile.revealed) continue; if (tile.cover && tile.cover.alpha > 0.1) { // Check if (x, y) is inside tile var dx = x - tile.x; var dy = y - tile.y; if (Math.abs(dx) < TILE_SIZE / 2 && Math.abs(dy) < TILE_SIZE / 2) { // Reveal tile if (tile instanceof EmptyTile) { LK.getSound('tap').play(); revealEmptyTiles(Math.floor((tile.y - GRID_OFFSET_Y) / TILE_SIZE), Math.floor((tile.x - GRID_OFFSET_X) / TILE_SIZE)); updateGUI(); // Win check // (Removed: revealing all safe tiles no longer triggers win. Level is won only when all monsters are defeated.) } else if (tile instanceof ChestTile) { LK.getSound('tap').play(); // Reveal chest and show chest screen tile.reveal(); chestScreen.startChest(tile); } else if (tile instanceof MonsterTile) { LK.getSound('tap').play(); // Reveal monster and start battle tile.reveal(); // Reveal the monster visually // Start the battle sequence battleScreen.startBattle(tile); } break; } } } } // --- Level Up UI --- // Old level up UI functions removed // --- Event Handlers --- game.down = function (x, y, obj) { if (mainScreen.visible) return; // Prevent interaction if main screen is visible // The click handling for levelUpBtn and levelUpChoiceBtns // has been moved to their own 'down' event handlers. // Check for tile interactions handleTileDown(x, y, obj); }; game.update = function () { if (mainScreen.visible || battleScreen.visible || battleScreen.battleItemsMenu.visible || chestScreen && chestScreen.visible || playerStatsScreen.visible || playerInventoryScreen.visible || levelEndScreen.visible || shopScreen.visible) return; // Don't update game elements when main, battle, battle item menu, stats, level end, shop, chest, stats, or inventory screen is up // Animate level up button (removed) // if (levelUpBtn.visible) { // levelUpBtn.x = 0; // Centered horizontally relative to LK.gui.top // levelUpBtn.y = 120; // levelUpBtn.alpha = 0.8 + 0.2 * Math.sin(LK.ticks / 20); // } // Animate level up choices (removed) // for (var i = 0; i < levelUpChoiceBtns.length; i++) { // var btn = levelUpChoiceBtns[i]; // if (btn.visible) { // btn.x = (i - 1) * 400; // x relative to LK.gui.bottom // btn.y = -2732 * 0.15; // Position choices above the bottom boundary of the battle area // btn.alpha = 0.9 + 0.1 * Math.sin(LK.ticks / 15 + i); // } // } // Animate stats button if skill points are available if (statsBtn.visible && player.skillPoints > 0) { statsBtn.alpha = 0.8 + 0.2 * Math.sin(LK.ticks / 20); } else if (statsBtn.visible) { statsBtn.alpha = 1; // Solid if no points } if (mainScreen.visible || battleScreen.visible || chestScreen && chestScreen.visible || playerStatsScreen.visible || playerInventoryScreen.visible || levelEndScreen.visible || shopScreen.visible) { for (var i = 0; i < tileObjs.length; i++) { tileObjs[i].visible = false; } background.visible = false; // Hide background } else { for (var i = 0; i < tileObjs.length; i++) { tileObjs[i].visible = true; } background.visible = true; // Show background } }; // --- Start Game --- function startGame() { generateBoard(); showGameGUIWithTransition(); // This will call updateGUI which handles levelText } // Show main screen at start mainScreen.startMainScreen();
/****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
/****
* Classes
****/
// BattleScreen: Handles the turn-based combat
var BattleScreen = Container.expand(function () {
var self = Container.call(this);
self.visible = false; // Initially hidden
self.alpha = 0; // Set alpha to 0 for fade-in animation
// Background overlay
self.overlay = self.attachAsset('emptyTileCover', {
width: 2048,
height: 2732,
alpha: 0.8
});
// Monster display
self.monsterDisplay = self.attachAsset('monsterTileMonster', {
anchorX: 0.5,
anchorY: 0.5,
x: 2048 - 300,
// Position near top-right corner within the battle area
y: 2732 * 0.2,
// Position near top-right corner within the battle area, moved higher
scaleX: 4,
// Increased scale
scaleY: 4 // Increased scale
});
// Monster stats text
self.monsterStatText = new Text2('', {
size: 60,
fill: "#fff",
font: "Impact" // Using a pixel-style font
});
self.monsterStatText.anchor.set(0.5, 0); // Anchor to top-center
self.monsterStatText.x = self.monsterDisplay.x;
self.monsterStatText.y = self.monsterDisplay.y + self.monsterDisplay.height * self.monsterDisplay.scaleY / 2 + 60; // Positioned closer below monster image
self.addChild(self.monsterStatText);
// Player stats text (in battle)
self.playerBattleStatText = new Text2('', {
size: 60,
fill: "#fff",
font: "Impact" // Using a pixel-style font
});
self.playerDisplay = self.attachAsset('character', {
anchorX: 0.5,
anchorY: 0.5,
x: 300,
// Position near bottom-left corner within the battle area
y: 2732 * 0.8 - 350,
// Position near bottom-left corner within the battle area, moved higher
scaleX: 4,
// Increased scale
scaleY: 4 // Increased scale
});
self.playerBattleStatText.anchor.set(0.5, 0); // Anchor to top-center
// Set stat text position to where hero will appear
self.playerBattleStatText.x = self.playerDisplay.x; // Relative to player display X
self.playerBattleStatText.y = self.playerDisplay.y + self.playerDisplay.height * self.playerDisplay.scaleY / 2 + 60; // Positioned closer below player, accounting for new scale
self.addChild(self.playerBattleStatText);
// Action button (Attack)
self.attackBtn = new Text2('ATTACK', {
size: 80,
fill: "#fff",
font: "Impact" // Using a pixel-style font
});
self.attackBtn.anchor.set(0.5, 0.5);
// Action button (Items) - Initial declaration for width calculation
// The actual self.itemsBtn will be (re)declared later. This corresponds to original first itemsBtn {J}
var tempItemsBtn = new Text2('ITEMS', {
size: 80,
fill: "#fff",
font: "Impact" // Using a pixel-style font
});
// Action button (Run) - Initial declaration for width calculation
// The actual self.runBtn will be (re)declared later. This corresponds to original first runBtn {D}
var tempRunBtn = new Text2('RUN', {
size: 80,
fill: "#fff",
font: "Impact" // Using a pixel-style font
});
// Calculate total width using the three distinct buttons' initial declarations
var totalButtonWidth = self.attackBtn.width + tempItemsBtn.width + tempRunBtn.width + 120; // Widths + 2*60px spacing
var startX = (2048 - totalButtonWidth) / 2;
// --- Position buttons in order: Attack, Items, Run ---
var commonButtonY = 2732 - 300; // Common Y position for all buttons
// 1. ATTACK Button (leftmost)
// self.attackBtn is already defined and its anchor is set.
self.attackBtn.x = startX + self.attackBtn.width / 2;
self.attackBtn.y = commonButtonY;
self.addChild(self.attackBtn);
self.attackBtn.down = function () {
if (!self.visible) return;
self.playerTurn();
};
// 2. ITEMS Button (center)
// Action button (Items)//{Z} // This (re)declaration corresponds to original second itemsBtn {Z}
self.itemsBtn = new Text2('ITEMS', {
size: 80,
fill: "#fff",
font: "Impact" // Using a pixel-style font
});
self.itemsBtn.anchor.set(0.5, 0.5);
self.itemsBtn.x = startX + self.attackBtn.width + 60 + self.itemsBtn.width / 2;
self.itemsBtn.y = commonButtonY;
self.addChild(self.itemsBtn);
self.itemsBtn.down = function () {
if (!self.visible || player.inventory.potionCount <= 0) return; // Only allow if visible and player has potions
self.showBattleItemsMenu();
};
// 3. RUN Button (rightmost)
// Action button (Run)//{R} // This (re)declaration corresponds to original second runBtn {R}
self.runBtn = new Text2('RUN', {
size: 80,
fill: "#fff",
font: "Impact" // Using a pixel-style font
});
self.runBtn.anchor.set(0.5, 0.5);
self.runBtn.x = startX + self.attackBtn.width + 60 + self.itemsBtn.width + 60 + self.runBtn.width / 2;
self.runBtn.y = commonButtonY;
self.addChild(self.runBtn);
self.runBtn.down = function () {
if (!self.visible) return;
self.tryToRun();
};
self.currentMonster = null; // Reference to the monster tile being fought
// Battle Items Menu
self.battleItemsMenu = new Container();
self.battleItemsMenu.visible = false;
self.battleItemsMenuOverlay = self.battleItemsMenu.attachAsset('emptyTileCover', {
width: 800,
height: 500,
alpha: 0.9,
anchorX: 0.5,
anchorY: 0.5,
x: 2048 / 2,
y: 2732 / 2
});
self.battleItemsMenu.titleText = new Text2('Use Item', {
size: 70,
fill: 0x7DD3FC,
// Light Blue
font: "Impact" // Using a pixel-style font
});
self.battleItemsMenu.titleText.anchor.set(0.5, 0.5);
self.battleItemsMenu.titleText.x = 2048 / 2;
self.battleItemsMenu.titleText.y = 2732 / 2 - 150;
self.battleItemsMenu.addChild(self.battleItemsMenu.titleText);
self.battleItemsMenu.potionCountText = new Text2('', {
size: 60,
fill: 0xA3E635,
// Green
font: "Impact" // Using a pixel-style font
});
self.battleItemsMenu.potionCountText.anchor.set(0, 0.5);
self.battleItemsMenu.potionCountText.x = 2048 / 2 - 120;
self.battleItemsMenu.potionCountText.y = 2732 / 2 - 50;
self.battleItemsMenu.addChild(self.battleItemsMenu.potionCountText);
// DRINK button for potion, placed next to quantity
self.battleItemsMenu.usePotionBtn = new Text2('DRINK', {
size: 60,
fill: 0xA3E635,
font: "Impact"
});
self.battleItemsMenu.usePotionBtn.anchor.set(0, 0.5);
self.battleItemsMenu.usePotionBtn.x = self.battleItemsMenu.potionCountText.x + 350;
self.battleItemsMenu.usePotionBtn.y = self.battleItemsMenu.potionCountText.y;
self.battleItemsMenu.addChild(self.battleItemsMenu.usePotionBtn);
self.battleItemsMenu.usePotionBtn.down = function () {
if (player.inventory.potionCount > 0 && player.hp < player.maxHp) {
player.inventory.potionCount--;
player.hp += 20; // Heal amount
if (player.hp > player.maxHp) player.hp = player.maxHp;
self.updateBattleStats(); // Update battle stats display
self.hideBattleItemsMenu();
self.showConfirmationText('Used a Life Potion!', 0xA3E635); // Show confirmation message
playerInventoryScreen.updateInventoryDisplay(); // Update inventory screen if open
showGameGUIWithTransition();
}
};
self.battleItemsMenu.closeBtn = new Text2('Close', {
size: 60,
fill: "#fff",
font: "Impact" // Using a pixel-style font
});
self.battleItemsMenu.closeBtn.anchor.set(0.5, 0.5);
self.battleItemsMenu.closeBtn.x = 2048 / 2;
self.battleItemsMenu.closeBtn.y = 2732 / 2 + 150;
self.battleItemsMenu.addChild(self.battleItemsMenu.closeBtn);
self.battleItemsMenu.closeBtn.down = function () {
self.hideBattleItemsMenu();
};
self.addChild(self.battleItemsMenu);
self._confirmationText = null; // Text for temporary messages (e.g., item used)
self.showConfirmationText = function (message, color) {
if (self._confirmationText && typeof self._confirmationText.destroy === 'function') {
self._confirmationText.destroy();
}
self._confirmationText = new Text2(message, {
size: 60,
fill: color,
font: "Impact" // Using a pixel-style font
});
self._confirmationText.anchor.set(0.5, 0.5);
self._confirmationText.x = 2048 / 2;
self._confirmationText.y = 2732 / 2 + 300; // Position below action buttons
self.addChild(self._confirmationText);
tween(self._confirmationText, {
alpha: 0
}, {
duration: 1000,
delay: 800,
onFinish: function onFinish() {
self._confirmationText.destroy();
self._confirmationText = null;
}
});
};
// Start battle
self.startBattle = function (monsterTile) {
self.currentMonster = monsterTile;
// Set monster image in battle screen based on monster type
if (monsterTile instanceof PoisonMonsterTile) {
// Remove old asset if present
if (self.monsterDisplay) {
self.removeChild(self.monsterDisplay);
}
self.monsterDisplay = self.attachAsset('poisonMonsterTileMonster', {
anchorX: 0.5,
anchorY: 0.5,
x: 2048 - 300,
y: 2732 * 0.2,
scaleX: 4,
scaleY: 4
});
// Reposition monsterStatText below the new monsterDisplay
self.monsterStatText.x = self.monsterDisplay.x;
self.monsterStatText.y = self.monsterDisplay.y + self.monsterDisplay.height * self.monsterDisplay.scaleY / 2 + 60;
self.addChild(self.monsterDisplay);
} else {
// Remove old asset if present
if (self.monsterDisplay) {
self.removeChild(self.monsterDisplay);
}
self.monsterDisplay = self.attachAsset('monsterTileMonster', {
anchorX: 0.5,
anchorY: 0.5,
x: 2048 - 300,
y: 2732 * 0.2,
scaleX: 4,
scaleY: 4
});
// Reposition monsterStatText below the new monsterDisplay
self.monsterStatText.x = self.monsterDisplay.x;
self.monsterStatText.y = self.monsterDisplay.y + self.monsterDisplay.height * self.monsterDisplay.scaleY / 2 + 60;
self.addChild(self.monsterDisplay);
}
self.visible = true;
self.alpha = 0;
// Play encounter music when a player fights a monster in the battle screen
LK.playMusic('encounter');
game.addChild(self); // Add battle screen to game
updateGUI(); // Ensure main GUI elements are hidden
// Hide hero/monster and attack button for intro animation
self.monsterDisplay.visible = false;
self.playerDisplay.visible = false;
self.monsterStatText.visible = false;
self.playerBattleStatText.visible = false;
self.attackBtn.visible = false;
self.runBtn.visible = false; // Initially hidden
self.itemsBtn.visible = false; // Hide items button
// Fade in battle screen
tween(self, {
alpha: 1
}, {
duration: 300,
easing: tween.easeOut,
onFinish: function onFinish() {
// Show "You encountered a monster!" message
var encounterText = new Text2('You encountered a monster!', {
size: 110,
fill: 0xFFE066,
font: "Impact" // Using a pixel-style font
});
encounterText.anchor.set(0.5, 0.5);
encounterText.x = 2048 / 2;
encounterText.y = 2732 / 2 - 200;
self.addChild(encounterText);
tween(encounterText, {
alpha: 0
}, {
duration: 900,
delay: 900,
easing: tween.easeOut,
onFinish: function onFinish() {
encounterText.destroy();
// Prepare hero/monster offscreen for slide-in
// Save original positions
var heroTargetX = 300;
var heroTargetY = 2732 * 0.8 - 350; // Adjusted Y position
var monsterTargetX = 2048 - 300;
var monsterTargetY = 2732 * 0.2; // Adjusted Y position
self.playerDisplay.x = -self.playerDisplay.width; // Offscreen left
self.playerDisplay.y = heroTargetY;
self.monsterDisplay.x = 2048 + self.monsterDisplay.width; // Offscreen right
self.monsterDisplay.y = monsterTargetY;
self.playerDisplay.visible = true;
self.monsterDisplay.visible = true;
self.monsterStatText.visible = false;
self.playerBattleStatText.visible = false;
// Slide in hero and monster simultaneously
var slideInCount = 0;
function afterSlideIn() {
slideInCount++;
if (slideInCount === 2) {
// Show stats and decide who attacks first
var firstAttacker = "monster"; // Always monster attacks first
self.monsterStatText.visible = true;
self.playerBattleStatText.visible = true;
self.updateBattleStats();
// Randomize who attacks first: 0 = player, 1 = monster
var firstAttacker = Math.random() < 0.5 ? "player" : "monster";
var firstText = firstAttacker === "monster" ? "Monster strikes first!" : "You start!";
var firstTextColor = firstAttacker === "monster" ? 0xff0000 : 0xFFE066;
var whoFirstText = new Text2(firstText, {
size: 110,
fill: firstTextColor,
font: "Impact" // Using a pixel-style font
});
whoFirstText.anchor.set(0.5, 0.5);
whoFirstText.x = 2048 / 2;
whoFirstText.y = 2732 / 2;
self.addChild(whoFirstText);
tween(whoFirstText, {
alpha: 0
}, {
duration: 900,
delay: 900,
easing: tween.easeOut,
onFinish: function onFinish() {
whoFirstText.destroy();
// Show attack button and start the correct turn after a short delay
if (firstAttacker === "monster") {
// Hide player actions until after monster strikes
self.attackBtn.visible = false;
self.runBtn.visible = false;
self.itemsBtn.visible = false;
LK.setTimeout(self.monsterTurn, 500);
} else {
self.attackBtn.visible = true;
self.runBtn.visible = true; // Show run button after intro
self.itemsBtn.visible = true; // Show items button after intro
}
// If player starts, do nothing: player must press attackBtn
}
});
}
}
tween(self.playerDisplay, {
x: heroTargetX
}, {
duration: 600,
easing: tween.cubicOut,
onFinish: afterSlideIn
});
tween(self.monsterDisplay, {
x: monsterTargetX
}, {
duration: 600,
easing: tween.cubicOut,
onFinish: afterSlideIn
});
}
});
}
});
};
// End battle
self.endBattle = function (win) {
self.visible = false;
self.alpha = 0; // Reset alpha
self.attackBtn.visible = false;
self.runBtn.visible = false; // Hide run button
self.itemsBtn.visible = false; // Hide items button
self.hideBattleItemsMenu(); // Hide item menu
if (self._confirmationText) {
// Hide any confirmation text
self._confirmationText.destroy();
self._confirmationText = null;
}
if (win) {
// expGained and player.gainExp(expGained) are now handled in playerTurn's onFinish monster death animation
self.currentMonster.defeat();
monstersLeft--;
showGameGUIWithTransition();
if (monstersLeft <= 0) {
// If not last level, advance to next level
if (currentLevel < LEVELS.length - 1) {
// Show level end screen
levelEndScreen.startLevelEndScreen();
} else {
LK.showYouWin();
gameOver = true;
}
} else {}
// Player lost, game over is handled in handleTileDown
}
self.currentMonster = null;
self.parent.removeChild(self); // Remove battle screen from game
// Play gametitle music when battle screen ends
LK.playMusic('gametitle');
};
// Update battle stats display
self.updateBattleStats = function () {
if (self.currentMonster) {
self.monsterStatText.setText('HP:' + self.currentMonster.hp + '/' + self.currentMonster.maxHp + ' DMG:' + self.currentMonster.damage);
self.playerBattleStatText.setText('HP:' + player.hp + '/' + player.maxHp + ' DMG:' + player.damage);
}
// Grey out and disable ITEMS button if player has no potions
if (player.inventory.potionCount <= 0) {
self.itemsBtn.alpha = 0.5;
self.itemsBtn.interactive = false;
} else {
self.itemsBtn.alpha = 1;
self.itemsBtn.interactive = true;
}
};
// Player's turn
self.playerTurn = function () {
if (!self.currentMonster) return;
// Animate player attack: slide playerDisplay forward diagonally, then back, then apply damage
var originalX = self.playerDisplay.x;
var originalY = self.playerDisplay.y;
var attackX = self.monsterDisplay.x - self.monsterDisplay.width * self.monsterDisplay.scaleX / 2 - 60; // Stop a bit before monster
var attackY = self.monsterDisplay.y + self.monsterDisplay.height * self.monsterDisplay.scaleY / 2 - 60; // Move up towards monster
self.attackBtn.visible = false; // Hide attack button during animation
self.runBtn.visible = false; // Hide run button during animation
self.itemsBtn.visible = false; // Hide items button during animation
// Show "Player Attacks!" text
var playerAttackText = new Text2('Player Attacks!', {
size: 90,
fill: 0xffe066,
font: "Impact" // Using a pixel-style font
});
playerAttackText.anchor.set(0.5, 0.5);
playerAttackText.x = 2048 / 2;
playerAttackText.y = self.monsterStatText.y - 100;
self.addChild(playerAttackText);
function doAttack() {
// Damage and flash
self.currentMonster.hp -= player.damage;
LK.effects.flashObject(self.monsterDisplay, 0xffe066, 300);
// Show animated -X text above monster in battle
if (self.currentMonster && self.monsterDisplay) {
var monsterDmgText = new Text2('-' + player.damage, {
size: 100,
fill: 0xff0000,
font: "Impact" // Using a pixel-style font
});
monsterDmgText.anchor.set(0.5, 1);
// Position above monster sprite
monsterDmgText.x = self.monsterDisplay.x;
monsterDmgText.y = self.monsterDisplay.y - self.monsterDisplay.height / 2 - 20;
self.addChild(monsterDmgText);
// Animate: pop up and fade out
tween(monsterDmgText, {
y: monsterDmgText.y - 80,
alpha: 0
}, {
duration: 700,
easing: tween.cubicOut,
onFinish: function onFinish() {
monsterDmgText.destroy();
}
});
}
self.updateBattleStats();
// Fade out attack text
tween(playerAttackText, {
alpha: 0
}, {
duration: 600,
easing: tween.easeOut,
onFinish: function onFinish() {
playerAttackText.destroy();
}
});
// If monster dies, animate monster death
if (self.currentMonster.hp <= 0) {
// Play victory sound when player defeats monster
LK.getSound('victory').play();
// Monster death animation: fade out and scale down
tween(self.monsterDisplay, {
alpha: 0,
scaleX: 0.1,
scaleY: 0.1
}, {
duration: 600,
easing: tween.cubicIn,
onFinish: function onFinish() {
var expGained = self.currentMonster.exp;
var lootMessages = [];
// Award EXP
player.gainExp(expGained);
// Coin Drop
var coinsDropped = Math.floor(self.currentMonster.exp * (2 + Math.random() * 4)); // 2-5 coins per EXP point
if (coinsDropped > 0) {
player.gold += coinsDropped;
lootMessages.push(coinsDropped + ' Coins');
}
// Gear Drop (Rare)
var gearDropChance = 0.1; // 10% chance for now, we can adjust this later!
if (Math.random() < gearDropChance) {
if (chestScreen && chestScreen._rewardTypes && chestScreen._rewardTypes.length > 2 && chestScreen._lootQualities && chestScreen._lootQualities.length > 0) {
var potentialGearRewards = chestScreen._rewardTypes.slice(2); // Exclude potion and coins
if (potentialGearRewards.length > 0) {
var gearReward = potentialGearRewards[Math.floor(Math.random() * potentialGearRewards.length)];
var gearQuality = chestScreen._lootQualities[Math.floor(Math.random() * chestScreen._lootQualities.length)];
player.equipGear(gearReward.type, gearQuality); // This also calls player.updateEquippedStats()
var gearLabel = gearQuality.label + ' ' + gearReward.label;
lootMessages.push(gearLabel);
// playerStatsScreen and playerInventoryScreen will be updated via updateGUI() when battle ends
}
}
}
// Construct Victory Message
var victoryMessage = 'Victory!\nGained ' + expGained + ' EXP!';
if (lootMessages.length > 0) {
victoryMessage += '\n\nLoot:\n' + lootMessages.join('\n');
}
// Show "Victory!" message before closing battle screen
var victoryText = new Text2(victoryMessage, {
size: 110,
fill: 0xA3E635,
align: 'center',
// Ensure multi-line text is centered
font: "Impact" // Using a pixel-style font
});
victoryText.anchor.set(0.5, 0.5);
victoryText.x = 2048 / 2;
victoryText.y = 2732 / 2;
self.addChild(victoryText);
victoryText.alpha = 0;
// Fade in, hold, then fade out
tween(victoryText, {
alpha: 1
}, {
duration: 300,
easing: tween.easeOut,
onFinish: function onFinish() {
tween(victoryText, {
alpha: 0
}, {
duration: 500,
delay: 1000,
// Increased delay to read loot
easing: tween.easeIn,
onFinish: function onFinish() {
victoryText.destroy();
self.endBattle(true); // Player wins
// Reset monsterDisplay for next battle
self.monsterDisplay.alpha = 1;
self.monsterDisplay.scaleX = 4; // Adjusted to new scale
self.monsterDisplay.scaleY = 4; // Adjusted to new scale
}
});
}
});
}
});
} else {
// Monster survives, monster's turn after a delay
LK.setTimeout(self.monsterTurn, 500);
self.attackBtn.visible = true;
self.runBtn.visible = true; // Show run button again
self.itemsBtn.visible = true; // Show items button again
}
}
// Animate player forward diagonally, then back, then do attack
// Play slash sound when player attacks
LK.getSound('slash').play();
tween(self.playerDisplay, {
x: attackX,
y: attackY
}, {
duration: 180,
easing: tween.cubicOut,
onFinish: function onFinish() {
tween(self.playerDisplay, {
x: originalX,
y: originalY
}, {
duration: 180,
easing: tween.cubicIn,
onFinish: doAttack
});
}
});
};
// Monster's turn
self.monsterTurn = function () {
if (!self.currentMonster) return;
// Animate monster attack: slide monsterDisplay forward diagonally, then back, then apply damage
var originalX = self.monsterDisplay.x;
var originalY = self.monsterDisplay.y;
var attackX = self.playerDisplay.x + self.playerDisplay.width * self.playerDisplay.scaleX / 2 + 60; // Stop a bit before player
var attackY = self.playerDisplay.y - self.playerDisplay.height * self.playerDisplay.scaleY / 2 + 60; // Move down towards player
// Play monster1 sound when monster attacks
LK.getSound('monster1').play();
self.attackBtn.visible = false; // Hide attack button during animation
self.runBtn.visible = false; // Hide run button during animation
self.itemsBtn.visible = false; // Hide items button during animation
// Show "Monster Attacks!" text
var monsterAttackText = new Text2('Monster Attacks!', {
size: 90,
fill: 0xff0000,
font: "Impact" // Using a pixel-style font
});
monsterAttackText.anchor.set(0.5, 0.5);
monsterAttackText.x = 2048 / 2;
monsterAttackText.y = self.playerBattleStatText.y + 100;
self.addChild(monsterAttackText);
function doMonsterAttack() {
player.takeDamage(self.currentMonster.damage);
// Flash monster and player
LK.effects.flashObject(self.monsterDisplay, 0xff0000, 300); // Flash monster indicating it attacked
LK.effects.flashObject(self.playerDisplay, 0xff0000, 300); // Flash player sprite
LK.effects.flashObject(self.playerBattleStatText, 0xff0000, 300); // Flash player stats indicating damage
// Show animated -X text above player in battle (damage taken) or show "Blocked!" text
if (player && self.playerDisplay) {
var damageTaken = Math.max(0, self.currentMonster.damage - player.defense);
var playerDmgText;
var textColor;
if (damageTaken > 0) {
playerDmgText = new Text2('-' + damageTaken, {
size: 100,
fill: 0xff0000,
// Red for damage
font: "Impact" // Using a pixel-style font
});
textColor = 0xff0000;
} else {
playerDmgText = new Text2('Blocked!', {
size: 90,
fill: 0x7DD3FC,
// Light blue for blocked
font: "Impact" // Using a pixel-style font
});
textColor = 0x7DD3FC;
}
playerDmgText.anchor.set(0.5, 1);
// Position above player sprite
playerDmgText.x = self.playerDisplay.x;
playerDmgText.y = self.playerDisplay.y - self.playerDisplay.height / 2 - 20;
self.addChild(playerDmgText);
// Animate: pop up and fade out
tween(playerDmgText, {
y: playerDmgText.y - 80,
alpha: 0
}, {
duration: 700,
easing: tween.cubicOut,
onFinish: function onFinish() {
playerDmgText.destroy();
}
});
}
// Fade out attack text
tween(monsterAttackText, {
alpha: 0
}, {
duration: 600,
easing: tween.easeOut,
onFinish: function onFinish() {
monsterAttackText.destroy();
// Show attack button again if player is still alive and battle is ongoing
if (player.hp > 0 && self.currentMonster && !gameOver) {
self.attackBtn.visible = true;
self.runBtn.visible = true; // Show run button again
self.itemsBtn.visible = true; // Show items button again
}
}
});
self.updateBattleStats();
if (player.hp <= 0) {
LK.effects.flashScreen(0xff0000, 1000);
LK.showGameOver();
gameOver = true;
revealAllMonsters();
self.endBattle(false); // Player loses
}
}
// Animate monster forward diagonally, then back, then do attack
tween(self.monsterDisplay, {
x: attackX,
y: attackY
}, {
duration: 180,
easing: tween.cubicOut,
onFinish: function onFinish() {
tween(self.monsterDisplay, {
x: originalX,
y: originalY
}, {
duration: 180,
easing: tween.cubicIn,
onFinish: doMonsterAttack
});
}
});
};
self.tryToRun = function () {
if (!self.currentMonster) return;
self.attackBtn.visible = false; // Hide attack and run buttons
self.runBtn.visible = false;
self.itemsBtn.visible = false; // Hide items button
self.itemsBtn.visible = false; // Hide items button
var runSuccess = true;
// Calculate chance to fail run based on monster level vs player level
if (self.currentMonster.exp > player.level) {
// Use monster EXP as a rough proxy for level/difficulty
var failChance = Math.min(0.8, (self.currentMonster.exp - player.level) * 0.1); // 10% fail chance per monster level difference, max 80%
if (Math.random() < failChance) {
runSuccess = false;
}
}
var runMessageText;
var runMessageColor;
if (runSuccess) {
runMessageText = new Text2('You successfully ran away!', {
size: 90,
fill: 0xA3E635,
// Green
font: "Impact" // Using a pixel-style font
});
} else {
runMessageText = new Text2('Failed to run!', {
size: 90,
fill: 0xFF0000,
// Red
font: "Impact" // Using a pixel-style font
});
}
runMessageText.anchor.set(0.5, 0.5);
runMessageText.x = 2048 / 2;
runMessageText.y = 2732 / 2;
self.addChild(runMessageText);
tween(runMessageText, {
alpha: 0
}, {
duration: 800,
delay: 600,
onFinish: function onFinish() {
runMessageText.destroy();
if (runSuccess) {
LK.getSound('steps').play(); // Play steps sound on successful run
// Reset monster HP and mark as not defeated
self.currentMonster.hp = self.currentMonster.maxHp;
self.currentMonster.defeated = false; // Important: Reset defeated state
// Make sure monster remains visible but cover is restored
self.currentMonster.cover.alpha = 1; // Restore cover to original state
self.currentMonster.monster.alpha = 1; // Ensure monster image remains visible
self.currentMonster.revealed = false; // Set revealed to false
// End battle and return to grid
self.endBattle(false); // Pass false, as it's not a win. This sets battleScreen.visible to false.
showGameGUIWithTransition();
} else {
// Running failed, monster attacks
LK.setTimeout(self.monsterTurn, 300); // Monster attacks immediately after message
}
}
});
};
// Show the battle items menu
self.showBattleItemsMenu = function () {
self.battleItemsMenu.visible = true;
self.attackBtn.visible = false; // Hide action buttons
self.runBtn.visible = false;
self.itemsBtn.visible = false;
// Update potion count display
self.battleItemsMenu.potionCountText.setText('Life Potions: ' + player.inventory.potionCount);
// Disable DRINK button if player is full HP or no potions
if (player.inventory.potionCount <= 0 || player.hp >= player.maxHp) {
self.battleItemsMenu.usePotionBtn.alpha = 0.5;
self.battleItemsMenu.usePotionBtn.interactive = false;
} else {
self.battleItemsMenu.usePotionBtn.alpha = 1;
self.battleItemsMenu.usePotionBtn.interactive = true;
}
// Also update ITEMS button state in battle screen
if (player.inventory.potionCount <= 0) {
self.itemsBtn.alpha = 0.5;
self.itemsBtn.interactive = false;
} else {
self.itemsBtn.alpha = 1;
self.itemsBtn.interactive = true;
}
};
// Hide the battle items menu
self.hideBattleItemsMenu = function () {
self.battleItemsMenu.visible = false;
// Show action buttons again if player is alive and battle is ongoing
if (player.hp > 0 && self.currentMonster && !gameOver) {
self.attackBtn.visible = true;
self.runBtn.visible = true;
self.itemsBtn.visible = true;
// Update ITEMS button state
if (player.inventory.potionCount <= 0) {
self.itemsBtn.alpha = 0.5;
self.itemsBtn.interactive = false;
} else {
self.itemsBtn.alpha = 1;
self.itemsBtn.interactive = true;
}
}
};
return self;
});
// ChestScreen: Handles the chest found popup
var ChestScreen = Container.expand(function () {
var self = Container.call(this);
self.visible = false;
self.alpha = 0;
// Background overlay
self.overlay = self.attachAsset('emptyTileCover', {
width: 2048,
height: 2732,
alpha: 0.8
});
// Chest display (center) - use chest image asset
self.chestDisplay = self.attachAsset('chest', {
anchorX: 0.5,
anchorY: 0.5,
x: 2048 / 2,
y: 2732 / 2 - 100,
scaleX: 4,
// Increased scale
scaleY: 4 // Increased scale
});
// "You found a chest!" text
self.chestText = new Text2('You found a chest!', {
size: 120,
// Increased size
fill: 0xFFD700,
font: "Impact" // Using a pixel-style font
});
self.chestText.anchor.set(0.5, 0.5);
self.chestText.x = 2048 / 2;
self.chestText.y = 2732 * 0.25; // Adjusted Y position to be higher
self.addChild(self.chestText);
// Player display (bottom, like battle screen)
self.playerDisplay = self.attachAsset('character', {
anchorX: 0.5,
anchorY: 0.5,
x: 2048 / 2,
y: 2732 * 0.8,
scaleX: 4,
scaleY: 4
});
// --- Double-tap to open chest logic ---
self._lastTapTime = 0;
self._tapCount = 0;
self._rewardPopup = null;
// Chest reward types
self._rewardTypes = [{
type: "potion",
label: "Life Potion",
color: 0xA3E635
}, {
type: "coins",
label: "Coins",
color: 0xFFD700
}, {
type: "shield",
label: "Shield",
color: 0x7DD3FC
}, {
type: "sword",
label: "Sword",
color: 0xF87171
}, {
type: "helmet",
label: "Helmet",
color: 0xFACC15
}, {
type: "armour",
label: "Armour",
color: 0xA78BFA
}];
// Loot qualities
self._allLootQualities = [{
label: "Wooden",
modifier: 1,
color: 0x8B4513,
// SaddleBrown
minLevel: 1
}, {
label: "Bronze",
modifier: 2,
color: 0xCD7F32,
// Bronze
minLevel: 2
}, {
label: "Silver",
modifier: 3,
color: 0xC0C0C0,
// Silver
minLevel: 3
}];
// This will be set on chest open, based on player.level
self._lootQualities = self._allLootQualities;
// Helper to show reward popup
self._showRewardPopup = function (reward) {
if (self._rewardPopup) {
self._rewardPopup.destroy();
self._rewardPopup = null;
}
var popup = new Container();
// Overlay
var overlay = LK.getAsset('emptyTileBg', {
width: 1200,
height: 600,
alpha: 0.95,
anchorX: 0.5,
anchorY: 0.5,
x: 2048 / 2,
y: 2732 / 2
});
popup.addChild(overlay);
// Reward text
var rewardText = new Text2('You found: ' + reward.label + '!', {
size: 90,
fill: "#fff",
font: "Impact" // Using a pixel-style font
});
rewardText.anchor.set(0.5, 0.5);
rewardText.x = 2048 / 2;
rewardText.y = 2732 / 2 - 50;
popup.addChild(rewardText);
// "Close" button
var okBtn = new Text2('Close', {
size: 70,
fill: "#fff",
font: "Impact" // Using a pixel-style font
});
okBtn.anchor.set(0.5, 0.5);
okBtn.x = 2048 / 2;
okBtn.y = 2732 / 2 + 80;
okBtn.down = function () {
LK.getSound('tap').play();
popup.destroy();
self._rewardPopup = null;
self.endChest();
};
popup.addChild(okBtn);
self._rewardPopup = popup;
game.addChild(popup);
};
// Double-tap handler for chest
self.chestDisplay.down = function (x, y, obj) {
var now = Date.now();
if (now - self._lastTapTime < 400) {
self._tapCount++;
} else {
self._tapCount = 1;
}
self._lastTapTime = now;
if (self._tapCount === 2 && self.visible && !self._rewardPopup) {
LK.getSound('openchest').play();
// Open chest
if (self.currentChest && !self.currentChest.opened) {
self.currentChest.open();
// Pick a random reward
var reward = self._rewardTypes[Math.floor(Math.random() * self._rewardTypes.length)];
var quality = null;
if (reward.type !== "potion" && reward.type !== "coins") {
// For gear, pick a random quality from those unlocked by player level
var unlockedQualities = [];
for (var i = 0; i < self._allLootQualities.length; i++) {
if (player.level >= self._allLootQualities[i].minLevel) {
unlockedQualities.push(self._allLootQualities[i]);
}
}
// Fallback: always at least wooden
if (unlockedQualities.length === 0) unlockedQualities.push(self._allLootQualities[0]);
quality = unlockedQualities[Math.floor(Math.random() * unlockedQualities.length)];
}
// Apply reward effect (for now, just heal for potion, add coins, or do nothing for gear)
if (reward.type === "potion") {
player.inventory.potionCount++; // Add potion to inventory
self._showRewardPopup(reward);
playerInventoryScreen.updateInventoryDisplay(); // Update inventory screen if open
} else if (reward.type === "coins") {
player.gold += 10 + Math.floor(Math.random() * 10); // Add 10-20 coins
self._showRewardPopup(reward);
playerInventoryScreen.updateInventoryDisplay(); // Update inventory screen if open
} else if (quality) {
// Add gear to player inventory with quality modifier
player.equipGear(reward.type, quality);
var lootLabel = quality.label + ' ' + reward.label;
self._showRewardPopup({
label: lootLabel,
color: quality.color
});
playerStatsScreen.updateStatsDisplay(); // Update stats screen if open
playerInventoryScreen.updateInventoryDisplay(); // Update inventory screen if open
}
}
self._tapCount = 0;
}
};
// Start chest screen
self.startChest = function (chestTile) {
self.currentChest = chestTile;
// Set available loot qualities for this chest based on player level
var unlockedQualities = [];
for (var i = 0; i < self._allLootQualities.length; i++) {
if (player.level >= self._allLootQualities[i].minLevel) {
unlockedQualities.push(self._allLootQualities[i]);
}
}
// Fallback: always at least wooden
if (unlockedQualities.length === 0) unlockedQualities.push(self._allLootQualities[0]);
self._lootQualities = unlockedQualities;
self.visible = true;
self.alpha = 0;
game.addChild(self);
updateGUI(); // Hide main GUI
// Fade in
tween(self, {
alpha: 1
}, {
duration: 300,
easing: tween.easeOut
});
};
// End chest screen
self.endChest = function () {
self.visible = false;
self.alpha = 0;
self.currentChest = null;
if (self._rewardPopup) {
self._rewardPopup.destroy();
self._rewardPopup = null;
}
if (self.parent) self.parent.removeChild(self);
showGameGUIWithTransition();
};
return self;
});
// ChestTile: Represents a chest hidden under a tile
var ChestTile = Container.expand(function () {
var self = Container.call(this);
self.revealed = false;
self.opened = false;
self.outline = self.attachAsset('tileOutline', {
// Attach black outline
anchorX: 0.5,
anchorY: 0.5
});
self.cover = self.attachAsset('emptyTileCover', {
anchorX: 0.5,
anchorY: 0.5
});
// Use a chest image asset
self.chest = self.attachAsset('chest', {
anchorX: 0.5,
anchorY: 0.5,
alpha: 0 // Hidden until revealed
});
// Reveal the chest
self.reveal = function () {
if (self.revealed) return;
self.revealed = true;
self.cover.alpha = 0.2;
self.chest.alpha = 1;
};
// Mark as opened
self.open = function () {
self.opened = true;
LK.getSound('openchest').play();
self.cover.alpha = 0.1; // Grey out cover
self.chest.alpha = 0.5; // Grey out chest image
// Optionally animate chest opening here
};
return self;
});
// EmptyTile: Represents a safe tile (no monster)
var EmptyTile = Container.expand(function () {
var self = Container.call(this);
self.revealed = false;
self.adjacentMonsterDamage = 0; // Change to store damage sum
self.outline = self.attachAsset('tileOutline', {
// Attach black outline
anchorX: 0.5,
anchorY: 0.5
});
self.cover = self.attachAsset('emptyTileCover', {
anchorX: 0.5,
anchorY: 0.5
});
self.bg = self.attachAsset('emptyTileBg', {
anchorX: 0.5,
anchorY: 0.5,
alpha: 0 // Hidden until revealed
});
self.adjText = new Text2('', {
size: 48,
fill: "#fff",
font: "Impact" // Using a pixel-style font
});
self.adjText.anchor.set(0.5, 0.5);
self.adjText.alpha = 0;
self.addChild(self.adjText);
self.reveal = function () {
if (self.revealed) return;
self.revealed = true;
self.cover.alpha = 0.2;
self.bg.alpha = 1;
if (self.adjacentMonsterDamage > 0) {
// Check damage sum
self.adjText.setText(self.adjacentMonsterDamage + ''); // Display damage sum
self.adjText.alpha = 1;
}
};
return self;
});
// LevelEndScreen: Appears after clearing a level, offering Next Level and Shop options
var LevelEndScreen = Container.expand(function () {
var self = Container.call(this);
self.visible = false;
self.alpha = 0;
// Background overlay
self.overlay = self.attachAsset('emptyTileCover', {
width: 2048,
height: 2732,
alpha: 0.9
});
// Main panel background
self.panel = self.attachAsset('emptyTileBg', {
width: 1200,
height: 1000,
anchorX: 0.5,
anchorY: 0.5,
x: 2048 / 2,
y: 2732 / 2
});
// Title
self.titleText = new Text2('Level Complete!', {
size: 110,
fill: 0xA3E635,
font: "Impact" // Using a pixel-style font
});
self.titleText.anchor.set(0.5, 0.5);
self.titleText.x = 2048 / 2;
self.titleText.y = self.panel.y - self.panel.height / 2 + 120;
self.addChild(self.titleText);
// Next Level Button
self.nextLevelBtn = new Text2('NEXT LEVEL', {
size: 80,
fill: "#fff",
font: "Impact" // Using a pixel-style font
});
self.nextLevelBtn.anchor.set(0.5, 0.5);
self.nextLevelBtn.x = 2048 / 2;
self.nextLevelBtn.y = self.panel.y + 100;
self.nextLevelBtn.down = function () {
if (!self.visible) return;
LK.getSound('tap').play();
self.endLevelEndScreen();
currentLevel++;
MONSTER_COUNT = LEVELS[currentLevel].monsters;
generateBoard();
updateGUI();
levelText.setText('LVL: ' + (currentLevel + 1));
gameOver = false;
};
self.addChild(self.nextLevelBtn);
// Shop Button
self.shopBtn = new Text2('SHOP', {
size: 80,
fill: "#fff",
font: "Impact" // Using a pixel-style font
});
self.shopBtn.anchor.set(0.5, 0.5);
self.shopBtn.x = 2048 / 2;
self.shopBtn.y = self.panel.y + 300;
self.shopBtn.down = function () {
if (!self.visible) return;
LK.getSound('tap').play();
self.endLevelEndScreen();
shopScreen.startShopScreen();
};
self.addChild(self.shopBtn);
// Start the level end screen
self.startLevelEndScreen = function () {
self.visible = true;
self.alpha = 0;
game.addChild(self);
updateGUI(); // Hide main GUI
// Fade in
tween(self, {
alpha: 1
}, {
duration: 300,
easing: tween.easeOut
});
};
// End the level end screen
self.endLevelEndScreen = function () {
self.visible = false;
self.alpha = 0;
if (self.parent) self.parent.removeChild(self);
showGameGUIWithTransition();
};
return self;
});
var MainScreen = Container.expand(function () {
var self = Container.call(this);
self.visible = false; // Initially hidden
self.alpha = 0; // Set alpha to 0 for fade-in animation
// Background overlay
// self.overlay = self.attachAsset('emptyTileCover', {
// width: 2048,
// height: 2732,
// alpha: 0.9
// });
self.overlay = new Container(); // Replace the overlay with an empty container
// Main panel background
// self.panel = self.attachAsset('emptyTileBg', {
// width: 1200,
// height: 1000,
// anchorX: 0.5,
// anchorY: 0.5,
// x: 2048 / 2,
// y: 2732 / 2
// });
self.panel = new Container(); // Replace the panel with an empty container
// Title
// Title - now using image asset
self.titleImage = self.attachAsset('gameTitle', {
// Using the new gameTitle image asset
anchorX: 0.5,
// Anchor to center
anchorY: 0.5,
// Anchor to center
x: 2048 / 2,
// Center horizontally
y: 2732 * 0.3 // Position the game title image higher on the screen
});
// Remove the old text title
// self.titleText.destroy(); // Assuming the original text is added somewhere before this block
// Re-add the title image to ensure it's a child of MainScreen.
self.addChild(self.titleImage); // Add the title image to the screen container
// Update the reference if needed
// self.titleText = self.titleImage; // If any other code references self.titleText, this line can replace it
// Animate the title image up and down (gentle float)
(function animateTitleFloat() {
var startY = self.titleImage.y;
var floatDistance = 40;
tween(self.titleImage, {
y: startY + floatDistance
}, {
duration: 1200,
easing: tween.easeInOut,
onFinish: function onFinish() {
tween(self.titleImage, {
y: startY
}, {
duration: 1200,
easing: tween.easeInOut,
onFinish: animateTitleFloat
});
}
});
})();
// Start Game Button with background and animation
self.startGameBtnBg = self.attachAsset('emptyTileBg', {
anchorX: 0.5,
anchorY: 0.5,
x: 2048 / 2,
y: 2732 * 0.7 + 100,
width: 500,
height: 140,
alpha: 0.85
});
self.startGameBtn = new Text2('ENTER', {
size: 80,
fill: "#fff",
font: "Impact"
});
self.startGameBtn.anchor.set(0.5, 0.5);
self.startGameBtn.x = 2048 / 2;
self.startGameBtn.y = 2732 * 0.7 + 100; // Position start button lower
// Animate the button background (pulse)
// tween(self.startGameBtnBg, {
// scaleX: 1.1,
// scaleY: 1.1
// }, {
// duration: 700,
// easing: tween.easeInOut,
// onFinish: function onFinish() {
// tween(self.startGameBtnBg, {
// scaleX: 1,
// scaleY: 1
// }, {
// duration: 700,
// easing: tween.easeInOut,
// onFinish: function pulseAgain() {
// // Loop the pulse
// tween(self.startGameBtnBg, {
// scaleX: 1.1,
// scaleY: 1.1
// }, {
// duration: 700,
// easing: tween.easeInOut,
// onFinish: function onFinish() {
// tween(self.startGameBtnBg, {
// scaleX: 1,
// scaleY: 1
// }, {
// duration: 700,
// easing: tween.easeInOut,
// onFinish: pulseAgain
// });
// }
// });
// }
// });
// }
// });
// Animate the button text (gentle scale pulse)
// tween(self.startGameBtn, {
// scaleX: 1.08,
// scaleY: 1.08
// }, {
// duration: 700,
// easing: tween.easeInOut,
// onFinish: function onFinish() {
// tween(self.startGameBtn, {
// scaleX: 1,
// scaleY: 1
// }, {
// duration: 700,
// easing: tween.easeInOut,
// onFinish: function pulseTextAgain() {
// tween(self.startGameBtn, {
// scaleX: 1.08,
// scaleY: 1.08
// }, {
// duration: 700,
// easing: tween.easeInOut,
// onFinish: function onFinish() {
// tween(self.startGameBtn, {
// scaleX: 1,
// scaleY: 1
// }, {
// duration: 700,
// easing: tween.easeInOut,
// onFinish: pulseTextAgain
// });
// }
// });
// }
// });
// }
// });
self.startGameBtn.down = function () {
if (!self.visible) return;
// Play steps sound when player touches start game
LK.getSound('steps').play();
self.endMainScreen();
// Start the game logic here (e.g., generateBoard)
startGame(); // Call a new function to start the game
};
self.addChild(self.startGameBtnBg);
self.addChild(self.startGameBtn);
// (Reset Progress Button removed)
// Start the main screen
self.startMainScreen = function () {
self.visible = true;
self.alpha = 0;
game.addChild(self);
// Play gametitle music when main screen starts
LK.playMusic('gametitle');
// Fade in
tween(self, {
alpha: 1
}, {
duration: 300,
easing: tween.easeOut
});
};
// End the main screen
self.endMainScreen = function () {
self.visible = false;
self.alpha = 0;
if (self.parent) self.parent.removeChild(self);
};
return self;
});
// MonsterTile: Represents a monster hidden under a tile
var MonsterTile = Container.expand(function () {
var self = Container.call(this);
// Monster stats
self.hp = 1;
self.maxHp = 1;
self.damage = 1;
self.exp = 1;
self.revealed = false;
self.defeated = false;
// Visuals
self.outline = self.attachAsset('tileOutline', {
// Attach black outline
anchorX: 0.5,
anchorY: 0.5
});
self.cover = self.attachAsset('monsterTileCover', {
anchorX: 0.5,
anchorY: 0.5
});
self.monster = self.attachAsset('monsterTileMonster', {
anchorX: 0.5,
anchorY: 0.5,
alpha: 0 // Hidden until revealed
});
// Show monster stats as text (hidden until revealed)
self.statText = new Text2('', {
size: 48,
fill: "#fff",
font: "Impact" // Using a pixel-style font
});
self.statText.anchor.set(0.5, 0.5);
self.statText.alpha = 0;
self.addChild(self.statText);
// Reveal the monster
self.reveal = function () {
if (self.revealed) return;
self.revealed = true;
self.cover.alpha = 0.2;
self.monster.alpha = 1;
self.statText.setText('');
self.statText.alpha = 0;
};
// Mark as defeated
self.defeat = function () {
self.defeated = true;
self.monster.alpha = 0.3;
self.cover.alpha = 0.1;
self.statText.setText('');
self.statText.alpha = 0;
// Update adjacent empty tiles' numbers since this monster is no longer generating damage
if (typeof grid !== "undefined") {
// Find this monster's position in the grid
for (var row = 0; row < grid.length; row++) {
for (var col = 0; col < grid[row].length; col++) {
if (grid[row][col] === self) {
var adj = getAdjacent(row, col);
for (var i = 0; i < adj.length; i++) {
var nr = adj[i][0],
nc = adj[i][1];
var t = grid[nr][nc];
if (t instanceof EmptyTile) {
// Recalculate adjacentMonsterDamage for this empty tile
var adj2 = getAdjacent(nr, nc);
var damageSum = 0;
for (var j = 0; j < adj2.length; j++) {
var ar = adj2[j][0],
ac = adj2[j][1];
var adjTile = grid[ar][ac];
if (adjTile instanceof MonsterTile && !adjTile.defeated) {
damageSum += adjTile.damage;
}
}
t.adjacentMonsterDamage = damageSum;
// If revealed, update the displayed number
if (t.revealed) {
if (damageSum > 0) {
t.adjText.setText(damageSum + '');
t.adjText.alpha = 1;
} else {
t.adjText.setText('');
t.adjText.alpha = 0;
}
}
}
}
// Only need to update once for the found monster
row = grid.length; // break outer loop
break;
}
}
}
}
};
return self;
});
// PoisonMonsterTile: A monster that can poison the player
var PoisonMonsterTile = MonsterTile.expand(function () {
var self = MonsterTile.call(this);
// Override monster asset to use poison monster image
if (self.monster) {
self.removeChild(self.monster);
}
self.monster = self.attachAsset('poisonMonsterTileMonster', {
anchorX: 0.5,
anchorY: 0.5,
alpha: 0 // Hidden until revealed
});
// Add stat text again if needed
if (!self.statText) {
self.statText = new Text2('', {
size: 48,
fill: "#fff",
font: "Impact"
});
self.statText.anchor.set(0.5, 0.5);
self.statText.alpha = 0;
self.addChild(self.statText);
}
// These will be set during board generation based on level stats
self.poisonChance = 0.3; // 30% chance to poison
self.poisonDamage = 1; // -1 HP per turn when poisoned
// Override the doMonsterAttack function to add poison effect
var originalDoMonsterAttack = self.doMonsterAttack; // Keep reference to original
self.doMonsterAttack = function () {
originalDoMonsterAttack(); // Call the original monster attack logic
// Check if player was hit and apply poison effect
var damageTaken = Math.max(0, self.damage - player.defense);
if (damageTaken > 0 && Math.random() < self.poisonChance) {
if (!player.isPoisoned) {
player.applyPoison(1); // Always poison for -1 per turn
battleScreen.showConfirmationText('You have been poisoned!', 0xA3E635); // Green message
LK.getSound('poison').play(); // Play poison sound
} else {
// Already poisoned, do nothing (could extend duration in future)
}
}
};
// Override monster's turn to call the new doMonsterAttack logic
var originalMonsterTurn = self.monsterTurn; // Keep reference to original
self.monsterTurn = function () {
if (!self.currentMonster) return;
// Animate monster attack: slide monsterDisplay forward diagonally, then back
var originalX = self.monsterDisplay.x;
var originalY = self.monsterDisplay.y;
var attackX = self.playerDisplay.x + self.playerDisplay.width * self.playerDisplay.scaleX / 2 + 60; // Stop a bit before player
var attackY = self.playerDisplay.y - self.playerDisplay.height * self.playerDisplay.scaleY / 2 + 60; // Move down towards player
LK.getSound('monster1').play(); // Play monster1 sound when monster attacks
self.attackBtn.visible = false; // Hide attack button during animation
self.runBtn.visible = false; // Hide run button during animation
self.itemsBtn.visible = false; // Hide items button during animation
// Show "Monster Attacks!" text
var monsterAttackText = new Text2('Monster Attacks!', {
size: 90,
fill: 0xff0000,
font: "Impact" // Using a pixel-style font
});
monsterAttackText.anchor.set(0.5, 0.5);
monsterAttackText.x = 2048 / 2;
monsterAttackText.y = self.playerBattleStatText.y + 100;
self.addChild(monsterAttackText);
// Animate monster forward diagonally, then back, then do attack (calling the new doMonsterAttack)
tween(self.monsterDisplay, {
x: attackX,
y: attackY
}, {
duration: 180,
easing: tween.cubicOut,
onFinish: function onFinish() {
tween(self.monsterDisplay, {
x: originalX,
y: originalY
}, {
duration: 180,
easing: tween.cubicIn,
onFinish: function onFinish() {
// Now call the potentially overridden doMonsterAttack
self.doMonsterAttack();
// Fade out attack text
tween(monsterAttackText, {
alpha: 0
}, {
duration: 600,
easing: tween.easeOut,
onFinish: function onFinish() {
monsterAttackText.destroy();
// Show attack button again if player is still alive and battle is ongoing
if (player.hp > 0 && self.currentMonster && !gameOver) {
self.attackBtn.visible = true;
self.runBtn.visible = true; // Show run button again
self.itemsBtn.visible = true; // Show items button again
}
}
});
self.updateBattleStats();
if (player.hp <= 0) {
LK.effects.flashScreen(0xff0000, 1000);
LK.showGameOver();
gameOver = true;
revealAllMonsters();
self.endBattle(false); // Player loses
}
}
});
}
});
};
return self;
});
// Add event listener to MonsterTile for clicking
// Player: Holds player stats and methods
var Player = Container.expand(function () {
var self = Container.call(this);
// Player stats
self.level = 1;
self.exp = 0;
self.expToLevel = 3; // Start with 3 exp needed for level up
self.maxHp = 5;
self.hp = self.maxHp;
self.damage = 1; // Start with 1 damage
self.defense = 0; // Initial defense is 0
self.skillPoints = 0; // Points to spend on upgrades
self.gold = 0; // Player's currency
self.inventory = {
// Player inventory
potionCount: 0,
antidoteCount: 0,
// Add antidote count
sword: null,
// Equipped items
shield: null,
helmet: null,
armour: null
};
self.baseDamage = 1; // Base damage before gear
self.baseDefense = 0; // Base defense before gear
// Gain experience
self.gainExp = function (amount) {
self.exp += amount;
while (self.exp >= self.expToLevel) {
self.exp -= self.expToLevel;
self.levelUp();
}
};
// Level up player
self.levelUp = function () {
self.level++;
self.skillPoints++;
self.expToLevel = Math.ceil(self.expToLevel * 1.2); // Increase EXP needed for next level, ensuring it consistently grows
// Increase base stats on level up
// self.maxHp += 5; // HP is no longer gained automatically on level up
// self.damage += 1; // Example stat increase
// self.hp = self.maxHp; // Do not heal to full HP on level up
self.updateEquippedStats(); // Update stats after base increase
};
// Take damage
self.takeDamage = function (amount) {
var damageTaken = Math.max(0, amount - self.defense); // Damage reduced by defense
self.hp -= damageTaken;
if (self.hp < 0) self.hp = 0;
};
// Spend skill points
self.spendSkillPoint = function (stat) {
if (self.skillPoints <= 0) return;
self.skillPoints--;
if (stat === "hp") {
self.maxHp += 5; // Gain 5 HP per point
self.hp = self.maxHp; // Heal to full
} else if (stat === "damage") {
self.baseDamage += 2; // Increase base damage
} else if (stat === "defense") {
self.baseDefense += 1; // Increase base defense
}
self.updateEquippedStats(); // Update stats after spending points
};
// Equip gear
self.equipGear = function (type, quality) {
self.inventory[type] = {
type: type,
label: quality.label,
modifier: quality.modifier,
color: quality.color
};
self.updateEquippedStats(); // Update stats after equipping
};
// Update stats based on equipped gear
self.updateEquippedStats = function () {
self.damage = self.baseDamage;
self.defense = self.baseDefense;
if (self.inventory.sword) self.damage += self.inventory.sword.modifier;
if (self.inventory.shield) self.defense += self.inventory.shield.modifier;
if (self.inventory.helmet) self.defense += Math.floor(self.inventory.helmet.modifier / 2); // Helmet gives half defense
if (self.inventory.armour) self.defense += self.inventory.armour.modifier; // Armour gives full defense
// Ensure current HP doesn't exceed new max HP after stat changes
if (self.hp > self.maxHp) self.hp = self.maxHp;
};
// Poison state
self.isPoisoned = false;
self.poisonDamagePerTurn = 0;
self._poisonTimer = null;
// Apply poison effect
self.applyPoison = function (damage) {
self.isPoisoned = true;
self.poisonDamagePerTurn = 1; // Always -1 per turn
// Show green message when poisoned
if (battleScreen && battleScreen.visible) {
battleScreen.showConfirmationText('You have been poisoned!', 0xA3E635);
}
// Start a timer for poison effect
if (self._poisonTimer) {
LK.clearInterval(self._poisonTimer); // Clear existing timer if any
}
// Apply poison damage at the start of each turn (simulated by timer)
self._poisonTimer = LK.setInterval(function () {
if (self.hp > 0) {
self.takeDamage(self.poisonDamagePerTurn);
// Show damage text for poison
if (battleScreen.visible && battleScreen.playerDisplay) {
var poisonDmgText = new Text2('-' + self.poisonDamagePerTurn, {
size: 70,
fill: 0x800080,
// Purple for poison
font: "Impact" // Using a pixel-style font
});
poisonDmgText.anchor.set(0.5, 1);
// Position above player sprite
poisonDmgText.x = battleScreen.playerDisplay.x;
poisonDmgText.y = battleScreen.playerDisplay.y - battleScreen.playerDisplay.height / 2 - 50;
battleScreen.addChild(poisonDmgText);
LK.getSound('poison').play(); // Play poison sound
// Animate: pop up and fade out
tween(poisonDmgText, {
y: poisonDmgText.y - 60,
alpha: 0
}, {
duration: 600,
easing: tween.cubicOut,
onFinish: function onFinish() {
poisonDmgText.destroy();
}
});
}
battleScreen.updateBattleStats(); // Update battle stats display
updateGUI(); // Update main GUI HP display
if (self.hp <= 0) {
// Player died from poison
LK.effects.flashScreen(0xff0000, 1000);
LK.showGameOver();
gameOver = true;
revealAllMonsters();
battleScreen.endBattle(false); // Player loses
}
} else {
self.curePoison(); // Player is already dead
}
}, 1000); // Apply poison damage every 1 second (simulating turn-based)
// Note: This is a simplified turn-based simulation using a timer.
// In a true turn-based system, poison would apply at the start of the player's turn.
};
// Cure poison effect
self.curePoison = function () {
self.isPoisoned = false;
self.poisonDamagePerTurn = 0;
if (self._poisonTimer) {
LK.clearInterval(self._poisonTimer);
self._poisonTimer = null;
}
};
return self;
});
// PlayerInventoryScreen: Displays player's inventory and allows using potions
var PlayerInventoryScreen = Container.expand(function () {
var self = Container.call(this);
self.visible = false; // Initially hidden
self.alpha = 0; // Set alpha to 0 for fade-in animation
// Background overlay
self.overlay = self.attachAsset('emptyTileCover', {
width: 2048,
height: 2732,
alpha: 0.9
});
// Main panel background
self.panel = self.attachAsset('emptyTileBg', {
width: 1600,
// Increased width for grid layout
height: 1800,
// Increased height for more items
anchorX: 0.5,
anchorY: 0.5,
x: 2048 / 2,
y: 2732 / 2
});
var panelLeftEdge = self.panel.x - self.panel.width / 2;
var contentStartX = panelLeftEdge + 80;
var valueColumnX = contentStartX + 350;
var buttonColumnX = valueColumnX + 200;
var itemSpacingY = 70;
// Title
self.titleText = new Text2('INVENTORY', {
size: 110,
fill: 0x7DD3FC,
// Light Blue
font: "Impact" // Using a pixel-style font
});
self.titleText.anchor.set(0.5, 0.5);
self.titleText.x = 2048 / 2;
self.titleText.y = self.panel.y - self.panel.height / 2 + 120;
self.addChild(self.titleText);
// Gold display
self.goldText = new Text2('', {
size: 60,
fill: 0xFFD700,
// Gold
font: "Impact" // Using a pixel-style font
});
self.goldText.anchor.set(0, 0.5);
self.goldText.x = contentStartX;
self.goldText.y = self.panel.y - self.panel.height / 2 + 220;
self.addChild(self.goldText);
// --- Consumables Section ---
self.consumablesTitle = new Text2('Consumables:', {
size: 70,
fill: 0x7DD3FC,
font: "Impact" // Using a pixel-style font
});
self.consumablesTitle.anchor.set(0, 0.5);
self.consumablesTitle.x = contentStartX;
self.consumablesTitle.y = self.goldText.y + 100;
self.addChild(self.consumablesTitle);
// Potions
var potionY = self.consumablesTitle.y + itemSpacingY;
self.potionLabelText = new Text2('Life Potions:', {
size: 60,
fill: "#fff",
font: "Impact"
});
self.potionLabelText.anchor.set(0, 0.5);
self.potionLabelText.x = contentStartX + 40; // Indent
self.potionLabelText.y = potionY;
self.addChild(self.potionLabelText);
self.potionValueText = new Text2('', {
size: 60,
fill: 0xA3E635,
font: "Impact"
});
self.potionValueText.anchor.set(0, 0.5);
self.potionValueText.x = valueColumnX;
self.potionValueText.y = potionY;
self.addChild(self.potionValueText);
self.usePotionBtn = new Text2('DRINK', {
size: 60,
fill: 0xA3E635,
font: "Impact"
});
self.usePotionBtn.anchor.set(0, 0.5);
self.usePotionBtn.x = buttonColumnX;
self.usePotionBtn.y = potionY;
self.usePotionBtn.visible = false; // Initially hidden
self.usePotionBtn.down = function () {
if (!self.visible || player.inventory.potionCount <= 0 || player.hp >= player.maxHp) return;
player.inventory.potionCount--;
player.hp += 20; // Heal amount
if (player.hp > player.maxHp) player.hp = player.maxHp;
self.updateInventoryDisplay();
playerStatsScreen.updateStatsDisplay(); // Update stats screen if open
updateGUI(); // Update main GUI HP display
};
self.addChild(self.usePotionBtn);
// Antidotes
var antidoteY = potionY + itemSpacingY;
self.antidoteLabelText = new Text2('Antidotes:', {
size: 60,
fill: 0xA78BFA,
// Light purple for better readability
font: "Impact"
});
self.antidoteLabelText.anchor.set(0, 0.5);
self.antidoteLabelText.x = contentStartX + 40; // Indent
self.antidoteLabelText.y = antidoteY;
self.addChild(self.antidoteLabelText);
self.antidoteValueText = new Text2('', {
size: 60,
fill: 0x800080,
font: "Impact"
});
self.antidoteValueText.anchor.set(0, 0.5);
self.antidoteValueText.x = valueColumnX;
self.antidoteValueText.y = antidoteY;
self.addChild(self.antidoteValueText);
self.useAntidoteBtn = new Text2('DRINK', {
size: 60,
fill: 0x800080,
font: "Impact"
});
self.useAntidoteBtn.anchor.set(0, 0.5);
self.useAntidoteBtn.x = buttonColumnX;
self.useAntidoteBtn.y = antidoteY;
self.useAntidoteBtn.visible = false; // Initially hidden
self.useAntidoteBtn.down = function () {
if (!self.visible || player.inventory.antidoteCount <= 0 || !player.isPoisoned) return;
player.inventory.antidoteCount--;
player.curePoison();
self.updateInventoryDisplay();
updateGUI(); // Update main GUI HP display (poison effect removed)
};
self.addChild(self.useAntidoteBtn);
// --- Equipped Gear Section ---
self.gearTitle = new Text2('Equipped Gear:', {
size: 70,
fill: 0xFFE066,
font: "Impact" // Using a pixel-style font
});
self.gearTitle.anchor.set(0, 0.5);
self.gearTitle.x = contentStartX;
self.gearTitle.y = antidoteY + itemSpacingY + 40; // Position below consumables
self.addChild(self.gearTitle);
var gearStartY = self.gearTitle.y + itemSpacingY;
var gearLabelColumnX = contentStartX + 40; // Indent
var gearItemColumnX = valueColumnX - 80; // Align with consumable values, adjust for wider text
self.swordSlotLabel = new Text2('Sword:', {
size: 60,
fill: "#fff",
font: "Impact"
});
self.swordSlotLabel.anchor.set(0, 0.5);
self.swordSlotLabel.x = gearLabelColumnX;
self.swordSlotLabel.y = gearStartY;
self.addChild(self.swordSlotLabel);
self.swordItemText = new Text2('-', {
size: 55,
fill: "#fff",
font: "Impact"
}); // Slightly smaller for item details
self.swordItemText.anchor.set(0, 0.5);
self.swordItemText.x = gearItemColumnX;
self.swordItemText.y = gearStartY;
self.addChild(self.swordItemText);
self.shieldSlotLabel = new Text2('Shield:', {
size: 60,
fill: "#fff",
font: "Impact"
});
self.shieldSlotLabel.anchor.set(0, 0.5);
self.shieldSlotLabel.x = gearLabelColumnX;
self.shieldSlotLabel.y = gearStartY + itemSpacingY;
self.addChild(self.shieldSlotLabel);
self.shieldItemText = new Text2('-', {
size: 55,
fill: "#fff",
font: "Impact"
});
self.shieldItemText.anchor.set(0, 0.5);
self.shieldItemText.x = gearItemColumnX;
self.shieldItemText.y = gearStartY + itemSpacingY;
self.addChild(self.shieldItemText);
self.helmetSlotLabel = new Text2('Helmet:', {
size: 60,
fill: "#fff",
font: "Impact"
});
self.helmetSlotLabel.anchor.set(0, 0.5);
self.helmetSlotLabel.x = gearLabelColumnX;
self.helmetSlotLabel.y = gearStartY + itemSpacingY * 2;
self.addChild(self.helmetSlotLabel);
self.helmetItemText = new Text2('-', {
size: 55,
fill: "#fff",
font: "Impact"
});
self.helmetItemText.anchor.set(0, 0.5);
self.helmetItemText.x = gearItemColumnX;
self.helmetItemText.y = gearStartY + itemSpacingY * 2;
self.addChild(self.helmetItemText);
self.armourSlotLabel = new Text2('Armour:', {
size: 60,
fill: "#fff",
font: "Impact"
});
self.armourSlotLabel.anchor.set(0, 0.5);
self.armourSlotLabel.x = gearLabelColumnX;
self.armourSlotLabel.y = gearStartY + itemSpacingY * 3;
self.addChild(self.armourSlotLabel);
self.armourItemText = new Text2('-', {
size: 55,
fill: "#fff",
font: "Impact"
});
self.armourItemText.anchor.set(0, 0.5);
self.armourItemText.x = gearItemColumnX;
self.armourItemText.y = gearStartY + itemSpacingY * 3;
self.addChild(self.armourItemText);
// Close button
self.closeBtn = new Text2('CLOSE', {
size: 70,
fill: "#fff",
font: "Impact" // Using a pixel-style font
});
self.closeBtn.anchor.set(0.5, 0.5);
self.closeBtn.x = 2048 / 2;
self.closeBtn.y = self.panel.y + self.panel.height / 2 - 80;
self.closeBtn.down = function () {
if (!self.visible) return;
self.endInventoryScreen();
};
self.addChild(self.closeBtn);
// Update the displayed inventory
self.updateInventoryDisplay = function () {
// Animate gold text if value changed
if (typeof self._lastGold !== "undefined" && self._lastGold !== player.gold) {
tween(self.goldText, {
scaleX: 1.25,
scaleY: 1.25
}, {
duration: 120,
easing: tween.cubicOut,
onFinish: function onFinish() {
tween(self.goldText, {
scaleX: 1,
scaleY: 1
}, {
duration: 120,
easing: tween.cubicIn
});
}
});
}
self.goldText.setText('Coins: ' + player.gold);
// Update Potion count and animate if changed
if (typeof self._lastPotionCount !== "undefined" && self._lastPotionCount !== player.inventory.potionCount) {
tween(self.potionValueText, {
scaleX: 1.25,
scaleY: 1.25
}, {
duration: 120,
easing: tween.cubicOut,
onFinish: function onFinish() {
tween(self.potionValueText, {
scaleX: 1,
scaleY: 1
}, {
duration: 120,
easing: tween.cubicIn
});
}
});
}
self.potionValueText.setText('' + player.inventory.potionCount);
// Update Antidote count and animate if changed
if (typeof self._lastAntidoteCount !== "undefined" && self._lastAntidoteCount !== player.inventory.antidoteCount) {
tween(self.antidoteValueText, {
scaleX: 1.25,
scaleY: 1.25
}, {
duration: 120,
easing: tween.cubicOut,
onFinish: function onFinish() {
tween(self.antidoteValueText, {
scaleX: 1,
scaleY: 1
}, {
duration: 120,
easing: tween.cubicIn
});
}
});
}
self.antidoteValueText.setText('' + player.inventory.antidoteCount);
// Update Equipped Gear Display
var gearMap = {
sword: self.swordItemText,
shield: self.shieldItemText,
helmet: self.helmetItemText,
armour: self.armourItemText
};
var gearTypes = ['sword', 'shield', 'helmet', 'armour'];
for (var i = 0; i < gearTypes.length; i++) {
var type = gearTypes[i];
var item = player.inventory[type];
var itemTextObj = gearMap[type];
if (item) {
var attributeText = '';
if (type === 'sword') {
attributeText = '(+' + item.modifier + ' DMG)';
} else if (type === 'shield' || type === 'helmet' || type === 'armour') {
var defenseBonus = 0;
if (type === 'shield') defenseBonus = item.modifier;
if (type === 'helmet') defenseBonus = Math.floor(item.modifier / 2);
if (type === 'armour') defenseBonus = item.modifier;
attributeText = '(+' + defenseBonus + ' DEF)';
}
itemTextObj.setText(item.label + ' ' + attributeText);
if (itemTextObj.style) {
itemTextObj.style.fill = item.color || "#fff"; // Set color based on item quality
}
} else {
itemTextObj.setText('-');
if (itemTextObj.style) {
itemTextObj.style.fill = "#fff"; // Default color for empty slot
}
}
}
// Store last values for next update
self._lastGold = player.gold;
self._lastPotionCount = player.inventory.potionCount;
self._lastAntidoteCount = player.inventory.antidoteCount; // Store last antidote count
// Show/hide DRINK button for potion
if (player.inventory.potionCount > 0 && player.hp < player.maxHp) {
self.usePotionBtn.visible = true;
self.usePotionBtn.alpha = 1;
self.usePotionBtn.interactive = true;
} else {
self.usePotionBtn.visible = true; //{iL} // Keep visible but disabled for layout consistency
self.usePotionBtn.alpha = 0.5;
self.usePotionBtn.interactive = false;
}
// Show/hide DRINK button for antidote
if (player.inventory.antidoteCount > 0 && player.isPoisoned) {
self.useAntidoteBtn.visible = true;
self.useAntidoteBtn.alpha = 1;
self.useAntidoteBtn.interactive = true;
} else {
self.useAntidoteBtn.visible = true; //{iP} // Keep visible but disabled
self.useAntidoteBtn.alpha = 0.5;
self.useAntidoteBtn.interactive = false;
}
};
// Start the inventory screen
self.startInventoryScreen = function () {
self.visible = true;
self.alpha = 0;
game.addChild(self);
LK.getSound('tap').play();
updateGUI(); // Hide main GUI
self.updateInventoryDisplay(); // Update inventory when opening
// Fade in
tween(self, {
alpha: 1
}, {
duration: 300,
easing: tween.easeOut
});
};
// End the inventory screen
self.endInventoryScreen = function () {
self.visible = false;
self.alpha = 0;
if (self.parent) self.parent.removeChild(self);
LK.getSound('tap').play();
showGameGUIWithTransition();
};
return self;
});
// PlayerStatsScreen: Displays and allows spending skill points
var PlayerStatsScreen = Container.expand(function () {
var self = Container.call(this);
self.visible = false;
self.alpha = 0;
// Background overlay
self.overlay = self.attachAsset('emptyTileCover', {
width: 2048,
height: 2732,
alpha: 0.9
});
// Main panel background
self.panel = self.attachAsset('emptyTileBg', {
width: 1200,
height: 1600,
// Adjusted height
anchorX: 0.5,
anchorY: 0.5,
x: 2048 / 2,
y: 2732 / 2
});
// Title
self.titleText = new Text2('Player Stats', {
size: 110,
fill: 0xFFE066,
font: "Impact" // Using a pixel-style font
});
self.titleText.anchor.set(0.5, 0.5);
self.titleText.x = 2048 / 2;
self.titleText.y = self.panel.y - self.panel.height / 2 + 120;
self.addChild(self.titleText);
var panelLeftEdge = self.panel.x - self.panel.width / 2;
var labelColumnX = panelLeftEdge + 80;
var valueColumnX = labelColumnX + 300; // For stat values
var buttonColumnX = self.panel.x + self.panel.width / 2 - 250; // For upgrade buttons, pushed more to the right
var itemSpacingY = 80;
var currentY = self.panel.y - self.panel.height / 2 + 250;
// --- Player Info Section ---
// Level
self.levelLabelText = new Text2('Level:', {
size: 60,
fill: "#fff",
font: "Impact"
});
self.levelLabelText.anchor.set(0, 0.5);
self.levelLabelText.x = labelColumnX;
self.levelLabelText.y = currentY;
self.addChild(self.levelLabelText);
self.levelValueText = new Text2('', {
size: 60,
fill: 0xA3E635,
font: "Impact"
});
self.levelValueText.anchor.set(0, 0.5);
self.levelValueText.x = valueColumnX;
self.levelValueText.y = currentY;
self.addChild(self.levelValueText);
currentY += itemSpacingY;
// EXP
self.expLabelText = new Text2('EXP:', {
size: 60,
fill: "#fff",
font: "Impact"
});
self.expLabelText.anchor.set(0, 0.5);
self.expLabelText.x = labelColumnX;
self.expLabelText.y = currentY;
self.addChild(self.expLabelText);
self.expValueText = new Text2('', {
size: 60,
fill: 0xFFE066,
font: "Impact"
});
self.expValueText.anchor.set(0, 0.5);
self.expValueText.x = valueColumnX;
self.expValueText.y = currentY;
self.addChild(self.expValueText);
currentY += itemSpacingY * 1.5; // Extra space before stats
// --- Core Stats Section ---
// HP
self.hpLabelText = new Text2('HP:', {
size: 60,
fill: "#fff",
font: "Impact"
});
self.hpLabelText.anchor.set(0, 0.5);
self.hpLabelText.x = labelColumnX;
self.hpLabelText.y = currentY;
self.addChild(self.hpLabelText);
self.hpValueText = new Text2('', {
size: 60,
fill: 0xA3E635,
font: "Impact"
});
self.hpValueText.anchor.set(0, 0.5);
self.hpValueText.x = valueColumnX;
self.hpValueText.y = currentY;
self.addChild(self.hpValueText);
currentY += itemSpacingY;
// DMG
self.dmgLabelText = new Text2('Damage:', {
size: 60,
fill: "#fff",
font: "Impact"
});
self.dmgLabelText.anchor.set(0, 0.5);
self.dmgLabelText.x = labelColumnX;
self.dmgLabelText.y = currentY;
self.addChild(self.dmgLabelText);
self.dmgValueText = new Text2('', {
size: 60,
fill: 0xF87171,
font: "Impact"
});
self.dmgValueText.anchor.set(0, 0.5);
self.dmgValueText.x = valueColumnX;
self.dmgValueText.y = currentY;
self.addChild(self.dmgValueText);
currentY += itemSpacingY;
// DEF
self.defLabelText = new Text2('Defense:', {
size: 60,
fill: "#fff",
font: "Impact"
});
self.defLabelText.anchor.set(0, 0.5);
self.defLabelText.x = labelColumnX;
self.defLabelText.y = currentY;
self.addChild(self.defLabelText);
self.defValueText = new Text2('', {
size: 60,
fill: 0x7DD3FC,
font: "Impact"
});
self.defValueText.anchor.set(0, 0.5);
self.defValueText.x = valueColumnX;
self.defValueText.y = currentY;
self.addChild(self.defValueText);
currentY += itemSpacingY * 1.5; // Extra space
// --- Skill Points & Upgrades Section ---
self.skillPointsLabelText = new Text2('Skill Points:', {
size: 60,
fill: "#fff",
font: "Impact"
});
self.skillPointsLabelText.anchor.set(0, 0.5);
self.skillPointsLabelText.x = labelColumnX;
self.skillPointsLabelText.y = currentY;
self.addChild(self.skillPointsLabelText);
self.skillPointsValueText = new Text2('', {
size: 60,
fill: 0xFFE066,
font: "Impact"
});
self.skillPointsValueText.anchor.set(0, 0.5);
self.skillPointsValueText.x = valueColumnX;
self.skillPointsValueText.y = currentY;
self.addChild(self.skillPointsValueText);
currentY += itemSpacingY;
// Upgrade Buttons
self.upgradeBtns = [];
var upgradeOptions = [{
label: "+HP & Heal",
stat: "hp",
color: 0xA3E635
}, {
label: "+Damage",
// Shortened for consistency
stat: "damage",
color: 0xF87171
}, {
label: "+Defense",
// Shortened
stat: "defense",
color: 0x7DD3FC
}];
var upgradeButtonYStart = currentY;
for (var i = 0; i < upgradeOptions.length; i++) {
var option = upgradeOptions[i];
// Upgrade Label (optional, if we want "Upgrade HP:" next to button)
// var upgradeLabel = new Text2('Upgrade ' + option.stat.toUpperCase() + ':', { size: 55, fill: "#fff", font: "Impact" });
// upgradeLabel.anchor.set(0, 0.5);
// upgradeLabel.x = labelColumnX;
// upgradeLabel.y = upgradeButtonYStart + i * itemSpacingY;
// self.addChild(upgradeLabel);
var btn = new Text2(option.label, {
size: 60,
fill: option.color,
font: "Impact" // Using a pixel-style font
});
btn.anchor.set(0.5, 0.5); // Anchor to its center
btn.x = valueColumnX + 150; // Position next to the stat value
btn.y = upgradeButtonYStart + i * (itemSpacingY + 10); // Adjusted Y position for buttons
btn.optionStat = option.stat;
btn.visible = false;
btn.down = function () {
if (!self.visible || player.skillPoints <= 0) return;
player.spendSkillPoint(this.optionStat);
self.updateStatsDisplay();
updateGUI();
if (player.skillPoints <= 0) {
self.hideUpgradeButtons();
}
};
self.addChild(btn);
self.upgradeBtns.push(btn);
}
// Close button
self.closeBtn = new Text2('CLOSE', {
size: 70,
fill: "#fff",
font: "Impact" // Using a pixel-style font
});
self.closeBtn.anchor.set(0.5, 0.5);
self.closeBtn.x = 2048 / 2;
self.closeBtn.y = self.panel.y + self.panel.height / 2 - 80;
self.closeBtn.down = function () {
if (!self.visible) return;
self.endStatsScreen();
};
self.addChild(self.closeBtn);
// Update the displayed stats
self.updateStatsDisplay = function () {
// Animate stat value text if changed
function animateStatChange(textObject, newValue, oldValue) {
if (typeof oldValue !== "undefined" && oldValue !== newValue) {
tween(textObject, {
scaleX: 1.25,
scaleY: 1.25
}, {
duration: 120,
easing: tween.cubicOut,
onFinish: function onFinish() {
tween(textObject, {
scaleX: 1,
scaleY: 1
}, {
duration: 120,
easing: tween.cubicIn
});
}
});
}
}
animateStatChange(self.levelValueText, player.level, self._lastLevel);
self.levelValueText.setText('' + player.level);
var expString = player.exp + '/' + player.expToLevel;
animateStatChange(self.expValueText, expString, self._lastExpString);
self.expValueText.setText(expString);
var hpString = player.hp + '/' + player.maxHp;
animateStatChange(self.hpValueText, hpString, self._lastHpString);
self.hpValueText.setText(hpString);
animateStatChange(self.dmgValueText, player.damage, self._lastDmg);
self.dmgValueText.setText('' + player.damage);
animateStatChange(self.defValueText, player.defense, self._lastDef);
self.defValueText.setText('' + player.defense);
animateStatChange(self.skillPointsValueText, player.skillPoints, self._lastSkillPoints);
self.skillPointsValueText.setText('' + player.skillPoints);
// Store last values for next update
self._lastLevel = player.level;
self._lastExpString = expString;
self._lastHpString = hpString;
self._lastDmg = player.damage;
self._lastDef = player.defense;
self._lastSkillPoints = player.skillPoints;
if (player.skillPoints > 0) {
self.showUpgradeButtons();
} else {
self.hideUpgradeButtons();
}
};
// Show upgrade buttons
self.showUpgradeButtons = function () {
for (var i = 0; i < self.upgradeBtns.length; i++) {
self.upgradeBtns[i].visible = true;
}
};
// Hide upgrade buttons
self.hideUpgradeButtons = function () {
for (var i = 0; i < self.upgradeBtns.length; i++) {
self.upgradeBtns[i].visible = false;
}
};
// Start the stats screen
self.startStatsScreen = function () {
self.visible = true;
self.alpha = 0;
game.addChild(self);
LK.getSound('tap').play();
updateGUI(); // Hide main GUI
self.updateStatsDisplay(); // Update stats when opening
// Fade in
tween(self, {
alpha: 1
}, {
duration: 300,
easing: tween.easeOut
});
};
// End the stats screen
self.endStatsScreen = function () {
self.visible = false;
self.alpha = 0;
if (self.parent) self.parent.removeChild(self);
LK.getSound('tap').play();
showGameGUIWithTransition();
};
return self;
});
// ShopScreen: Allows buying and selling items
var ShopScreen = Container.expand(function () {
var self = Container.call(this);
self.visible = false;
self.alpha = 0;
// Background overlay
self.overlay = self.attachAsset('emptyTileCover', {
width: 2048,
height: 2732,
alpha: 0.9
});
// Main panel background
self.panel = self.attachAsset('emptyTileBg', {
width: 1400,
height: 2000,
anchorX: 0.5,
anchorY: 0.5,
x: 2048 / 2,
y: 2732 / 2
});
// Title
self.titleText = new Text2('Shop', {
size: 110,
fill: 0xADD8E6,
// LightBlue
font: "Impact" // Using a pixel-style font
});
self.titleText.anchor.set(0.5, 0.5);
self.titleText.x = 2048 / 2;
self.titleText.y = self.panel.y - self.panel.height / 2 + 120;
self.addChild(self.titleText);
// Coins display
self.coinsText = new Text2('Coins: ' + player.gold, {
size: 60,
fill: 0xFFD700,
font: "Impact" // Using a pixel-style font
});
self.coinsText.anchor.set(0, 0.5);
self.coinsText.x = self.panel.x - self.panel.width / 2 + 80;
self.coinsText.y = self.panel.y - self.panel.height / 2 + 220;
self.addChild(self.coinsText);
// Items for Sale (expanded: quality depends on level)
self.itemsForSale = [];
// Always sell potions and antidotes
self.itemsForSale.push({
type: "potion",
label: "Life Potion",
color: 0xA3E635,
cost: 10
});
self.itemsForSale.push({
type: "antidote",
label: "Antidote Potion",
color: 0x800080,
cost: 15
});
// Determine available gear qualities for this shop based on currentLevel
var availableQualities = [];
if (chestScreen && chestScreen._allLootQualities) {
for (var i = 0; i < chestScreen._allLootQualities.length; i++) {
if (currentLevel + 1 >= chestScreen._allLootQualities[i].minLevel) {
availableQualities.push(chestScreen._allLootQualities[i]);
}
}
}
// Lower level shops only sell lower quality gear
var maxQualityIndex = Math.min(currentLevel, availableQualities.length - 1);
if (maxQualityIndex >= 0) {
// Only offer up to maxQualityIndex quality
for (var q = 0; q <= maxQualityIndex; q++) {
var quality = availableQualities[q];
// For each gear type
var gearTypes = [{
type: "sword",
label: "Sword"
}, {
type: "shield",
label: "Shield"
}, {
type: "helmet",
label: "Helmet"
}, {
type: "armour",
label: "Armour"
}];
for (var g = 0; g < gearTypes.length; g++) {
var gear = gearTypes[g];
// Cost scales with quality modifier
var cost = 20 + quality.modifier * 15 + currentLevel * 5;
self.itemsForSale.push({
type: gear.type,
label: quality.label + " " + gear.label,
color: quality.color,
cost: cost,
quality: quality
});
}
}
}
self.saleButtons = [];
var startY = self.coinsText.y + 100;
for (var i = 0; i < self.itemsForSale.length; i++) {
var item = self.itemsForSale[i];
var buyBtn = new Text2('BUY ' + item.label + ' (' + item.cost + ' Coins)', {
size: 60,
fill: item.color,
font: "Impact"
});
buyBtn.anchor.set(0, 0.5);
buyBtn.x = self.panel.x - self.panel.width / 2 + 80;
buyBtn.y = startY + i * 100;
buyBtn.item = item;
// Add stat benefit text next to the buy button
var statBenefitText = new Text2('', {
size: 45,
// Smaller size
fill: item.color,
font: "Impact"
});
statBenefitText.anchor.set(0, 0.5);
statBenefitText.x = buyBtn.x + buyBtn.width + 20; // Position to the right of the button
statBenefitText.y = buyBtn.y;
self.addChild(statBenefitText);
buyBtn.statBenefitText = statBenefitText; // Store reference to the text
// Set the stat benefit text based on item type
if (item.type === "potion") {
statBenefitText.setText("(Heals 20 HP)");
} else if (item.type === "antidote") {
statBenefitText.setText("(Cures Poison)");
} else if (item.quality) {
var benefit = '';
if (item.type === 'sword') {
benefit = '+' + item.quality.modifier + ' DMG';
} else if (item.type === 'shield') {
benefit = '+' + item.quality.modifier + ' DEF';
} else if (item.type === 'helmet') {
benefit = '+' + Math.floor(item.quality.modifier / 2) + ' DEF';
} else if (item.type === 'armour') {
benefit = '+' + item.quality.modifier + ' DEF';
}
statBenefitText.setText("(" + benefit + ")");
}
buyBtn.down = function () {
if (!self.visible) return;
var itemToBuy = this.item;
if (player.gold >= itemToBuy.cost) {
player.gold -= itemToBuy.cost;
if (itemToBuy.type === "potion") {
player.inventory.potionCount++;
} else if (itemToBuy.type === "antidote") {
player.inventory.antidoteCount = (player.inventory.antidoteCount || 0) + 1;
} else if (itemToBuy.quality) {
// Gear purchase: equip or replace
player.equipGear(itemToBuy.type, itemToBuy.quality);
playerStatsScreen.updateStatsDisplay();
}
self.updateShopDisplay();
playerInventoryScreen.updateInventoryDisplay();
self.showConfirmationText('Bought ' + itemToBuy.label + '!', itemToBuy.color);
} else {
self.showConfirmationText('Not enough coins!', 0xFF0000);
}
};
self.addChild(buyBtn);
self.saleButtons.push(buyBtn);
}
// Items to Sell (simplified for now: gear and potions)
self.sellButtons = [];
var sellTitle = new Text2('Sell Items:', {
size: 70,
fill: 0xFFE066,
// Yellow
font: "Impact" // Using a pixel-style font
});
sellTitle.anchor.set(0, 0.5);
sellTitle.x = self.panel.x - self.panel.width / 2 + 80;
sellTitle.y = startY + self.itemsForSale.length * 100 + 80;
self.addChild(sellTitle);
self.updateSellButtons = function () {
// Clear existing sell buttons
for (var i = 0; i < self.sellButtons.length; i++) {
self.sellButtons[i].destroy();
}
self.sellButtons = [];
var currentSellY = sellTitle.y + 80;
// Sell gear
var gearTypes = ['sword', 'shield', 'helmet', 'armour'];
for (var i = 0; i < gearTypes.length; i++) {
var gearType = gearTypes[i];
var item = player.inventory[gearType];
if (item) {
var sellPrice = item.modifier * 5; // Example sell price
var sellBtn = new Text2('SELL ' + item.label + ' ' + gearType.charAt(0).toUpperCase() + gearType.slice(1) + ' (' + sellPrice + ' Coins)', {
size: 50,
fill: item.color,
font: "Impact" // Using a pixel-style font
});
sellBtn.anchor.set(0, 0.5);
sellBtn.x = self.panel.x - self.panel.width / 2 + 80;
sellBtn.y = currentSellY;
sellBtn.itemType = gearType;
sellBtn.sellPrice = sellPrice;
sellBtn.down = function () {
if (!self.visible) return;
var type = this.itemType;
var price = this.sellPrice;
player.gold += price;
player.inventory[type] = null; // Remove item from inventory
player.updateEquippedStats(); // Update player stats
self.updateShopDisplay();
playerInventoryScreen.updateInventoryDisplay(); // Update inventory screen if open
playerStatsScreen.updateStatsDisplay(); // Update stats screen if open
self.updateSellButtons(); // Refresh sell buttons
self.showConfirmationText('Sold ' + type.charAt(0).toUpperCase() + type.slice(1) + ' for ' + price + ' coins!', 0xFFD700);
};
self.addChild(sellBtn);
self.sellButtons.push(sellBtn);
currentSellY += 60;
}
}
// Sell potions
if (player.inventory.potionCount > 0) {
var potionSellPrice = 10; // Example sell price for potion
var sellPotionBtn = new Text2('SELL Life Potion (' + potionSellPrice + ' Coins)', {
size: 50,
fill: 0xA3E635,
font: "Impact" // Using a pixel-style font
});
sellPotionBtn.anchor.set(0, 0.5);
sellPotionBtn.x = self.panel.x - self.panel.width / 2 + 80;
sellPotionBtn.y = currentSellY;
sellPotionBtn.sellPrice = potionSellPrice;
sellPotionBtn.down = function () {
if (!self.visible || player.inventory.potionCount <= 0) return;
var price = this.sellPrice;
player.gold += price;
player.inventory.potionCount--; // Remove potion
self.updateShopDisplay();
playerInventoryScreen.updateInventoryDisplay(); // Update inventory screen if open
self.updateSellButtons(); // Refresh sell buttons
self.showConfirmationText('Sold a Life Potion for ' + price + ' coins!', 0xFFD700);
};
self.addChild(sellPotionBtn);
self.sellButtons.push(sellPotionBtn);
currentSellY += 60;
}
// Sell antidotes
if (player.inventory.antidoteCount > 0) {
var antidoteSellPrice = 10; // Example sell price for antidote
var sellAntidoteBtn = new Text2('SELL Antidote Potion (' + antidoteSellPrice + ' Coins)', {
size: 50,
fill: 0x800080,
// Purple
font: "Impact" // Using a pixel-style font
});
sellAntidoteBtn.anchor.set(0, 0.5);
sellAntidoteBtn.x = self.panel.x - self.panel.width / 2 + 80;
sellAntidoteBtn.y = currentSellY;
sellAntidoteBtn.sellPrice = antidoteSellPrice;
sellAntidoteBtn.down = function () {
if (!self.visible || player.inventory.antidoteCount <= 0) return;
var price = this.sellPrice;
player.gold += price;
player.inventory.antidoteCount--;
self.updateShopDisplay();
playerInventoryScreen.updateInventoryDisplay(); // Update inventory screen if open
self.updateSellButtons(); // Refresh sell buttons
self.showConfirmationText('Sold an Antidote Potion for ' + price + ' coins!', 0xFFD700);
};
self.addChild(sellAntidoteBtn);
self.sellButtons.push(sellAntidoteBtn);
currentSellY += 60;
}
};
// Confirmation text display
self._confirmationText = null;
self.showConfirmationText = function (message, color) {
if (self._confirmationText && typeof self._confirmationText.destroy === 'function') {
self._confirmationText.destroy();
}
self._confirmationText = new Text2(message, {
size: 60,
fill: color,
font: "Impact" // Using a pixel-style font
});
self._confirmationText.anchor.set(0.5, 0.5);
self._confirmationText.x = 2048 / 2;
self._confirmationText.y = self.panel.y + self.panel.height / 2 - 150;
self.addChild(self._confirmationText);
tween(self._confirmationText, {
alpha: 0
}, {
duration: 1000,
delay: 800,
onFinish: function onFinish() {
if (self._confirmationText && typeof self._confirmationText.destroy === 'function') {
self._confirmationText.destroy();
}
self._confirmationText = null;
}
});
};
// Close button
self.closeBtn = new Text2('CLOSE', {
size: 70,
fill: "#fff",
font: "Impact" // Using a pixel-style font
});
self.closeBtn.anchor.set(0.5, 0.5);
self.closeBtn.x = 2048 / 2;
self.closeBtn.y = self.panel.y + self.panel.height / 2 - 80;
self.closeBtn.down = function () {
if (!self.visible) return;
self.endShopScreen();
};
self.addChild(self.closeBtn);
// Update the displayed shop info
self.updateShopDisplay = function () {
// Animate coins text if value changed
if (typeof self._lastGold !== "undefined" && self._lastGold !== player.gold) {
tween(self.coinsText, {
scaleX: 1.25,
scaleY: 1.25
}, {
duration: 120,
easing: tween.cubicOut,
onFinish: function onFinish() {
tween(self.coinsText, {
scaleX: 1,
scaleY: 1
}, {
duration: 120,
easing: tween.cubicIn
});
}
});
}
self.coinsText.setText('Coins: ' + player.gold);
self._lastGold = player.gold;
self.updateSellButtons(); // Refresh sell buttons
};
// Start the shop screen
self.startShopScreen = function () {
self.visible = true;
self.alpha = 0;
game.addChild(self);
self.updateShopDisplay(); // Update shop info when opening
// Fade in
tween(self, {
alpha: 1
}, {
duration: 300,
easing: tween.easeOut
});
};
// End the shop screen
self.endShopScreen = function () {
self.visible = false;
self.alpha = 0;
if (self._confirmationText && typeof self._confirmationText.destroy === 'function') {
self._confirmationText.destroy();
self._confirmationText = null;
}
if (self.parent) self.parent.removeChild(self);
if (monstersLeft <= 0 && currentLevel < LEVELS.length - 1) {
levelEndScreen.startLevelEndScreen();
} else {
showGameGUIWithTransition();
}
};
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0x22223a
});
/****
* Game Code
****/
// New shape for black outline
// --- Game Constants ---
// Add event listener to MonsterTile for clicking
// New asset for game title image
MonsterTile.prototype.down = function (x, y, obj) {
if (gameOver) return;
// Prevent interaction if any overlay screen is visible
if (mainScreen.visible || battleScreen.visible || battleScreen.battleItemsMenu.visible || chestScreen.visible || playerStatsScreen.visible || playerInventoryScreen.visible || levelEndScreen.visible || shopScreen.visible) return;
// If the monster is defeated, do nothing
if (this.defeated) return;
// If the monster is not defeated (either initially or after running away), a click should initiate a battle.
// Ensure it's revealed before starting the battle.
// The reveal() method has an internal check (if (self.revealed) return;)
// so it's safe to call even if already revealed.
this.reveal();
battleScreen.startBattle(this);
};
function _slicedToArray(r, e) {
return _arrayWithHoles(r) || _iterableToArrayLimit(r, e) || _unsupportedIterableToArray(r, e) || _nonIterableRest();
}
function _nonIterableRest() {
throw new TypeError("Invalid attempt to destructure non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.");
}
function _unsupportedIterableToArray(r, a) {
if (r) {
if ("string" == typeof r) return _arrayLikeToArray(r, a);
var t = {}.toString.call(r).slice(8, -1);
return "Object" === t && r.constructor && (t = r.constructor.name), "Map" === t || "Set" === t ? Array.from(r) : "Arguments" === t || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(t) ? _arrayLikeToArray(r, a) : void 0;
}
}
function _arrayLikeToArray(r, a) {
(null == a || a > r.length) && (a = r.length);
for (var e = 0, n = Array(a); e < a; e++) n[e] = r[e];
return n;
}
function _iterableToArrayLimit(r, l) {
var t = null == r ? null : "undefined" != typeof Symbol && r[Symbol.iterator] || r["@@iterator"];
if (null != t) {
var e,
n,
i,
u,
a = [],
f = !0,
o = !1;
try {
if (i = (t = t.call(r)).next, 0 === l) {
if (Object(t) !== t) return;
f = !1;
} else for (; !(f = (e = i.call(t)).done) && (a.push(e.value), a.length !== l); f = !0);
} catch (r) {
o = !0, n = r;
} finally {
try {
if (!f && null != t["return"] && (u = t["return"](), Object(u) !== u)) return;
} finally {
if (o) throw n;
}
}
return a;
}
}
function _arrayWithHoles(r) {
if (Array.isArray(r)) return r;
}
var GRID_COLS = 8;
var GRID_ROWS = 8;
var TILE_SIZE = 180;
var GRID_OFFSET_X = Math.floor((2048 - GRID_COLS * TILE_SIZE) / 2);
var GRID_OFFSET_Y = Math.floor(2732 * 0.2 + (2732 * 0.6 - GRID_ROWS * TILE_SIZE) / 2); // Center the grid vertically within the middle 60%
// --- Level System ---
var LEVELS = [{
monsters: 3,
// Adjusted for level 1
monsterStats: {
minHp: 1,
maxHp: 2,
minDmg: 1,
maxDmg: 1,
minExp: 1,
maxExp: 1
},
// Level 1: 3 normal monsters, no poison monster
monsterTypes: [MonsterTile, MonsterTile, MonsterTile] // Adjusted for level 1
}, {
monsters: 5,
monsterStats: {
minHp: 3,
maxHp: 5,
minDmg: 2,
maxDmg: 3,
minExp: 2,
maxExp: 3
},
monsterTypes: [MonsterTile, PoisonMonsterTile] // Level 2 can have basic or poison monsters
}, {
monsters: 7,
monsterStats: {
minHp: 5,
maxHp: 8,
minDmg: 3,
maxDmg: 5,
minExp: 3,
maxExp: 4
},
monsterTypes: [MonsterTile, PoisonMonsterTile] // Level 3 can also have both monster types
}];
var currentLevel = 0;
var MONSTER_COUNT = LEVELS[currentLevel].monsters;
// --- Asset Initialization ---
// --- Game State ---
var player = new Player();
player.damage = 1;
player.baseDamage = 1;
player.exp = 0;
var grid = []; // 2D array [row][col]
var tileObjs = []; // Flat array of all tile objects for easy iteration
var monstersLeft = MONSTER_COUNT;
var revealedTiles = 0;
var totalSafeTiles = GRID_COLS * GRID_ROWS - MONSTER_COUNT;
var gameOver = false;
var mainScreen = new MainScreen(); // Initialize main screen
var background = game.addChild(LK.getAsset('background', {
anchorX: 0.5,
anchorY: 0.5,
x: 2048 / 2,
y: 2732 / 2
}));
var battleScreen = new BattleScreen();
var chestScreen = new ChestScreen();
var playerStatsScreen = new PlayerStatsScreen(); // Initialize stats screen
var playerInventoryScreen = new PlayerInventoryScreen(); // Initialize inventory screen
var levelEndScreen = new LevelEndScreen(); // Initialize level end screen
var shopScreen = new ShopScreen(); // Initialize shop screen
// --- GUI Elements ---
var hpText = new Text2('', {
size: 60,
fill: 0xFF6666,
font: "Impact" // Using a pixel-style font
});
hpText.anchor.set(0.5, 0); // Anchor to top-center
LK.gui.top.addChild(hpText);
var expText = new Text2('', {
size: 60,
fill: 0xFFE066,
font: "Impact" // Using a pixel-style font
});
expText.anchor.set(0.5, 0); // Anchor to top-center
LK.gui.top.addChild(expText);
var levelText = new Text2('', {
size: 60,
fill: 0xA3E635,
font: "Impact" // Using a pixel-style font
});
levelText.anchor.set(0, 0.5); // Anchor to left-middle
LK.gui.top.addChild(levelText);
// var monstersLeftText = new Text2('', {
// size: 40,
// fill: "#fff"
// });
// monstersLeftText.anchor.set(0, 0.5); // Anchor to left-middle
// LK.gui.top.addChild(monstersLeftText);
var statsBtn = new Text2('STATS', {
size: 70,
fill: "#fff",
font: "Impact" // Using a pixel-style font
});
statsBtn.anchor.set(0.5, 1); // Anchor to center-bottom
statsBtn.x = -100; // Position to the left of the right edge (adjusted for safe area)
statsBtn.y = -40; // Position near the bottom edge
statsBtn.visible = false; // Set initial visibility to false
statsBtn.down = function () {
if (gameOver || battleScreen.visible || chestScreen.visible || playerStatsScreen.visible || playerInventoryScreen.visible) return; // Don't open if another screen is active
playerStatsScreen.startStatsScreen();
};
LK.gui.bottom.addChild(statsBtn);
var inventoryBtn = new Text2('INVENTORY', {
size: 70,
fill: "#fff",
font: "Impact" // Using a pixel-style font
});
inventoryBtn.anchor.set(0.5, 1); // Anchor to center-bottom
inventoryBtn.x = -100 - statsBtn.width - 40; // Position to the left of the right edge (adjusted for safe area and other button)
inventoryBtn.y = -40; // Position near the bottom edge
inventoryBtn.visible = false; // Set initial visibility to false
inventoryBtn.down = function () {
if (gameOver || battleScreen.visible || chestScreen.visible || playerStatsScreen.visible || playerInventoryScreen.visible) return; // Don't open if another screen is active
playerInventoryScreen.startInventoryScreen();
};
LK.gui.bottom.addChild(inventoryBtn);
var levelUpBtn = new Text2('LEVEL UP', {
size: 70,
fill: "#fff",
font: "Impact" // Using a pixel-style font
});
levelUpBtn.anchor.set(0.5, 0.5);
levelUpBtn.alpha = 0.7;
levelUpBtn.visible = false;
levelUpBtn.down = function () {
if (!this.visible) return;
// Old level up choices removed
};
LK.gui.top.addChild(levelUpBtn);
var managedGameGuiElements = [hpText, expText, levelText, statsBtn, inventoryBtn];
// Old level up choice buttons and logic removed
// --- Helper Functions ---
// Helper function to show main GUI elements with a fade-in transition
function showGameGUIWithTransition() {
updateGUI(); // Set visibility and content first
for (var i = 0; i < managedGameGuiElements.length; i++) {
var el = managedGameGuiElements[i];
// Tween alpha to 1. statsBtn's specific animation in game.update will take over if active.
tween(el, {
alpha: 1
}, {
duration: 300
});
}
}
function updateGUI() {
var showMainGui = !mainScreen.visible && !battleScreen.visible && !(chestScreen && chestScreen.visible) && !(playerStatsScreen && playerStatsScreen.visible) && !(playerInventoryScreen && playerInventoryScreen.visible) && !(levelEndScreen && levelEndScreen.visible) && !(shopScreen && shopScreen.visible);
for (var i = 0; i < managedGameGuiElements.length; i++) {
var el = managedGameGuiElements[i];
if (showMainGui) {
el.visible = true;
// Alpha is primarily handled by transitions or default to 1 after transition.
// Specific alpha animations (like statsBtn) are handled in game.update.
} else {
el.visible = false;
el.alpha = 0; // Ensure they are fully transparent when hidden
}
}
// Ensure levelUpBtn is also hidden if main GUI is hidden or if it's simply not used
levelUpBtn.visible = false;
// Specific content updates if GUI is shown
if (showMainGui) {
hpText.setText('HP: ' + player.hp + '/' + player.maxHp);
hpText.x = 450; // Position HP more to the right
hpText.y = 50; // Adjusted vertical position
var canLevelUp = player.exp >= player.expToLevel;
var plusStr = canLevelUp ? " [color=#FFE066]+[/color]" : "";
expText.setText('EXP: ' + player.exp + '/' + player.expToLevel + plusStr);
expText.x = 450; // Position EXP more to the right
expText.y = 120; // Adjusted vertical position below HP
var lvlPlusStr = canLevelUp ? " [color=#FFE066]+[/color]" : "";
levelText.setText('LEVEL: ' + player.level + lvlPlusStr);
levelText.x = 0; // Position LVL in the center of the top gui
levelText.y = 80; // Move level text a little lower
levelText.anchor.set(0.5, 0.5);
// monstersLeftText logic remains commented out as in original
// monstersLeftText.setText('Monsters: ' + monstersLeft);
// monstersLeftText.x = levelText.x + levelText.width + 40;
// monstersLeftText.y = 60;
// Position and style statsBtn and inventoryBtn
// Their visibility is already handled by the loop above.
var totalBtnWidth = statsBtn.width + inventoryBtn.width + 40;
var centerX = 0; // LK.gui.bottom center is x=0, these are added to LK.gui.bottom
statsBtn.x = centerX + totalBtnWidth / 2 - statsBtn.width / 2;
statsBtn.y = -40; // Position near the bottom edge
inventoryBtn.x = centerX - totalBtnWidth / 2 + inventoryBtn.width / 2;
inventoryBtn.y = -40; // Position near the bottom edge
if (player.skillPoints > 0) {
statsBtn.setText('STATS +');
if (statsBtn.style) statsBtn.style.fill = 0xFFE066; // Yellow color
if (typeof statsBtn.tint !== "undefined") statsBtn.tint = 0xFFE066; // Set yellow tint
// Alpha for statsBtn (pulsing animation) is handled in game.update
} else {
statsBtn.setText('STATS');
if (statsBtn.style) statsBtn.style.fill = "#fff"; // Default white color
if (typeof statsBtn.tint !== "undefined") statsBtn.tint = 0xFFFFFF; // Reset to white tint
// Alpha for statsBtn (solid) is handled in game.update or by transition
}
} else {
// If GUI is hidden, and battle screen is the one visible, update its stats
if (battleScreen.visible) {
battleScreen.updateBattleStats();
}
}
}
// Returns true if (row, col) is inside grid
function inBounds(row, col) {
return row >= 0 && row < GRID_ROWS && col >= 0 && col < GRID_COLS;
}
// Get all adjacent tile positions
function getAdjacent(row, col) {
var adj = [];
for (var dr = -1; dr <= 1; dr++) {
for (var dc = -1; dc <= 1; dc++) {
if (dr === 0 && dc === 0) continue;
var nr = row + dr,
nc = col + dc;
if (inBounds(nr, nc)) adj.push([nr, nc]);
}
}
return adj;
}
// Reveal empty tiles recursively (flood fill)
function revealEmptyTiles(row, col) {
var tile = grid[row][col];
if (tile.revealed) return;
tile.reveal();
revealedTiles++;
if (tile.adjacentMonsterDamage === 0) {
var adj = getAdjacent(row, col);
for (var i = 0; i < adj.length; i++) {
var _adj$i = _slicedToArray(adj[i], 2),
nr = _adj$i[0],
nc = _adj$i[1];
var t = grid[nr][nc];
if (t instanceof EmptyTile && !t.revealed) {
revealEmptyTiles(nr, nc);
}
}
}
}
// Reveal all monsters (on game over)
function revealAllMonsters() {
for (var i = 0; i < tileObjs.length; i++) {
var t = tileObjs[i];
if (t instanceof MonsterTile && !t.revealed) {
t.reveal();
}
}
}
// --- Board Generation ---
function generateBoard() {
// Clear previous
for (var i = 0; i < tileObjs.length; i++) {
tileObjs[i].destroy();
}
grid = [];
tileObjs = [];
monstersLeft = LEVELS[currentLevel].monsters;
revealedTiles = 0;
totalSafeTiles = GRID_COLS * GRID_ROWS - LEVELS[currentLevel].monsters;
gameOver = false;
player.curePoison(); // Cure poison at the start of a new level
// Place monsters
var monsterPositions = [];
while (monsterPositions.length < LEVELS[currentLevel].monsters) {
var r = Math.floor(Math.random() * GRID_ROWS);
var c = Math.floor(Math.random() * GRID_COLS);
// Prevent monsters from being placed at (0,0) where the chest is always placed
if (r === 0 && c === 0) continue;
var found = false;
for (var i = 0; i < monsterPositions.length; i++) {
if (monsterPositions[i][0] === r && monsterPositions[i][1] === c) {
found = true;
break;
}
}
if (!found) monsterPositions.push([r, c]);
}
// --- Randomize chest position ---
var chestRow = -1,
chestCol = -1;
while (true) {
var tryRow = Math.floor(Math.random() * GRID_ROWS);
var tryCol = Math.floor(Math.random() * GRID_COLS);
// Chest cannot be placed on a monster
var isMonsterCell = false;
for (var i = 0; i < monsterPositions.length; i++) {
if (monsterPositions[i][0] === tryRow && monsterPositions[i][1] === tryCol) {
isMonsterCell = true;
break;
}
}
if (!isMonsterCell) {
chestRow = tryRow;
chestCol = tryCol;
break;
}
}
// Build grid
var stats = LEVELS[currentLevel].monsterStats;
for (var row = 0; row < GRID_ROWS; row++) {
grid[row] = [];
for (var col = 0; col < GRID_COLS; col++) {
var isMonster = false;
for (var i = 0; i < monsterPositions.length; i++) {
if (monsterPositions[i][0] === row && monsterPositions[i][1] === col) {
isMonster = true;
break;
}
}
var tile;
// Place chest at randomized position
if (row === chestRow && col === chestCol) {
tile = new ChestTile();
} else if (isMonster) {
// Choose a monster type for this monster position
var monsterTypesForLevel = LEVELS[currentLevel].monsterTypes;
var MonsterClass;
if (currentLevel === 0 && monsterTypesForLevel.length === 4) {
// For level 1, assign the monster type based on the monster index
MonsterClass = monsterTypesForLevel[monsterPositions.findIndex(function (pos) {
return pos[0] === row && pos[1] === col;
})];
// Fallback to MonsterTile if not found (should not happen)
if (!MonsterClass) MonsterClass = MonsterTile;
} else {
// For other levels, pick randomly
MonsterClass = monsterTypesForLevel[Math.floor(Math.random() * monsterTypesForLevel.length)];
}
tile = new MonsterClass(); // Use the selected monster class
// Level-based monster stats
tile.maxHp = tile.hp = stats.minHp + Math.floor(Math.random() * (stats.maxHp - stats.minHp + 1));
tile.damage = stats.minDmg + Math.floor(Math.random() * (stats.maxDmg - stats.minDmg + 1));
tile.exp = stats.minExp + Math.floor(Math.random() * (stats.maxExp - stats.minExp + 1));
} else {
tile = new EmptyTile();
}
tile.x = GRID_OFFSET_X + col * TILE_SIZE + TILE_SIZE / 2;
tile.y = GRID_OFFSET_Y + row * TILE_SIZE + TILE_SIZE / 2;
game.addChild(tile);
grid[row][col] = tile;
tileObjs.push(tile);
}
}
// Set adjacent monster counts for empty tiles
for (var row = 0; row < GRID_ROWS; row++) {
for (var col = 0; col < GRID_COLS; col++) {
var tile = grid[row][col];
if (tile instanceof EmptyTile) {
var adj = getAdjacent(row, col);
var damageSum = 0; // Variable to store the sum of damage
for (var i = 0; i < adj.length; i++) {
var _adj$i2 = _slicedToArray(adj[i], 2),
nr = _adj$i2[0],
nc = _adj$i2[1];
if (grid[nr][nc] instanceof MonsterTile) {
damageSum += grid[nr][nc].damage; // Add monster's damage to the sum
}
}
tile.adjacentMonsterDamage = damageSum; // Store the damage sum
}
}
}
}
// --- Game Logic ---
function handleTileDown(x, y, obj) {
if (gameOver) return;
if (battleScreen.visible || battleScreen.battleItemsMenu.visible || levelEndScreen.visible || shopScreen.visible || chestScreen.visible || playerStatsScreen.visible || playerInventoryScreen.visible) return; // Don't allow tile interaction if battle screen, battle item menu, level end, shop, chest, stats, or inventory screen is up
// Find which tile was pressed
for (var i = 0; i < tileObjs.length; i++) {
var tile = tileObjs[i];
if (tile.revealed) continue;
if (tile.cover && tile.cover.alpha > 0.1) {
// Check if (x, y) is inside tile
var dx = x - tile.x;
var dy = y - tile.y;
if (Math.abs(dx) < TILE_SIZE / 2 && Math.abs(dy) < TILE_SIZE / 2) {
// Reveal tile
if (tile instanceof EmptyTile) {
LK.getSound('tap').play();
revealEmptyTiles(Math.floor((tile.y - GRID_OFFSET_Y) / TILE_SIZE), Math.floor((tile.x - GRID_OFFSET_X) / TILE_SIZE));
updateGUI();
// Win check
// (Removed: revealing all safe tiles no longer triggers win. Level is won only when all monsters are defeated.)
} else if (tile instanceof ChestTile) {
LK.getSound('tap').play();
// Reveal chest and show chest screen
tile.reveal();
chestScreen.startChest(tile);
} else if (tile instanceof MonsterTile) {
LK.getSound('tap').play();
// Reveal monster and start battle
tile.reveal(); // Reveal the monster visually
// Start the battle sequence
battleScreen.startBattle(tile);
}
break;
}
}
}
}
// --- Level Up UI ---
// Old level up UI functions removed
// --- Event Handlers ---
game.down = function (x, y, obj) {
if (mainScreen.visible) return; // Prevent interaction if main screen is visible
// The click handling for levelUpBtn and levelUpChoiceBtns
// has been moved to their own 'down' event handlers.
// Check for tile interactions
handleTileDown(x, y, obj);
};
game.update = function () {
if (mainScreen.visible || battleScreen.visible || battleScreen.battleItemsMenu.visible || chestScreen && chestScreen.visible || playerStatsScreen.visible || playerInventoryScreen.visible || levelEndScreen.visible || shopScreen.visible) return; // Don't update game elements when main, battle, battle item menu, stats, level end, shop, chest, stats, or inventory screen is up
// Animate level up button (removed)
// if (levelUpBtn.visible) {
// levelUpBtn.x = 0; // Centered horizontally relative to LK.gui.top
// levelUpBtn.y = 120;
// levelUpBtn.alpha = 0.8 + 0.2 * Math.sin(LK.ticks / 20);
// }
// Animate level up choices (removed)
// for (var i = 0; i < levelUpChoiceBtns.length; i++) {
// var btn = levelUpChoiceBtns[i];
// if (btn.visible) {
// btn.x = (i - 1) * 400; // x relative to LK.gui.bottom
// btn.y = -2732 * 0.15; // Position choices above the bottom boundary of the battle area
// btn.alpha = 0.9 + 0.1 * Math.sin(LK.ticks / 15 + i);
// }
// }
// Animate stats button if skill points are available
if (statsBtn.visible && player.skillPoints > 0) {
statsBtn.alpha = 0.8 + 0.2 * Math.sin(LK.ticks / 20);
} else if (statsBtn.visible) {
statsBtn.alpha = 1; // Solid if no points
}
if (mainScreen.visible || battleScreen.visible || chestScreen && chestScreen.visible || playerStatsScreen.visible || playerInventoryScreen.visible || levelEndScreen.visible || shopScreen.visible) {
for (var i = 0; i < tileObjs.length; i++) {
tileObjs[i].visible = false;
}
background.visible = false; // Hide background
} else {
for (var i = 0; i < tileObjs.length; i++) {
tileObjs[i].visible = true;
}
background.visible = true; // Show background
}
};
// --- Start Game ---
function startGame() {
generateBoard();
showGameGUIWithTransition(); // This will call updateGUI which handles levelText
}
// Show main screen at start
mainScreen.startMainScreen();