User prompt
Please fix the bug: 'Uncaught TypeError: Cannot read properties of undefined (reading 'setAsset')' in or related to this line: 'if (workBtnCountdownTxt && typeof workBtnCountdownTxt.destroyed === "boolean" && !workBtnCountdownTxt.destroyed && workBtnCountdownTxt.visible && typeof workBtnCountdownTxt.setText === "function") {' Line Number: 2069
User prompt
Please fix the bug: 'Uncaught TypeError: Cannot read properties of undefined (reading 'setAsset')' in or related to this line: 'if (workBtnCountdownTxt && typeof workBtnCountdownTxt.destroyed === "boolean" && !workBtnCountdownTxt.destroyed && workBtnCountdownTxt.visible) {' Line Number: 2069
User prompt
Please fix the bug: 'Uncaught TypeError: Cannot read properties of undefined (reading 'setAsset')' in or related to this line: 'if (workBtnCountdownTxt && workBtnCountdownTxt.visible) {' Line Number: 2069
User prompt
Please fix the bug: 'Uncaught TypeError: Cannot read properties of undefined (reading 'setAsset')' in or related to this line: 'if (workBtnCountdownTxt && workBtnCountdownTxt.visible && typeof workBtn !== "undefined" && workBtn) {' Line Number: 2069
User prompt
Please fix the bug: 'Uncaught TypeError: Cannot read properties of undefined (reading 'setAsset')' in or related to this line: 'if (workBtnCountdownTxt && workBtnCountdownTxt.visible) {' Line Number: 2069
User prompt
Please fix the bug: 'Uncaught TypeError: Cannot read properties of undefined (reading 'setAsset')' in or related to this line: 'if (workBtnCountdownTxt && workBtnCountdownTxt.visible) {' Line Number: 2068
User prompt
bu butona ait yeni bir görsel olsun
User prompt
Please fix the bug: 'dynamicAssets[t].push is not a function' in or related to this line: 'var leftBtn = LK.getAsset('map', {' Line Number: 239
User prompt
bu butonun assets ti "map" isimli asset olsun
User prompt
upgradeBtn nin soluna workBtdd boyutlarında bir buton ekle
User prompt
Please fix the bug: 'dynamicAssets[t].push is not a function' in or related to this line: 'var mapImg = LK.getAsset('map', {' Line Number: 169
User prompt
Please fix the bug: 'dynamicAssets[t].push is not a function' in or related to this line: 'var mapImg = LK.getAsset('map', {' Line Number: 169
User prompt
Please fix the bug: 'dynamicAssets[t].push is not a function' in or related to this line: 'var mapImg = LK.getAsset('map', {' Line Number: 169
User prompt
Please fix the bug: 'dynamicAssets[t].push is not a function' in or related to this line: 'var mapImg = LK.getAsset('map', {' Line Number: 169
User prompt
Please fix the bug: 'dynamicAssets[t].push is not a function' in or related to this line: 'var mapImg = LK.getAsset('map', {' Line Number: 169
User prompt
Please fix the bug: 'dynamicAssets[t].push is not a function' in or related to this line: 'var mapImg = LK.getAsset('map', {' Line Number: 169
User prompt
Please fix the bug: 'dynamicAssets[t].push is not a function' in or related to this line: 'var mapImg = LK.getAsset('map', {' Line Number: 169
User prompt
Please fix the bug: 'dynamicAssets[t].push is not a function' in or related to this line: 'var mapImg = LK.getAsset('map', {' Line Number: 169
User prompt
Please fix the bug: 'dynamicAssets[t].push is not a function' in or related to this line: 'var mapImg = LK.getAsset('map', {' Line Number: 170
User prompt
Please fix the bug: 'dynamicAssets[t].push is not a function' in or related to this line: 'var mapImg = LK.getAsset('map', {' Line Number: 170
User prompt
Please fix the bug: 'dynamicAssets[t].push is not a function' in or related to this line: 'var mapImg = LK.getAsset('map', {' Line Number: 170
User prompt
Please fix the bug: 'dynamicAssets[t].push is not a function' in or related to this line: 'var mapImg = LK.getAsset('map', {' Line Number: 170
User prompt
Please fix the bug: 'dynamicAssets[t].push is not a function' in or related to this line: 'var mapImg = LK.getAsset('map', {' Line Number: 170
User prompt
Please fix the bug: 'dynamicAssets[t].push is not a function' in or related to this line: 'var mapImg = LK.getAsset('map', {' Line Number: 170
User prompt
Please fix the bug: 'dynamicAssets[t].push is not a function' in or related to this line: 'var mapImg = LK.getAsset('map', {' Line Number: 170
/**** * Plugins ****/ var tween = LK.import("@upit/tween.v1"); /**** * Classes ****/ // Enemy class var Enemy = Container.expand(function () { var self = Container.call(this); var enemyGfx = self.attachAsset('enemy', { anchorX: 0.5, anchorY: 0.5 }); self.hp = 1; self.x = 600; self.y = 2732 / 2; self.isAlive = true; // Animate enemy in self.spawnAnim = function () { self.scaleX = 0.5; self.scaleY = 0.5; tween(self, { scaleX: 1, scaleY: 1 }, { duration: 180, easing: tween.bounceOut }); }; // Animate enemy defeat self.defeatAnim = function (onFinish) { tween(self, { alpha: 0, scaleX: 1.3, scaleY: 1.3 }, { duration: 180, easing: tween.easeIn, onFinish: onFinish }); }; return self; }); // Gold class var Gold = Container.expand(function () { var self = Container.call(this); var goldGfx = self.attachAsset('gold', { anchorX: 0.5, anchorY: 0.5 }); self.x = 600; self.y = 2732 / 2; // Animate gold drop self.dropAnim = function (targetX, targetY, onFinish) { tween(self, { y: targetY }, { duration: 350, easing: tween.bounceOut, onFinish: onFinish }); }; // Animate gold collect self.collectAnim = function (targetX, targetY, onFinish) { tween(self, { x: targetX, y: targetY, scaleX: 0.3, scaleY: 0.3, alpha: 0 }, { duration: 320, easing: tween.cubicIn, onFinish: onFinish }); }; return self; }); // Hero class var Hero = Container.expand(function () { var self = Container.call(this); var heroGfx = self.attachAsset('hero', { anchorX: 0.5, anchorY: 0.5 }); self.level = 1; self.x = 200; self.y = 2732 / 2; // For upgrade animation self.flashUpgrade = function () { tween(self, { scaleX: 1.2, scaleY: 1.2 }, { duration: 120, easing: tween.easeOut, onFinish: function onFinish() { tween(self, { scaleX: 1, scaleY: 1 }, { duration: 120, easing: tween.easeIn }); } }); }; return self; }); /**** * Initialize Game ****/ var game = new LK.Game({ backgroundColor: 0x222a36 }); /**** * Game Code ****/ // Upgrade Button: orange box // Spawn Enemy Button: green box // Gold: yellow ellipse // Enemy: blue ellipse // Hero: red box, left side // --- Map Image (added behind background) --- // Defensive: ensure 'map' asset is initialized as an image in Assets section // Use LK.getAsset with correct parameters and object for anchor/position/size // Defensive: ensure 'map' asset is initialized as an image in Assets section // Use LK.getAsset with correct parameters and object for anchor/position/size function _typeof2(o) { "@babel/helpers - typeof"; return _typeof2 = "function" == typeof Symbol && "symbol" == typeof Symbol.iterator ? function (o) { return typeof o; } : function (o) { return o && "function" == typeof Symbol && o.constructor === Symbol && o !== Symbol.prototype ? "symbol" : typeof o; }, _typeof2(o); } function _typeof(o) { "@babel/helpers - typeof"; return _typeof = "function" == typeof Symbol && "symbol" == typeof Symbol.iterator ? function (o) { return typeof o; } : function (o) { return o && "function" == typeof Symbol && o.constructor === Symbol && o !== Symbol.prototype ? "symbol" : typeof o; }, _typeof(o); } // Defensive: ensure 'map' asset is initialized as an image in Assets section // Add the map image as a background, behind all other elements // Defensive: do not mutate or push to dynamicAssets, just use LK.getAsset directly var mapImg = LK.getAsset('map', { anchorX: 0, anchorY: 0, x: 0, y: 0, width: 2048, height: 2732 }); game.addChild(mapImg); // --- Background Image --- var backgroundImg = LK.getAsset('backgraund', { anchorX: 0, anchorY: 0, x: 0, y: 0, width: 2048, height: 2732 }); game.addChild(backgroundImg); // --- Global State --- var hero = new Hero(); game.addChild(hero); var currentEnemy = null; var golds = []; var goldAmount = 0; var heroLevel = 1; // --- Inventory and Items --- var inventory = []; var equippedItems = { "shield-1": false }; // --- Shield-1 barrier state --- var heroShield1Barrier = 0; // If >0, this is the current shield-1 barrier HP var heroShield1BarrierMax = 0; // Max barrier HP (100 if equipped) // Shield-1 item data var itemData = { "shield-1": { name: "Shield-1", type: "shield", bonusHp: 250, // +250 extra HP atkSpdPenalty: 2, // -2 attack speed buyPrice: 50, sellPrice: 15 } }; // --- GoldBag effect state --- var heroIsImmortal = false; var heroOneShotEnemies = false; // Auto-fight timer handles var heroAutoAttackTimer = null; var enemyAutoAttackTimer = null; // --- GUI Elements --- // Gold display (top right) var goldTxt = new Text2('0', { size: 70, fill: 0xFFE066, glow: { color: 0xffffff, distance: 10, strength: 2 } }); goldTxt.anchor.set(1, 0); // right, top LK.gui.topRight.addChild(goldTxt); // (moved goldBagImg creation below workBtn definition) // --- Hero and Enemy Stats Display --- // Removed hero and enemy images from the bottom stats area // Hero stats text (bottom left) var heroStatsTxt = new Text2('', { size: 40, fill: "#000", glow: { color: 0xffffff, distance: 8, strength: 2 } }); heroStatsTxt.anchor.set(0, 1); heroStatsTxt.x = 30; heroStatsTxt.y = LK.gui.bottom.height - 30; LK.gui.bottomLeft.addChild(heroStatsTxt); // Enemy stats text (bottom right, moved slightly to the right) var enemyStatsTxt = new Text2('', { size: 40, fill: "#000", glow: { color: 0xffffff, distance: 8, strength: 2 } }); enemyStatsTxt.anchor.set(1, 1); // Move enemy stats slightly to the right (less offset from right edge) enemyStatsTxt.x = LK.gui.bottomRight.width - 130; enemyStatsTxt.y = LK.gui.bottom.height - 30; LK.gui.bottomRight.addChild(enemyStatsTxt); // Upgrade button (top center) var upgradeBtn = LK.getAsset('upgradeBtn', { anchorX: 0.5, anchorY: 0.5 }); // Work button (to the right of upgradeBtn) var workBtn = LK.getAsset('workBtn', { anchorX: 0.5, anchorY: 0.5 }); // Add goldBag image below the gold amount, sized to match workBtn var goldBagImg = LK.getAsset('goldBag', { anchorX: 1, anchorY: 0, x: goldTxt.x + 40, //{1F} // Move 40px more to the right y: goldTxt.y + goldTxt.height + 10, // 10px gap below gold text width: workBtn.width, height: workBtn.height }); LK.gui.topRight.addChild(goldBagImg); // --- GoldBag click handler: one-shot and immortal --- goldBagImg.down = function (x, y, obj) { // Toggle immortal/one-shot state heroIsImmortal = !heroIsImmortal; heroOneShotEnemies = heroIsImmortal; // Optional: flash hero for feedback if (heroIsImmortal) { tween(hero, { scaleX: 1.3, scaleY: 1.3, tint: 0xFFD700 }, { duration: 200, easing: tween.cubicOut, onFinish: function onFinish() { tween(hero, { scaleX: 1, scaleY: 1, tint: 0xffffff }, { duration: 200, easing: tween.cubicIn }); } }); } else { // If disabling, reset hero color/scale tween.stop(hero, { tint: true, scaleX: true, scaleY: true }); hero.tint = 0xffffff; hero.scaleX = 1; hero.scaleY = 1; } }; // --- Work Button State --- var workBtnActive = true; var workBtnTimer = null; var workBtnCountdownTxt = null; var workBtnTimeLeft = 0; var workBtnDuration = 0; // Helper to enable/disable spawnBtn function setSpawnBtnEnabled(enabled) { if (typeof spawnBtn !== "undefined" && spawnBtn) { if (enabled) { spawnBtn.alpha = 1; spawnBtn.interactive = true; spawnBtn.buttonMode = true; } else { spawnBtn.alpha = 0.5; spawnBtn.interactive = false; spawnBtn.buttonMode = false; } } } setSpawnBtnEnabled(true); // Helper to show/hide workBtn countdown text function showWorkBtnCountdown(timeLeft) { if (!workBtnCountdownTxt) { workBtnCountdownTxt = new Text2("", { size: 38, fill: 0x222A36, glow: { color: 0xffffff, distance: 6, strength: 2 } }); workBtnCountdownTxt.anchor.set(0.5, 0); workBtnCountdownTxt.x = workBtn.x; workBtnCountdownTxt.y = workBtn.y + workBtn.height / 2 + 8; LK.gui.top.addChild(workBtnCountdownTxt); } workBtnCountdownTxt.visible = true; workBtnCountdownTxt.setText(timeLeft > 0 ? timeLeft.toFixed(1) + "s" : ""); } function hideWorkBtnCountdown() { if (workBtnCountdownTxt) { workBtnCountdownTxt.visible = false; } } // Place upgradeBtn at top center, but not in the top left 100x100 area // Use LK.gui.top (centered horizontally, below top edge) upgradeBtn.x = LK.gui.top.width / 2; upgradeBtn.y = 100 + upgradeBtn.height / 2; // Place workBtn to the right of upgradeBtn, with a small gap workBtn.x = upgradeBtn.x + upgradeBtn.width / 2 + workBtn.width / 2 + 30; workBtn.y = upgradeBtn.y; // Add gold required text under the upgrade button var upgradeCostTxt = new Text2("", { size: 40, fill: 0x222A36, glow: { color: 0xffffff, distance: 8, strength: 2 } }); upgradeCostTxt.anchor.set(0.5, 0); upgradeCostTxt.x = upgradeBtn.x; upgradeCostTxt.y = upgradeBtn.y + upgradeBtn.height / 2 + 10; LK.gui.top.addChild(upgradeCostTxt); LK.gui.top.addChild(upgradeBtn); LK.gui.top.addChild(workBtn); // Spawn enemy button (bottom center) var spawnBtn = LK.getAsset('spawnBtn', { anchorX: 0.5, anchorY: 0.5 }); // Place spawnBtn at bottom center, above the very bottom edge spawnBtn.x = LK.gui.bottom.width / 2; spawnBtn.y = LK.gui.bottom.height - 100 - spawnBtn.height / 2; // Add "find enemy!" text above the spawn button var findEnemyTxt = new Text2("find enemy!", { size: 45, fill: 0x222A36, glow: { color: 0xffffff, distance: 8, strength: 2 } }); findEnemyTxt.anchor.set(0.5, 1); findEnemyTxt.x = spawnBtn.x; findEnemyTxt.y = spawnBtn.y - spawnBtn.height / 2 - 20; LK.gui.bottom.addChild(findEnemyTxt); LK.gui.bottom.addChild(spawnBtn); // Add itemBag image, 10x size, on the left diagonal of spawnBtn so they do not overlap // Set itemBag to width 300px, height 250px, anchor (0.3, 0.20) // Place on the left diagonal of spawnBtn, with a gap so they do not overlap var itemBagWidth = 300; var itemBagHeight = 200; var itemBagAnchorX = 0.3; var itemBagAnchorY = 0.20; var diagonalGap = 40; var diagonalOffset = (spawnBtn.width * (1 - itemBagAnchorX) + itemBagWidth * itemBagAnchorX + diagonalGap) / Math.sqrt(2); var itemBagImg = LK.getAsset('itemBag', { anchorX: itemBagAnchorX, anchorY: itemBagAnchorY, x: spawnBtn.x - diagonalOffset, y: spawnBtn.y - diagonalOffset, width: itemBagWidth, height: itemBagHeight }); LK.gui.bottom.addChild(itemBagImg); // --- Item drop logic on itemBag tap --- itemBagImg.down = function (x, y, obj) { // Animate shrink and grow like other buttons tween.stop(itemBagImg, { scaleX: true, scaleY: true }); tween(itemBagImg, { scaleX: 0.9, scaleY: 0.9 }, { duration: 100, easing: tween.cubicIn, onFinish: function onFinish() { tween(itemBagImg, { scaleX: 1, scaleY: 1 }, { duration: 100, easing: tween.cubicOut, onFinish: function onFinish() { // --- Show 1000x1000 black, 30% transparent window in center --- // Remove previous if exists if (typeof itemBagWindow !== "undefined" && itemBagWindow && itemBagWindow.parent) { itemBagWindow.parent.removeChild(itemBagWindow); itemBagWindow = null; } // Create window container itemBagWindow = new Container(); // Use a box shape for the window background var winWidth = 1000; var winHeight = 1000; var winColor = 0x000000; // black var winAlpha = 0.3; var winBg = LK.getAsset('centerCircle', { width: winWidth, height: winHeight, color: winColor, shape: 'box', anchorX: 0.5, anchorY: 0.5, x: 0, y: 0 }); winBg.alpha = winAlpha; itemBagWindow.addChild(winBg); // Add X close button to top right of window var closeBtnSize = 90; var closeBtn = LK.getAsset('centerCircle', { width: closeBtnSize, height: closeBtnSize, color: 0x222222, shape: 'box', anchorX: 0.5, anchorY: 0.5, x: winWidth / 2 - closeBtnSize / 2 - 20, y: -winHeight / 2 + closeBtnSize / 2 + 20 }); closeBtn.alpha = 0.7; itemBagWindow.addChild(closeBtn); // Add X text on top of closeBtn var closeBtnTxt = new Text2("X", { size: 60, fill: "#fff" }); closeBtnTxt.anchor.set(0.5, 0.5); closeBtnTxt.x = closeBtn.x; closeBtnTxt.y = closeBtn.y; itemBagWindow.addChild(closeBtnTxt); // --- Hide spawnBtn, workBtn, upgradeBtn, and all on-screen texts when X is visible --- spawnBtn.visible = false; workBtn.visible = false; upgradeBtn.visible = false; findEnemyTxt.visible = false; upgradeCostTxt.visible = false; heroStatsTxt.visible = false; enemyStatsTxt.visible = false; goldTxt.visible = false; goldBagImg.visible = false; if (workBtnCountdownTxt) workBtnCountdownTxt.visible = false; // Close logic for X button closeBtn.down = function () { if (itemBagWindow && itemBagWindow.parent) { itemBagWindow.parent.removeChild(itemBagWindow); itemBagWindow = null; } // Restore all UI elements spawnBtn.visible = true; workBtn.visible = true; upgradeBtn.visible = true; findEnemyTxt.visible = true; upgradeCostTxt.visible = true; heroStatsTxt.visible = true; enemyStatsTxt.visible = true; goldTxt.visible = true; goldBagImg.visible = true; if (workBtnCountdownTxt && workBtnActive === false && workBtnTimeLeft > 0) { workBtnCountdownTxt.visible = true; } }; // Center in game area itemBagWindow.x = 2048 / 2; itemBagWindow.y = 2732 / 2; // Add to game game.addChild(itemBagWindow); // (Removed: close window on tap anywhere on window. Now only X closes the window) // --- Inventory UI inside itemBagWindow --- // Remove any previous inventory UI if (typeof itemBagInventoryUI !== "undefined" && itemBagInventoryUI && itemBagInventoryUI.parent) { itemBagInventoryUI.parent.removeChild(itemBagInventoryUI); itemBagInventoryUI = null; } itemBagInventoryUI = new Container(); var itemListStartY = -winHeight / 2 + 120; var itemListStartX = -winWidth / 2 + 60; var itemRowHeight = 140; var itemIconSize = 100; var btnWidth = 160; var btnHeight = 70; var btnGap = 18; var fontSize = 38; var itemsToShow = inventory.length; if (itemsToShow === 0) { var noItemTxt = new Text2("Envanter boş!", { size: 48, fill: "#fff" }); noItemTxt.anchor.set(0.5, 0.5); noItemTxt.x = 0; noItemTxt.y = 0; itemBagInventoryUI.addChild(noItemTxt); } else { // Build a count of each item in inventory, and track equipped index for each type var itemStacks = []; var itemStackMap = {}; // itemId -> array of {idx, equipped} for (var i = 0; i < inventory.length; i++) { var id = inventory[i]; if (!itemStackMap[id]) itemStackMap[id] = []; // If this is equipped and not already marked, mark as equipped var isEquipped = false; if (equippedItems[id]) { // Only one equipped per type, so only one stack can be equipped var alreadyEquipped = false; for (var s = 0; s < itemStackMap[id].length; s++) { if (itemStackMap[id][s].equipped) alreadyEquipped = true; } if (!alreadyEquipped) isEquipped = true; } itemStackMap[id].push({ idx: i, equipped: isEquipped }); } // Now, for each item type, create a stack for equipped and for each group of unequipped var stackRows = []; for (var id in itemStackMap) { var stack = itemStackMap[id]; // Find equipped var equippedIdx = -1; for (var s = 0; s < stack.length; s++) { if (stack[s].equipped) equippedIdx = stack[s].idx; } if (equippedIdx !== -1) { stackRows.push({ itemId: id, count: 1, equipped: true, indices: [equippedIdx] }); } // Now, group unequipped into stacks (could be more than one stack if equipped is in the middle) var unequippedIndices = []; for (var s = 0; s < stack.length; s++) { if (!stack[s].equipped) unequippedIndices.push(stack[s].idx); } if (unequippedIndices.length > 0) { // Group all unequipped as one stack stackRows.push({ itemId: id, count: unequippedIndices.length, equipped: false, indices: unequippedIndices }); } } // Now, render each stackRow as a separate row in the inventory UI for (var row = 0; row < stackRows.length; row++) { (function (rowIdx) { var stack = stackRows[rowIdx]; var itemId = stack.itemId; var data = itemData[itemId]; var count = stack.count; var isEquipped = stack.equipped; // Icon var icon = LK.getAsset(itemId, { anchorX: 0.5, anchorY: 0.5, x: itemListStartX + itemIconSize / 2, y: itemListStartY + (rowIdx + 1) * itemRowHeight - itemRowHeight + itemIconSize / 2, width: itemIconSize, height: itemIconSize }); itemBagInventoryUI.addChild(icon); // Name and stats, show count and equipped var labelText = data ? data.name : itemId; if (count > 1) labelText += " x" + count; if (isEquipped) labelText += " (Kuşanıldı)"; var label = new Text2(labelText, { size: fontSize, fill: "#fff" }); label.anchor.set(0, 0.5); label.x = icon.x + itemIconSize / 2 + 20; label.y = icon.y; itemBagInventoryUI.addChild(label); // Equip/Remove button (only for equipped or first unequipped stack) var showEquipBtn = !isEquipped && !equippedItems[itemId] || isEquipped; var equipBtn = LK.getAsset('centerCircle', { width: btnWidth, height: btnHeight, color: 0x2a6bde, shape: 'box', anchorX: 0.5, anchorY: 0.5, x: label.x + 340, y: icon.y }); equipBtn.alpha = 0.85; itemBagInventoryUI.addChild(equipBtn); var equipTxt = new Text2(isEquipped ? "Çıkart" : "Kuşan", { size: fontSize, fill: "#fff" }); equipTxt.anchor.set(0.5, 0.5); equipTxt.x = equipBtn.x; equipTxt.y = equipBtn.y; itemBagInventoryUI.addChild(equipTxt); equipBtn.visible = showEquipBtn; equipTxt.visible = showEquipBtn; // Equip/Remove logic equipBtn.down = function () { if (!isEquipped) { // Equip this stack (first unequipped) // Unequip all of this type for (var k in equippedItems) { if (itemData[k] && itemData[k].type === data.type) { equippedItems[k] = false; if (typeof hero.equippedVisuals !== "undefined" && hero.equippedVisuals[k]) { hero.removeChild(hero.equippedVisuals[k]); hero.equippedVisuals[k].destroy(); hero.equippedVisuals[k] = null; } } } equippedItems[itemId] = true; // Show item visual on hero (front) if (typeof hero.equippedVisuals === "undefined") hero.equippedVisuals = {}; if (hero.equippedVisuals[itemId]) { hero.removeChild(hero.equippedVisuals[itemId]); hero.equippedVisuals[itemId].destroy(); hero.equippedVisuals[itemId] = null; } var equipVisual; if (itemId === "shield-1") { equipVisual = LK.getAsset(itemId, { anchorX: 0.5, anchorY: 0.5, x: 240, // Move shield visual even further right on hero's body y: 0, width: 300, height: 698 }); } else { equipVisual = LK.getAsset(itemId, { anchorX: 0.5, anchorY: 0.5, x: 90, y: 0, width: 320, height: 320 }); } hero.addChild(equipVisual); hero.equippedVisuals[itemId] = equipVisual; // Shield-1 barrier logic if (itemId === "shield-1") { heroShield1Barrier = 250; heroShield1BarrierMax = 250; } var stats = getStatsForLevel(heroLevel, "hero"); hero.attack = stats.atk; hero.attackSpeed = stats.atkSpd; hero.currentHp = stats.hp; updateHeroStatsDisplay(); if (itemBagWindow && itemBagWindow.parent) { itemBagWindow.parent.removeChild(itemBagWindow); itemBagWindow = null; } itemBagImg.down(); } else { // Remove (Çıkart) equippedItems[itemId] = false; if (typeof hero.equippedVisuals !== "undefined" && hero.equippedVisuals[itemId]) { hero.removeChild(hero.equippedVisuals[itemId]); hero.equippedVisuals[itemId].destroy(); hero.equippedVisuals[itemId] = null; } if (itemId === "shield-1") { heroShield1Barrier = 0; heroShield1BarrierMax = 0; } var stats = getStatsForLevel(heroLevel, "hero"); hero.attack = stats.atk; hero.attackSpeed = stats.atkSpd; hero.currentHp = stats.hp; updateHeroStatsDisplay(); if (itemBagWindow && itemBagWindow.parent) { itemBagWindow.parent.removeChild(itemBagWindow); itemBagWindow = null; } itemBagImg.down(); } }; // Sell button var sellBtn = LK.getAsset('centerCircle', { width: btnWidth, height: btnHeight, color: 0x8B4513, shape: 'box', anchorX: 0.5, anchorY: 0.5, x: equipBtn.x + btnWidth + btnGap, y: icon.y }); sellBtn.alpha = 0.85; itemBagInventoryUI.addChild(sellBtn); var sellTxt = new Text2("Sat (" + (data ? data.sellPrice : 1) + ")", { size: fontSize, fill: "#fff" }); sellTxt.anchor.set(0.5, 0.5); sellTxt.x = sellBtn.x; sellTxt.y = sellBtn.y; itemBagInventoryUI.addChild(sellTxt); // Sell logic sellBtn.down = function () { // If this stack is equipped, and only one exists, prevent selling if (isEquipped && count === 1) { tween(sellBtn, { scaleX: 1.15, scaleY: 1.15, tint: 0xff4444 }, { duration: 120, onFinish: function onFinish() { tween(sellBtn, { scaleX: 1, scaleY: 1, tint: 0x8B4513 }, { duration: 120 }); } }); return; } // Remove one from inventory (from this stack) var removedIdx = -1; for (var j = 0; j < stack.indices.length; j++) { var idx = stack.indices[j]; // If equipped, skip if this is the equipped one if (isEquipped && count === 1) continue; removedIdx = idx; break; } if (removedIdx === -1) { // Defensive: fallback to remove first found for (var j = 0; j < inventory.length; j++) { if (inventory[j] === itemId && (!isEquipped || isEquipped && count > 1)) { removedIdx = j; break; } } } if (removedIdx !== -1) { inventory.splice(removedIdx, 1); } // If equipped and now zero remain, unequip var stillHas = false; for (var j = 0; j < inventory.length; j++) { if (inventory[j] === itemId) stillHas = true; } if (isEquipped && !stillHas) { equippedItems[itemId] = false; var stats = getStatsForLevel(heroLevel, "hero"); hero.attack = stats.atk; hero.attackSpeed = stats.atkSpd; hero.currentHp = stats.hp; updateHeroStatsDisplay(); } // Add gold goldAmount += data ? data.sellPrice : 1; updateGoldDisplay(); // Animate gold to gold counter var goldDisplayGlobal = LK.gui.topRight.toGlobal({ x: goldTxt.x, y: goldTxt.y }); var goldDisplayGamePos = game.toLocal(goldDisplayGlobal); var goldAnim = LK.getAsset('gold', { anchorX: 0.5, anchorY: 0.5, x: sellBtn.x + itemBagWindow.x, y: sellBtn.y + itemBagWindow.y, width: 80, height: 54 }); game.addChild(goldAnim); tween(goldAnim, { x: goldDisplayGamePos.x, y: goldDisplayGamePos.y, scaleX: 0.3, scaleY: 0.3, alpha: 0 }, { duration: 420, easing: tween.cubicIn, onFinish: function onFinish() { goldAnim.destroy(); } }); // Refresh inventory UI if (itemBagWindow && itemBagWindow.parent) { itemBagWindow.parent.removeChild(itemBagWindow); itemBagWindow = null; } itemBagImg.down(); }; })(row); } } itemBagInventoryUI.x = 0; itemBagInventoryUI.y = 0; itemBagWindow.addChild(itemBagInventoryUI); // --- (Keep original drop animation for demo) --- var dropX = itemBagImg.x; var dropY = 0 - itemBagHeight; // Start above screen var item = LK.getAsset('gold', { anchorX: 0.5, anchorY: 0.5, x: dropX, y: dropY, width: 150, height: 102 }); game.addChild(item); // Animate drop to itemBag tween(item, { x: itemBagImg.x, y: itemBagImg.y }, { duration: 600, easing: tween.bounceOut, onFinish: function onFinish() { // Optionally: fade out and destroy after reaching itemBag tween(item, { alpha: 0 }, { duration: 250, onFinish: function onFinish() { item.destroy(); } }); } }); } }); } }); }; // --- Helper Functions --- function updateGoldDisplay() { goldTxt.setText(goldAmount); } function updateUpgradeDisplay() { // Show gold required to upgrade under the upgrade button if (typeof upgradeCostTxt !== "undefined") { var nextUpgradeCost = heroLevel * 5; upgradeCostTxt.setText("Gold required: " + nextUpgradeCost); } } // Update hero stats display function updateHeroStatsDisplay() { var stats = getStatsForLevel(heroLevel, "hero"); var hpDisplay = typeof hero.currentHp === "number" ? Math.max(0, Math.round(hero.currentHp)) + " / " + stats.hp : stats.hp; var barrierDisplay = ""; if (equippedItems && equippedItems["shield-1"] && heroShield1Barrier > 0) { barrierDisplay = " +" + heroShield1Barrier; } heroStatsTxt.setText("Hero\n" + "Level: " + heroLevel + "\n" + "Health: " + hpDisplay + barrierDisplay + "\n" + "Attack: " + stats.atk + "\n" + "Atk Spd: " + stats.atkSpd); } // Update enemy stats display function updateEnemyStatsDisplay() { if (currentEnemy && currentEnemy.isAlive) { var enemyLevel = currentEnemy.level || heroLevel; var stats = getStatsForLevel(enemyLevel, "enemy"); var hpDisplay = typeof currentEnemy.hp === "number" ? Math.max(0, Math.round(currentEnemy.hp)) + " / " + stats.hp : stats.hp; enemyStatsTxt.setText("Enemy\n" + "Level: " + enemyLevel + "\n" + "Health: " + hpDisplay + "\n" + "Attack: " + currentEnemy.attack + "\n" + "Atk Spd: " + currentEnemy.attackSpeed); } else { enemyStatsTxt.setText("Enemy\n-"); } } // --- Game Logic --- // Spawn enemy logic // Enemy stat table for levels 1-10 (fixed stats) var enemyStatsByLevel = [ // Level 1 { hp: 60, atk: 10, atkSpd: 2 }, // Level 2 { hp: 95, atk: 14, atkSpd: 3 }, // Level 3 { hp: 130, atk: 18, atkSpd: 4 }, // Level 4 { hp: 170, atk: 23, atkSpd: 4 }, // Level 5 { hp: 215, atk: 27, atkSpd: 5 }, // Level 6 { hp: 265, atk: 32, atkSpd: 6 }, // Level 7 { hp: 320, atk: 38, atkSpd: 6 }, // Level 8 { hp: 380, atk: 45, atkSpd: 7 }, // Level 9 { hp: 445, atk: 53, atkSpd: 8 }, // Level 10 { hp: 515, atk: 62, atkSpd: 9 }]; // --- Stat scaling logic for hero and enemy after level 10 --- // Store hero stat growth per level (compounded) and enemy stat growth per level (linear from level 10 base) var heroStatGrowth = { hp: [enemyStatsByLevel[9].hp], atk: [enemyStatsByLevel[9].atk], atkSpd: [enemyStatsByLevel[9].atkSpd] }; var enemyStatGrowth = { hp: [enemyStatsByLevel[9].hp], atk: [enemyStatsByLevel[9].atk], atkSpd: [enemyStatsByLevel[9].atkSpd] }; // Store enemy stat percent increases per level (for linear scaling) var enemyStatPercents = { hp: [], atk: [], atkSpd: [] }; // Store hero stat percent increases per level (for compounded scaling) var heroStatPercents = { hp: [], atk: [], atkSpd: [] }; // Helper to get random percent between 5% and 20% function randomPercent() { return 0.05 + Math.random() * 0.15; } // Precompute stat growth up to a reasonable max level (e.g. 100) function ensureStatGrowthUpTo(level) { var maxComputed = heroStatGrowth.hp.length + 9; // since index 0 is level 10 for (var lvl = maxComputed + 1; lvl <= level; lvl++) { // HERO: compounded var prevHeroHp = heroStatGrowth.hp[heroStatGrowth.hp.length - 1]; var prevHeroAtk = heroStatGrowth.atk[heroStatGrowth.atk.length - 1]; var prevHeroAtkSpd = heroStatGrowth.atkSpd[heroStatGrowth.atkSpd.length - 1]; var heroHpPct = randomPercent(); var heroAtkPct = randomPercent(); // After level 15, hero attack speed no longer increases var heroAtkSpdPct = lvl > 15 ? 0 : randomPercent(); heroStatPercents.hp.push(heroHpPct); heroStatPercents.atk.push(heroAtkPct); heroStatPercents.atkSpd.push(heroAtkSpdPct); heroStatGrowth.hp.push(Math.round(prevHeroHp * (1 + heroHpPct))); heroStatGrowth.atk.push(Math.round(prevHeroAtk * (1 + heroAtkPct))); heroStatGrowth.atkSpd.push(Math.round(prevHeroAtkSpd * (1 + heroAtkSpdPct))); // ENEMY: linear from level 10 base var baseHp = enemyStatsByLevel[9].hp; var baseAtk = enemyStatsByLevel[9].atk; var baseAtkSpd = enemyStatsByLevel[9].atkSpd; var enemyHpPct = randomPercent(); var enemyAtkPct = randomPercent(); // After level 15, enemy attack speed no longer increases var enemyAtkSpdPct = lvl > 15 ? 0 : randomPercent(); enemyStatPercents.hp.push(enemyHpPct); enemyStatPercents.atk.push(enemyAtkPct); enemyStatPercents.atkSpd.push(enemyAtkSpdPct); // For linear, sum all previous percent increases var totalHpPct = 0; var totalAtkPct = 0; var totalAtkSpdPct = 0; for (var i = 0; i < enemyStatPercents.hp.length; i++) totalHpPct += enemyStatPercents.hp[i]; for (var i = 0; i < enemyStatPercents.atk.length; i++) totalAtkPct += enemyStatPercents.atk[i]; for (var i = 0; i < enemyStatPercents.atkSpd.length; i++) totalAtkSpdPct += enemyStatPercents.atkSpd[i]; // After level 20, enemy health increases by an additional 50% per level var extraHpMultiplier = 1; if (lvl > 20) { // For each level above 20, multiply by 1.5 for each extra level extraHpMultiplier = Math.pow(1.5, lvl - 20); } enemyStatGrowth.hp.push(Math.round(baseHp * (1 + totalHpPct) * extraHpMultiplier)); enemyStatGrowth.atk.push(Math.round(baseAtk * (1 + totalAtkPct))); enemyStatGrowth.atkSpd.push(Math.round(baseAtkSpd * (1 + totalAtkSpdPct))); } } // Returns stats for a given level and type ('hero' or 'enemy') function getStatsForLevel(level, type) { if (level <= 10) { var idx = Math.max(0, Math.min(enemyStatsByLevel.length - 1, level - 1)); var stats = enemyStatsByLevel[idx]; return { hp: stats.hp, atk: stats.atk, atkSpd: stats.atkSpd }; } ensureStatGrowthUpTo(level); var idx = level - 10; if (type === "hero") { // Base stats var baseHp = heroStatGrowth.hp[idx]; var baseAtk = heroStatGrowth.atk[idx]; var baseAtkSpd = heroStatGrowth.atkSpd[idx]; // Apply shield-1 effects if equipped if (equippedItems && equippedItems["shield-1"]) { baseHp += itemData["shield-1"].bonusHp; baseAtkSpd = Math.max(1, baseAtkSpd - itemData["shield-1"].atkSpdPenalty); } return { hp: baseHp, atk: baseAtk, atkSpd: baseAtkSpd }; } else { return { hp: enemyStatGrowth.hp[idx], atk: enemyStatGrowth.atk[idx], atkSpd: enemyStatGrowth.atkSpd[idx] }; } } // For backward compatibility, keep getEnemyStatsForLevel for hero stats (used in refill, etc) function getEnemyStatsForLevel(level) { return getStatsForLevel(level, "hero"); } function spawnEnemy() { if (currentEnemy && currentEnemy.isAlive) return; // Only one at a time if (typeof hero.healthRefilling !== "undefined" && hero.healthRefilling) return; // Don't allow spawn while refilling var enemy = new Enemy(); // Place enemy on the far right, vertically aligned with hero enemy.x = 2048 - 200; // 200px from the right edge, matching hero's 200px from left enemy.y = hero.y; // Determine enemy level: always one of heroLevel-1, heroLevel, heroLevel+1, or heroLevel+2 var possibleLevels = [Math.max(1, heroLevel - 1), heroLevel, heroLevel + 1, heroLevel + 2]; // Remove duplicates and clamp to at least 1 var uniqueLevels = []; for (var i = 0; i < possibleLevels.length; i++) { var lvl = Math.max(1, possibleLevels[i]); if (uniqueLevels.indexOf(lvl) === -1) uniqueLevels.push(lvl); } // Randomly pick one var enemyLevel = uniqueLevels[Math.floor(Math.random() * uniqueLevels.length)]; // Clamp enemyLevel to valid range for stats table enemyLevel = Math.max(1, Math.min(enemyStatsByLevel.length + heroStatGrowth.hp.length, enemyLevel)); // Get stats for this level (enemy uses linear scaling after level 10) var stats = getStatsForLevel(enemyLevel, "enemy"); enemy.hp = stats.hp; enemy.attack = stats.atk; enemy.attackSpeed = stats.atkSpd; enemy.isAlive = true; enemy.spawnAnim(); game.addChild(enemy); currentEnemy = enemy; // Store the enemy's level for display currentEnemy.level = enemyLevel; updateEnemyStatsDisplay(); // --- Start auto-fight timers --- if (typeof heroAutoAttackTimer !== "undefined" && heroAutoAttackTimer) { LK.clearInterval(heroAutoAttackTimer); heroAutoAttackTimer = null; } if (typeof enemyAutoAttackTimer !== "undefined" && enemyAutoAttackTimer) { LK.clearInterval(enemyAutoAttackTimer); enemyAutoAttackTimer = null; } // Get hero stats for this level (hero uses compounded scaling after level 10) var heroStats = getStatsForLevel(heroLevel, "hero"); hero.currentHp = heroStats.hp; hero.attack = heroStats.atk; hero.attackSpeed = heroStats.atkSpd; // Hero attacks enemy heroAutoAttackTimer = LK.setInterval(function () { if (!currentEnemy || !currentEnemy.isAlive) { LK.clearInterval(heroAutoAttackTimer); heroAutoAttackTimer = null; return; } // If one-shot mode, instantly defeat enemy if (heroOneShotEnemies) { currentEnemy.hp = 0; } else { currentEnemy.hp -= hero.attack; } // Flash enemy if (currentEnemy) { tween(currentEnemy, { tint: 0xffffff }, { duration: 60, onFinish: function onFinish() { if (currentEnemy) { tween(currentEnemy, { tint: 0x2a6bde }, { duration: 60 }); } } }); } updateEnemyStatsDisplay(); if (currentEnemy.hp <= 0) { defeatEnemy(); LK.clearInterval(heroAutoAttackTimer); heroAutoAttackTimer = null; LK.clearInterval(enemyAutoAttackTimer); enemyAutoAttackTimer = null; } }, 1000 / hero.attackSpeed); // Enemy attacks hero enemyAutoAttackTimer = LK.setInterval(function () { if (!currentEnemy || !currentEnemy.isAlive) { LK.clearInterval(enemyAutoAttackTimer); enemyAutoAttackTimer = null; return; } if (typeof hero.currentHp !== "number") { // Defensive: ensure hero.currentHp is set var hStats = getEnemyStatsForLevel(heroLevel); hero.currentHp = hStats.hp; } if (!heroIsImmortal) { // --- Shield-1 barrier logic: block damage with barrier first --- if (equippedItems && equippedItems["shield-1"] && heroShield1Barrier > 0) { var dmg = currentEnemy.attack; if (heroShield1Barrier >= dmg) { heroShield1Barrier -= dmg; dmg = 0; } else { dmg -= heroShield1Barrier; heroShield1Barrier = 0; } if (dmg > 0) { hero.currentHp -= dmg; } } else { hero.currentHp -= currentEnemy.attack; } // Flash hero tween(hero, { tint: 0xffffff }, { duration: 60, onFinish: function onFinish() { tween(hero, { tint: 0xd83318 }, { duration: 60 }); } }); updateHeroStatsDisplay(); if (hero.currentHp <= 0) { // Hero defeated, stop fighting hero.currentHp = 0; updateHeroStatsDisplay(); // Optionally, you can add defeat logic here (e.g. show game over) LK.clearInterval(heroAutoAttackTimer); heroAutoAttackTimer = null; LK.clearInterval(enemyAutoAttackTimer); enemyAutoAttackTimer = null; // Start health refill (handled in update loop) hero.healthRefilling = true; // Set to true so refill starts immediately } } else { // If immortal, keep HP at max and flash gold var stats = getStatsForLevel(heroLevel, "hero"); hero.currentHp = stats.hp; updateHeroStatsDisplay(); tween(hero, { tint: 0xFFD700 }, { duration: 60, onFinish: function onFinish() { tween(hero, { tint: 0xffffff }, { duration: 60 }); } }); } }, 1000 / currentEnemy.attackSpeed); } // Defeat enemy logic function defeatEnemy() { if (!currentEnemy || !currentEnemy.isAlive) return; currentEnemy.isAlive = false; // Stop auto-fight timers if (typeof heroAutoAttackTimer !== "undefined" && heroAutoAttackTimer) { LK.clearInterval(heroAutoAttackTimer); heroAutoAttackTimer = null; } if (typeof enemyAutoAttackTimer !== "undefined" && enemyAutoAttackTimer) { LK.clearInterval(enemyAutoAttackTimer); enemyAutoAttackTimer = null; } currentEnemy.defeatAnim(function () { if (currentEnemy) { // --- Shield-1 drop logic --- // Only drop for enemy levels 1-10 if (typeof currentEnemy.level !== "undefined" && currentEnemy.level >= 1 && currentEnemy.level <= 10) { // 5% per level, capped at 50% for level 10 var shieldDropChance = currentEnemy.level * 0.05; if (Math.random() < shieldDropChance) { // Drop shield-1 at enemy's position var shieldAsset = LK.getAsset('shield-1', { anchorX: 0.5, anchorY: 0.5, x: currentEnemy.x, y: currentEnemy.y }); game.addChild(shieldAsset); // Animate drop: move to itemBag position and shrink/fade out // Get itemBag position in game coordinates var itemBagGlobal = LK.gui.bottom.toGlobal({ x: itemBagImg.x, y: itemBagImg.y }); var itemBagGamePos = game.toLocal(itemBagGlobal); tween(shieldAsset, { x: itemBagGamePos.x, y: itemBagGamePos.y, scaleX: 0.3, scaleY: 0.3, alpha: 0.2 }, { duration: 700, easing: tween.cubicIn, onFinish: function onFinish() { shieldAsset.destroy(); // Add to inventory only (do not auto-equip) inventory.push("shield-1"); } }); } } currentEnemy.destroy(); currentEnemy = null; updateEnemyStatsDisplay(); // If shield-1 is equipped, restore hero HP to new max after enemy defeat if (equippedItems["shield-1"]) { heroShield1Barrier = 250; heroShield1BarrierMax = 250; var stats = getStatsForLevel(heroLevel, "hero"); hero.currentHp = stats.hp; updateHeroStatsDisplay(); } } // --- spawnBtn rotation logic on enemy defeat --- // If hero HP is full, rotate 45deg right, else 90deg left var heroStats = getStatsForLevel(heroLevel, "hero"); if (typeof hero.currentHp === "number" && hero.currentHp >= heroStats.hp) { // Full health: rotate 45deg right spawnBtnRotationQueue.push({ angle: Math.PI / 4 }); } else { // Not full: rotate 90deg left spawnBtnRotationQueue.push({ angle: -Math.PI / 2, onFinish: function onFinish() { // After 90deg left, if hero HP is still not full and no enemy, resume right rotation if ((!currentEnemy || !currentEnemy.isAlive) && typeof hero.currentHp === "number" && hero.currentHp < getStatsForLevel(heroLevel, "hero").hp) { if (!spawnBtn._rotatingWhileNotFull) { spawnBtn._rotatingWhileNotFull = true; // Start continuous right rotation (1 full spin per 1.2s) var _rotateRightLoop = function rotateRightLoop() { if (!spawnBtn._rotatingWhileNotFull) return; var startRot = spawnBtn.rotation; var endRot = startRot + Math.PI * 2; tween(spawnBtn, { rotation: endRot }, { duration: 1200, easing: tween.linear, onFinish: function onFinish() { if (spawnBtn._rotatingWhileNotFull && (!currentEnemy || !currentEnemy.isAlive)) { _rotateRightLoop(); } } }); }; tween.stop(spawnBtn, { rotation: true }); _rotateRightLoop(); } } } }); } processSpawnBtnRotationQueue(); // Start hero health refill after any battle (at same refill speed as on defeat) // If hero died (currentHp <= 0), double the refill duration if (typeof hero.currentHp === "number" && hero.currentHp < getStatsForLevel(heroLevel, "hero").hp && !hero.healthRefilling) { hero.healthRefilling = true; hero.refillStartTime = Date.now(); var stats = getStatsForLevel(heroLevel, "hero"); hero.refillFromHp = typeof hero.currentHp === "number" ? hero.currentHp : 0; hero.refillToHp = stats.hp; // Set refill duration to 1/5th speed (5x slower), or 1/10th (10x slower) if hero died var baseDuration = 1000; // original duration if (typeof hero.currentHp === "number" && hero.currentHp <= 0) { hero.refillDuration = baseDuration * 10; // 10x slower if died } else { hero.refillDuration = baseDuration * 5; // 5x slower otherwise } } }); // --- Gold drop amount table per level --- var goldDropByLevel = [{ min: 1, max: 5 }, // Level 1 { min: 3, max: 8 }, // Level 2 { min: 5, max: 12 }, // Level 3 { min: 8, max: 16 }, // Level 4 { min: 12, max: 20 }, // Level 5 { min: 15, max: 25 }, // Level 6 { min: 20, max: 32 }, // Level 7 { min: 25, max: 40 }, // Level 8 { min: 30, max: 48 }, // Level 9 { min: 35, max: 60 } // Level 10 ]; // Determine gold drop amount for this level var goldDropStats = goldDropByLevel[Math.max(0, Math.min(heroLevel - 1, goldDropByLevel.length - 1))]; var goldDropAmount = Math.floor(Math.random() * (goldDropStats.max - goldDropStats.min + 1)) + goldDropStats.min; // Drop gold at the far right (where enemy was) // Animate and auto-collect gold after enemy defeat // Calculate the gold display position in game coordinates (top right, where goldTxt is) var goldDisplayGlobal = LK.gui.topRight.toGlobal({ x: goldTxt.x, y: goldTxt.y }); var goldDisplayGamePos = game.toLocal(goldDisplayGlobal); for (var i = 0; i < goldDropAmount; i++) { var gold = new Gold(); gold.x = 2048 - 200; gold.y = hero.y; gold.scaleX = 1; gold.scaleY = 1; gold.alpha = 1; game.addChild(gold); golds.push(gold); // Drop to random y near enemy var dropY = gold.y + (Math.random() * 120 - 60); (function (goldObj, idx) { goldObj.dropAnim(goldObj.x, dropY, function () { // Stagger collection for nice effect LK.setTimeout(function () { // Animate to gold display and collect goldObj.collectAnim(goldDisplayGamePos.x, goldDisplayGamePos.y, function () { goldAmount += 1; updateGoldDisplay(); goldObj.destroy(); }); }, 80 * idx); }); })(gold, i); } // --- Shield-1 drop logic --- // Only drop for enemy levels 1-10 if (typeof enemyLevel !== "undefined" && enemyLevel >= 1 && enemyLevel <= 10) { // 5% per level, capped at 50% for level 10 var shieldDropChance = enemyLevel * 0.05; if (Math.random() < shieldDropChance) { // Drop shield-1 at enemy's position var shieldAsset = LK.getAsset('shield-1', { anchorX: 0.5, anchorY: 0.5, x: 2048 - 200, y: hero.y }); game.addChild(shieldAsset); // Animate drop (fall down a bit, then fade out) var dropTargetY = shieldAsset.y + 180 + (Math.random() * 40 - 20); tween(shieldAsset, { y: dropTargetY }, { duration: 400, easing: tween.bounceOut, onFinish: function onFinish() { // After a short delay, fade out and destroy LK.setTimeout(function () { tween(shieldAsset, { alpha: 0 }, { duration: 350, onFinish: function onFinish() { shieldAsset.destroy(); } }); }, 900); } }); } } } // --- Shield-1 drop logic --- // Only drop for enemy levels 1-10 if (typeof enemyLevel !== "undefined" && enemyLevel >= 1 && enemyLevel <= 10) { // 5% per level, capped at 50% for level 10 var shieldDropChance = enemyLevel * 0.05; if (Math.random() < shieldDropChance) { // Drop shield-1 at enemy's position var shieldAsset = LK.getAsset('shield-1', { anchorX: 0.5, anchorY: 0.5, x: 2048 - 200, y: hero.y }); game.addChild(shieldAsset); // Animate drop (fall down a bit, then fade out) var dropTargetY = shieldAsset.y + 180 + (Math.random() * 40 - 20); tween(shieldAsset, { y: dropTargetY }, { duration: 400, easing: tween.bounceOut, onFinish: function onFinish() { // After a short delay, fade out and destroy LK.setTimeout(function () { tween(shieldAsset, { alpha: 0 }, { duration: 350, onFinish: function onFinish() { shieldAsset.destroy(); } }); }, 900); } }); } } // Collect gold logic function collectGold(gold) { // Animate to gold display var guiGoldPos = LK.gui.topRight.toLocal(gold.toGlobal({ x: 0, y: 0 })); goldAmount += 1; updateGoldDisplay(); gold.collectAnim(guiGoldPos.x, guiGoldPos.y, function () { gold.destroy(); }); } // Upgrade logic function upgradeHero() { var upgradeCost = heroLevel * 5; if (goldAmount < upgradeCost) { // Flash gold text red tween(goldTxt, { tint: 0xff4444 }, { duration: 120, onFinish: function onFinish() { tween(goldTxt, { tint: 0xFFE066 }, { duration: 120 }); } }); return; } goldAmount -= upgradeCost; heroLevel += 1; hero.level = heroLevel; // Refill hero health to full on level up var stats = getStatsForLevel(heroLevel, "hero"); hero.attack = stats.atk; hero.attackSpeed = stats.atkSpd; hero.currentHp = stats.hp; hero.healthRefilling = false; updateGoldDisplay(); updateUpgradeDisplay(); updateHeroStatsDisplay(); updateEnemyStatsDisplay(); hero.flashUpgrade(); } // --- Event Handlers --- // Spawn button tap // --- spawnBtn rotation queue state --- var spawnBtnRotationQueue = []; var spawnBtnIsRotating = false; function processSpawnBtnRotationQueue() { if (spawnBtnIsRotating || spawnBtnRotationQueue.length === 0) return; spawnBtnIsRotating = true; var next = spawnBtnRotationQueue.shift(); var targetRotation = spawnBtn.rotation + next.angle; tween(spawnBtn, { rotation: targetRotation }, { duration: 180, easing: tween.cubicInOut, onFinish: function onFinish() { spawnBtn.rotation = targetRotation; spawnBtnIsRotating = false; if (typeof next.onFinish === "function") next.onFinish(); // Process next in queue processSpawnBtnRotationQueue(); } }); } spawnBtn.down = function (x, y, obj) { // Animate shrink tween.stop(spawnBtn, { scaleX: true, scaleY: true }); tween(spawnBtn, { scaleX: 0.9, scaleY: 0.9 }, { duration: 100, easing: tween.cubicIn, onFinish: function onFinish() { tween(spawnBtn, { scaleX: 1, scaleY: 1 }, { duration: 100, easing: tween.cubicOut, onFinish: function onFinish() { // After animation, trigger action // Queue 45deg right rotation spawnBtnRotationQueue.push({ angle: Math.PI / 4 }); processSpawnBtnRotationQueue(); spawnEnemy(); } }); } }); }; // Enemy tap (defeat) function onEnemyDown(x, y, obj) { if (!currentEnemy || !currentEnemy.isAlive) return; currentEnemy.hp -= 1; // Flash enemy tween(currentEnemy, { tint: 0xffffff }, { duration: 60, onFinish: function onFinish() { tween(currentEnemy, { tint: 0x2a6bde }, { duration: 60 }); } }); if (currentEnemy.hp <= 0) { defeatEnemy(); } } // Gold tap (collect) function onGoldDown(x, y, obj) { for (var i = golds.length - 1; i >= 0; i--) { var gold = golds[i]; if (gold && gold.containsPoint && gold.containsPoint({ x: x, y: y })) { collectGold(gold); golds.splice(i, 1); break; } } } // Upgrade button tap upgradeBtn.down = function (x, y, obj) { // Animate shrink tween.stop(upgradeBtn, { scaleX: true, scaleY: true }); tween(upgradeBtn, { scaleX: 0.9, scaleY: 0.9 }, { duration: 100, easing: tween.cubicIn, onFinish: function onFinish() { tween(upgradeBtn, { scaleX: 1, scaleY: 1 }, { duration: 100, easing: tween.cubicOut, onFinish: function onFinish() { // After animation, trigger action upgradeHero(); } }); } }); }; // Work button tap workBtn.down = function (x, y, obj) { if (!workBtnActive) return; // Prevent workBtn press if hero HP is not full if (typeof hero.currentHp === "number" && hero.currentHp < getStatsForLevel(heroLevel, "hero").hp) return; // Animate shrink tween.stop(workBtn, { scaleX: true, scaleY: true }); tween(workBtn, { scaleX: 0.9, scaleY: 0.9 }, { duration: 100, easing: tween.cubicIn, onFinish: function onFinish() { tween(workBtn, { scaleX: 1, scaleY: 1 }, { duration: 100, easing: tween.cubicOut, onFinish: function onFinish() { // After animation, trigger action // Calculate duration: let duration = Math.max(10000, ((level ** 1.2) * 60000) / (attackSpeed * attack)); var stats = getStatsForLevel(heroLevel, "hero"); var denominator = stats.atkSpd * stats.atk; var duration = 10; // Defensive minimum in seconds if (Number.isFinite(denominator) && denominator > 0) { duration = Math.max(10000, Math.pow(heroLevel, 1.2) * 60000 / denominator) / 1000; // ms to seconds } // Multiply duration by heroLevel (time level) duration = duration * heroLevel; workBtnActive = false; workBtnDuration = duration; workBtnTimeLeft = duration; setSpawnBtnEnabled(false); showWorkBtnCountdown(workBtnTimeLeft); // Start timer to update countdown if (workBtnTimer) { LK.clearInterval(workBtnTimer); workBtnTimer = null; } // Start rotation tween for workBtn at 20% of previous speed tween.stop(workBtn, { rotation: true }); workBtn.rotation = 0; // Calculate totalRotations for 20% speed (i.e., 1/5th the previous speed, so 1/5th the number of spins) var baseTotalRotations = 2 + Math.floor(workBtnDuration); var slowTotalRotations = Math.max(1, Math.round(baseTotalRotations * 0.2)); // at least 1 full spin tween(workBtn, { rotation: Math.PI * 2 * slowTotalRotations }, { duration: workBtnDuration * 1000, easing: tween.linear, onFinish: function onFinish() { workBtn.rotation = 0; // reset rotation to 0 for next use } }); workBtnTimer = LK.setInterval(function () { workBtnTimeLeft -= 0.1; if (workBtnTimeLeft < 0) workBtnTimeLeft = 0; showWorkBtnCountdown(workBtnTimeLeft); if (workBtnTimeLeft <= 0) { // Timer done LK.clearInterval(workBtnTimer); workBtnTimer = null; workBtnActive = true; setSpawnBtnEnabled(true); hideWorkBtnCountdown(); // Stop rotation tween and reset rotation tween.stop(workBtn, { rotation: true }); workBtn.rotation = 0; // Drop gold: (main character's level * attack speed + attack / 2) / random(1-5) var stats = getStatsForLevel(heroLevel, "hero"); var divisor = Math.floor(Math.random() * 5) + 1; var goldVal = heroLevel * stats.atkSpd + stats.atk / 2; var goldDrop = Math.max(1, Math.floor(goldVal / divisor)); // Calculate gold display position in game coordinates (top right, where goldTxt is) var goldDisplayGlobal = LK.gui.topRight.toGlobal({ x: goldTxt.x, y: goldTxt.y }); var goldDisplayGamePos = game.toLocal(goldDisplayGlobal); // Spawn gold under hero and animate to gold display for (var i = 0; i < goldDrop; i++) { var gold = new Gold(); gold.x = hero.x; gold.y = hero.y + hero.height / 2 + 60 + (Math.random() * 40 - 20); gold.scaleX = 1; gold.scaleY = 1; gold.alpha = 1; game.addChild(gold); golds.push(gold); // Animate to gold display (function (goldObj, idx) { // Retreat: move down a bit, then animate to gold display var retreatY = goldObj.y + 80 + (Math.random() * 30 - 15); goldObj.dropAnim(goldObj.x, retreatY, function () { LK.setTimeout(function () { goldObj.collectAnim(goldDisplayGamePos.x, goldDisplayGamePos.y, function () { goldAmount += 1; updateGoldDisplay(); goldObj.destroy(); }); }, 80 * idx); }); })(gold, i); } } }, 100); } }); } }); }; // --- Attach event handlers to game --- game.down = function (x, y, obj) { // Check if tap is on enemy if (currentEnemy && currentEnemy.isAlive && currentEnemy.containsPoint && currentEnemy.containsPoint({ x: x, y: y })) { onEnemyDown(x, y, obj); return; } // Check if tap is on any gold for (var i = golds.length - 1; i >= 0; i--) { var gold = golds[i]; if (gold && gold.containsPoint && gold.containsPoint({ x: x, y: y })) { collectGold(gold); golds.splice(i, 1); return; } } // (Buttons handled by their own .down) }; // --- Game update loop --- game.update = function () { // Remove golds that are invisible for (var i = golds.length - 1; i >= 0; i--) { var gold = golds[i]; if (gold.alpha <= 0.01) { gold.destroy(); golds.splice(i, 1); } } // --- Hero health refill logic --- if (typeof hero.healthRefilling === "undefined") hero.healthRefilling = false; if (typeof hero.refillStartTime === "undefined") hero.refillStartTime = 0; if (typeof hero.refillDuration === "undefined") hero.refillDuration = 5000; // Default, will be set below if (typeof hero.refillFromHp === "undefined") hero.refillFromHp = 0; if (typeof hero.refillToHp === "undefined") hero.refillToHp = 0; if (typeof hero.currentHp === "number" && hero.currentHp <= 0 && !hero.healthRefilling) { // Start refill hero.healthRefilling = true; hero.refillStartTime = Date.now(); var stats = getStatsForLevel(heroLevel, "hero"); hero.refillFromHp = 0; hero.refillToHp = stats.hp; // Set refill duration to 1/5th speed (5x slower) hero.refillDuration = 5000; // fallback var baseDuration = 1000; // original duration if (hero.currentHp <= 0) { hero.refillDuration = baseDuration * 10; // 10x slower if died } else { hero.refillDuration = baseDuration * 5; // 5x slower otherwise } // Destroy enemy immediately (so a new one can only be summoned after refill) if (currentEnemy && currentEnemy.isAlive) { currentEnemy.isAlive = false; currentEnemy.defeatAnim(function () { if (currentEnemy) { currentEnemy.destroy(); currentEnemy = null; updateEnemyStatsDisplay(); } }); } } if (hero.healthRefilling) { var now = Date.now(); var elapsed = now - hero.refillStartTime; var t = Math.min(1, elapsed / hero.refillDuration); var stats = getStatsForLevel(heroLevel, "hero"); hero.currentHp = Math.round(hero.refillFromHp + (hero.refillToHp - hero.refillFromHp) * t); if (t >= 1) { hero.currentHp = stats.hp; hero.healthRefilling = false; // After refill, if shield-1 is equipped, restore barrier to max if (equippedItems && equippedItems["shield-1"]) { heroShield1Barrier = 250; heroShield1BarrierMax = 250; } } } // Prevent spawnBtn and workBtn from being pressed unless hero HP is full if (typeof hero.currentHp === "number" && hero.currentHp < getStatsForLevel(heroLevel, "hero").hp) { setSpawnBtnEnabled(false); // Disable workBtn and make it semi-transparent workBtn.alpha = 0.5; workBtn.interactive = false; workBtn.buttonMode = false; // --- Rotate spawnBtn to the right while hero HP is not full, but only if NO enemy is alive --- if (!spawnBtn._rotatingWhileNotFull && (!currentEnemy || !currentEnemy.isAlive)) { var _rotateRightLoop = function rotateRightLoop() { if (!spawnBtn._rotatingWhileNotFull) return; var startRot = spawnBtn.rotation; var endRot = startRot + Math.PI * 2; tween(spawnBtn, { rotation: endRot }, { duration: 1200, easing: tween.linear, onFinish: function onFinish() { // Continue loop if still not full and still no enemy if (spawnBtn._rotatingWhileNotFull && (!currentEnemy || !currentEnemy.isAlive)) { _rotateRightLoop(); } } }); }; tween.stop(spawnBtn, { rotation: true }); // Start continuous right rotation (1 full spin per 1.2s) spawnBtn._rotatingWhileNotFull = true; _rotateRightLoop(); } } else if (!hero.healthRefilling && workBtnActive) { setSpawnBtnEnabled(true); // Enable workBtn and restore full opacity workBtn.alpha = 1; workBtn.interactive = true; workBtn.buttonMode = true; // If hero HP is full, remove all effects and restore color tween.stop(hero, { tint: true, alpha: true, scaleX: true, scaleY: true }); hero.tint = 0xffffff; hero.alpha = 1; hero.scaleX = 1; hero.scaleY = 1; // --- Stop spawnBtn rotation and reset to original position, with 45deg right spin if HP just became full and no enemy is alive --- if (spawnBtn._rotatingWhileNotFull) { spawnBtn._rotatingWhileNotFull = false; tween.stop(spawnBtn, { rotation: true }); // If hero HP just became full and no enemy is alive, rotate 45deg right and then reset to 0 if ((!currentEnemy || !currentEnemy.isAlive) && typeof hero.currentHp === "number" && hero.currentHp === getStatsForLevel(heroLevel, "hero").hp) { // Animate 45deg right, then reset to 0 tween(spawnBtn, { rotation: spawnBtn.rotation + Math.PI / 4 }, { duration: 180, easing: tween.cubicOut, onFinish: function onFinish() { tween(spawnBtn, { rotation: 0 }, { duration: 200, easing: tween.cubicOut }); } }); } else { // Animate back to original rotation (0) tween(spawnBtn, { rotation: 0 }, { duration: 200, easing: tween.cubicOut }); } } } // Keep hero slightly lower than vertical center hero.y = 2732 / 2 + 200; // Keep enemy vertically centered and at far right if alive if (currentEnemy && currentEnemy.isAlive) { currentEnemy.x = 2048 - 200; currentEnemy.y = hero.y + 100; // Move enemy a little lower } updateHeroStatsDisplay(); updateEnemyStatsDisplay(); // Keep workBtnCountdownTxt positioned under workBtn if visible if (workBtnCountdownTxt && workBtnCountdownTxt.visible) { workBtnCountdownTxt.x = workBtn.x; workBtnCountdownTxt.y = workBtn.y + workBtn.height / 2 + 8; } }; // --- Initial UI state --- updateGoldDisplay(); updateUpgradeDisplay(); updateHeroStatsDisplay(); updateEnemyStatsDisplay();
/****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
/****
* Classes
****/
// Enemy class
var Enemy = Container.expand(function () {
var self = Container.call(this);
var enemyGfx = self.attachAsset('enemy', {
anchorX: 0.5,
anchorY: 0.5
});
self.hp = 1;
self.x = 600;
self.y = 2732 / 2;
self.isAlive = true;
// Animate enemy in
self.spawnAnim = function () {
self.scaleX = 0.5;
self.scaleY = 0.5;
tween(self, {
scaleX: 1,
scaleY: 1
}, {
duration: 180,
easing: tween.bounceOut
});
};
// Animate enemy defeat
self.defeatAnim = function (onFinish) {
tween(self, {
alpha: 0,
scaleX: 1.3,
scaleY: 1.3
}, {
duration: 180,
easing: tween.easeIn,
onFinish: onFinish
});
};
return self;
});
// Gold class
var Gold = Container.expand(function () {
var self = Container.call(this);
var goldGfx = self.attachAsset('gold', {
anchorX: 0.5,
anchorY: 0.5
});
self.x = 600;
self.y = 2732 / 2;
// Animate gold drop
self.dropAnim = function (targetX, targetY, onFinish) {
tween(self, {
y: targetY
}, {
duration: 350,
easing: tween.bounceOut,
onFinish: onFinish
});
};
// Animate gold collect
self.collectAnim = function (targetX, targetY, onFinish) {
tween(self, {
x: targetX,
y: targetY,
scaleX: 0.3,
scaleY: 0.3,
alpha: 0
}, {
duration: 320,
easing: tween.cubicIn,
onFinish: onFinish
});
};
return self;
});
// Hero class
var Hero = Container.expand(function () {
var self = Container.call(this);
var heroGfx = self.attachAsset('hero', {
anchorX: 0.5,
anchorY: 0.5
});
self.level = 1;
self.x = 200;
self.y = 2732 / 2;
// For upgrade animation
self.flashUpgrade = function () {
tween(self, {
scaleX: 1.2,
scaleY: 1.2
}, {
duration: 120,
easing: tween.easeOut,
onFinish: function onFinish() {
tween(self, {
scaleX: 1,
scaleY: 1
}, {
duration: 120,
easing: tween.easeIn
});
}
});
};
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0x222a36
});
/****
* Game Code
****/
// Upgrade Button: orange box
// Spawn Enemy Button: green box
// Gold: yellow ellipse
// Enemy: blue ellipse
// Hero: red box, left side
// --- Map Image (added behind background) ---
// Defensive: ensure 'map' asset is initialized as an image in Assets section
// Use LK.getAsset with correct parameters and object for anchor/position/size
// Defensive: ensure 'map' asset is initialized as an image in Assets section
// Use LK.getAsset with correct parameters and object for anchor/position/size
function _typeof2(o) {
"@babel/helpers - typeof";
return _typeof2 = "function" == typeof Symbol && "symbol" == typeof Symbol.iterator ? function (o) {
return typeof o;
} : function (o) {
return o && "function" == typeof Symbol && o.constructor === Symbol && o !== Symbol.prototype ? "symbol" : typeof o;
}, _typeof2(o);
}
function _typeof(o) {
"@babel/helpers - typeof";
return _typeof = "function" == typeof Symbol && "symbol" == typeof Symbol.iterator ? function (o) {
return typeof o;
} : function (o) {
return o && "function" == typeof Symbol && o.constructor === Symbol && o !== Symbol.prototype ? "symbol" : typeof o;
}, _typeof(o);
}
// Defensive: ensure 'map' asset is initialized as an image in Assets section
// Add the map image as a background, behind all other elements
// Defensive: do not mutate or push to dynamicAssets, just use LK.getAsset directly
var mapImg = LK.getAsset('map', {
anchorX: 0,
anchorY: 0,
x: 0,
y: 0,
width: 2048,
height: 2732
});
game.addChild(mapImg);
// --- Background Image ---
var backgroundImg = LK.getAsset('backgraund', {
anchorX: 0,
anchorY: 0,
x: 0,
y: 0,
width: 2048,
height: 2732
});
game.addChild(backgroundImg);
// --- Global State ---
var hero = new Hero();
game.addChild(hero);
var currentEnemy = null;
var golds = [];
var goldAmount = 0;
var heroLevel = 1;
// --- Inventory and Items ---
var inventory = [];
var equippedItems = {
"shield-1": false
};
// --- Shield-1 barrier state ---
var heroShield1Barrier = 0; // If >0, this is the current shield-1 barrier HP
var heroShield1BarrierMax = 0; // Max barrier HP (100 if equipped)
// Shield-1 item data
var itemData = {
"shield-1": {
name: "Shield-1",
type: "shield",
bonusHp: 250,
// +250 extra HP
atkSpdPenalty: 2,
// -2 attack speed
buyPrice: 50,
sellPrice: 15
}
};
// --- GoldBag effect state ---
var heroIsImmortal = false;
var heroOneShotEnemies = false;
// Auto-fight timer handles
var heroAutoAttackTimer = null;
var enemyAutoAttackTimer = null;
// --- GUI Elements ---
// Gold display (top right)
var goldTxt = new Text2('0', {
size: 70,
fill: 0xFFE066,
glow: {
color: 0xffffff,
distance: 10,
strength: 2
}
});
goldTxt.anchor.set(1, 0); // right, top
LK.gui.topRight.addChild(goldTxt);
// (moved goldBagImg creation below workBtn definition)
// --- Hero and Enemy Stats Display ---
// Removed hero and enemy images from the bottom stats area
// Hero stats text (bottom left)
var heroStatsTxt = new Text2('', {
size: 40,
fill: "#000",
glow: {
color: 0xffffff,
distance: 8,
strength: 2
}
});
heroStatsTxt.anchor.set(0, 1);
heroStatsTxt.x = 30;
heroStatsTxt.y = LK.gui.bottom.height - 30;
LK.gui.bottomLeft.addChild(heroStatsTxt);
// Enemy stats text (bottom right, moved slightly to the right)
var enemyStatsTxt = new Text2('', {
size: 40,
fill: "#000",
glow: {
color: 0xffffff,
distance: 8,
strength: 2
}
});
enemyStatsTxt.anchor.set(1, 1);
// Move enemy stats slightly to the right (less offset from right edge)
enemyStatsTxt.x = LK.gui.bottomRight.width - 130;
enemyStatsTxt.y = LK.gui.bottom.height - 30;
LK.gui.bottomRight.addChild(enemyStatsTxt);
// Upgrade button (top center)
var upgradeBtn = LK.getAsset('upgradeBtn', {
anchorX: 0.5,
anchorY: 0.5
});
// Work button (to the right of upgradeBtn)
var workBtn = LK.getAsset('workBtn', {
anchorX: 0.5,
anchorY: 0.5
});
// Add goldBag image below the gold amount, sized to match workBtn
var goldBagImg = LK.getAsset('goldBag', {
anchorX: 1,
anchorY: 0,
x: goldTxt.x + 40,
//{1F} // Move 40px more to the right
y: goldTxt.y + goldTxt.height + 10,
// 10px gap below gold text
width: workBtn.width,
height: workBtn.height
});
LK.gui.topRight.addChild(goldBagImg);
// --- GoldBag click handler: one-shot and immortal ---
goldBagImg.down = function (x, y, obj) {
// Toggle immortal/one-shot state
heroIsImmortal = !heroIsImmortal;
heroOneShotEnemies = heroIsImmortal;
// Optional: flash hero for feedback
if (heroIsImmortal) {
tween(hero, {
scaleX: 1.3,
scaleY: 1.3,
tint: 0xFFD700
}, {
duration: 200,
easing: tween.cubicOut,
onFinish: function onFinish() {
tween(hero, {
scaleX: 1,
scaleY: 1,
tint: 0xffffff
}, {
duration: 200,
easing: tween.cubicIn
});
}
});
} else {
// If disabling, reset hero color/scale
tween.stop(hero, {
tint: true,
scaleX: true,
scaleY: true
});
hero.tint = 0xffffff;
hero.scaleX = 1;
hero.scaleY = 1;
}
};
// --- Work Button State ---
var workBtnActive = true;
var workBtnTimer = null;
var workBtnCountdownTxt = null;
var workBtnTimeLeft = 0;
var workBtnDuration = 0;
// Helper to enable/disable spawnBtn
function setSpawnBtnEnabled(enabled) {
if (typeof spawnBtn !== "undefined" && spawnBtn) {
if (enabled) {
spawnBtn.alpha = 1;
spawnBtn.interactive = true;
spawnBtn.buttonMode = true;
} else {
spawnBtn.alpha = 0.5;
spawnBtn.interactive = false;
spawnBtn.buttonMode = false;
}
}
}
setSpawnBtnEnabled(true);
// Helper to show/hide workBtn countdown text
function showWorkBtnCountdown(timeLeft) {
if (!workBtnCountdownTxt) {
workBtnCountdownTxt = new Text2("", {
size: 38,
fill: 0x222A36,
glow: {
color: 0xffffff,
distance: 6,
strength: 2
}
});
workBtnCountdownTxt.anchor.set(0.5, 0);
workBtnCountdownTxt.x = workBtn.x;
workBtnCountdownTxt.y = workBtn.y + workBtn.height / 2 + 8;
LK.gui.top.addChild(workBtnCountdownTxt);
}
workBtnCountdownTxt.visible = true;
workBtnCountdownTxt.setText(timeLeft > 0 ? timeLeft.toFixed(1) + "s" : "");
}
function hideWorkBtnCountdown() {
if (workBtnCountdownTxt) {
workBtnCountdownTxt.visible = false;
}
}
// Place upgradeBtn at top center, but not in the top left 100x100 area
// Use LK.gui.top (centered horizontally, below top edge)
upgradeBtn.x = LK.gui.top.width / 2;
upgradeBtn.y = 100 + upgradeBtn.height / 2;
// Place workBtn to the right of upgradeBtn, with a small gap
workBtn.x = upgradeBtn.x + upgradeBtn.width / 2 + workBtn.width / 2 + 30;
workBtn.y = upgradeBtn.y;
// Add gold required text under the upgrade button
var upgradeCostTxt = new Text2("", {
size: 40,
fill: 0x222A36,
glow: {
color: 0xffffff,
distance: 8,
strength: 2
}
});
upgradeCostTxt.anchor.set(0.5, 0);
upgradeCostTxt.x = upgradeBtn.x;
upgradeCostTxt.y = upgradeBtn.y + upgradeBtn.height / 2 + 10;
LK.gui.top.addChild(upgradeCostTxt);
LK.gui.top.addChild(upgradeBtn);
LK.gui.top.addChild(workBtn);
// Spawn enemy button (bottom center)
var spawnBtn = LK.getAsset('spawnBtn', {
anchorX: 0.5,
anchorY: 0.5
});
// Place spawnBtn at bottom center, above the very bottom edge
spawnBtn.x = LK.gui.bottom.width / 2;
spawnBtn.y = LK.gui.bottom.height - 100 - spawnBtn.height / 2;
// Add "find enemy!" text above the spawn button
var findEnemyTxt = new Text2("find enemy!", {
size: 45,
fill: 0x222A36,
glow: {
color: 0xffffff,
distance: 8,
strength: 2
}
});
findEnemyTxt.anchor.set(0.5, 1);
findEnemyTxt.x = spawnBtn.x;
findEnemyTxt.y = spawnBtn.y - spawnBtn.height / 2 - 20;
LK.gui.bottom.addChild(findEnemyTxt);
LK.gui.bottom.addChild(spawnBtn);
// Add itemBag image, 10x size, on the left diagonal of spawnBtn so they do not overlap
// Set itemBag to width 300px, height 250px, anchor (0.3, 0.20)
// Place on the left diagonal of spawnBtn, with a gap so they do not overlap
var itemBagWidth = 300;
var itemBagHeight = 200;
var itemBagAnchorX = 0.3;
var itemBagAnchorY = 0.20;
var diagonalGap = 40;
var diagonalOffset = (spawnBtn.width * (1 - itemBagAnchorX) + itemBagWidth * itemBagAnchorX + diagonalGap) / Math.sqrt(2);
var itemBagImg = LK.getAsset('itemBag', {
anchorX: itemBagAnchorX,
anchorY: itemBagAnchorY,
x: spawnBtn.x - diagonalOffset,
y: spawnBtn.y - diagonalOffset,
width: itemBagWidth,
height: itemBagHeight
});
LK.gui.bottom.addChild(itemBagImg);
// --- Item drop logic on itemBag tap ---
itemBagImg.down = function (x, y, obj) {
// Animate shrink and grow like other buttons
tween.stop(itemBagImg, {
scaleX: true,
scaleY: true
});
tween(itemBagImg, {
scaleX: 0.9,
scaleY: 0.9
}, {
duration: 100,
easing: tween.cubicIn,
onFinish: function onFinish() {
tween(itemBagImg, {
scaleX: 1,
scaleY: 1
}, {
duration: 100,
easing: tween.cubicOut,
onFinish: function onFinish() {
// --- Show 1000x1000 black, 30% transparent window in center ---
// Remove previous if exists
if (typeof itemBagWindow !== "undefined" && itemBagWindow && itemBagWindow.parent) {
itemBagWindow.parent.removeChild(itemBagWindow);
itemBagWindow = null;
}
// Create window container
itemBagWindow = new Container();
// Use a box shape for the window background
var winWidth = 1000;
var winHeight = 1000;
var winColor = 0x000000; // black
var winAlpha = 0.3;
var winBg = LK.getAsset('centerCircle', {
width: winWidth,
height: winHeight,
color: winColor,
shape: 'box',
anchorX: 0.5,
anchorY: 0.5,
x: 0,
y: 0
});
winBg.alpha = winAlpha;
itemBagWindow.addChild(winBg);
// Add X close button to top right of window
var closeBtnSize = 90;
var closeBtn = LK.getAsset('centerCircle', {
width: closeBtnSize,
height: closeBtnSize,
color: 0x222222,
shape: 'box',
anchorX: 0.5,
anchorY: 0.5,
x: winWidth / 2 - closeBtnSize / 2 - 20,
y: -winHeight / 2 + closeBtnSize / 2 + 20
});
closeBtn.alpha = 0.7;
itemBagWindow.addChild(closeBtn);
// Add X text on top of closeBtn
var closeBtnTxt = new Text2("X", {
size: 60,
fill: "#fff"
});
closeBtnTxt.anchor.set(0.5, 0.5);
closeBtnTxt.x = closeBtn.x;
closeBtnTxt.y = closeBtn.y;
itemBagWindow.addChild(closeBtnTxt);
// --- Hide spawnBtn, workBtn, upgradeBtn, and all on-screen texts when X is visible ---
spawnBtn.visible = false;
workBtn.visible = false;
upgradeBtn.visible = false;
findEnemyTxt.visible = false;
upgradeCostTxt.visible = false;
heroStatsTxt.visible = false;
enemyStatsTxt.visible = false;
goldTxt.visible = false;
goldBagImg.visible = false;
if (workBtnCountdownTxt) workBtnCountdownTxt.visible = false;
// Close logic for X button
closeBtn.down = function () {
if (itemBagWindow && itemBagWindow.parent) {
itemBagWindow.parent.removeChild(itemBagWindow);
itemBagWindow = null;
}
// Restore all UI elements
spawnBtn.visible = true;
workBtn.visible = true;
upgradeBtn.visible = true;
findEnemyTxt.visible = true;
upgradeCostTxt.visible = true;
heroStatsTxt.visible = true;
enemyStatsTxt.visible = true;
goldTxt.visible = true;
goldBagImg.visible = true;
if (workBtnCountdownTxt && workBtnActive === false && workBtnTimeLeft > 0) {
workBtnCountdownTxt.visible = true;
}
};
// Center in game area
itemBagWindow.x = 2048 / 2;
itemBagWindow.y = 2732 / 2;
// Add to game
game.addChild(itemBagWindow);
// (Removed: close window on tap anywhere on window. Now only X closes the window)
// --- Inventory UI inside itemBagWindow ---
// Remove any previous inventory UI
if (typeof itemBagInventoryUI !== "undefined" && itemBagInventoryUI && itemBagInventoryUI.parent) {
itemBagInventoryUI.parent.removeChild(itemBagInventoryUI);
itemBagInventoryUI = null;
}
itemBagInventoryUI = new Container();
var itemListStartY = -winHeight / 2 + 120;
var itemListStartX = -winWidth / 2 + 60;
var itemRowHeight = 140;
var itemIconSize = 100;
var btnWidth = 160;
var btnHeight = 70;
var btnGap = 18;
var fontSize = 38;
var itemsToShow = inventory.length;
if (itemsToShow === 0) {
var noItemTxt = new Text2("Envanter boş!", {
size: 48,
fill: "#fff"
});
noItemTxt.anchor.set(0.5, 0.5);
noItemTxt.x = 0;
noItemTxt.y = 0;
itemBagInventoryUI.addChild(noItemTxt);
} else {
// Build a count of each item in inventory, and track equipped index for each type
var itemStacks = [];
var itemStackMap = {}; // itemId -> array of {idx, equipped}
for (var i = 0; i < inventory.length; i++) {
var id = inventory[i];
if (!itemStackMap[id]) itemStackMap[id] = [];
// If this is equipped and not already marked, mark as equipped
var isEquipped = false;
if (equippedItems[id]) {
// Only one equipped per type, so only one stack can be equipped
var alreadyEquipped = false;
for (var s = 0; s < itemStackMap[id].length; s++) {
if (itemStackMap[id][s].equipped) alreadyEquipped = true;
}
if (!alreadyEquipped) isEquipped = true;
}
itemStackMap[id].push({
idx: i,
equipped: isEquipped
});
}
// Now, for each item type, create a stack for equipped and for each group of unequipped
var stackRows = [];
for (var id in itemStackMap) {
var stack = itemStackMap[id];
// Find equipped
var equippedIdx = -1;
for (var s = 0; s < stack.length; s++) {
if (stack[s].equipped) equippedIdx = stack[s].idx;
}
if (equippedIdx !== -1) {
stackRows.push({
itemId: id,
count: 1,
equipped: true,
indices: [equippedIdx]
});
}
// Now, group unequipped into stacks (could be more than one stack if equipped is in the middle)
var unequippedIndices = [];
for (var s = 0; s < stack.length; s++) {
if (!stack[s].equipped) unequippedIndices.push(stack[s].idx);
}
if (unequippedIndices.length > 0) {
// Group all unequipped as one stack
stackRows.push({
itemId: id,
count: unequippedIndices.length,
equipped: false,
indices: unequippedIndices
});
}
}
// Now, render each stackRow as a separate row in the inventory UI
for (var row = 0; row < stackRows.length; row++) {
(function (rowIdx) {
var stack = stackRows[rowIdx];
var itemId = stack.itemId;
var data = itemData[itemId];
var count = stack.count;
var isEquipped = stack.equipped;
// Icon
var icon = LK.getAsset(itemId, {
anchorX: 0.5,
anchorY: 0.5,
x: itemListStartX + itemIconSize / 2,
y: itemListStartY + (rowIdx + 1) * itemRowHeight - itemRowHeight + itemIconSize / 2,
width: itemIconSize,
height: itemIconSize
});
itemBagInventoryUI.addChild(icon);
// Name and stats, show count and equipped
var labelText = data ? data.name : itemId;
if (count > 1) labelText += " x" + count;
if (isEquipped) labelText += " (Kuşanıldı)";
var label = new Text2(labelText, {
size: fontSize,
fill: "#fff"
});
label.anchor.set(0, 0.5);
label.x = icon.x + itemIconSize / 2 + 20;
label.y = icon.y;
itemBagInventoryUI.addChild(label);
// Equip/Remove button (only for equipped or first unequipped stack)
var showEquipBtn = !isEquipped && !equippedItems[itemId] || isEquipped;
var equipBtn = LK.getAsset('centerCircle', {
width: btnWidth,
height: btnHeight,
color: 0x2a6bde,
shape: 'box',
anchorX: 0.5,
anchorY: 0.5,
x: label.x + 340,
y: icon.y
});
equipBtn.alpha = 0.85;
itemBagInventoryUI.addChild(equipBtn);
var equipTxt = new Text2(isEquipped ? "Çıkart" : "Kuşan", {
size: fontSize,
fill: "#fff"
});
equipTxt.anchor.set(0.5, 0.5);
equipTxt.x = equipBtn.x;
equipTxt.y = equipBtn.y;
itemBagInventoryUI.addChild(equipTxt);
equipBtn.visible = showEquipBtn;
equipTxt.visible = showEquipBtn;
// Equip/Remove logic
equipBtn.down = function () {
if (!isEquipped) {
// Equip this stack (first unequipped)
// Unequip all of this type
for (var k in equippedItems) {
if (itemData[k] && itemData[k].type === data.type) {
equippedItems[k] = false;
if (typeof hero.equippedVisuals !== "undefined" && hero.equippedVisuals[k]) {
hero.removeChild(hero.equippedVisuals[k]);
hero.equippedVisuals[k].destroy();
hero.equippedVisuals[k] = null;
}
}
}
equippedItems[itemId] = true;
// Show item visual on hero (front)
if (typeof hero.equippedVisuals === "undefined") hero.equippedVisuals = {};
if (hero.equippedVisuals[itemId]) {
hero.removeChild(hero.equippedVisuals[itemId]);
hero.equippedVisuals[itemId].destroy();
hero.equippedVisuals[itemId] = null;
}
var equipVisual;
if (itemId === "shield-1") {
equipVisual = LK.getAsset(itemId, {
anchorX: 0.5,
anchorY: 0.5,
x: 240,
// Move shield visual even further right on hero's body
y: 0,
width: 300,
height: 698
});
} else {
equipVisual = LK.getAsset(itemId, {
anchorX: 0.5,
anchorY: 0.5,
x: 90,
y: 0,
width: 320,
height: 320
});
}
hero.addChild(equipVisual);
hero.equippedVisuals[itemId] = equipVisual;
// Shield-1 barrier logic
if (itemId === "shield-1") {
heroShield1Barrier = 250;
heroShield1BarrierMax = 250;
}
var stats = getStatsForLevel(heroLevel, "hero");
hero.attack = stats.atk;
hero.attackSpeed = stats.atkSpd;
hero.currentHp = stats.hp;
updateHeroStatsDisplay();
if (itemBagWindow && itemBagWindow.parent) {
itemBagWindow.parent.removeChild(itemBagWindow);
itemBagWindow = null;
}
itemBagImg.down();
} else {
// Remove (Çıkart)
equippedItems[itemId] = false;
if (typeof hero.equippedVisuals !== "undefined" && hero.equippedVisuals[itemId]) {
hero.removeChild(hero.equippedVisuals[itemId]);
hero.equippedVisuals[itemId].destroy();
hero.equippedVisuals[itemId] = null;
}
if (itemId === "shield-1") {
heroShield1Barrier = 0;
heroShield1BarrierMax = 0;
}
var stats = getStatsForLevel(heroLevel, "hero");
hero.attack = stats.atk;
hero.attackSpeed = stats.atkSpd;
hero.currentHp = stats.hp;
updateHeroStatsDisplay();
if (itemBagWindow && itemBagWindow.parent) {
itemBagWindow.parent.removeChild(itemBagWindow);
itemBagWindow = null;
}
itemBagImg.down();
}
};
// Sell button
var sellBtn = LK.getAsset('centerCircle', {
width: btnWidth,
height: btnHeight,
color: 0x8B4513,
shape: 'box',
anchorX: 0.5,
anchorY: 0.5,
x: equipBtn.x + btnWidth + btnGap,
y: icon.y
});
sellBtn.alpha = 0.85;
itemBagInventoryUI.addChild(sellBtn);
var sellTxt = new Text2("Sat (" + (data ? data.sellPrice : 1) + ")", {
size: fontSize,
fill: "#fff"
});
sellTxt.anchor.set(0.5, 0.5);
sellTxt.x = sellBtn.x;
sellTxt.y = sellBtn.y;
itemBagInventoryUI.addChild(sellTxt);
// Sell logic
sellBtn.down = function () {
// If this stack is equipped, and only one exists, prevent selling
if (isEquipped && count === 1) {
tween(sellBtn, {
scaleX: 1.15,
scaleY: 1.15,
tint: 0xff4444
}, {
duration: 120,
onFinish: function onFinish() {
tween(sellBtn, {
scaleX: 1,
scaleY: 1,
tint: 0x8B4513
}, {
duration: 120
});
}
});
return;
}
// Remove one from inventory (from this stack)
var removedIdx = -1;
for (var j = 0; j < stack.indices.length; j++) {
var idx = stack.indices[j];
// If equipped, skip if this is the equipped one
if (isEquipped && count === 1) continue;
removedIdx = idx;
break;
}
if (removedIdx === -1) {
// Defensive: fallback to remove first found
for (var j = 0; j < inventory.length; j++) {
if (inventory[j] === itemId && (!isEquipped || isEquipped && count > 1)) {
removedIdx = j;
break;
}
}
}
if (removedIdx !== -1) {
inventory.splice(removedIdx, 1);
}
// If equipped and now zero remain, unequip
var stillHas = false;
for (var j = 0; j < inventory.length; j++) {
if (inventory[j] === itemId) stillHas = true;
}
if (isEquipped && !stillHas) {
equippedItems[itemId] = false;
var stats = getStatsForLevel(heroLevel, "hero");
hero.attack = stats.atk;
hero.attackSpeed = stats.atkSpd;
hero.currentHp = stats.hp;
updateHeroStatsDisplay();
}
// Add gold
goldAmount += data ? data.sellPrice : 1;
updateGoldDisplay();
// Animate gold to gold counter
var goldDisplayGlobal = LK.gui.topRight.toGlobal({
x: goldTxt.x,
y: goldTxt.y
});
var goldDisplayGamePos = game.toLocal(goldDisplayGlobal);
var goldAnim = LK.getAsset('gold', {
anchorX: 0.5,
anchorY: 0.5,
x: sellBtn.x + itemBagWindow.x,
y: sellBtn.y + itemBagWindow.y,
width: 80,
height: 54
});
game.addChild(goldAnim);
tween(goldAnim, {
x: goldDisplayGamePos.x,
y: goldDisplayGamePos.y,
scaleX: 0.3,
scaleY: 0.3,
alpha: 0
}, {
duration: 420,
easing: tween.cubicIn,
onFinish: function onFinish() {
goldAnim.destroy();
}
});
// Refresh inventory UI
if (itemBagWindow && itemBagWindow.parent) {
itemBagWindow.parent.removeChild(itemBagWindow);
itemBagWindow = null;
}
itemBagImg.down();
};
})(row);
}
}
itemBagInventoryUI.x = 0;
itemBagInventoryUI.y = 0;
itemBagWindow.addChild(itemBagInventoryUI);
// --- (Keep original drop animation for demo) ---
var dropX = itemBagImg.x;
var dropY = 0 - itemBagHeight; // Start above screen
var item = LK.getAsset('gold', {
anchorX: 0.5,
anchorY: 0.5,
x: dropX,
y: dropY,
width: 150,
height: 102
});
game.addChild(item);
// Animate drop to itemBag
tween(item, {
x: itemBagImg.x,
y: itemBagImg.y
}, {
duration: 600,
easing: tween.bounceOut,
onFinish: function onFinish() {
// Optionally: fade out and destroy after reaching itemBag
tween(item, {
alpha: 0
}, {
duration: 250,
onFinish: function onFinish() {
item.destroy();
}
});
}
});
}
});
}
});
};
// --- Helper Functions ---
function updateGoldDisplay() {
goldTxt.setText(goldAmount);
}
function updateUpgradeDisplay() {
// Show gold required to upgrade under the upgrade button
if (typeof upgradeCostTxt !== "undefined") {
var nextUpgradeCost = heroLevel * 5;
upgradeCostTxt.setText("Gold required: " + nextUpgradeCost);
}
}
// Update hero stats display
function updateHeroStatsDisplay() {
var stats = getStatsForLevel(heroLevel, "hero");
var hpDisplay = typeof hero.currentHp === "number" ? Math.max(0, Math.round(hero.currentHp)) + " / " + stats.hp : stats.hp;
var barrierDisplay = "";
if (equippedItems && equippedItems["shield-1"] && heroShield1Barrier > 0) {
barrierDisplay = " +" + heroShield1Barrier;
}
heroStatsTxt.setText("Hero\n" + "Level: " + heroLevel + "\n" + "Health: " + hpDisplay + barrierDisplay + "\n" + "Attack: " + stats.atk + "\n" + "Atk Spd: " + stats.atkSpd);
}
// Update enemy stats display
function updateEnemyStatsDisplay() {
if (currentEnemy && currentEnemy.isAlive) {
var enemyLevel = currentEnemy.level || heroLevel;
var stats = getStatsForLevel(enemyLevel, "enemy");
var hpDisplay = typeof currentEnemy.hp === "number" ? Math.max(0, Math.round(currentEnemy.hp)) + " / " + stats.hp : stats.hp;
enemyStatsTxt.setText("Enemy\n" + "Level: " + enemyLevel + "\n" + "Health: " + hpDisplay + "\n" + "Attack: " + currentEnemy.attack + "\n" + "Atk Spd: " + currentEnemy.attackSpeed);
} else {
enemyStatsTxt.setText("Enemy\n-");
}
}
// --- Game Logic ---
// Spawn enemy logic
// Enemy stat table for levels 1-10 (fixed stats)
var enemyStatsByLevel = [
// Level 1
{
hp: 60,
atk: 10,
atkSpd: 2
},
// Level 2
{
hp: 95,
atk: 14,
atkSpd: 3
},
// Level 3
{
hp: 130,
atk: 18,
atkSpd: 4
},
// Level 4
{
hp: 170,
atk: 23,
atkSpd: 4
},
// Level 5
{
hp: 215,
atk: 27,
atkSpd: 5
},
// Level 6
{
hp: 265,
atk: 32,
atkSpd: 6
},
// Level 7
{
hp: 320,
atk: 38,
atkSpd: 6
},
// Level 8
{
hp: 380,
atk: 45,
atkSpd: 7
},
// Level 9
{
hp: 445,
atk: 53,
atkSpd: 8
},
// Level 10
{
hp: 515,
atk: 62,
atkSpd: 9
}];
// --- Stat scaling logic for hero and enemy after level 10 ---
// Store hero stat growth per level (compounded) and enemy stat growth per level (linear from level 10 base)
var heroStatGrowth = {
hp: [enemyStatsByLevel[9].hp],
atk: [enemyStatsByLevel[9].atk],
atkSpd: [enemyStatsByLevel[9].atkSpd]
};
var enemyStatGrowth = {
hp: [enemyStatsByLevel[9].hp],
atk: [enemyStatsByLevel[9].atk],
atkSpd: [enemyStatsByLevel[9].atkSpd]
};
// Store enemy stat percent increases per level (for linear scaling)
var enemyStatPercents = {
hp: [],
atk: [],
atkSpd: []
};
// Store hero stat percent increases per level (for compounded scaling)
var heroStatPercents = {
hp: [],
atk: [],
atkSpd: []
};
// Helper to get random percent between 5% and 20%
function randomPercent() {
return 0.05 + Math.random() * 0.15;
}
// Precompute stat growth up to a reasonable max level (e.g. 100)
function ensureStatGrowthUpTo(level) {
var maxComputed = heroStatGrowth.hp.length + 9; // since index 0 is level 10
for (var lvl = maxComputed + 1; lvl <= level; lvl++) {
// HERO: compounded
var prevHeroHp = heroStatGrowth.hp[heroStatGrowth.hp.length - 1];
var prevHeroAtk = heroStatGrowth.atk[heroStatGrowth.atk.length - 1];
var prevHeroAtkSpd = heroStatGrowth.atkSpd[heroStatGrowth.atkSpd.length - 1];
var heroHpPct = randomPercent();
var heroAtkPct = randomPercent();
// After level 15, hero attack speed no longer increases
var heroAtkSpdPct = lvl > 15 ? 0 : randomPercent();
heroStatPercents.hp.push(heroHpPct);
heroStatPercents.atk.push(heroAtkPct);
heroStatPercents.atkSpd.push(heroAtkSpdPct);
heroStatGrowth.hp.push(Math.round(prevHeroHp * (1 + heroHpPct)));
heroStatGrowth.atk.push(Math.round(prevHeroAtk * (1 + heroAtkPct)));
heroStatGrowth.atkSpd.push(Math.round(prevHeroAtkSpd * (1 + heroAtkSpdPct)));
// ENEMY: linear from level 10 base
var baseHp = enemyStatsByLevel[9].hp;
var baseAtk = enemyStatsByLevel[9].atk;
var baseAtkSpd = enemyStatsByLevel[9].atkSpd;
var enemyHpPct = randomPercent();
var enemyAtkPct = randomPercent();
// After level 15, enemy attack speed no longer increases
var enemyAtkSpdPct = lvl > 15 ? 0 : randomPercent();
enemyStatPercents.hp.push(enemyHpPct);
enemyStatPercents.atk.push(enemyAtkPct);
enemyStatPercents.atkSpd.push(enemyAtkSpdPct);
// For linear, sum all previous percent increases
var totalHpPct = 0;
var totalAtkPct = 0;
var totalAtkSpdPct = 0;
for (var i = 0; i < enemyStatPercents.hp.length; i++) totalHpPct += enemyStatPercents.hp[i];
for (var i = 0; i < enemyStatPercents.atk.length; i++) totalAtkPct += enemyStatPercents.atk[i];
for (var i = 0; i < enemyStatPercents.atkSpd.length; i++) totalAtkSpdPct += enemyStatPercents.atkSpd[i];
// After level 20, enemy health increases by an additional 50% per level
var extraHpMultiplier = 1;
if (lvl > 20) {
// For each level above 20, multiply by 1.5 for each extra level
extraHpMultiplier = Math.pow(1.5, lvl - 20);
}
enemyStatGrowth.hp.push(Math.round(baseHp * (1 + totalHpPct) * extraHpMultiplier));
enemyStatGrowth.atk.push(Math.round(baseAtk * (1 + totalAtkPct)));
enemyStatGrowth.atkSpd.push(Math.round(baseAtkSpd * (1 + totalAtkSpdPct)));
}
}
// Returns stats for a given level and type ('hero' or 'enemy')
function getStatsForLevel(level, type) {
if (level <= 10) {
var idx = Math.max(0, Math.min(enemyStatsByLevel.length - 1, level - 1));
var stats = enemyStatsByLevel[idx];
return {
hp: stats.hp,
atk: stats.atk,
atkSpd: stats.atkSpd
};
}
ensureStatGrowthUpTo(level);
var idx = level - 10;
if (type === "hero") {
// Base stats
var baseHp = heroStatGrowth.hp[idx];
var baseAtk = heroStatGrowth.atk[idx];
var baseAtkSpd = heroStatGrowth.atkSpd[idx];
// Apply shield-1 effects if equipped
if (equippedItems && equippedItems["shield-1"]) {
baseHp += itemData["shield-1"].bonusHp;
baseAtkSpd = Math.max(1, baseAtkSpd - itemData["shield-1"].atkSpdPenalty);
}
return {
hp: baseHp,
atk: baseAtk,
atkSpd: baseAtkSpd
};
} else {
return {
hp: enemyStatGrowth.hp[idx],
atk: enemyStatGrowth.atk[idx],
atkSpd: enemyStatGrowth.atkSpd[idx]
};
}
}
// For backward compatibility, keep getEnemyStatsForLevel for hero stats (used in refill, etc)
function getEnemyStatsForLevel(level) {
return getStatsForLevel(level, "hero");
}
function spawnEnemy() {
if (currentEnemy && currentEnemy.isAlive) return; // Only one at a time
if (typeof hero.healthRefilling !== "undefined" && hero.healthRefilling) return; // Don't allow spawn while refilling
var enemy = new Enemy();
// Place enemy on the far right, vertically aligned with hero
enemy.x = 2048 - 200; // 200px from the right edge, matching hero's 200px from left
enemy.y = hero.y;
// Determine enemy level: always one of heroLevel-1, heroLevel, heroLevel+1, or heroLevel+2
var possibleLevels = [Math.max(1, heroLevel - 1), heroLevel, heroLevel + 1, heroLevel + 2];
// Remove duplicates and clamp to at least 1
var uniqueLevels = [];
for (var i = 0; i < possibleLevels.length; i++) {
var lvl = Math.max(1, possibleLevels[i]);
if (uniqueLevels.indexOf(lvl) === -1) uniqueLevels.push(lvl);
}
// Randomly pick one
var enemyLevel = uniqueLevels[Math.floor(Math.random() * uniqueLevels.length)];
// Clamp enemyLevel to valid range for stats table
enemyLevel = Math.max(1, Math.min(enemyStatsByLevel.length + heroStatGrowth.hp.length, enemyLevel));
// Get stats for this level (enemy uses linear scaling after level 10)
var stats = getStatsForLevel(enemyLevel, "enemy");
enemy.hp = stats.hp;
enemy.attack = stats.atk;
enemy.attackSpeed = stats.atkSpd;
enemy.isAlive = true;
enemy.spawnAnim();
game.addChild(enemy);
currentEnemy = enemy;
// Store the enemy's level for display
currentEnemy.level = enemyLevel;
updateEnemyStatsDisplay();
// --- Start auto-fight timers ---
if (typeof heroAutoAttackTimer !== "undefined" && heroAutoAttackTimer) {
LK.clearInterval(heroAutoAttackTimer);
heroAutoAttackTimer = null;
}
if (typeof enemyAutoAttackTimer !== "undefined" && enemyAutoAttackTimer) {
LK.clearInterval(enemyAutoAttackTimer);
enemyAutoAttackTimer = null;
}
// Get hero stats for this level (hero uses compounded scaling after level 10)
var heroStats = getStatsForLevel(heroLevel, "hero");
hero.currentHp = heroStats.hp;
hero.attack = heroStats.atk;
hero.attackSpeed = heroStats.atkSpd;
// Hero attacks enemy
heroAutoAttackTimer = LK.setInterval(function () {
if (!currentEnemy || !currentEnemy.isAlive) {
LK.clearInterval(heroAutoAttackTimer);
heroAutoAttackTimer = null;
return;
}
// If one-shot mode, instantly defeat enemy
if (heroOneShotEnemies) {
currentEnemy.hp = 0;
} else {
currentEnemy.hp -= hero.attack;
}
// Flash enemy
if (currentEnemy) {
tween(currentEnemy, {
tint: 0xffffff
}, {
duration: 60,
onFinish: function onFinish() {
if (currentEnemy) {
tween(currentEnemy, {
tint: 0x2a6bde
}, {
duration: 60
});
}
}
});
}
updateEnemyStatsDisplay();
if (currentEnemy.hp <= 0) {
defeatEnemy();
LK.clearInterval(heroAutoAttackTimer);
heroAutoAttackTimer = null;
LK.clearInterval(enemyAutoAttackTimer);
enemyAutoAttackTimer = null;
}
}, 1000 / hero.attackSpeed);
// Enemy attacks hero
enemyAutoAttackTimer = LK.setInterval(function () {
if (!currentEnemy || !currentEnemy.isAlive) {
LK.clearInterval(enemyAutoAttackTimer);
enemyAutoAttackTimer = null;
return;
}
if (typeof hero.currentHp !== "number") {
// Defensive: ensure hero.currentHp is set
var hStats = getEnemyStatsForLevel(heroLevel);
hero.currentHp = hStats.hp;
}
if (!heroIsImmortal) {
// --- Shield-1 barrier logic: block damage with barrier first ---
if (equippedItems && equippedItems["shield-1"] && heroShield1Barrier > 0) {
var dmg = currentEnemy.attack;
if (heroShield1Barrier >= dmg) {
heroShield1Barrier -= dmg;
dmg = 0;
} else {
dmg -= heroShield1Barrier;
heroShield1Barrier = 0;
}
if (dmg > 0) {
hero.currentHp -= dmg;
}
} else {
hero.currentHp -= currentEnemy.attack;
}
// Flash hero
tween(hero, {
tint: 0xffffff
}, {
duration: 60,
onFinish: function onFinish() {
tween(hero, {
tint: 0xd83318
}, {
duration: 60
});
}
});
updateHeroStatsDisplay();
if (hero.currentHp <= 0) {
// Hero defeated, stop fighting
hero.currentHp = 0;
updateHeroStatsDisplay();
// Optionally, you can add defeat logic here (e.g. show game over)
LK.clearInterval(heroAutoAttackTimer);
heroAutoAttackTimer = null;
LK.clearInterval(enemyAutoAttackTimer);
enemyAutoAttackTimer = null;
// Start health refill (handled in update loop)
hero.healthRefilling = true; // Set to true so refill starts immediately
}
} else {
// If immortal, keep HP at max and flash gold
var stats = getStatsForLevel(heroLevel, "hero");
hero.currentHp = stats.hp;
updateHeroStatsDisplay();
tween(hero, {
tint: 0xFFD700
}, {
duration: 60,
onFinish: function onFinish() {
tween(hero, {
tint: 0xffffff
}, {
duration: 60
});
}
});
}
}, 1000 / currentEnemy.attackSpeed);
}
// Defeat enemy logic
function defeatEnemy() {
if (!currentEnemy || !currentEnemy.isAlive) return;
currentEnemy.isAlive = false;
// Stop auto-fight timers
if (typeof heroAutoAttackTimer !== "undefined" && heroAutoAttackTimer) {
LK.clearInterval(heroAutoAttackTimer);
heroAutoAttackTimer = null;
}
if (typeof enemyAutoAttackTimer !== "undefined" && enemyAutoAttackTimer) {
LK.clearInterval(enemyAutoAttackTimer);
enemyAutoAttackTimer = null;
}
currentEnemy.defeatAnim(function () {
if (currentEnemy) {
// --- Shield-1 drop logic ---
// Only drop for enemy levels 1-10
if (typeof currentEnemy.level !== "undefined" && currentEnemy.level >= 1 && currentEnemy.level <= 10) {
// 5% per level, capped at 50% for level 10
var shieldDropChance = currentEnemy.level * 0.05;
if (Math.random() < shieldDropChance) {
// Drop shield-1 at enemy's position
var shieldAsset = LK.getAsset('shield-1', {
anchorX: 0.5,
anchorY: 0.5,
x: currentEnemy.x,
y: currentEnemy.y
});
game.addChild(shieldAsset);
// Animate drop: move to itemBag position and shrink/fade out
// Get itemBag position in game coordinates
var itemBagGlobal = LK.gui.bottom.toGlobal({
x: itemBagImg.x,
y: itemBagImg.y
});
var itemBagGamePos = game.toLocal(itemBagGlobal);
tween(shieldAsset, {
x: itemBagGamePos.x,
y: itemBagGamePos.y,
scaleX: 0.3,
scaleY: 0.3,
alpha: 0.2
}, {
duration: 700,
easing: tween.cubicIn,
onFinish: function onFinish() {
shieldAsset.destroy();
// Add to inventory only (do not auto-equip)
inventory.push("shield-1");
}
});
}
}
currentEnemy.destroy();
currentEnemy = null;
updateEnemyStatsDisplay();
// If shield-1 is equipped, restore hero HP to new max after enemy defeat
if (equippedItems["shield-1"]) {
heroShield1Barrier = 250;
heroShield1BarrierMax = 250;
var stats = getStatsForLevel(heroLevel, "hero");
hero.currentHp = stats.hp;
updateHeroStatsDisplay();
}
}
// --- spawnBtn rotation logic on enemy defeat ---
// If hero HP is full, rotate 45deg right, else 90deg left
var heroStats = getStatsForLevel(heroLevel, "hero");
if (typeof hero.currentHp === "number" && hero.currentHp >= heroStats.hp) {
// Full health: rotate 45deg right
spawnBtnRotationQueue.push({
angle: Math.PI / 4
});
} else {
// Not full: rotate 90deg left
spawnBtnRotationQueue.push({
angle: -Math.PI / 2,
onFinish: function onFinish() {
// After 90deg left, if hero HP is still not full and no enemy, resume right rotation
if ((!currentEnemy || !currentEnemy.isAlive) && typeof hero.currentHp === "number" && hero.currentHp < getStatsForLevel(heroLevel, "hero").hp) {
if (!spawnBtn._rotatingWhileNotFull) {
spawnBtn._rotatingWhileNotFull = true;
// Start continuous right rotation (1 full spin per 1.2s)
var _rotateRightLoop = function rotateRightLoop() {
if (!spawnBtn._rotatingWhileNotFull) return;
var startRot = spawnBtn.rotation;
var endRot = startRot + Math.PI * 2;
tween(spawnBtn, {
rotation: endRot
}, {
duration: 1200,
easing: tween.linear,
onFinish: function onFinish() {
if (spawnBtn._rotatingWhileNotFull && (!currentEnemy || !currentEnemy.isAlive)) {
_rotateRightLoop();
}
}
});
};
tween.stop(spawnBtn, {
rotation: true
});
_rotateRightLoop();
}
}
}
});
}
processSpawnBtnRotationQueue();
// Start hero health refill after any battle (at same refill speed as on defeat)
// If hero died (currentHp <= 0), double the refill duration
if (typeof hero.currentHp === "number" && hero.currentHp < getStatsForLevel(heroLevel, "hero").hp && !hero.healthRefilling) {
hero.healthRefilling = true;
hero.refillStartTime = Date.now();
var stats = getStatsForLevel(heroLevel, "hero");
hero.refillFromHp = typeof hero.currentHp === "number" ? hero.currentHp : 0;
hero.refillToHp = stats.hp;
// Set refill duration to 1/5th speed (5x slower), or 1/10th (10x slower) if hero died
var baseDuration = 1000; // original duration
if (typeof hero.currentHp === "number" && hero.currentHp <= 0) {
hero.refillDuration = baseDuration * 10; // 10x slower if died
} else {
hero.refillDuration = baseDuration * 5; // 5x slower otherwise
}
}
});
// --- Gold drop amount table per level ---
var goldDropByLevel = [{
min: 1,
max: 5
},
// Level 1
{
min: 3,
max: 8
},
// Level 2
{
min: 5,
max: 12
},
// Level 3
{
min: 8,
max: 16
},
// Level 4
{
min: 12,
max: 20
},
// Level 5
{
min: 15,
max: 25
},
// Level 6
{
min: 20,
max: 32
},
// Level 7
{
min: 25,
max: 40
},
// Level 8
{
min: 30,
max: 48
},
// Level 9
{
min: 35,
max: 60
} // Level 10
];
// Determine gold drop amount for this level
var goldDropStats = goldDropByLevel[Math.max(0, Math.min(heroLevel - 1, goldDropByLevel.length - 1))];
var goldDropAmount = Math.floor(Math.random() * (goldDropStats.max - goldDropStats.min + 1)) + goldDropStats.min;
// Drop gold at the far right (where enemy was)
// Animate and auto-collect gold after enemy defeat
// Calculate the gold display position in game coordinates (top right, where goldTxt is)
var goldDisplayGlobal = LK.gui.topRight.toGlobal({
x: goldTxt.x,
y: goldTxt.y
});
var goldDisplayGamePos = game.toLocal(goldDisplayGlobal);
for (var i = 0; i < goldDropAmount; i++) {
var gold = new Gold();
gold.x = 2048 - 200;
gold.y = hero.y;
gold.scaleX = 1;
gold.scaleY = 1;
gold.alpha = 1;
game.addChild(gold);
golds.push(gold);
// Drop to random y near enemy
var dropY = gold.y + (Math.random() * 120 - 60);
(function (goldObj, idx) {
goldObj.dropAnim(goldObj.x, dropY, function () {
// Stagger collection for nice effect
LK.setTimeout(function () {
// Animate to gold display and collect
goldObj.collectAnim(goldDisplayGamePos.x, goldDisplayGamePos.y, function () {
goldAmount += 1;
updateGoldDisplay();
goldObj.destroy();
});
}, 80 * idx);
});
})(gold, i);
}
// --- Shield-1 drop logic ---
// Only drop for enemy levels 1-10
if (typeof enemyLevel !== "undefined" && enemyLevel >= 1 && enemyLevel <= 10) {
// 5% per level, capped at 50% for level 10
var shieldDropChance = enemyLevel * 0.05;
if (Math.random() < shieldDropChance) {
// Drop shield-1 at enemy's position
var shieldAsset = LK.getAsset('shield-1', {
anchorX: 0.5,
anchorY: 0.5,
x: 2048 - 200,
y: hero.y
});
game.addChild(shieldAsset);
// Animate drop (fall down a bit, then fade out)
var dropTargetY = shieldAsset.y + 180 + (Math.random() * 40 - 20);
tween(shieldAsset, {
y: dropTargetY
}, {
duration: 400,
easing: tween.bounceOut,
onFinish: function onFinish() {
// After a short delay, fade out and destroy
LK.setTimeout(function () {
tween(shieldAsset, {
alpha: 0
}, {
duration: 350,
onFinish: function onFinish() {
shieldAsset.destroy();
}
});
}, 900);
}
});
}
}
}
// --- Shield-1 drop logic ---
// Only drop for enemy levels 1-10
if (typeof enemyLevel !== "undefined" && enemyLevel >= 1 && enemyLevel <= 10) {
// 5% per level, capped at 50% for level 10
var shieldDropChance = enemyLevel * 0.05;
if (Math.random() < shieldDropChance) {
// Drop shield-1 at enemy's position
var shieldAsset = LK.getAsset('shield-1', {
anchorX: 0.5,
anchorY: 0.5,
x: 2048 - 200,
y: hero.y
});
game.addChild(shieldAsset);
// Animate drop (fall down a bit, then fade out)
var dropTargetY = shieldAsset.y + 180 + (Math.random() * 40 - 20);
tween(shieldAsset, {
y: dropTargetY
}, {
duration: 400,
easing: tween.bounceOut,
onFinish: function onFinish() {
// After a short delay, fade out and destroy
LK.setTimeout(function () {
tween(shieldAsset, {
alpha: 0
}, {
duration: 350,
onFinish: function onFinish() {
shieldAsset.destroy();
}
});
}, 900);
}
});
}
}
// Collect gold logic
function collectGold(gold) {
// Animate to gold display
var guiGoldPos = LK.gui.topRight.toLocal(gold.toGlobal({
x: 0,
y: 0
}));
goldAmount += 1;
updateGoldDisplay();
gold.collectAnim(guiGoldPos.x, guiGoldPos.y, function () {
gold.destroy();
});
}
// Upgrade logic
function upgradeHero() {
var upgradeCost = heroLevel * 5;
if (goldAmount < upgradeCost) {
// Flash gold text red
tween(goldTxt, {
tint: 0xff4444
}, {
duration: 120,
onFinish: function onFinish() {
tween(goldTxt, {
tint: 0xFFE066
}, {
duration: 120
});
}
});
return;
}
goldAmount -= upgradeCost;
heroLevel += 1;
hero.level = heroLevel;
// Refill hero health to full on level up
var stats = getStatsForLevel(heroLevel, "hero");
hero.attack = stats.atk;
hero.attackSpeed = stats.atkSpd;
hero.currentHp = stats.hp;
hero.healthRefilling = false;
updateGoldDisplay();
updateUpgradeDisplay();
updateHeroStatsDisplay();
updateEnemyStatsDisplay();
hero.flashUpgrade();
}
// --- Event Handlers ---
// Spawn button tap
// --- spawnBtn rotation queue state ---
var spawnBtnRotationQueue = [];
var spawnBtnIsRotating = false;
function processSpawnBtnRotationQueue() {
if (spawnBtnIsRotating || spawnBtnRotationQueue.length === 0) return;
spawnBtnIsRotating = true;
var next = spawnBtnRotationQueue.shift();
var targetRotation = spawnBtn.rotation + next.angle;
tween(spawnBtn, {
rotation: targetRotation
}, {
duration: 180,
easing: tween.cubicInOut,
onFinish: function onFinish() {
spawnBtn.rotation = targetRotation;
spawnBtnIsRotating = false;
if (typeof next.onFinish === "function") next.onFinish();
// Process next in queue
processSpawnBtnRotationQueue();
}
});
}
spawnBtn.down = function (x, y, obj) {
// Animate shrink
tween.stop(spawnBtn, {
scaleX: true,
scaleY: true
});
tween(spawnBtn, {
scaleX: 0.9,
scaleY: 0.9
}, {
duration: 100,
easing: tween.cubicIn,
onFinish: function onFinish() {
tween(spawnBtn, {
scaleX: 1,
scaleY: 1
}, {
duration: 100,
easing: tween.cubicOut,
onFinish: function onFinish() {
// After animation, trigger action
// Queue 45deg right rotation
spawnBtnRotationQueue.push({
angle: Math.PI / 4
});
processSpawnBtnRotationQueue();
spawnEnemy();
}
});
}
});
};
// Enemy tap (defeat)
function onEnemyDown(x, y, obj) {
if (!currentEnemy || !currentEnemy.isAlive) return;
currentEnemy.hp -= 1;
// Flash enemy
tween(currentEnemy, {
tint: 0xffffff
}, {
duration: 60,
onFinish: function onFinish() {
tween(currentEnemy, {
tint: 0x2a6bde
}, {
duration: 60
});
}
});
if (currentEnemy.hp <= 0) {
defeatEnemy();
}
}
// Gold tap (collect)
function onGoldDown(x, y, obj) {
for (var i = golds.length - 1; i >= 0; i--) {
var gold = golds[i];
if (gold && gold.containsPoint && gold.containsPoint({
x: x,
y: y
})) {
collectGold(gold);
golds.splice(i, 1);
break;
}
}
}
// Upgrade button tap
upgradeBtn.down = function (x, y, obj) {
// Animate shrink
tween.stop(upgradeBtn, {
scaleX: true,
scaleY: true
});
tween(upgradeBtn, {
scaleX: 0.9,
scaleY: 0.9
}, {
duration: 100,
easing: tween.cubicIn,
onFinish: function onFinish() {
tween(upgradeBtn, {
scaleX: 1,
scaleY: 1
}, {
duration: 100,
easing: tween.cubicOut,
onFinish: function onFinish() {
// After animation, trigger action
upgradeHero();
}
});
}
});
};
// Work button tap
workBtn.down = function (x, y, obj) {
if (!workBtnActive) return;
// Prevent workBtn press if hero HP is not full
if (typeof hero.currentHp === "number" && hero.currentHp < getStatsForLevel(heroLevel, "hero").hp) return;
// Animate shrink
tween.stop(workBtn, {
scaleX: true,
scaleY: true
});
tween(workBtn, {
scaleX: 0.9,
scaleY: 0.9
}, {
duration: 100,
easing: tween.cubicIn,
onFinish: function onFinish() {
tween(workBtn, {
scaleX: 1,
scaleY: 1
}, {
duration: 100,
easing: tween.cubicOut,
onFinish: function onFinish() {
// After animation, trigger action
// Calculate duration: let duration = Math.max(10000, ((level ** 1.2) * 60000) / (attackSpeed * attack));
var stats = getStatsForLevel(heroLevel, "hero");
var denominator = stats.atkSpd * stats.atk;
var duration = 10; // Defensive minimum in seconds
if (Number.isFinite(denominator) && denominator > 0) {
duration = Math.max(10000, Math.pow(heroLevel, 1.2) * 60000 / denominator) / 1000; // ms to seconds
}
// Multiply duration by heroLevel (time level)
duration = duration * heroLevel;
workBtnActive = false;
workBtnDuration = duration;
workBtnTimeLeft = duration;
setSpawnBtnEnabled(false);
showWorkBtnCountdown(workBtnTimeLeft);
// Start timer to update countdown
if (workBtnTimer) {
LK.clearInterval(workBtnTimer);
workBtnTimer = null;
}
// Start rotation tween for workBtn at 20% of previous speed
tween.stop(workBtn, {
rotation: true
});
workBtn.rotation = 0;
// Calculate totalRotations for 20% speed (i.e., 1/5th the previous speed, so 1/5th the number of spins)
var baseTotalRotations = 2 + Math.floor(workBtnDuration);
var slowTotalRotations = Math.max(1, Math.round(baseTotalRotations * 0.2)); // at least 1 full spin
tween(workBtn, {
rotation: Math.PI * 2 * slowTotalRotations
}, {
duration: workBtnDuration * 1000,
easing: tween.linear,
onFinish: function onFinish() {
workBtn.rotation = 0; // reset rotation to 0 for next use
}
});
workBtnTimer = LK.setInterval(function () {
workBtnTimeLeft -= 0.1;
if (workBtnTimeLeft < 0) workBtnTimeLeft = 0;
showWorkBtnCountdown(workBtnTimeLeft);
if (workBtnTimeLeft <= 0) {
// Timer done
LK.clearInterval(workBtnTimer);
workBtnTimer = null;
workBtnActive = true;
setSpawnBtnEnabled(true);
hideWorkBtnCountdown();
// Stop rotation tween and reset rotation
tween.stop(workBtn, {
rotation: true
});
workBtn.rotation = 0;
// Drop gold: (main character's level * attack speed + attack / 2) / random(1-5)
var stats = getStatsForLevel(heroLevel, "hero");
var divisor = Math.floor(Math.random() * 5) + 1;
var goldVal = heroLevel * stats.atkSpd + stats.atk / 2;
var goldDrop = Math.max(1, Math.floor(goldVal / divisor));
// Calculate gold display position in game coordinates (top right, where goldTxt is)
var goldDisplayGlobal = LK.gui.topRight.toGlobal({
x: goldTxt.x,
y: goldTxt.y
});
var goldDisplayGamePos = game.toLocal(goldDisplayGlobal);
// Spawn gold under hero and animate to gold display
for (var i = 0; i < goldDrop; i++) {
var gold = new Gold();
gold.x = hero.x;
gold.y = hero.y + hero.height / 2 + 60 + (Math.random() * 40 - 20);
gold.scaleX = 1;
gold.scaleY = 1;
gold.alpha = 1;
game.addChild(gold);
golds.push(gold);
// Animate to gold display
(function (goldObj, idx) {
// Retreat: move down a bit, then animate to gold display
var retreatY = goldObj.y + 80 + (Math.random() * 30 - 15);
goldObj.dropAnim(goldObj.x, retreatY, function () {
LK.setTimeout(function () {
goldObj.collectAnim(goldDisplayGamePos.x, goldDisplayGamePos.y, function () {
goldAmount += 1;
updateGoldDisplay();
goldObj.destroy();
});
}, 80 * idx);
});
})(gold, i);
}
}
}, 100);
}
});
}
});
};
// --- Attach event handlers to game ---
game.down = function (x, y, obj) {
// Check if tap is on enemy
if (currentEnemy && currentEnemy.isAlive && currentEnemy.containsPoint && currentEnemy.containsPoint({
x: x,
y: y
})) {
onEnemyDown(x, y, obj);
return;
}
// Check if tap is on any gold
for (var i = golds.length - 1; i >= 0; i--) {
var gold = golds[i];
if (gold && gold.containsPoint && gold.containsPoint({
x: x,
y: y
})) {
collectGold(gold);
golds.splice(i, 1);
return;
}
}
// (Buttons handled by their own .down)
};
// --- Game update loop ---
game.update = function () {
// Remove golds that are invisible
for (var i = golds.length - 1; i >= 0; i--) {
var gold = golds[i];
if (gold.alpha <= 0.01) {
gold.destroy();
golds.splice(i, 1);
}
}
// --- Hero health refill logic ---
if (typeof hero.healthRefilling === "undefined") hero.healthRefilling = false;
if (typeof hero.refillStartTime === "undefined") hero.refillStartTime = 0;
if (typeof hero.refillDuration === "undefined") hero.refillDuration = 5000; // Default, will be set below
if (typeof hero.refillFromHp === "undefined") hero.refillFromHp = 0;
if (typeof hero.refillToHp === "undefined") hero.refillToHp = 0;
if (typeof hero.currentHp === "number" && hero.currentHp <= 0 && !hero.healthRefilling) {
// Start refill
hero.healthRefilling = true;
hero.refillStartTime = Date.now();
var stats = getStatsForLevel(heroLevel, "hero");
hero.refillFromHp = 0;
hero.refillToHp = stats.hp;
// Set refill duration to 1/5th speed (5x slower)
hero.refillDuration = 5000; // fallback
var baseDuration = 1000; // original duration
if (hero.currentHp <= 0) {
hero.refillDuration = baseDuration * 10; // 10x slower if died
} else {
hero.refillDuration = baseDuration * 5; // 5x slower otherwise
}
// Destroy enemy immediately (so a new one can only be summoned after refill)
if (currentEnemy && currentEnemy.isAlive) {
currentEnemy.isAlive = false;
currentEnemy.defeatAnim(function () {
if (currentEnemy) {
currentEnemy.destroy();
currentEnemy = null;
updateEnemyStatsDisplay();
}
});
}
}
if (hero.healthRefilling) {
var now = Date.now();
var elapsed = now - hero.refillStartTime;
var t = Math.min(1, elapsed / hero.refillDuration);
var stats = getStatsForLevel(heroLevel, "hero");
hero.currentHp = Math.round(hero.refillFromHp + (hero.refillToHp - hero.refillFromHp) * t);
if (t >= 1) {
hero.currentHp = stats.hp;
hero.healthRefilling = false;
// After refill, if shield-1 is equipped, restore barrier to max
if (equippedItems && equippedItems["shield-1"]) {
heroShield1Barrier = 250;
heroShield1BarrierMax = 250;
}
}
}
// Prevent spawnBtn and workBtn from being pressed unless hero HP is full
if (typeof hero.currentHp === "number" && hero.currentHp < getStatsForLevel(heroLevel, "hero").hp) {
setSpawnBtnEnabled(false);
// Disable workBtn and make it semi-transparent
workBtn.alpha = 0.5;
workBtn.interactive = false;
workBtn.buttonMode = false;
// --- Rotate spawnBtn to the right while hero HP is not full, but only if NO enemy is alive ---
if (!spawnBtn._rotatingWhileNotFull && (!currentEnemy || !currentEnemy.isAlive)) {
var _rotateRightLoop = function rotateRightLoop() {
if (!spawnBtn._rotatingWhileNotFull) return;
var startRot = spawnBtn.rotation;
var endRot = startRot + Math.PI * 2;
tween(spawnBtn, {
rotation: endRot
}, {
duration: 1200,
easing: tween.linear,
onFinish: function onFinish() {
// Continue loop if still not full and still no enemy
if (spawnBtn._rotatingWhileNotFull && (!currentEnemy || !currentEnemy.isAlive)) {
_rotateRightLoop();
}
}
});
};
tween.stop(spawnBtn, {
rotation: true
});
// Start continuous right rotation (1 full spin per 1.2s)
spawnBtn._rotatingWhileNotFull = true;
_rotateRightLoop();
}
} else if (!hero.healthRefilling && workBtnActive) {
setSpawnBtnEnabled(true);
// Enable workBtn and restore full opacity
workBtn.alpha = 1;
workBtn.interactive = true;
workBtn.buttonMode = true;
// If hero HP is full, remove all effects and restore color
tween.stop(hero, {
tint: true,
alpha: true,
scaleX: true,
scaleY: true
});
hero.tint = 0xffffff;
hero.alpha = 1;
hero.scaleX = 1;
hero.scaleY = 1;
// --- Stop spawnBtn rotation and reset to original position, with 45deg right spin if HP just became full and no enemy is alive ---
if (spawnBtn._rotatingWhileNotFull) {
spawnBtn._rotatingWhileNotFull = false;
tween.stop(spawnBtn, {
rotation: true
});
// If hero HP just became full and no enemy is alive, rotate 45deg right and then reset to 0
if ((!currentEnemy || !currentEnemy.isAlive) && typeof hero.currentHp === "number" && hero.currentHp === getStatsForLevel(heroLevel, "hero").hp) {
// Animate 45deg right, then reset to 0
tween(spawnBtn, {
rotation: spawnBtn.rotation + Math.PI / 4
}, {
duration: 180,
easing: tween.cubicOut,
onFinish: function onFinish() {
tween(spawnBtn, {
rotation: 0
}, {
duration: 200,
easing: tween.cubicOut
});
}
});
} else {
// Animate back to original rotation (0)
tween(spawnBtn, {
rotation: 0
}, {
duration: 200,
easing: tween.cubicOut
});
}
}
}
// Keep hero slightly lower than vertical center
hero.y = 2732 / 2 + 200;
// Keep enemy vertically centered and at far right if alive
if (currentEnemy && currentEnemy.isAlive) {
currentEnemy.x = 2048 - 200;
currentEnemy.y = hero.y + 100; // Move enemy a little lower
}
updateHeroStatsDisplay();
updateEnemyStatsDisplay();
// Keep workBtnCountdownTxt positioned under workBtn if visible
if (workBtnCountdownTxt && workBtnCountdownTxt.visible) {
workBtnCountdownTxt.x = workBtn.x;
workBtnCountdownTxt.y = workBtn.y + workBtn.height / 2 + 8;
}
};
// --- Initial UI state ---
updateGoldDisplay();
updateUpgradeDisplay();
updateHeroStatsDisplay();
updateEnemyStatsDisplay();