/**** * Plugins ****/ var tween = LK.import("@upit/tween.v1"); /**** * Classes ****/ // Burrower monster var Burrower = Container.expand(function () { var self = Container.call(this); var burrowerGfx = self.attachAsset('burrower', { anchorX: 0.5, anchorY: 0.5 }); self.radius = burrowerGfx.width / 2; self.speed = 2.2; self.hp = 1; self.type = 'burrower'; self.burrowTimer = 0; self.burrowInterval = 90 + Math.floor(Math.random() * 60); self.burrowed = false; // Track lastX for direction self.lastX = self.x; self.facing = 1; // 1 = right, -1 = left self.update = function () { if (self.burrowed) { self.burrowTimer--; if (self.burrowTimer <= 0) { // Unburrow self.burrowed = false; burrowerGfx.alpha = 1; self.burrowTimer = self.burrowInterval; } // Move fast toward hero var dx = hero.x - self.x; var dy = hero.y - self.y; var d = Math.sqrt(dx * dx + dy * dy); if (d > 0.1) { self.x += dx / d * (self.speed * 2.2); self.y += dy / d * (self.speed * 2.2); } } else { self.burrowTimer--; if (self.burrowTimer <= 0) { // Burrow self.burrowed = true; burrowerGfx.alpha = 0.3; self.burrowTimer = 36 + Math.floor(Math.random() * 24); } // Move toward hero var dx = hero.x - self.x; var dy = hero.y - self.y; var d = Math.sqrt(dx * dx + dy * dy); if (d > 0.1) { self.x += dx / d * self.speed; self.y += dy / d * self.speed; } } // Flip sprite depending on direction if (self.x > self.lastX) { if (self.facing !== 1) { burrowerGfx.scaleX = 1; self.facing = 1; } } else if (self.x < self.lastX) { if (self.facing !== -1) { burrowerGfx.scaleX = -1; self.facing = -1; } } self.lastX = self.x; }; self.flash = function () { tween(burrowerGfx, { alpha: 0.1 }, { duration: 40, onFinish: function onFinish() { tween(burrowerGfx, { alpha: self.burrowed ? 0.3 : 1 }, { duration: 40 }); } }); }; return self; }); // Chaser monster var Chaser = Container.expand(function () { var self = Container.call(this); var chaserGfx = self.attachAsset('chaser', { anchorX: 0.5, anchorY: 0.5 }); self.radius = chaserGfx.width / 2; self.speed = 1.5; self.hp = 2; self.type = 'chaser'; // Track lastX for direction self.lastX = self.x; self.facing = 1; // 1 = right, -1 = left self.update = function () { // Move toward hero var dx = hero.x - self.x; var dy = hero.y - self.y; var d = Math.sqrt(dx * dx + dy * dy); if (d > 0.1) { self.x += dx / d * self.speed; self.y += dy / d * self.speed; } // Flip sprite depending on direction if (self.x > self.lastX) { if (self.facing !== 1) { chaserGfx.scaleX = 1; self.facing = 1; } } else if (self.x < self.lastX) { if (self.facing !== -1) { chaserGfx.scaleX = -1; self.facing = -1; } } self.lastX = self.x; }; self.flash = function () { tween(chaserGfx, { alpha: 0.3 }, { duration: 60, onFinish: function onFinish() { tween(chaserGfx, { alpha: 1 }, { duration: 60 }); } }); }; return self; }); // Gold coin var Gold = Container.expand(function () { var self = Container.call(this); var goldGfx = self.attachAsset('gold', { anchorX: 0.5, anchorY: 0.5 }); self.radius = goldGfx.width / 2; self.vx = 0; self.vy = 0; self.update = function () { self.x += self.vx; self.y += self.vy; // Slow down self.vx *= 0.92; self.vy *= 0.92; }; 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 }); // Add sword asset var swordGfx = self.attachAsset('herosword', { anchorX: 0.1, anchorY: 0.5, x: heroGfx.width * 0.5 + 30, y: 0, rotation: 0 }); swordGfx.rotation = 0; swordGfx.visible = true; self.addChild(swordGfx); self.radius = heroGfx.width / 2; self.speed = 22; self.shootCooldown = 0; self.shootInterval = 18; // frames self.invuln = 0; self.hp = 3; // Sword swing state self.swordSwing = 0; // 0 = idle, >0 = animating self.swordSwingDir = 1; // 1 = right, -1 = left self.update = function () { if (self.shootCooldown > 0) self.shootCooldown--; if (self.invuln > 0) self.invuln--; // --- Rotate hero photo only left and right (flip horizontally) --- if (typeof self.lastX === "undefined") self.lastX = self.x; var dxMove = self.x - self.lastX; if (Math.abs(dxMove) > 0.5) { // Only flip if actually moving horizontally if (dxMove > 0) { // Moving right heroGfx.scaleX = 1; } else if (dxMove < 0) { // Moving left heroGfx.scaleX = -1; } } self.lastX = self.x; // --- End hero photo left/right flip --- // Always point sword at nearest monster, animate a subtle swing var nearest = null; var minDist = 999999; // Defensive: check if monsters is defined and is an array if (typeof monsters !== "undefined" && monsters && typeof monsters.length === "number") { // Defensive: check if monsters is defined and is an array if (typeof monsters !== "undefined" && monsters && typeof monsters.length === "number") { for (var mi = 0; mi < monsters.length; ++mi) { var m = monsters[mi]; if (m.type === 'burrower' && m.burrowed) continue; var dx = m.x - self.x; var dy = m.y - self.y; var d = Math.sqrt(dx * dx + dy * dy); if (d < minDist) { minDist = d; nearest = m; } } } } if (nearest) { var dx = nearest.x - self.x; var dy = nearest.y - self.y; var angle = Math.atan2(dy, dx); // Animate a subtle swing: oscillate +/- 0.18 radians var swing = Math.sin(LK.ticks / 10) * 0.18; // Sword hit animation: if recently hit, flash sword if (self.swordHitAnim && self.swordHitAnim > 0) { // Animate sword: scale up and flash color swordGfx.scaleX = 1.3 + 0.3 * Math.sin(self.swordHitAnim * Math.PI / 6); swordGfx.scaleY = 1.3 + 0.3 * Math.sin(self.swordHitAnim * Math.PI / 6); swordGfx.tint = 0xffe066; self.swordHitAnim--; if (self.swordHitAnim === 0) { swordGfx.scaleX = 1; swordGfx.scaleY = 1; swordGfx.tint = 0xf5e663; } } else { swordGfx.scaleX = 1; swordGfx.scaleY = 1; swordGfx.tint = 0xf5e663; } swordGfx.rotation = angle + swing; // Position sword at edge of hero var dist = heroGfx.width * 0.5 + 30; swordGfx.x = Math.cos(angle) * dist; swordGfx.y = Math.sin(angle) * dist; } else { // No monsters: point up, subtle swing var angle = -Math.PI / 2; var swing = Math.sin(LK.ticks / 10) * 0.18; swordGfx.rotation = angle + swing; var dist = heroGfx.width * 0.5 + 30; swordGfx.x = Math.cos(angle) * dist; swordGfx.y = Math.sin(angle) * dist; } }; // Flash when hit self.flash = function () { tween(heroGfx, { alpha: 0.3 }, { duration: 80, onFinish: function onFinish() { tween(heroGfx, { alpha: 1 }, { duration: 80 }); } }); }; // Sword swing trigger self.swingSword = function (dir) { self.swordSwing = self.swordSwingMax = 10; self.swordSwingDir = dir || 1; swordGfx.rotation = 0; }; return self; }); // Monster bullet (spit) var MonsterBullet = Container.expand(function () { var self = Container.call(this); var bulletGfx = self.attachAsset('herobullet', { anchorX: 0.5, anchorY: 0.5 }); bulletGfx.tint = 0x7cfd3a; self.radius = bulletGfx.width / 2; self.speed = 7; self.dirX = 0; self.dirY = 1; self.damage = 1; self.update = function () { self.x += self.dirX * self.speed; self.y += self.dirY * self.speed; // Rotate bullet to match direction if (typeof bulletGfx !== "undefined") { bulletGfx.rotation = Math.atan2(self.dirY, self.dirX); } }; return self; }); // Spitter monster var Spitter = Container.expand(function () { var self = Container.call(this); var spitterGfx = self.attachAsset('spitter', { anchorX: 0.5, anchorY: 0.5 }); self.radius = spitterGfx.width / 2; self.speed = 1.1; self.hp = 2; self.type = 'spitter'; self.shootCooldown = 0; self.shootInterval = 160 + Math.floor(Math.random() * 60); // fire even slower // Track lastX for direction self.lastX = self.x; self.facing = 1; // 1 = right, -1 = left self.update = function () { // Keep distance from hero, shoot var dx = hero.x - self.x; var dy = hero.y - self.y; var d = Math.sqrt(dx * dx + dy * dy); if (d > 400) { self.x += dx / d * self.speed; self.y += dy / d * self.speed; } else if (d < 320) { self.x -= dx / d * self.speed; self.y -= dy / d * self.speed; } // Flip sprite depending on direction if (self.x > self.lastX) { if (self.facing !== 1) { spitterGfx.scaleX = 1; self.facing = 1; } } else if (self.x < self.lastX) { if (self.facing !== -1) { spitterGfx.scaleX = -1; self.facing = -1; } } self.lastX = self.x; if (self.shootCooldown > 0) self.shootCooldown--;else { // Spit at hero var spit = new MonsterBullet(); spit.x = self.x; spit.y = self.y; var tx = hero.x - self.x; var ty = hero.y - self.y; var td = Math.sqrt(tx * tx + ty * ty); spit.dirX = td > 0.1 ? tx / td : 0; spit.dirY = td > 0.1 ? ty / td : 0; spit.speed = 3.2; // even slower bullet speed spit.radius = 32; // Set initial rotation to match direction if (spit.children && spit.children.length > 0 && typeof spit.children[0].rotation !== "undefined") { spit.children[0].rotation = Math.atan2(spit.dirY, spit.dirX); } monsterBullets.push(spit); game.addChild(spit); self.shootCooldown = self.shootInterval; } }; self.flash = function () { tween(spitterGfx, { alpha: 0.3 }, { duration: 60, onFinish: function onFinish() { tween(spitterGfx, { alpha: 1 }, { duration: 60 }); } }); }; return self; }); /**** * Initialize Game ****/ var game = new LK.Game({ backgroundColor: 0x181818 }); /**** * Game Code ****/ // Global variables // Hero // Chaser monster // Spitter monster // Burrower monster // Hero bullet // Gold coin // Store button background // Store button highlight // Sound effects var hero = new Hero(); hero.maxhp = 3; hero.speed = 22; // Ensure base speed is set var monsters = []; var monsterBullets = []; var golds = []; var gold = 0; var storeBtns = []; var passives = []; var availablePassives = [{ id: 'lifesteal', name: 'Lifesteal', desc: 'Gain 1 HP every 8 gold picked up.', weight: 2, // More likely to appear apply: function apply() { passives.push({ id: 'lifesteal', stacks: 1, counter: 0 }); } }, { id: 'reflect', name: 'Reflect Shield', desc: 'Occasionally reflects a monster bullet.', weight: 1, // Less likely apply: function apply() { passives.push({ id: 'reflect', stacks: 1, timer: 0 }); } }, { id: 'regen', name: 'Regeneration', desc: 'Regenerate 1 HP every 12 seconds.', weight: 1, // Less likely apply: function apply() { passives.push({ id: 'regen', stacks: 1, timer: 0 }); } }, { id: 'speed', name: 'Speed Boost', desc: 'Move much faster.', weight: 2, // More likely apply: function apply() { // Add speed passive to passives so it persists and can stack var found = false; for (var i = 0; i < passives.length; ++i) { if (passives[i].id === 'speed') { passives[i].stacks++; found = true; break; } } if (!found) { passives.push({ id: 'speed', stacks: 1 }); } // Recalculate hero speed based on number of speed passives var baseSpeed = 22; var speedStacks = 0; for (var i = 0; i < passives.length; ++i) { if (passives[i].id === 'speed') { speedStacks = passives[i].stacks; break; } } hero.speed = baseSpeed + 7 * speedStacks; } }, { id: 'goldboost', name: 'Gold Magnet', desc: 'Gold is attracted to you.', weight: 3, // Most likely to appear apply: function apply() { passives.push({ id: 'goldboost', stacks: 1 }); } }, { id: 'maxhp', name: 'Max HP Up', desc: 'Increase max HP by 1.', weight: 2, // More likely apply: function apply() { if (typeof hero.maxhp === "undefined") hero.maxhp = 3; hero.maxhp++; if (hero.hp > hero.maxhp) hero.hp = hero.maxhp; updateHP(); } }, // --- New items below --- { id: 'atkboost', name: 'Attack Up', desc: 'Deal +1 damage with sword.', weight: 2, apply: function apply() { var found = false; for (var i = 0; i < passives.length; ++i) { if (passives[i].id === 'atkboost') { passives[i].stacks++; found = true; break; } } if (!found) { passives.push({ id: 'atkboost', stacks: 1 }); } } }, { id: 'greed', name: 'Greed', desc: 'Gain +1 gold per pickup.', weight: 2, apply: function apply() { var found = false; for (var i = 0; i < passives.length; ++i) { if (passives[i].id === 'greed') { passives[i].stacks++; found = true; break; } } if (!found) { passives.push({ id: 'greed', stacks: 1 }); } } }, { id: 'healburst', name: 'Heal Burst', desc: 'Heal 3 HP instantly.', weight: 1, apply: function apply() { hero.hp += 3; if (typeof hero.maxhp === "number" && hero.hp > hero.maxhp) hero.hp = hero.maxhp; updateHP(); } }, { id: 'safeshield', name: 'Safe Shield', desc: 'Become invulnerable for 3 seconds.', weight: 1, apply: function apply() { hero.invuln = 60 * 3; hero.flash(); } }]; // Store state var storeChoices = []; function getRandomPassive(excludeIds) { var pool = []; // Build weighted pool for (var i = 0; i < availablePassives.length; ++i) { var id = availablePassives[i].id; var already = false; for (var j = 0; j < excludeIds.length; ++j) { if (excludeIds[j] === id) { already = true; break; } } if (!already) { var weight = availablePassives[i].weight || 1; for (var w = 0; w < weight; ++w) { pool.push(availablePassives[i]); } } } if (pool.length === 0) { // fallback: all passives, weighted for (var i = 0; i < availablePassives.length; ++i) { var weight = availablePassives[i].weight || 1; for (var w = 0; w < weight; ++w) { pool.push(availablePassives[i]); } } } return pool[Math.floor(Math.random() * pool.length)]; } function rerollStore() { storeChoices = []; var exclude = []; for (var i = 0; i < 3; ++i) { var p = getRandomPassive(exclude); storeChoices.push(p); exclude.push(p.id); } updateStoreBtns(); } function updateStoreBtns() { for (var i = 0; i < storeBtns.length; ++i) { var btn = storeBtns[i]; var p = storeChoices[i]; btn.passive = p; // Add a little sparkle/emoji to the name for beauty var prettyName = "★ " + p.name + " ★"; btn.txt.setText(prettyName); btn.shadow.setText(prettyName); btn.desc.setText(p.desc); btn.price.setText("5"); } } // GUI var goldTxt = new Text2('0', { size: 90, fill: 0xFFE066 }); goldTxt.anchor.set(1, 0); LK.gui.topRight.addChild(goldTxt); var hpTxt = new Text2('❤❤❤', { size: 90, fill: 0xFF3A3A }); hpTxt.anchor.set(0, 0); LK.gui.top.addChild(hpTxt); // Store buttons (top center, beautified) for (var i = 0; i < 3; ++i) { var btn = new Container(); var bg = btn.attachAsset('storebtn', { anchorX: 0.5, anchorY: 0 }); // Center horizontally, stack vertically with spacing btn.x = 2048 / 2; btn.y = 160 + i * 160; // Add a subtle drop shadow effect by layering a slightly offset, darker text btn.shadow = new Text2('', { size: 64, fill: 0x222222 }); btn.shadow.anchor.set(0.5, 0); btn.shadow.x = 0 + 3; btn.shadow.y = 10 + 3; btn.addChild(btn.shadow); // Beautified item name: larger, bold, gold color, centered btn.txt = new Text2('', { size: 64, fill: 0xFFE066, font: "Impact, 'Arial Black', Tahoma" }); btn.txt.anchor.set(0.5, 0); btn.txt.x = 0; btn.txt.y = 10; btn.addChild(btn.txt); // Description: slightly larger, italic, silver color, centered btn.desc = new Text2('', { size: 40, fill: 0xCCCCCC, font: "italic 36px Tahoma" }); btn.desc.anchor.set(0.5, 0); btn.desc.x = 0; btn.desc.y = 80; btn.addChild(btn.desc); // Price: gold coin color, bold, left side btn.price = new Text2('5', { size: 54, fill: 0xFFD700, font: "bold 48px Tahoma" }); btn.price.anchor.set(0, 0); btn.price.x = -180; btn.price.y = 40; btn.addChild(btn.price); btn.index = i; storeBtns.push(btn); game.addChild(btn); } // Place hero in center hero.x = 2048 / 2; hero.y = 2732 / 2; hero.targetX = hero.x; hero.targetY = hero.y; game.addChild(hero); // Initial monsters function spawnMonster() { var edge = Math.floor(Math.random() * 4); var x, y; if (edge === 0) { // top x = 200 + Math.random() * (2048 - 400); y = -80; } else if (edge === 1) { // bottom x = 200 + Math.random() * (2048 - 400); y = 2732 + 80; } else if (edge === 2) { // left x = -80; y = 200 + Math.random() * (2732 - 400); } else { // right x = 2048 + 80; y = 200 + Math.random() * (2732 - 400); } var t = Math.random(); var m; // Make Spitter and Burrower much more rare: Chaser 80%, Spitter 7%, Burrower 13% if (t < 0.8) { m = new Chaser(); } else if (t < 0.87) { m = new Spitter(); } else { m = new Burrower(); } m.x = x; m.y = y; monsters.push(m); game.addChild(m); } for (var i = 0; i < 5; ++i) spawnMonster(); // Store logic rerollStore(); // Dragging var dragNode = null; function handleMove(x, y, obj) { if (dragNode) { // Set target position for smooth movement var r = hero.radius; dragNode.targetX = Math.max(r, Math.min(2048 - r, x)); dragNode.targetY = Math.max(r, Math.min(2732 - r, y)); } } game.move = handleMove; game.down = function (x, y, obj) { // Check store buttons first for (var i = 0; i < storeBtns.length; ++i) { var btn = storeBtns[i]; var bx = btn.x - 420, by = btn.y, bw = 420, bh = 120; if (x >= bx && x <= bx + bw && y >= by && y <= by + bh) { // Buy if (gold >= 5) { gold -= 5; goldTxt.setText(gold); btn.passive.apply(); LK.getSound('buy').play(); rerollStore(); } return; } } // Drag hero dragNode = hero; handleMove(x, y, obj); }; game.up = function (x, y, obj) { dragNode = null; }; // Helper: collision function circlesIntersect(a, b) { var dx = a.x - b.x, dy = a.y - b.y; var r = (a.radius || 40) + (b.radius || 40); return dx * dx + dy * dy < r * r; } // Update GUI function updateHP() { var s = ''; var maxhp = typeof hero.maxhp === "number" ? hero.maxhp : 3; var curhp = Math.max(0, Math.min(hero.hp, maxhp)); for (var i = 0; i < curhp; ++i) s += '❤'; for (var i = curhp; i < maxhp; ++i) s += '♡'; hpTxt.setText(s); } // Main update game.update = function () { // Hero smooth movement toward target if (typeof hero.targetX === "number" && typeof hero.targetY === "number") { var dx = hero.targetX - hero.x; var dy = hero.targetY - hero.y; var dist = Math.sqrt(dx * dx + dy * dy); var moveSpeed = hero.speed; if (dist > 1) { var step = Math.min(dist, moveSpeed); hero.x += dx / dist * step; hero.y += dy / dist * step; } else { hero.x = hero.targetX; hero.y = hero.targetY; } } // Clamp hero position to stay within bounds var r = hero.radius; hero.x = Math.max(r, Math.min(2048 - r, hero.x)); hero.y = Math.max(r, Math.min(2732 - r, hero.y)); hero.update(); // Regen passive for (var i = 0; i < passives.length; ++i) { if (passives[i].id === 'regen') { passives[i].timer++; if (passives[i].timer >= 60 * 12) { hero.hp++; if (typeof hero.maxhp === "number" && hero.hp > hero.maxhp) hero.hp = hero.maxhp; updateHP(); passives[i].timer = 0; } } } // Monster bullets for (var i = monsterBullets.length - 1; i >= 0; --i) { var b = monsterBullets[i]; b.update(); // Reflect Shield passive: reflect a bullet every 7 seconds for (var j = 0; j < passives.length; ++j) { if (passives[j].id === 'reflect') { passives[j].timer++; if (passives[j].timer >= 60 * 7) { if (circlesIntersect(b, hero)) { b.dirX *= -1; b.dirY *= -1; b.x += b.dirX * 30; b.y += b.dirY * 30; passives[j].timer = 0; break; } } } } if (b.x < -60 || b.x > 2048 + 60 || b.y < -60 || b.y > 2732 + 60) { b.destroy(); monsterBullets.splice(i, 1); continue; } // Hit hero if (hero.invuln === 0 && circlesIntersect(b, hero)) { hero.hp--; updateHP(); hero.invuln = 40; hero.flash(); b.destroy(); monsterBullets.splice(i, 1); if (hero.hp <= 0) { LK.effects.flashScreen(0xff0000, 1000); LK.showGameOver(); return; } } } // Monsters for (var i = monsters.length - 1; i >= 0; --i) { var m = monsters[i]; m.update(); // Hit hero if (hero.invuln === 0 && circlesIntersect(m, hero)) { hero.hp--; updateHP(); hero.invuln = 40; hero.flash(); if (hero.hp <= 0) { LK.effects.flashScreen(0xff0000, 1000); LK.showGameOver(); return; } } // --- Sword hit detection in update --- if (m.type !== 'burrower' || !m.burrowed) { // Sword hitbox: check monsters in a larger arc and range in front of hero var swordRange = 270; // Increased from 180 var swordArc = Math.PI * 1.1; // Increased from Math.PI/2 (now ~198 deg arc) // Find sword angle (same as in Hero.update) var nearest = null; var minDist = 999999; for (var mi = 0; mi < monsters.length; ++mi) { var mm = monsters[mi]; if (mm.type === 'burrower' && mm.burrowed) continue; var dx = mm.x - hero.x; var dy = mm.y - hero.y; var d = Math.sqrt(dx * dx + dy * dy); if (d < minDist) { minDist = d; nearest = mm; } } var swordAngle = -Math.PI / 2; if (nearest) { swordAngle = Math.atan2(nearest.y - hero.y, nearest.x - hero.x); } var mx = m.x - hero.x, my = m.y - hero.y; var mdist = Math.sqrt(mx * mx + my * my); if (mdist < swordRange) { var mangle = Math.atan2(my, mx); var diff = Math.abs((mangle - swordAngle + Math.PI * 3) % (Math.PI * 2) - Math.PI); if (diff < swordArc / 2) { var swordDmg = 1; for (var j = 0; j < passives.length; ++j) { if (passives[j].id === 'atkboost') { swordDmg += passives[j].stacks; } } m.hp -= swordDmg; m.flash(); LK.getSound('hit').play(); // --- Sword hit animation trigger --- if (typeof hero.swordHitAnim === "undefined" || hero.swordHitAnim === 0) { hero.swordHitAnim = 8; // frames of animation } else { hero.swordHitAnim = 8; } if (m.hp <= 0) { // Drop gold var g = new Gold(); g.x = m.x; g.y = m.y; g.vx = (Math.random() - 0.5) * 8; g.vy = (Math.random() - 0.5) * 8; golds.push(g); game.addChild(g); LK.getSound('monsterdie').play(); m.destroy(); monsters.splice(i, 1); continue; } } } } } // Gold for (var i = golds.length - 1; i >= 0; --i) { var g = golds[i]; g.update(); // Magnet passive for (var j = 0; j < passives.length; ++j) { if (passives[j].id === 'goldboost') { var dx = hero.x - g.x, dy = hero.y - g.y; var d = Math.sqrt(dx * dx + dy * dy); if (d < 600) { g.vx += dx / d * 2.5; g.vy += dy / d * 2.5; } } } // Pick up if (circlesIntersect(g, hero)) { var goldGain = 1; for (var j = 0; j < passives.length; ++j) { if (passives[j].id === 'greed') { goldGain += passives[j].stacks; } } gold += goldGain; goldTxt.setText(gold); LK.getSound('pickup').play(); // Lifesteal for (var j = 0; j < passives.length; ++j) { if (passives[j].id === 'lifesteal') { passives[j].counter++; if (passives[j].counter >= 8) { hero.hp++; if (typeof hero.maxhp === "number" && hero.hp > hero.maxhp) hero.hp = hero.maxhp; updateHP(); passives[j].counter = 0; } } } g.destroy(); golds.splice(i, 1); } } // Store button highlight for (var i = 0; i < storeBtns.length; ++i) { var btn = storeBtns[i]; var bx = btn.x - 420, by = btn.y, bw = 420, bh = 120; // Only show highlight if pointer is over the button if (game.lastDown && game.lastDown.x >= bx && game.lastDown.x <= bx + bw && game.lastDown.y >= by && game.lastDown.y <= by + bh) { if (!btn.hl) { btn.hl = btn.attachAsset('storebtnhl', { anchorX: 0.5, anchorY: 0 }); // Always ensure highlight is above the background but below text // Fallback: Remove and re-add children in correct order // Order: bg (0), hl (1), shadow (2), txt (3), desc (4), price (5) var children = []; for (var ci = 0; ci < btn.children.length; ++ci) { children.push(btn.children[ci]); } btn.removeChildren(); if (children.length > 0) btn.addChild(children[0]); // bg btn.addChild(btn.hl); // hl if (btn.shadow) btn.addChild(btn.shadow); // shadow if (btn.txt) btn.addChild(btn.txt); // txt if (btn.desc) btn.addChild(btn.desc); // desc if (btn.price) btn.addChild(btn.price); // price } } else { if (btn.hl) { btn.hl.destroy(); btn.hl = null; } } } // Spawn monsters if (LK.ticks % 120 === 0 && monsters.length < 7) { spawnMonster(); } }; // Track last down for store highlight game.lastDown = null; game.on('down', function (x, y, obj) { game.lastDown = { x: x, y: y }; }); game.on('up', function (x, y, obj) { game.lastDown = null; });
/****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
/****
* Classes
****/
// Burrower monster
var Burrower = Container.expand(function () {
var self = Container.call(this);
var burrowerGfx = self.attachAsset('burrower', {
anchorX: 0.5,
anchorY: 0.5
});
self.radius = burrowerGfx.width / 2;
self.speed = 2.2;
self.hp = 1;
self.type = 'burrower';
self.burrowTimer = 0;
self.burrowInterval = 90 + Math.floor(Math.random() * 60);
self.burrowed = false;
// Track lastX for direction
self.lastX = self.x;
self.facing = 1; // 1 = right, -1 = left
self.update = function () {
if (self.burrowed) {
self.burrowTimer--;
if (self.burrowTimer <= 0) {
// Unburrow
self.burrowed = false;
burrowerGfx.alpha = 1;
self.burrowTimer = self.burrowInterval;
}
// Move fast toward hero
var dx = hero.x - self.x;
var dy = hero.y - self.y;
var d = Math.sqrt(dx * dx + dy * dy);
if (d > 0.1) {
self.x += dx / d * (self.speed * 2.2);
self.y += dy / d * (self.speed * 2.2);
}
} else {
self.burrowTimer--;
if (self.burrowTimer <= 0) {
// Burrow
self.burrowed = true;
burrowerGfx.alpha = 0.3;
self.burrowTimer = 36 + Math.floor(Math.random() * 24);
}
// Move toward hero
var dx = hero.x - self.x;
var dy = hero.y - self.y;
var d = Math.sqrt(dx * dx + dy * dy);
if (d > 0.1) {
self.x += dx / d * self.speed;
self.y += dy / d * self.speed;
}
}
// Flip sprite depending on direction
if (self.x > self.lastX) {
if (self.facing !== 1) {
burrowerGfx.scaleX = 1;
self.facing = 1;
}
} else if (self.x < self.lastX) {
if (self.facing !== -1) {
burrowerGfx.scaleX = -1;
self.facing = -1;
}
}
self.lastX = self.x;
};
self.flash = function () {
tween(burrowerGfx, {
alpha: 0.1
}, {
duration: 40,
onFinish: function onFinish() {
tween(burrowerGfx, {
alpha: self.burrowed ? 0.3 : 1
}, {
duration: 40
});
}
});
};
return self;
});
// Chaser monster
var Chaser = Container.expand(function () {
var self = Container.call(this);
var chaserGfx = self.attachAsset('chaser', {
anchorX: 0.5,
anchorY: 0.5
});
self.radius = chaserGfx.width / 2;
self.speed = 1.5;
self.hp = 2;
self.type = 'chaser';
// Track lastX for direction
self.lastX = self.x;
self.facing = 1; // 1 = right, -1 = left
self.update = function () {
// Move toward hero
var dx = hero.x - self.x;
var dy = hero.y - self.y;
var d = Math.sqrt(dx * dx + dy * dy);
if (d > 0.1) {
self.x += dx / d * self.speed;
self.y += dy / d * self.speed;
}
// Flip sprite depending on direction
if (self.x > self.lastX) {
if (self.facing !== 1) {
chaserGfx.scaleX = 1;
self.facing = 1;
}
} else if (self.x < self.lastX) {
if (self.facing !== -1) {
chaserGfx.scaleX = -1;
self.facing = -1;
}
}
self.lastX = self.x;
};
self.flash = function () {
tween(chaserGfx, {
alpha: 0.3
}, {
duration: 60,
onFinish: function onFinish() {
tween(chaserGfx, {
alpha: 1
}, {
duration: 60
});
}
});
};
return self;
});
// Gold coin
var Gold = Container.expand(function () {
var self = Container.call(this);
var goldGfx = self.attachAsset('gold', {
anchorX: 0.5,
anchorY: 0.5
});
self.radius = goldGfx.width / 2;
self.vx = 0;
self.vy = 0;
self.update = function () {
self.x += self.vx;
self.y += self.vy;
// Slow down
self.vx *= 0.92;
self.vy *= 0.92;
};
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
});
// Add sword asset
var swordGfx = self.attachAsset('herosword', {
anchorX: 0.1,
anchorY: 0.5,
x: heroGfx.width * 0.5 + 30,
y: 0,
rotation: 0
});
swordGfx.rotation = 0;
swordGfx.visible = true;
self.addChild(swordGfx);
self.radius = heroGfx.width / 2;
self.speed = 22;
self.shootCooldown = 0;
self.shootInterval = 18; // frames
self.invuln = 0;
self.hp = 3;
// Sword swing state
self.swordSwing = 0; // 0 = idle, >0 = animating
self.swordSwingDir = 1; // 1 = right, -1 = left
self.update = function () {
if (self.shootCooldown > 0) self.shootCooldown--;
if (self.invuln > 0) self.invuln--;
// --- Rotate hero photo only left and right (flip horizontally) ---
if (typeof self.lastX === "undefined") self.lastX = self.x;
var dxMove = self.x - self.lastX;
if (Math.abs(dxMove) > 0.5) {
// Only flip if actually moving horizontally
if (dxMove > 0) {
// Moving right
heroGfx.scaleX = 1;
} else if (dxMove < 0) {
// Moving left
heroGfx.scaleX = -1;
}
}
self.lastX = self.x;
// --- End hero photo left/right flip ---
// Always point sword at nearest monster, animate a subtle swing
var nearest = null;
var minDist = 999999;
// Defensive: check if monsters is defined and is an array
if (typeof monsters !== "undefined" && monsters && typeof monsters.length === "number") {
// Defensive: check if monsters is defined and is an array
if (typeof monsters !== "undefined" && monsters && typeof monsters.length === "number") {
for (var mi = 0; mi < monsters.length; ++mi) {
var m = monsters[mi];
if (m.type === 'burrower' && m.burrowed) continue;
var dx = m.x - self.x;
var dy = m.y - self.y;
var d = Math.sqrt(dx * dx + dy * dy);
if (d < minDist) {
minDist = d;
nearest = m;
}
}
}
}
if (nearest) {
var dx = nearest.x - self.x;
var dy = nearest.y - self.y;
var angle = Math.atan2(dy, dx);
// Animate a subtle swing: oscillate +/- 0.18 radians
var swing = Math.sin(LK.ticks / 10) * 0.18;
// Sword hit animation: if recently hit, flash sword
if (self.swordHitAnim && self.swordHitAnim > 0) {
// Animate sword: scale up and flash color
swordGfx.scaleX = 1.3 + 0.3 * Math.sin(self.swordHitAnim * Math.PI / 6);
swordGfx.scaleY = 1.3 + 0.3 * Math.sin(self.swordHitAnim * Math.PI / 6);
swordGfx.tint = 0xffe066;
self.swordHitAnim--;
if (self.swordHitAnim === 0) {
swordGfx.scaleX = 1;
swordGfx.scaleY = 1;
swordGfx.tint = 0xf5e663;
}
} else {
swordGfx.scaleX = 1;
swordGfx.scaleY = 1;
swordGfx.tint = 0xf5e663;
}
swordGfx.rotation = angle + swing;
// Position sword at edge of hero
var dist = heroGfx.width * 0.5 + 30;
swordGfx.x = Math.cos(angle) * dist;
swordGfx.y = Math.sin(angle) * dist;
} else {
// No monsters: point up, subtle swing
var angle = -Math.PI / 2;
var swing = Math.sin(LK.ticks / 10) * 0.18;
swordGfx.rotation = angle + swing;
var dist = heroGfx.width * 0.5 + 30;
swordGfx.x = Math.cos(angle) * dist;
swordGfx.y = Math.sin(angle) * dist;
}
};
// Flash when hit
self.flash = function () {
tween(heroGfx, {
alpha: 0.3
}, {
duration: 80,
onFinish: function onFinish() {
tween(heroGfx, {
alpha: 1
}, {
duration: 80
});
}
});
};
// Sword swing trigger
self.swingSword = function (dir) {
self.swordSwing = self.swordSwingMax = 10;
self.swordSwingDir = dir || 1;
swordGfx.rotation = 0;
};
return self;
});
// Monster bullet (spit)
var MonsterBullet = Container.expand(function () {
var self = Container.call(this);
var bulletGfx = self.attachAsset('herobullet', {
anchorX: 0.5,
anchorY: 0.5
});
bulletGfx.tint = 0x7cfd3a;
self.radius = bulletGfx.width / 2;
self.speed = 7;
self.dirX = 0;
self.dirY = 1;
self.damage = 1;
self.update = function () {
self.x += self.dirX * self.speed;
self.y += self.dirY * self.speed;
// Rotate bullet to match direction
if (typeof bulletGfx !== "undefined") {
bulletGfx.rotation = Math.atan2(self.dirY, self.dirX);
}
};
return self;
});
// Spitter monster
var Spitter = Container.expand(function () {
var self = Container.call(this);
var spitterGfx = self.attachAsset('spitter', {
anchorX: 0.5,
anchorY: 0.5
});
self.radius = spitterGfx.width / 2;
self.speed = 1.1;
self.hp = 2;
self.type = 'spitter';
self.shootCooldown = 0;
self.shootInterval = 160 + Math.floor(Math.random() * 60); // fire even slower
// Track lastX for direction
self.lastX = self.x;
self.facing = 1; // 1 = right, -1 = left
self.update = function () {
// Keep distance from hero, shoot
var dx = hero.x - self.x;
var dy = hero.y - self.y;
var d = Math.sqrt(dx * dx + dy * dy);
if (d > 400) {
self.x += dx / d * self.speed;
self.y += dy / d * self.speed;
} else if (d < 320) {
self.x -= dx / d * self.speed;
self.y -= dy / d * self.speed;
}
// Flip sprite depending on direction
if (self.x > self.lastX) {
if (self.facing !== 1) {
spitterGfx.scaleX = 1;
self.facing = 1;
}
} else if (self.x < self.lastX) {
if (self.facing !== -1) {
spitterGfx.scaleX = -1;
self.facing = -1;
}
}
self.lastX = self.x;
if (self.shootCooldown > 0) self.shootCooldown--;else {
// Spit at hero
var spit = new MonsterBullet();
spit.x = self.x;
spit.y = self.y;
var tx = hero.x - self.x;
var ty = hero.y - self.y;
var td = Math.sqrt(tx * tx + ty * ty);
spit.dirX = td > 0.1 ? tx / td : 0;
spit.dirY = td > 0.1 ? ty / td : 0;
spit.speed = 3.2; // even slower bullet speed
spit.radius = 32;
// Set initial rotation to match direction
if (spit.children && spit.children.length > 0 && typeof spit.children[0].rotation !== "undefined") {
spit.children[0].rotation = Math.atan2(spit.dirY, spit.dirX);
}
monsterBullets.push(spit);
game.addChild(spit);
self.shootCooldown = self.shootInterval;
}
};
self.flash = function () {
tween(spitterGfx, {
alpha: 0.3
}, {
duration: 60,
onFinish: function onFinish() {
tween(spitterGfx, {
alpha: 1
}, {
duration: 60
});
}
});
};
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0x181818
});
/****
* Game Code
****/
// Global variables
// Hero
// Chaser monster
// Spitter monster
// Burrower monster
// Hero bullet
// Gold coin
// Store button background
// Store button highlight
// Sound effects
var hero = new Hero();
hero.maxhp = 3;
hero.speed = 22; // Ensure base speed is set
var monsters = [];
var monsterBullets = [];
var golds = [];
var gold = 0;
var storeBtns = [];
var passives = [];
var availablePassives = [{
id: 'lifesteal',
name: 'Lifesteal',
desc: 'Gain 1 HP every 8 gold picked up.',
weight: 2,
// More likely to appear
apply: function apply() {
passives.push({
id: 'lifesteal',
stacks: 1,
counter: 0
});
}
}, {
id: 'reflect',
name: 'Reflect Shield',
desc: 'Occasionally reflects a monster bullet.',
weight: 1,
// Less likely
apply: function apply() {
passives.push({
id: 'reflect',
stacks: 1,
timer: 0
});
}
}, {
id: 'regen',
name: 'Regeneration',
desc: 'Regenerate 1 HP every 12 seconds.',
weight: 1,
// Less likely
apply: function apply() {
passives.push({
id: 'regen',
stacks: 1,
timer: 0
});
}
}, {
id: 'speed',
name: 'Speed Boost',
desc: 'Move much faster.',
weight: 2,
// More likely
apply: function apply() {
// Add speed passive to passives so it persists and can stack
var found = false;
for (var i = 0; i < passives.length; ++i) {
if (passives[i].id === 'speed') {
passives[i].stacks++;
found = true;
break;
}
}
if (!found) {
passives.push({
id: 'speed',
stacks: 1
});
}
// Recalculate hero speed based on number of speed passives
var baseSpeed = 22;
var speedStacks = 0;
for (var i = 0; i < passives.length; ++i) {
if (passives[i].id === 'speed') {
speedStacks = passives[i].stacks;
break;
}
}
hero.speed = baseSpeed + 7 * speedStacks;
}
}, {
id: 'goldboost',
name: 'Gold Magnet',
desc: 'Gold is attracted to you.',
weight: 3,
// Most likely to appear
apply: function apply() {
passives.push({
id: 'goldboost',
stacks: 1
});
}
}, {
id: 'maxhp',
name: 'Max HP Up',
desc: 'Increase max HP by 1.',
weight: 2,
// More likely
apply: function apply() {
if (typeof hero.maxhp === "undefined") hero.maxhp = 3;
hero.maxhp++;
if (hero.hp > hero.maxhp) hero.hp = hero.maxhp;
updateHP();
}
},
// --- New items below ---
{
id: 'atkboost',
name: 'Attack Up',
desc: 'Deal +1 damage with sword.',
weight: 2,
apply: function apply() {
var found = false;
for (var i = 0; i < passives.length; ++i) {
if (passives[i].id === 'atkboost') {
passives[i].stacks++;
found = true;
break;
}
}
if (!found) {
passives.push({
id: 'atkboost',
stacks: 1
});
}
}
}, {
id: 'greed',
name: 'Greed',
desc: 'Gain +1 gold per pickup.',
weight: 2,
apply: function apply() {
var found = false;
for (var i = 0; i < passives.length; ++i) {
if (passives[i].id === 'greed') {
passives[i].stacks++;
found = true;
break;
}
}
if (!found) {
passives.push({
id: 'greed',
stacks: 1
});
}
}
}, {
id: 'healburst',
name: 'Heal Burst',
desc: 'Heal 3 HP instantly.',
weight: 1,
apply: function apply() {
hero.hp += 3;
if (typeof hero.maxhp === "number" && hero.hp > hero.maxhp) hero.hp = hero.maxhp;
updateHP();
}
}, {
id: 'safeshield',
name: 'Safe Shield',
desc: 'Become invulnerable for 3 seconds.',
weight: 1,
apply: function apply() {
hero.invuln = 60 * 3;
hero.flash();
}
}];
// Store state
var storeChoices = [];
function getRandomPassive(excludeIds) {
var pool = [];
// Build weighted pool
for (var i = 0; i < availablePassives.length; ++i) {
var id = availablePassives[i].id;
var already = false;
for (var j = 0; j < excludeIds.length; ++j) {
if (excludeIds[j] === id) {
already = true;
break;
}
}
if (!already) {
var weight = availablePassives[i].weight || 1;
for (var w = 0; w < weight; ++w) {
pool.push(availablePassives[i]);
}
}
}
if (pool.length === 0) {
// fallback: all passives, weighted
for (var i = 0; i < availablePassives.length; ++i) {
var weight = availablePassives[i].weight || 1;
for (var w = 0; w < weight; ++w) {
pool.push(availablePassives[i]);
}
}
}
return pool[Math.floor(Math.random() * pool.length)];
}
function rerollStore() {
storeChoices = [];
var exclude = [];
for (var i = 0; i < 3; ++i) {
var p = getRandomPassive(exclude);
storeChoices.push(p);
exclude.push(p.id);
}
updateStoreBtns();
}
function updateStoreBtns() {
for (var i = 0; i < storeBtns.length; ++i) {
var btn = storeBtns[i];
var p = storeChoices[i];
btn.passive = p;
// Add a little sparkle/emoji to the name for beauty
var prettyName = "★ " + p.name + " ★";
btn.txt.setText(prettyName);
btn.shadow.setText(prettyName);
btn.desc.setText(p.desc);
btn.price.setText("5");
}
}
// GUI
var goldTxt = new Text2('0', {
size: 90,
fill: 0xFFE066
});
goldTxt.anchor.set(1, 0);
LK.gui.topRight.addChild(goldTxt);
var hpTxt = new Text2('❤❤❤', {
size: 90,
fill: 0xFF3A3A
});
hpTxt.anchor.set(0, 0);
LK.gui.top.addChild(hpTxt);
// Store buttons (top center, beautified)
for (var i = 0; i < 3; ++i) {
var btn = new Container();
var bg = btn.attachAsset('storebtn', {
anchorX: 0.5,
anchorY: 0
});
// Center horizontally, stack vertically with spacing
btn.x = 2048 / 2;
btn.y = 160 + i * 160;
// Add a subtle drop shadow effect by layering a slightly offset, darker text
btn.shadow = new Text2('', {
size: 64,
fill: 0x222222
});
btn.shadow.anchor.set(0.5, 0);
btn.shadow.x = 0 + 3;
btn.shadow.y = 10 + 3;
btn.addChild(btn.shadow);
// Beautified item name: larger, bold, gold color, centered
btn.txt = new Text2('', {
size: 64,
fill: 0xFFE066,
font: "Impact, 'Arial Black', Tahoma"
});
btn.txt.anchor.set(0.5, 0);
btn.txt.x = 0;
btn.txt.y = 10;
btn.addChild(btn.txt);
// Description: slightly larger, italic, silver color, centered
btn.desc = new Text2('', {
size: 40,
fill: 0xCCCCCC,
font: "italic 36px Tahoma"
});
btn.desc.anchor.set(0.5, 0);
btn.desc.x = 0;
btn.desc.y = 80;
btn.addChild(btn.desc);
// Price: gold coin color, bold, left side
btn.price = new Text2('5', {
size: 54,
fill: 0xFFD700,
font: "bold 48px Tahoma"
});
btn.price.anchor.set(0, 0);
btn.price.x = -180;
btn.price.y = 40;
btn.addChild(btn.price);
btn.index = i;
storeBtns.push(btn);
game.addChild(btn);
}
// Place hero in center
hero.x = 2048 / 2;
hero.y = 2732 / 2;
hero.targetX = hero.x;
hero.targetY = hero.y;
game.addChild(hero);
// Initial monsters
function spawnMonster() {
var edge = Math.floor(Math.random() * 4);
var x, y;
if (edge === 0) {
// top
x = 200 + Math.random() * (2048 - 400);
y = -80;
} else if (edge === 1) {
// bottom
x = 200 + Math.random() * (2048 - 400);
y = 2732 + 80;
} else if (edge === 2) {
// left
x = -80;
y = 200 + Math.random() * (2732 - 400);
} else {
// right
x = 2048 + 80;
y = 200 + Math.random() * (2732 - 400);
}
var t = Math.random();
var m;
// Make Spitter and Burrower much more rare: Chaser 80%, Spitter 7%, Burrower 13%
if (t < 0.8) {
m = new Chaser();
} else if (t < 0.87) {
m = new Spitter();
} else {
m = new Burrower();
}
m.x = x;
m.y = y;
monsters.push(m);
game.addChild(m);
}
for (var i = 0; i < 5; ++i) spawnMonster();
// Store logic
rerollStore();
// Dragging
var dragNode = null;
function handleMove(x, y, obj) {
if (dragNode) {
// Set target position for smooth movement
var r = hero.radius;
dragNode.targetX = Math.max(r, Math.min(2048 - r, x));
dragNode.targetY = Math.max(r, Math.min(2732 - r, y));
}
}
game.move = handleMove;
game.down = function (x, y, obj) {
// Check store buttons first
for (var i = 0; i < storeBtns.length; ++i) {
var btn = storeBtns[i];
var bx = btn.x - 420,
by = btn.y,
bw = 420,
bh = 120;
if (x >= bx && x <= bx + bw && y >= by && y <= by + bh) {
// Buy
if (gold >= 5) {
gold -= 5;
goldTxt.setText(gold);
btn.passive.apply();
LK.getSound('buy').play();
rerollStore();
}
return;
}
}
// Drag hero
dragNode = hero;
handleMove(x, y, obj);
};
game.up = function (x, y, obj) {
dragNode = null;
};
// Helper: collision
function circlesIntersect(a, b) {
var dx = a.x - b.x,
dy = a.y - b.y;
var r = (a.radius || 40) + (b.radius || 40);
return dx * dx + dy * dy < r * r;
}
// Update GUI
function updateHP() {
var s = '';
var maxhp = typeof hero.maxhp === "number" ? hero.maxhp : 3;
var curhp = Math.max(0, Math.min(hero.hp, maxhp));
for (var i = 0; i < curhp; ++i) s += '❤';
for (var i = curhp; i < maxhp; ++i) s += '♡';
hpTxt.setText(s);
}
// Main update
game.update = function () {
// Hero smooth movement toward target
if (typeof hero.targetX === "number" && typeof hero.targetY === "number") {
var dx = hero.targetX - hero.x;
var dy = hero.targetY - hero.y;
var dist = Math.sqrt(dx * dx + dy * dy);
var moveSpeed = hero.speed;
if (dist > 1) {
var step = Math.min(dist, moveSpeed);
hero.x += dx / dist * step;
hero.y += dy / dist * step;
} else {
hero.x = hero.targetX;
hero.y = hero.targetY;
}
}
// Clamp hero position to stay within bounds
var r = hero.radius;
hero.x = Math.max(r, Math.min(2048 - r, hero.x));
hero.y = Math.max(r, Math.min(2732 - r, hero.y));
hero.update();
// Regen passive
for (var i = 0; i < passives.length; ++i) {
if (passives[i].id === 'regen') {
passives[i].timer++;
if (passives[i].timer >= 60 * 12) {
hero.hp++;
if (typeof hero.maxhp === "number" && hero.hp > hero.maxhp) hero.hp = hero.maxhp;
updateHP();
passives[i].timer = 0;
}
}
}
// Monster bullets
for (var i = monsterBullets.length - 1; i >= 0; --i) {
var b = monsterBullets[i];
b.update();
// Reflect Shield passive: reflect a bullet every 7 seconds
for (var j = 0; j < passives.length; ++j) {
if (passives[j].id === 'reflect') {
passives[j].timer++;
if (passives[j].timer >= 60 * 7) {
if (circlesIntersect(b, hero)) {
b.dirX *= -1;
b.dirY *= -1;
b.x += b.dirX * 30;
b.y += b.dirY * 30;
passives[j].timer = 0;
break;
}
}
}
}
if (b.x < -60 || b.x > 2048 + 60 || b.y < -60 || b.y > 2732 + 60) {
b.destroy();
monsterBullets.splice(i, 1);
continue;
}
// Hit hero
if (hero.invuln === 0 && circlesIntersect(b, hero)) {
hero.hp--;
updateHP();
hero.invuln = 40;
hero.flash();
b.destroy();
monsterBullets.splice(i, 1);
if (hero.hp <= 0) {
LK.effects.flashScreen(0xff0000, 1000);
LK.showGameOver();
return;
}
}
}
// Monsters
for (var i = monsters.length - 1; i >= 0; --i) {
var m = monsters[i];
m.update();
// Hit hero
if (hero.invuln === 0 && circlesIntersect(m, hero)) {
hero.hp--;
updateHP();
hero.invuln = 40;
hero.flash();
if (hero.hp <= 0) {
LK.effects.flashScreen(0xff0000, 1000);
LK.showGameOver();
return;
}
}
// --- Sword hit detection in update ---
if (m.type !== 'burrower' || !m.burrowed) {
// Sword hitbox: check monsters in a larger arc and range in front of hero
var swordRange = 270; // Increased from 180
var swordArc = Math.PI * 1.1; // Increased from Math.PI/2 (now ~198 deg arc)
// Find sword angle (same as in Hero.update)
var nearest = null;
var minDist = 999999;
for (var mi = 0; mi < monsters.length; ++mi) {
var mm = monsters[mi];
if (mm.type === 'burrower' && mm.burrowed) continue;
var dx = mm.x - hero.x;
var dy = mm.y - hero.y;
var d = Math.sqrt(dx * dx + dy * dy);
if (d < minDist) {
minDist = d;
nearest = mm;
}
}
var swordAngle = -Math.PI / 2;
if (nearest) {
swordAngle = Math.atan2(nearest.y - hero.y, nearest.x - hero.x);
}
var mx = m.x - hero.x,
my = m.y - hero.y;
var mdist = Math.sqrt(mx * mx + my * my);
if (mdist < swordRange) {
var mangle = Math.atan2(my, mx);
var diff = Math.abs((mangle - swordAngle + Math.PI * 3) % (Math.PI * 2) - Math.PI);
if (diff < swordArc / 2) {
var swordDmg = 1;
for (var j = 0; j < passives.length; ++j) {
if (passives[j].id === 'atkboost') {
swordDmg += passives[j].stacks;
}
}
m.hp -= swordDmg;
m.flash();
LK.getSound('hit').play();
// --- Sword hit animation trigger ---
if (typeof hero.swordHitAnim === "undefined" || hero.swordHitAnim === 0) {
hero.swordHitAnim = 8; // frames of animation
} else {
hero.swordHitAnim = 8;
}
if (m.hp <= 0) {
// Drop gold
var g = new Gold();
g.x = m.x;
g.y = m.y;
g.vx = (Math.random() - 0.5) * 8;
g.vy = (Math.random() - 0.5) * 8;
golds.push(g);
game.addChild(g);
LK.getSound('monsterdie').play();
m.destroy();
monsters.splice(i, 1);
continue;
}
}
}
}
}
// Gold
for (var i = golds.length - 1; i >= 0; --i) {
var g = golds[i];
g.update();
// Magnet passive
for (var j = 0; j < passives.length; ++j) {
if (passives[j].id === 'goldboost') {
var dx = hero.x - g.x,
dy = hero.y - g.y;
var d = Math.sqrt(dx * dx + dy * dy);
if (d < 600) {
g.vx += dx / d * 2.5;
g.vy += dy / d * 2.5;
}
}
}
// Pick up
if (circlesIntersect(g, hero)) {
var goldGain = 1;
for (var j = 0; j < passives.length; ++j) {
if (passives[j].id === 'greed') {
goldGain += passives[j].stacks;
}
}
gold += goldGain;
goldTxt.setText(gold);
LK.getSound('pickup').play();
// Lifesteal
for (var j = 0; j < passives.length; ++j) {
if (passives[j].id === 'lifesteal') {
passives[j].counter++;
if (passives[j].counter >= 8) {
hero.hp++;
if (typeof hero.maxhp === "number" && hero.hp > hero.maxhp) hero.hp = hero.maxhp;
updateHP();
passives[j].counter = 0;
}
}
}
g.destroy();
golds.splice(i, 1);
}
}
// Store button highlight
for (var i = 0; i < storeBtns.length; ++i) {
var btn = storeBtns[i];
var bx = btn.x - 420,
by = btn.y,
bw = 420,
bh = 120;
// Only show highlight if pointer is over the button
if (game.lastDown && game.lastDown.x >= bx && game.lastDown.x <= bx + bw && game.lastDown.y >= by && game.lastDown.y <= by + bh) {
if (!btn.hl) {
btn.hl = btn.attachAsset('storebtnhl', {
anchorX: 0.5,
anchorY: 0
});
// Always ensure highlight is above the background but below text
// Fallback: Remove and re-add children in correct order
// Order: bg (0), hl (1), shadow (2), txt (3), desc (4), price (5)
var children = [];
for (var ci = 0; ci < btn.children.length; ++ci) {
children.push(btn.children[ci]);
}
btn.removeChildren();
if (children.length > 0) btn.addChild(children[0]); // bg
btn.addChild(btn.hl); // hl
if (btn.shadow) btn.addChild(btn.shadow); // shadow
if (btn.txt) btn.addChild(btn.txt); // txt
if (btn.desc) btn.addChild(btn.desc); // desc
if (btn.price) btn.addChild(btn.price); // price
}
} else {
if (btn.hl) {
btn.hl.destroy();
btn.hl = null;
}
}
}
// Spawn monsters
if (LK.ticks % 120 === 0 && monsters.length < 7) {
spawnMonster();
}
};
// Track last down for store highlight
game.lastDown = null;
game.on('down', function (x, y, obj) {
game.lastDown = {
x: x,
y: y
};
});
game.on('up', function (x, y, obj) {
game.lastDown = null;
});
a zombie but its half phantom. No background. Transparent background. Blank background. No shadows. 2d. In-Game asset. flat
a zombie. No background. Transparent background. Blank background. No shadows. 2d. In-Game asset. flat
a gold. No background. Transparent background. Blank background. No shadows. 2d. In-Game asset. flat
a spitter zombie. No background. Transparent background. Blank background. No shadows. 2d. In-Game asset. flat
a green zombi spitt. No background. Transparent background. Blank background. No shadows. 2d. In-Game asset. flat
a man with green tshirt and blue pant. No background. Transparent background. Blank background. No shadows. 2d. In-Game asset. flat
a man with green tshirt and blue pant walking up. No background. Transparent background. Blank background. No shadows. 2d. In-Game asset. flat
a sword . No background. Transparent background. Blank background. No shadows. 2d. In-Game asset. flat