/****
* 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();