User prompt
make longer but not width other long
User prompt
made up longer enemy box
User prompt
Please fix the bug: 's.apply(...).then is not a function' in or related to this line: 'self.patternTick++;' Line Number: 52
User prompt
put the enemy a little up
User prompt
a little longer
User prompt
bigger health bar
User prompt
put the enemy middle and put the health bar more down
User prompt
put the enemy up
User prompt
put the health bar under enemy
User prompt
put a bi
User prompt
put enemy health bar
User prompt
deletele choose spare or fight
User prompt
heart will always follow mouse
User prompt
put the enemy middle
User prompt
make easy attacks for enemy those attacks are ımpossible
User prompt
small heart
Code edit (1 edits merged)
Please save this source code
User prompt
Undertale: Heart of the Underground
Initial prompt
lets do a big proje.undertale what you think?
/**** * Plugins ****/ var tween = LK.import("@upit/tween.v1"); /**** * Classes ****/ // CircleLaserEnemy: A circle that appears and fires a thick laser once var CircleLaserEnemy = Container.expand(function () { var self = Container.call(this); // Attach a green circle asset var circle = self.attachAsset('centerCircle', { width: 120, height: 120, color: 0x00ff99, shape: 'ellipse', anchorX: 0.5, anchorY: 0.5 }); // Laser graphics (thick rectangle, hidden at first) var laser = self.attachAsset('boxBorder', { width: 40, height: boxHeight + 200, color: 0xff2222, shape: 'box', anchorX: 0.5, anchorY: 0 }); laser.visible = false; laser.y = 60; // Start just below the circle // State self.laserFired = false; self.laserDuration = 60; // 1 second self.laserTimer = 0; // For collision detection self.laserActive = false; // Called every tick self.update = function () { // Animate circle (optional: pulse) circle.scaleX = 1.0 + 0.08 * Math.sin(LK.ticks / 8); circle.scaleY = 1.0 + 0.08 * Math.sin(LK.ticks / 8); // Fire laser after short delay if (!self.laserFired) { if (!self._waitTicks) self._waitTicks = 0; self._waitTicks++; if (self._waitTicks === 40) { // Fire! laser.visible = true; self.laserActive = true; self.laserFired = true; self.laserTimer = 0; // Optional: flash effect LK.effects.flashObject(laser, 0xffffff, 120); } } // Laser active if (self.laserActive) { self.laserTimer++; // Check collision with soul // Laser is a vertical thick bar, check if soul overlaps horizontally var laserGlobalX = self.x; var laserLeft = laserGlobalX - laser.width / 2; var laserRight = laserGlobalX + laser.width / 2; var soulLeft = soul.x - soul.radius; var soulRight = soul.x + soul.radius; var soulTop = soul.y - soul.radius; var soulBottom = soul.y + soul.radius; var laserTop = self.y + laser.y; var laserBottom = laserTop + laser.height; // Only check if soul is inside the box if (invincibleTicks === 0 && soulRight > laserLeft && soulLeft < laserRight && soulBottom > laserTop && soulTop < laserBottom) { // Damage player hp--; soul.flash(); invincibleTicks = 60; hpText.setText('HP: ' + hp); LK.effects.flashObject(soul, 0xffffff, 300); if (hp <= 0) { LK.effects.flashScreen(0xff0000, 1000); LK.showGameOver(); return; } } // Laser lasts for laserDuration ticks if (self.laserTimer > self.laserDuration) { self.laserActive = false; laser.visible = false; } } }; return self; }); // Enemy (for future expansion, currently just a box at the top) var Enemy = Container.expand(function () { var self = Container.call(this); var enemyBox = self.attachAsset('enemyBox', { width: 400, height: 260, // Increased height for a longer (taller) enemy box, width unchanged color: 0x2222ff, shape: 'box', anchorX: 0.5, anchorY: 0.5 }); return self; }); // Enemy Bullet var EnemyBullet = Container.expand(function () { var self = Container.call(this); // Attach bullet asset (white circle) var bullet = self.attachAsset('enemyBullet', { width: 60, height: 60, color: 0xffffff, shape: 'ellipse', anchorX: 0.5, anchorY: 0.5 }); // Movement properties self.vx = 0; self.vy = 0; // For pattern logic self.pattern = null; // Always initialize patternTick to 0 for each bullet self.patternTick = 0; // Called every tick self.update = function () { if (self.pattern) { self.pattern(self, self.patternTick); // Defensive: ensure self.patternTick is a number and not a thenable before incrementing (fix for s.apply(...).then is not a function) if (typeof self.patternTick !== "number" || self.patternTick && typeof self.patternTick.then === "function") { self.patternTick = 0; } else { // Defensive: ensure self.patternTick is a number and not a thenable before incrementing (fix for s.apply(...).then is not a function) if (typeof self.patternTick !== "number" || self.patternTick && typeof self.patternTick.then === "function") { self.patternTick = 0; } else { // Defensive: ensure self.patternTick is a number and not a thenable before incrementing (fix for s.apply(...).then is not a function) if (typeof self.patternTick !== "number" || self.patternTick && typeof self.patternTick.then === "function") { self.patternTick = 1; } else { // Defensive: ensure self.patternTick is a number and not a thenable before incrementing (fix for s.apply(...).then is not a function) if (typeof self.patternTick !== "number" || self.patternTick && typeof self.patternTick.then === "function") { self.patternTick = 1; } else { // Defensive: ensure self.patternTick is a number and not a thenable before incrementing (fix for s.apply(...).then is not a function) if (typeof self.patternTick !== "number" || self.patternTick && typeof self.patternTick.then === "function") { self.patternTick = 1; } else { // Defensive: ensure self.patternTick is a number and not a thenable before incrementing (fix for s.apply(...).then is not a function) if (typeof self.patternTick !== "number" || self.patternTick && typeof self.patternTick.then === "function") { self.patternTick = 1; } else { if (typeof self.patternTick !== "number" || self.patternTick && typeof self.patternTick.then === "function") { self.patternTick = 1; } else { // Defensive: ensure self.patternTick is a number and not a thenable before incrementing (fix for s.apply(...).then is not a function) if (typeof self.patternTick !== "number" || self.patternTick && typeof self.patternTick.then === "function") { self.patternTick = 1; } else { // Defensive: ensure self.patternTick is a number and not a thenable before incrementing (fix for s.apply(...).then is not a function) if (typeof self.patternTick !== "number" || self.patternTick && typeof self.patternTick.then === "function") { self.patternTick = 1; } else { // Defensive: ensure self.patternTick is a number and not a thenable before incrementing (fix for s.apply(...).then is not a function) at line 91 try { var n = Number(self.patternTick); if (typeof n === "number" && !isNaN(n) && !(n && typeof n.then === "function")) { // Defensive: ensure n is a number and not a thenable before incrementing if (typeof n === "number" && !isNaN(n) && !(n && typeof n.then === "function")) { // Defensive: ensure n is a number and not a thenable before incrementing (fix for s.apply(...).then is not a function) at line 97 if (typeof n === "number" && !isNaN(n) && !(n && typeof n.then === "function")) { self.patternTick = n + 1; } else { self.patternTick = 1; } } else { self.patternTick = 1; } } else { self.patternTick = 1; } } catch (e) { self.patternTick = 1; } } } } } } } } } } } else { // Defensive: ensure self.vx is a number before using if (typeof self.vx === "number") { self.x += self.vx; } // Defensive: ensure self.vy is a number and not a thenable before using if (typeof self.vy === "number" && !(_typeof6(self.vy) === "object" && self.vy !== null && typeof self.vy.then === "function")) { self.y += self.vy; } } }; return self; }); // Freeze Bullet (special: only damages if player is not moving) var FreezeBullet = Container.expand(function () { var self = Container.call(this); // Attach freeze bullet asset (blue ellipse) var bullet = self.attachAsset('freezeBullet', { width: 60, height: 60, color: 0x3399ff, shape: 'ellipse', anchorX: 0.5, anchorY: 0.5 }); // Movement properties self.vx = 0; self.vy = 0; // For pattern logic self.pattern = null; self.patternTick = 0; self.update = function () { if (self.pattern) { self.pattern(self, self.patternTick); if (typeof self.patternTick !== "number" || _typeof5(self.patternTick) === "object" && self.patternTick !== null && typeof self.patternTick.then === "function") { self.patternTick = 0; } if (typeof self.patternTick === "number" && !(_typeof5(self.patternTick) === "object" && self.patternTick !== null && typeof self.patternTick.then === "function")) { self.patternTick += 1; } else { self.patternTick = 1; } } else { if (typeof self.vx === "number") { self.x += self.vx; } if (typeof self.vy === "number" && !(_typeof2(self.vy) === "object" && self.vy !== null && typeof self.vy.then === "function")) { self.y += self.vy; } } }; return self; }); // Heart-shaped Soul (Player) var Soul = Container.expand(function () { var self = Container.call(this); // Attach heart asset (ellipse, red, scaled to look like a heart) // Since we can't draw a heart, use an ellipse and scale it to look heart-like var heart = self.attachAsset('soulHeart', { width: 60, height: 50, color: 0xff2d55, shape: 'ellipse', anchorX: 0.5, anchorY: 0.5 }); // Squash to make it more heart-like heart.scaleY = 1.1; heart.scaleX = 1.2; // For hit feedback self.flash = function () { tween(heart, { tint: 0xffffff }, { duration: 80, onFinish: function onFinish() { tween(heart, { tint: 0xff2d55 }, { duration: 120 }); } }); }; // For movement bounds self.radius = 30; return self; }); /**** * Initialize Game ****/ var game = new LK.Game({ backgroundColor: 0x000000 }); /**** * Game Code ****/ // Game area (the "box" where the soul can move) // Tween plugin for movement and effects function _typeof12(o) { "@babel/helpers - typeof"; return _typeof12 = "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; }, _typeof12(o); } function _typeof11(o) { "@babel/helpers - typeof"; return _typeof11 = "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; }, _typeof11(o); } function _typeof10(o) { "@babel/helpers - typeof"; return _typeof10 = "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; }, _typeof10(o); } function _typeof1(o) { "@babel/helpers - typeof"; return _typeof1 = "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; }, _typeof1(o); } function _typeof0(o) { "@babel/helpers - typeof"; return _typeof0 = "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; }, _typeof0(o); } function _typeof9(o) { "@babel/helpers - typeof"; return _typeof9 = "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; }, _typeof9(o); } function _typeof8(o) { "@babel/helpers - typeof"; return _typeof8 = "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; }, _typeof8(o); } function _typeof7(o) { "@babel/helpers - typeof"; return _typeof7 = "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; }, _typeof7(o); } function _typeof6(o) { "@babel/helpers - typeof"; return _typeof6 = "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; }, _typeof6(o); } function _typeof5(o) { "@babel/helpers - typeof"; return _typeof5 = "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; }, _typeof5(o); } function _typeof4(o) { "@babel/helpers - typeof"; return _typeof4 = "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; }, _typeof4(o); } function _typeof3(o) { "@babel/helpers - typeof"; return _typeof3 = "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; }, _typeof3(o); } 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); } var boxWidth = 900; var boxHeight = 900; var boxX = (2048 - boxWidth) / 2; var boxY = 2732 - boxHeight - 200; // Draw the box (white border, transparent fill) var boxBorder = LK.getAsset('boxBorder', { width: boxWidth, height: boxHeight, color: 0xffffff, shape: 'box', anchorX: 0, anchorY: 0 }); boxBorder.alpha = 0.15; boxBorder.x = boxX; boxBorder.y = boxY; game.addChild(boxBorder); // Add a big enemy (bi) at the top of the game area var enemy = new Enemy(); enemy.x = 2048 / 2; enemy.y = 2732 / 2 - 120; // Move enemy a little up from center enemy.scaleX = 2.2; enemy.scaleY = 2.2; game.addChild(enemy); // Enemy health bar var enemyMaxHP = 20; var enemyHP = enemyMaxHP; var enemyHealthBarWidth = 700; var enemyHealthBarHeight = 60; var enemyHealthBarBg = LK.getAsset('boxBorder', { width: enemyHealthBarWidth, height: enemyHealthBarHeight, color: 0x333333, shape: 'box', anchorX: 0.5, anchorY: 0.5 }); // Place health bar background further below the enemy enemyHealthBarBg.x = enemy.x; enemyHealthBarBg.y = enemy.y + 260; // Move health bar further down to match new enemy position game.addChild(enemyHealthBarBg); var enemyHealthBar = LK.getAsset('boxBorder', { width: enemyHealthBarWidth - 8, height: enemyHealthBarHeight - 2, color: 0xFFFF00, // yellow shape: 'box', anchorX: 0.5, anchorY: 0.5 }); enemyHealthBar.x = enemy.x; enemyHealthBar.y = enemy.y + 260; game.addChild(enemyHealthBar); // Add soul (player) at center of box var soul = new Soul(); soul.x = boxX + boxWidth / 2; soul.y = boxY + boxHeight / 2; game.addChild(soul); // Health system var maxHP = 5; var hp = maxHP; // Score (number of waves survived) var score = 0; // Bullets array var enemyBullets = []; // GUI: HP display (top right, avoid top left) var hpText = new Text2('HP: ' + hp, { size: 90, fill: 0xFF2D55 }); hpText.anchor.set(1, 0); LK.gui.topRight.addChild(hpText); // GUI: Score display (top center) var scoreText = new Text2('WAVES: 0', { size: 90, fill: 0xFF2D55 }); scoreText.anchor.set(0.5, 0); LK.gui.top.addChild(scoreText); // Touch controls for soul movement var draggingSoul = false; var dragOffsetX = 0; var dragOffsetY = 0; // Helper: clamp soul inside box function clampSoulPosition(x, y) { var r = soul.radius; var minX = boxX + r; var maxX = boxX + boxWidth - r; var minY = boxY + r; var maxY = boxY + boxHeight - r; if (x < minX) x = minX; if (x > maxX) x = maxX; if (y < minY) y = minY; if (y > maxY) y = maxY; return { x: x, y: y }; } // Touch down: start dragging if inside soul game.down = function (x, y, obj) { // Only allow drag if inside soul var dx = x - soul.x; var dy = y - soul.y; if (dx * dx + dy * dy <= soul.radius * soul.radius) { draggingSoul = true; dragOffsetX = soul.x - x; dragOffsetY = soul.y - y; } }; // Touch up: stop dragging game.up = function (x, y, obj) { draggingSoul = false; }; // Touch move: move soul game.move = function (x, y, obj) { // Always follow the mouse/touch, clamped to the box var pos = clampSoulPosition(x, y); soul.x = pos.x; soul.y = pos.y; }; // Bullet patterns // Multi-attack per wave support var waveAttacks = [ // Each entry is an array of attack functions for that wave // Wave 0: 2 attacks: radial burst, then spiral [function () { // Radial burst var count = 8; var speed = 3.5; for (var i = 0; i < count; i++) { var angle = Math.PI * 2 * i / count; var b = new EnemyBullet(); b.x = 2048 / 2; b.y = boxY + 40; b.vx = Math.cos(angle) * speed; b.vy = Math.sin(angle) * speed; enemyBullets.push(b); game.addChild(b); } }, function () { // Spiral var spiralCount = 12; var spiralSpawnY = boxY + 180; var spiralSpeed = 3.2; for (var i = 0; i < spiralCount; i++) { var b = new EnemyBullet(); b.x = 2048 / 2; b.y = spiralSpawnY; var baseAngle = Math.PI * 2 * i / spiralCount; b.pattern = function (angleOffset) { return function (self, t) { var speed = spiralSpeed; var angle = angleOffset + t * 0.07; self.x = 2048 / 2 + Math.cos(angle) * speed * t; self.y = spiralSpawnY + Math.sin(angle) * speed * t; }; }(baseAngle); enemyBullets.push(b); game.addChild(b); } }], // Wave 1: Sine wave, then zigzag [function () { // Sine wave bullets from both sides var sineCount = 4; var sineSpeed = 4.2; for (var i = 0; i < sineCount; i++) { var b = new EnemyBullet(); b.x = boxX + 40; b.y = boxY + 120 + i * 120; b.vx = sineSpeed; b.vy = 0; b.pattern = function (self, t) { self.x += self.vx; self.y += Math.sin((self.x + t * 2) / 80) * 5; }; enemyBullets.push(b); game.addChild(b); } for (var i = 0; i < sineCount; i++) { var b = new EnemyBullet(); b.x = boxX + boxWidth - 40; b.y = boxY + 120 + i * 120; b.vx = -sineSpeed; b.vy = 0; b.pattern = function (self, t) { self.x += self.vx; self.y += Math.sin((self.x + t * 2) / 80) * 5; }; enemyBullets.push(b); game.addChild(b); } }, function () { // Zigzag bullets from above var zigzagCount = 8; var zigzagSpeed = 4.5; for (var i = 0; i < zigzagCount; i++) { var b = new EnemyBullet(); b.x = boxX + 80 + (boxWidth - 160) * (i / zigzagCount); b.y = boxY + 20; b.vx = 0; b.vy = zigzagSpeed; b.pattern = function (self, t) { self.y += self.vy; self.x += Math.sin(self.y / 60) * 3; }; enemyBullets.push(b); game.addChild(b); } }], // Wave 2: Spiral, then aimed shots [function () { // Spiral pattern from center var spiralCount = 18; var spiralSpawnY = boxY + 180; var spiralSpeed = 4.2; for (var i = 0; i < spiralCount; i++) { var b = new EnemyBullet(); b.x = 2048 / 2; b.y = spiralSpawnY; var baseAngle = Math.PI * 2 * i / spiralCount; b.pattern = function (angleOffset) { return function (self, t) { var speed = spiralSpeed; var angle = angleOffset + t * 0.07; self.x = 2048 / 2 + Math.cos(angle) * speed * t; self.y = spiralSpawnY + Math.sin(angle) * speed * t; }; }(baseAngle); enemyBullets.push(b); game.addChild(b); } }, function () { // Aimed shots at the player (soul), with some spread var shots = 7; var aimedSpeed = 5.2; for (var i = 0; i < shots; i++) { var b = new EnemyBullet(); b.x = 2048 / 2; b.y = boxY + 40; var dx = soul.x - b.x; var dy = soul.y - b.y; var angle = Math.atan2(dy, dx) + (i - (shots - 1) / 2) * 0.12; b.vx = Math.cos(angle) * aimedSpeed; b.vy = Math.sin(angle) * aimedSpeed; enemyBullets.push(b); game.addChild(b); } }], // Wave 3: Freeze bullets, then CircleLaserEnemy [function () { // Freeze bullets (special blue bullets) var freezeCount = 7; var freezeSpeed = 4.5; for (var i = 0; i < freezeCount; i++) { var b = new FreezeBullet(); b.x = boxX + 80 + (boxWidth - 160) * (i / (freezeCount - 1)); b.y = boxY + 20; b.vx = 0; b.vy = freezeSpeed; enemyBullets.push(b); game.addChild(b); } // Add freeze bullets from the left side var sideFreezeCount = 3; var sideFreezeSpeed = 4.2; for (var i = 0; i < sideFreezeCount; i++) { var fbLeft = new FreezeBullet(); fbLeft.x = boxX + 20; fbLeft.y = boxY + 120 + (boxHeight - 240) * (i / (sideFreezeCount - 1)); fbLeft.vx = sideFreezeSpeed; fbLeft.vy = 0; enemyBullets.push(fbLeft); game.addChild(fbLeft); } // Add freeze bullets from the right side for (var i = 0; i < sideFreezeCount; i++) { var fbRight = new FreezeBullet(); fbRight.x = boxX + boxWidth - 20; fbRight.y = boxY + 120 + (boxHeight - 240) * (i / (sideFreezeCount - 1)); fbRight.vx = -sideFreezeSpeed; fbRight.vy = 0; enemyBullets.push(fbRight); game.addChild(fbRight); } }, function () { // Multiple CircleLaserEnemy with different angles var numLasers = 3; var baseY = boxY + 100; var centerX = 2048 / 2; var radius = 220; for (var i = 0; i < numLasers; i++) { var angle = Math.PI * 2 * i / numLasers; var cle = new CircleLaserEnemy(); cle.x = centerX + Math.cos(angle) * radius; cle.y = baseY + Math.sin(angle) * radius * 0.5; cle._laserAngle = angle; (function (cle, angle) { var origUpdate = cle.update; cle.update = function () { origUpdate.call(cle); if (cle.children && cle.children.length > 1) { var laser = cle.children[1]; laser.rotation = angle; laser.x = Math.cos(angle) * 60; laser.y = Math.sin(angle) * 60; } }; })(cle, angle); game.addChild(cle); enemyBullets.push(cle); } }]]; // For higher waves, repeat and increase difficulty function getWaveAttacks(waveNum) { if (waveNum < waveAttacks.length) { return waveAttacks[waveNum]; } // For higher waves, repeat and add a third attack var base = waveAttacks[waveNum % waveAttacks.length]; // For higher waves, repeat and add a third attack var extraAttack = function extraAttack() { // Harder radial burst var count = 12 + Math.floor(waveNum / 2); var speed = 4.5 + waveNum * 0.12; for (var i = 0; i < count; i++) { var angle = Math.PI * 2 * i / count; var b = new EnemyBullet(); b.x = 2048 / 2; b.y = boxY + 40; b.vx = Math.cos(angle) * speed; b.vy = Math.sin(angle) * speed; enemyBullets.push(b); game.addChild(b); } // Add more freeze attacks for higher waves var freezeCount = 6 + Math.floor(waveNum / 2); var freezeSpeed = 4.5 + waveNum * 0.08; for (var i = 0; i < freezeCount; i++) { var fb = new FreezeBullet(); fb.x = boxX + 80 + (boxWidth - 160) * (i / (freezeCount - 1)); fb.y = boxY + 20; fb.vx = 0; fb.vy = freezeSpeed; enemyBullets.push(fb); game.addChild(fb); } // Add freeze bullets from the left side var sideFreezeCount = 2 + Math.floor(waveNum / 3); var sideFreezeSpeed = 4.2 + waveNum * 0.08; for (var i = 0; i < sideFreezeCount; i++) { var fbLeft = new FreezeBullet(); fbLeft.x = boxX + 20; fbLeft.y = boxY + 120 + (boxHeight - 240) * (i / (sideFreezeCount - 1)); fbLeft.vx = sideFreezeSpeed; fbLeft.vy = 0; enemyBullets.push(fbLeft); game.addChild(fbLeft); } // Add freeze bullets from the right side for (var i = 0; i < sideFreezeCount; i++) { var fbRight = new FreezeBullet(); fbRight.x = boxX + boxWidth - 20; fbRight.y = boxY + 120 + (boxHeight - 240) * (i / (sideFreezeCount - 1)); fbRight.vx = -sideFreezeSpeed; fbRight.vy = 0; enemyBullets.push(fbRight); game.addChild(fbRight); } }; return base.concat([extraAttack]); } var currentAttackIndex = 0; var currentWaveAttacks = getWaveAttacks(0); var attackInterval = 80; // ticks between attacks in a wave function spawnWave(waveNum) { // Clear bullets for (var i = 0; i < enemyBullets.length; i++) { enemyBullets[i].destroy(); } enemyBullets.length = 0; // Setup attacks for this wave currentAttackIndex = 0; currentWaveAttacks = getWaveAttacks(waveNum); // Immediately launch the first attack if (currentWaveAttacks.length > 0) { currentWaveAttacks[0](); } } // Game state var waveNum = 0; var waveTimer = 0; var waveDuration = 240; // 4 seconds per wave // Invincibility after hit var invincibleTicks = 0; // Main update loop game.update = function () { // Track last soul position for freeze bullet logic if (typeof lastSoulX !== "number") lastSoulX = soul.x; if (typeof lastSoulY !== "number") lastSoulY = soul.y; var prevSoulX = soul.x; var prevSoulY = soul.y; // Update bullets for (var i = enemyBullets.length - 1; i >= 0; i--) { var b = enemyBullets[i]; b.update && b.update(); // Remove if out of box (for normal bullets) if (b.constructor && b.constructor === CircleLaserEnemy) { // Remove CircleLaserEnemy after laser is done if (b.laserFired && !b.laserActive) { b.destroy && b.destroy(); enemyBullets.splice(i, 1); continue; } } else { if (b.x < boxX - 80 || b.x > boxX + boxWidth + 80 || b.y < boxY - 80 || b.y > boxY + boxHeight + 80) { b.destroy(); enemyBullets.splice(i, 1); continue; } } // Collision with soul if (invincibleTicks === 0 && b.intersects(soul)) { // If it's a FreezeBullet, only damage if player is not moving if (b.constructor && b.constructor === FreezeBullet) { // Check if player is moving: compare soul.x/y to lastSoulX/lastSoulY if (typeof lastSoulX === "number" && typeof lastSoulY === "number" && soul.x === lastSoulX && soul.y === lastSoulY) { // Player is NOT moving, do NOT damage // Optionally, flash blue to indicate safe LK.effects.flashObject(soul, 0x3399ff, 200); } else { // Player is moving, take damage hp--; soul.flash(); invincibleTicks = 60; // 1 second invincibility hpText.setText('HP: ' + hp); LK.effects.flashObject(soul, 0xffffff, 300); if (hp <= 0) { LK.effects.flashScreen(0xff0000, 1000); LK.showGameOver(); return; } } } else { // Normal bullet: always damage hp--; soul.flash(); invincibleTicks = 60; // 1 second invincibility hpText.setText('HP: ' + hp); LK.effects.flashObject(soul, 0xffffff, 300); if (hp <= 0) { LK.effects.flashScreen(0xff0000, 1000); LK.showGameOver(); return; } } } // Example: Decrease enemyHP for demonstration (remove this in real game, or connect to your attack logic) // if (someCondition) { enemyHP--; } enemyHealthBar.width = (enemyHealthBarWidth - 8) * Math.max(0, enemyHP) / enemyMaxHP; } // Invincibility timer if (invincibleTicks > 0) { invincibleTicks--; } // Wave logic waveTimer++; if (currentWaveAttacks && currentAttackIndex < currentWaveAttacks.length - 1) { // Time for next attack in this wave? if (waveTimer % attackInterval === 0) { currentAttackIndex++; if (currentWaveAttacks[currentAttackIndex]) { currentWaveAttacks[currentAttackIndex](); } } } // If all attacks in this wave have been launched, advance to next wave after waveDuration if (waveTimer >= waveDuration) { waveNum++; score++; scoreText.setText('WAVES: ' + score); waveTimer = 0; spawnWave(waveNum); } // After wave 15, allow player to attack sans (enemy) if (waveNum >= 15) { // Enable player attack mode if (!game.playerAttackEnabled) { game.playerAttackEnabled = true; // Show a visual cue or instruction (optional) LK.effects.flashObject(enemy, 0xffff00, 600); // Add "Attack" button to GUI if not already present if (!game.attackButton) { game.attackButton = new Text2('ATTACK', { size: 120, fill: 0xff2222, font: "Impact, Arial Black, Tahoma" }); game.attackButton.anchor.set(0.5, 0.5); // Place button at bottom center, above the box LK.gui.bottom.addChild(game.attackButton); // Button state game.attackButton.enabled = true; // Button handler game.attackButton.down = function (x, y, obj) { if (!game.attackButton.enabled) return; game.attackButton.enabled = false; // 99999 crit attack! enemyHP = 0; enemyHealthBar.width = 0; // Show crit text var critText = new Text2('99999 CRIT!', { size: 160, fill: 0xff2222, font: "Impact, Arial Black, Tahoma" }); critText.anchor.set(0.5, 0.5); critText.x = enemy.x; critText.y = enemy.y - 120; game.addChild(critText); LK.effects.flashObject(enemy, 0xffffff, 200); LK.effects.flashScreen(0xffff00, 600); // Remove all bullets for (var i = 0; i < enemyBullets.length; i++) { enemyBullets[i].destroy && enemyBullets[i].destroy(); } enemyBullets.length = 0; // Remove button after attack LK.setTimeout(function () { if (game.attackButton && game.attackButton.parent) { game.attackButton.parent.removeChild(game.attackButton); } if (critText && critText.parent) { critText.parent.removeChild(critText); } }, 1200); // Win after short delay LK.setTimeout(function () { LK.showYouWin(); }, 900); }; } } // Listen for tap on enemy to attack (legacy, still allow) if (!enemy._attackHandlerAttached) { enemy._attackHandlerAttached = true; enemy.down = function (x, y, obj) { // Only allow attack if player attack is enabled and enemy is alive if (game.playerAttackEnabled && enemyHP > 0) { enemyHP--; LK.getSound('sans').play(); LK.effects.flashObject(enemy, 0xffffff, 120); enemyHealthBar.width = (enemyHealthBarWidth - 8) * Math.max(0, enemyHP) / enemyMaxHP; if (enemyHP <= 0) { LK.effects.flashScreen(0x00ff00, 1000); LK.showYouWin(); } } }; } } // Update lastSoulX and lastSoulY for next frame lastSoulX = prevSoulX; lastSoulY = prevSoulY; }; // Start first wave spawnWave(waveNum);
/****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
/****
* Classes
****/
// CircleLaserEnemy: A circle that appears and fires a thick laser once
var CircleLaserEnemy = Container.expand(function () {
var self = Container.call(this);
// Attach a green circle asset
var circle = self.attachAsset('centerCircle', {
width: 120,
height: 120,
color: 0x00ff99,
shape: 'ellipse',
anchorX: 0.5,
anchorY: 0.5
});
// Laser graphics (thick rectangle, hidden at first)
var laser = self.attachAsset('boxBorder', {
width: 40,
height: boxHeight + 200,
color: 0xff2222,
shape: 'box',
anchorX: 0.5,
anchorY: 0
});
laser.visible = false;
laser.y = 60; // Start just below the circle
// State
self.laserFired = false;
self.laserDuration = 60; // 1 second
self.laserTimer = 0;
// For collision detection
self.laserActive = false;
// Called every tick
self.update = function () {
// Animate circle (optional: pulse)
circle.scaleX = 1.0 + 0.08 * Math.sin(LK.ticks / 8);
circle.scaleY = 1.0 + 0.08 * Math.sin(LK.ticks / 8);
// Fire laser after short delay
if (!self.laserFired) {
if (!self._waitTicks) self._waitTicks = 0;
self._waitTicks++;
if (self._waitTicks === 40) {
// Fire!
laser.visible = true;
self.laserActive = true;
self.laserFired = true;
self.laserTimer = 0;
// Optional: flash effect
LK.effects.flashObject(laser, 0xffffff, 120);
}
}
// Laser active
if (self.laserActive) {
self.laserTimer++;
// Check collision with soul
// Laser is a vertical thick bar, check if soul overlaps horizontally
var laserGlobalX = self.x;
var laserLeft = laserGlobalX - laser.width / 2;
var laserRight = laserGlobalX + laser.width / 2;
var soulLeft = soul.x - soul.radius;
var soulRight = soul.x + soul.radius;
var soulTop = soul.y - soul.radius;
var soulBottom = soul.y + soul.radius;
var laserTop = self.y + laser.y;
var laserBottom = laserTop + laser.height;
// Only check if soul is inside the box
if (invincibleTicks === 0 && soulRight > laserLeft && soulLeft < laserRight && soulBottom > laserTop && soulTop < laserBottom) {
// Damage player
hp--;
soul.flash();
invincibleTicks = 60;
hpText.setText('HP: ' + hp);
LK.effects.flashObject(soul, 0xffffff, 300);
if (hp <= 0) {
LK.effects.flashScreen(0xff0000, 1000);
LK.showGameOver();
return;
}
}
// Laser lasts for laserDuration ticks
if (self.laserTimer > self.laserDuration) {
self.laserActive = false;
laser.visible = false;
}
}
};
return self;
});
// Enemy (for future expansion, currently just a box at the top)
var Enemy = Container.expand(function () {
var self = Container.call(this);
var enemyBox = self.attachAsset('enemyBox', {
width: 400,
height: 260,
// Increased height for a longer (taller) enemy box, width unchanged
color: 0x2222ff,
shape: 'box',
anchorX: 0.5,
anchorY: 0.5
});
return self;
});
// Enemy Bullet
var EnemyBullet = Container.expand(function () {
var self = Container.call(this);
// Attach bullet asset (white circle)
var bullet = self.attachAsset('enemyBullet', {
width: 60,
height: 60,
color: 0xffffff,
shape: 'ellipse',
anchorX: 0.5,
anchorY: 0.5
});
// Movement properties
self.vx = 0;
self.vy = 0;
// For pattern logic
self.pattern = null;
// Always initialize patternTick to 0 for each bullet
self.patternTick = 0;
// Called every tick
self.update = function () {
if (self.pattern) {
self.pattern(self, self.patternTick);
// Defensive: ensure self.patternTick is a number and not a thenable before incrementing (fix for s.apply(...).then is not a function)
if (typeof self.patternTick !== "number" || self.patternTick && typeof self.patternTick.then === "function") {
self.patternTick = 0;
} else {
// Defensive: ensure self.patternTick is a number and not a thenable before incrementing (fix for s.apply(...).then is not a function)
if (typeof self.patternTick !== "number" || self.patternTick && typeof self.patternTick.then === "function") {
self.patternTick = 0;
} else {
// Defensive: ensure self.patternTick is a number and not a thenable before incrementing (fix for s.apply(...).then is not a function)
if (typeof self.patternTick !== "number" || self.patternTick && typeof self.patternTick.then === "function") {
self.patternTick = 1;
} else {
// Defensive: ensure self.patternTick is a number and not a thenable before incrementing (fix for s.apply(...).then is not a function)
if (typeof self.patternTick !== "number" || self.patternTick && typeof self.patternTick.then === "function") {
self.patternTick = 1;
} else {
// Defensive: ensure self.patternTick is a number and not a thenable before incrementing (fix for s.apply(...).then is not a function)
if (typeof self.patternTick !== "number" || self.patternTick && typeof self.patternTick.then === "function") {
self.patternTick = 1;
} else {
// Defensive: ensure self.patternTick is a number and not a thenable before incrementing (fix for s.apply(...).then is not a function)
if (typeof self.patternTick !== "number" || self.patternTick && typeof self.patternTick.then === "function") {
self.patternTick = 1;
} else {
if (typeof self.patternTick !== "number" || self.patternTick && typeof self.patternTick.then === "function") {
self.patternTick = 1;
} else {
// Defensive: ensure self.patternTick is a number and not a thenable before incrementing (fix for s.apply(...).then is not a function)
if (typeof self.patternTick !== "number" || self.patternTick && typeof self.patternTick.then === "function") {
self.patternTick = 1;
} else {
// Defensive: ensure self.patternTick is a number and not a thenable before incrementing (fix for s.apply(...).then is not a function)
if (typeof self.patternTick !== "number" || self.patternTick && typeof self.patternTick.then === "function") {
self.patternTick = 1;
} else {
// Defensive: ensure self.patternTick is a number and not a thenable before incrementing (fix for s.apply(...).then is not a function) at line 91
try {
var n = Number(self.patternTick);
if (typeof n === "number" && !isNaN(n) && !(n && typeof n.then === "function")) {
// Defensive: ensure n is a number and not a thenable before incrementing
if (typeof n === "number" && !isNaN(n) && !(n && typeof n.then === "function")) {
// Defensive: ensure n is a number and not a thenable before incrementing (fix for s.apply(...).then is not a function) at line 97
if (typeof n === "number" && !isNaN(n) && !(n && typeof n.then === "function")) {
self.patternTick = n + 1;
} else {
self.patternTick = 1;
}
} else {
self.patternTick = 1;
}
} else {
self.patternTick = 1;
}
} catch (e) {
self.patternTick = 1;
}
}
}
}
}
}
}
}
}
}
} else {
// Defensive: ensure self.vx is a number before using
if (typeof self.vx === "number") {
self.x += self.vx;
}
// Defensive: ensure self.vy is a number and not a thenable before using
if (typeof self.vy === "number" && !(_typeof6(self.vy) === "object" && self.vy !== null && typeof self.vy.then === "function")) {
self.y += self.vy;
}
}
};
return self;
});
// Freeze Bullet (special: only damages if player is not moving)
var FreezeBullet = Container.expand(function () {
var self = Container.call(this);
// Attach freeze bullet asset (blue ellipse)
var bullet = self.attachAsset('freezeBullet', {
width: 60,
height: 60,
color: 0x3399ff,
shape: 'ellipse',
anchorX: 0.5,
anchorY: 0.5
});
// Movement properties
self.vx = 0;
self.vy = 0;
// For pattern logic
self.pattern = null;
self.patternTick = 0;
self.update = function () {
if (self.pattern) {
self.pattern(self, self.patternTick);
if (typeof self.patternTick !== "number" || _typeof5(self.patternTick) === "object" && self.patternTick !== null && typeof self.patternTick.then === "function") {
self.patternTick = 0;
}
if (typeof self.patternTick === "number" && !(_typeof5(self.patternTick) === "object" && self.patternTick !== null && typeof self.patternTick.then === "function")) {
self.patternTick += 1;
} else {
self.patternTick = 1;
}
} else {
if (typeof self.vx === "number") {
self.x += self.vx;
}
if (typeof self.vy === "number" && !(_typeof2(self.vy) === "object" && self.vy !== null && typeof self.vy.then === "function")) {
self.y += self.vy;
}
}
};
return self;
});
// Heart-shaped Soul (Player)
var Soul = Container.expand(function () {
var self = Container.call(this);
// Attach heart asset (ellipse, red, scaled to look like a heart)
// Since we can't draw a heart, use an ellipse and scale it to look heart-like
var heart = self.attachAsset('soulHeart', {
width: 60,
height: 50,
color: 0xff2d55,
shape: 'ellipse',
anchorX: 0.5,
anchorY: 0.5
});
// Squash to make it more heart-like
heart.scaleY = 1.1;
heart.scaleX = 1.2;
// For hit feedback
self.flash = function () {
tween(heart, {
tint: 0xffffff
}, {
duration: 80,
onFinish: function onFinish() {
tween(heart, {
tint: 0xff2d55
}, {
duration: 120
});
}
});
};
// For movement bounds
self.radius = 30;
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0x000000
});
/****
* Game Code
****/
// Game area (the "box" where the soul can move)
// Tween plugin for movement and effects
function _typeof12(o) {
"@babel/helpers - typeof";
return _typeof12 = "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;
}, _typeof12(o);
}
function _typeof11(o) {
"@babel/helpers - typeof";
return _typeof11 = "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;
}, _typeof11(o);
}
function _typeof10(o) {
"@babel/helpers - typeof";
return _typeof10 = "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;
}, _typeof10(o);
}
function _typeof1(o) {
"@babel/helpers - typeof";
return _typeof1 = "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;
}, _typeof1(o);
}
function _typeof0(o) {
"@babel/helpers - typeof";
return _typeof0 = "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;
}, _typeof0(o);
}
function _typeof9(o) {
"@babel/helpers - typeof";
return _typeof9 = "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;
}, _typeof9(o);
}
function _typeof8(o) {
"@babel/helpers - typeof";
return _typeof8 = "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;
}, _typeof8(o);
}
function _typeof7(o) {
"@babel/helpers - typeof";
return _typeof7 = "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;
}, _typeof7(o);
}
function _typeof6(o) {
"@babel/helpers - typeof";
return _typeof6 = "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;
}, _typeof6(o);
}
function _typeof5(o) {
"@babel/helpers - typeof";
return _typeof5 = "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;
}, _typeof5(o);
}
function _typeof4(o) {
"@babel/helpers - typeof";
return _typeof4 = "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;
}, _typeof4(o);
}
function _typeof3(o) {
"@babel/helpers - typeof";
return _typeof3 = "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;
}, _typeof3(o);
}
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);
}
var boxWidth = 900;
var boxHeight = 900;
var boxX = (2048 - boxWidth) / 2;
var boxY = 2732 - boxHeight - 200;
// Draw the box (white border, transparent fill)
var boxBorder = LK.getAsset('boxBorder', {
width: boxWidth,
height: boxHeight,
color: 0xffffff,
shape: 'box',
anchorX: 0,
anchorY: 0
});
boxBorder.alpha = 0.15;
boxBorder.x = boxX;
boxBorder.y = boxY;
game.addChild(boxBorder);
// Add a big enemy (bi) at the top of the game area
var enemy = new Enemy();
enemy.x = 2048 / 2;
enemy.y = 2732 / 2 - 120; // Move enemy a little up from center
enemy.scaleX = 2.2;
enemy.scaleY = 2.2;
game.addChild(enemy);
// Enemy health bar
var enemyMaxHP = 20;
var enemyHP = enemyMaxHP;
var enemyHealthBarWidth = 700;
var enemyHealthBarHeight = 60;
var enemyHealthBarBg = LK.getAsset('boxBorder', {
width: enemyHealthBarWidth,
height: enemyHealthBarHeight,
color: 0x333333,
shape: 'box',
anchorX: 0.5,
anchorY: 0.5
});
// Place health bar background further below the enemy
enemyHealthBarBg.x = enemy.x;
enemyHealthBarBg.y = enemy.y + 260; // Move health bar further down to match new enemy position
game.addChild(enemyHealthBarBg);
var enemyHealthBar = LK.getAsset('boxBorder', {
width: enemyHealthBarWidth - 8,
height: enemyHealthBarHeight - 2,
color: 0xFFFF00,
// yellow
shape: 'box',
anchorX: 0.5,
anchorY: 0.5
});
enemyHealthBar.x = enemy.x;
enemyHealthBar.y = enemy.y + 260;
game.addChild(enemyHealthBar);
// Add soul (player) at center of box
var soul = new Soul();
soul.x = boxX + boxWidth / 2;
soul.y = boxY + boxHeight / 2;
game.addChild(soul);
// Health system
var maxHP = 5;
var hp = maxHP;
// Score (number of waves survived)
var score = 0;
// Bullets array
var enemyBullets = [];
// GUI: HP display (top right, avoid top left)
var hpText = new Text2('HP: ' + hp, {
size: 90,
fill: 0xFF2D55
});
hpText.anchor.set(1, 0);
LK.gui.topRight.addChild(hpText);
// GUI: Score display (top center)
var scoreText = new Text2('WAVES: 0', {
size: 90,
fill: 0xFF2D55
});
scoreText.anchor.set(0.5, 0);
LK.gui.top.addChild(scoreText);
// Touch controls for soul movement
var draggingSoul = false;
var dragOffsetX = 0;
var dragOffsetY = 0;
// Helper: clamp soul inside box
function clampSoulPosition(x, y) {
var r = soul.radius;
var minX = boxX + r;
var maxX = boxX + boxWidth - r;
var minY = boxY + r;
var maxY = boxY + boxHeight - r;
if (x < minX) x = minX;
if (x > maxX) x = maxX;
if (y < minY) y = minY;
if (y > maxY) y = maxY;
return {
x: x,
y: y
};
}
// Touch down: start dragging if inside soul
game.down = function (x, y, obj) {
// Only allow drag if inside soul
var dx = x - soul.x;
var dy = y - soul.y;
if (dx * dx + dy * dy <= soul.radius * soul.radius) {
draggingSoul = true;
dragOffsetX = soul.x - x;
dragOffsetY = soul.y - y;
}
};
// Touch up: stop dragging
game.up = function (x, y, obj) {
draggingSoul = false;
};
// Touch move: move soul
game.move = function (x, y, obj) {
// Always follow the mouse/touch, clamped to the box
var pos = clampSoulPosition(x, y);
soul.x = pos.x;
soul.y = pos.y;
};
// Bullet patterns
// Multi-attack per wave support
var waveAttacks = [
// Each entry is an array of attack functions for that wave
// Wave 0: 2 attacks: radial burst, then spiral
[function () {
// Radial burst
var count = 8;
var speed = 3.5;
for (var i = 0; i < count; i++) {
var angle = Math.PI * 2 * i / count;
var b = new EnemyBullet();
b.x = 2048 / 2;
b.y = boxY + 40;
b.vx = Math.cos(angle) * speed;
b.vy = Math.sin(angle) * speed;
enemyBullets.push(b);
game.addChild(b);
}
}, function () {
// Spiral
var spiralCount = 12;
var spiralSpawnY = boxY + 180;
var spiralSpeed = 3.2;
for (var i = 0; i < spiralCount; i++) {
var b = new EnemyBullet();
b.x = 2048 / 2;
b.y = spiralSpawnY;
var baseAngle = Math.PI * 2 * i / spiralCount;
b.pattern = function (angleOffset) {
return function (self, t) {
var speed = spiralSpeed;
var angle = angleOffset + t * 0.07;
self.x = 2048 / 2 + Math.cos(angle) * speed * t;
self.y = spiralSpawnY + Math.sin(angle) * speed * t;
};
}(baseAngle);
enemyBullets.push(b);
game.addChild(b);
}
}],
// Wave 1: Sine wave, then zigzag
[function () {
// Sine wave bullets from both sides
var sineCount = 4;
var sineSpeed = 4.2;
for (var i = 0; i < sineCount; i++) {
var b = new EnemyBullet();
b.x = boxX + 40;
b.y = boxY + 120 + i * 120;
b.vx = sineSpeed;
b.vy = 0;
b.pattern = function (self, t) {
self.x += self.vx;
self.y += Math.sin((self.x + t * 2) / 80) * 5;
};
enemyBullets.push(b);
game.addChild(b);
}
for (var i = 0; i < sineCount; i++) {
var b = new EnemyBullet();
b.x = boxX + boxWidth - 40;
b.y = boxY + 120 + i * 120;
b.vx = -sineSpeed;
b.vy = 0;
b.pattern = function (self, t) {
self.x += self.vx;
self.y += Math.sin((self.x + t * 2) / 80) * 5;
};
enemyBullets.push(b);
game.addChild(b);
}
}, function () {
// Zigzag bullets from above
var zigzagCount = 8;
var zigzagSpeed = 4.5;
for (var i = 0; i < zigzagCount; i++) {
var b = new EnemyBullet();
b.x = boxX + 80 + (boxWidth - 160) * (i / zigzagCount);
b.y = boxY + 20;
b.vx = 0;
b.vy = zigzagSpeed;
b.pattern = function (self, t) {
self.y += self.vy;
self.x += Math.sin(self.y / 60) * 3;
};
enemyBullets.push(b);
game.addChild(b);
}
}],
// Wave 2: Spiral, then aimed shots
[function () {
// Spiral pattern from center
var spiralCount = 18;
var spiralSpawnY = boxY + 180;
var spiralSpeed = 4.2;
for (var i = 0; i < spiralCount; i++) {
var b = new EnemyBullet();
b.x = 2048 / 2;
b.y = spiralSpawnY;
var baseAngle = Math.PI * 2 * i / spiralCount;
b.pattern = function (angleOffset) {
return function (self, t) {
var speed = spiralSpeed;
var angle = angleOffset + t * 0.07;
self.x = 2048 / 2 + Math.cos(angle) * speed * t;
self.y = spiralSpawnY + Math.sin(angle) * speed * t;
};
}(baseAngle);
enemyBullets.push(b);
game.addChild(b);
}
}, function () {
// Aimed shots at the player (soul), with some spread
var shots = 7;
var aimedSpeed = 5.2;
for (var i = 0; i < shots; i++) {
var b = new EnemyBullet();
b.x = 2048 / 2;
b.y = boxY + 40;
var dx = soul.x - b.x;
var dy = soul.y - b.y;
var angle = Math.atan2(dy, dx) + (i - (shots - 1) / 2) * 0.12;
b.vx = Math.cos(angle) * aimedSpeed;
b.vy = Math.sin(angle) * aimedSpeed;
enemyBullets.push(b);
game.addChild(b);
}
}],
// Wave 3: Freeze bullets, then CircleLaserEnemy
[function () {
// Freeze bullets (special blue bullets)
var freezeCount = 7;
var freezeSpeed = 4.5;
for (var i = 0; i < freezeCount; i++) {
var b = new FreezeBullet();
b.x = boxX + 80 + (boxWidth - 160) * (i / (freezeCount - 1));
b.y = boxY + 20;
b.vx = 0;
b.vy = freezeSpeed;
enemyBullets.push(b);
game.addChild(b);
}
// Add freeze bullets from the left side
var sideFreezeCount = 3;
var sideFreezeSpeed = 4.2;
for (var i = 0; i < sideFreezeCount; i++) {
var fbLeft = new FreezeBullet();
fbLeft.x = boxX + 20;
fbLeft.y = boxY + 120 + (boxHeight - 240) * (i / (sideFreezeCount - 1));
fbLeft.vx = sideFreezeSpeed;
fbLeft.vy = 0;
enemyBullets.push(fbLeft);
game.addChild(fbLeft);
}
// Add freeze bullets from the right side
for (var i = 0; i < sideFreezeCount; i++) {
var fbRight = new FreezeBullet();
fbRight.x = boxX + boxWidth - 20;
fbRight.y = boxY + 120 + (boxHeight - 240) * (i / (sideFreezeCount - 1));
fbRight.vx = -sideFreezeSpeed;
fbRight.vy = 0;
enemyBullets.push(fbRight);
game.addChild(fbRight);
}
}, function () {
// Multiple CircleLaserEnemy with different angles
var numLasers = 3;
var baseY = boxY + 100;
var centerX = 2048 / 2;
var radius = 220;
for (var i = 0; i < numLasers; i++) {
var angle = Math.PI * 2 * i / numLasers;
var cle = new CircleLaserEnemy();
cle.x = centerX + Math.cos(angle) * radius;
cle.y = baseY + Math.sin(angle) * radius * 0.5;
cle._laserAngle = angle;
(function (cle, angle) {
var origUpdate = cle.update;
cle.update = function () {
origUpdate.call(cle);
if (cle.children && cle.children.length > 1) {
var laser = cle.children[1];
laser.rotation = angle;
laser.x = Math.cos(angle) * 60;
laser.y = Math.sin(angle) * 60;
}
};
})(cle, angle);
game.addChild(cle);
enemyBullets.push(cle);
}
}]];
// For higher waves, repeat and increase difficulty
function getWaveAttacks(waveNum) {
if (waveNum < waveAttacks.length) {
return waveAttacks[waveNum];
}
// For higher waves, repeat and add a third attack
var base = waveAttacks[waveNum % waveAttacks.length];
// For higher waves, repeat and add a third attack
var extraAttack = function extraAttack() {
// Harder radial burst
var count = 12 + Math.floor(waveNum / 2);
var speed = 4.5 + waveNum * 0.12;
for (var i = 0; i < count; i++) {
var angle = Math.PI * 2 * i / count;
var b = new EnemyBullet();
b.x = 2048 / 2;
b.y = boxY + 40;
b.vx = Math.cos(angle) * speed;
b.vy = Math.sin(angle) * speed;
enemyBullets.push(b);
game.addChild(b);
}
// Add more freeze attacks for higher waves
var freezeCount = 6 + Math.floor(waveNum / 2);
var freezeSpeed = 4.5 + waveNum * 0.08;
for (var i = 0; i < freezeCount; i++) {
var fb = new FreezeBullet();
fb.x = boxX + 80 + (boxWidth - 160) * (i / (freezeCount - 1));
fb.y = boxY + 20;
fb.vx = 0;
fb.vy = freezeSpeed;
enemyBullets.push(fb);
game.addChild(fb);
}
// Add freeze bullets from the left side
var sideFreezeCount = 2 + Math.floor(waveNum / 3);
var sideFreezeSpeed = 4.2 + waveNum * 0.08;
for (var i = 0; i < sideFreezeCount; i++) {
var fbLeft = new FreezeBullet();
fbLeft.x = boxX + 20;
fbLeft.y = boxY + 120 + (boxHeight - 240) * (i / (sideFreezeCount - 1));
fbLeft.vx = sideFreezeSpeed;
fbLeft.vy = 0;
enemyBullets.push(fbLeft);
game.addChild(fbLeft);
}
// Add freeze bullets from the right side
for (var i = 0; i < sideFreezeCount; i++) {
var fbRight = new FreezeBullet();
fbRight.x = boxX + boxWidth - 20;
fbRight.y = boxY + 120 + (boxHeight - 240) * (i / (sideFreezeCount - 1));
fbRight.vx = -sideFreezeSpeed;
fbRight.vy = 0;
enemyBullets.push(fbRight);
game.addChild(fbRight);
}
};
return base.concat([extraAttack]);
}
var currentAttackIndex = 0;
var currentWaveAttacks = getWaveAttacks(0);
var attackInterval = 80; // ticks between attacks in a wave
function spawnWave(waveNum) {
// Clear bullets
for (var i = 0; i < enemyBullets.length; i++) {
enemyBullets[i].destroy();
}
enemyBullets.length = 0;
// Setup attacks for this wave
currentAttackIndex = 0;
currentWaveAttacks = getWaveAttacks(waveNum);
// Immediately launch the first attack
if (currentWaveAttacks.length > 0) {
currentWaveAttacks[0]();
}
}
// Game state
var waveNum = 0;
var waveTimer = 0;
var waveDuration = 240; // 4 seconds per wave
// Invincibility after hit
var invincibleTicks = 0;
// Main update loop
game.update = function () {
// Track last soul position for freeze bullet logic
if (typeof lastSoulX !== "number") lastSoulX = soul.x;
if (typeof lastSoulY !== "number") lastSoulY = soul.y;
var prevSoulX = soul.x;
var prevSoulY = soul.y;
// Update bullets
for (var i = enemyBullets.length - 1; i >= 0; i--) {
var b = enemyBullets[i];
b.update && b.update();
// Remove if out of box (for normal bullets)
if (b.constructor && b.constructor === CircleLaserEnemy) {
// Remove CircleLaserEnemy after laser is done
if (b.laserFired && !b.laserActive) {
b.destroy && b.destroy();
enemyBullets.splice(i, 1);
continue;
}
} else {
if (b.x < boxX - 80 || b.x > boxX + boxWidth + 80 || b.y < boxY - 80 || b.y > boxY + boxHeight + 80) {
b.destroy();
enemyBullets.splice(i, 1);
continue;
}
}
// Collision with soul
if (invincibleTicks === 0 && b.intersects(soul)) {
// If it's a FreezeBullet, only damage if player is not moving
if (b.constructor && b.constructor === FreezeBullet) {
// Check if player is moving: compare soul.x/y to lastSoulX/lastSoulY
if (typeof lastSoulX === "number" && typeof lastSoulY === "number" && soul.x === lastSoulX && soul.y === lastSoulY) {
// Player is NOT moving, do NOT damage
// Optionally, flash blue to indicate safe
LK.effects.flashObject(soul, 0x3399ff, 200);
} else {
// Player is moving, take damage
hp--;
soul.flash();
invincibleTicks = 60; // 1 second invincibility
hpText.setText('HP: ' + hp);
LK.effects.flashObject(soul, 0xffffff, 300);
if (hp <= 0) {
LK.effects.flashScreen(0xff0000, 1000);
LK.showGameOver();
return;
}
}
} else {
// Normal bullet: always damage
hp--;
soul.flash();
invincibleTicks = 60; // 1 second invincibility
hpText.setText('HP: ' + hp);
LK.effects.flashObject(soul, 0xffffff, 300);
if (hp <= 0) {
LK.effects.flashScreen(0xff0000, 1000);
LK.showGameOver();
return;
}
}
}
// Example: Decrease enemyHP for demonstration (remove this in real game, or connect to your attack logic)
// if (someCondition) { enemyHP--; }
enemyHealthBar.width = (enemyHealthBarWidth - 8) * Math.max(0, enemyHP) / enemyMaxHP;
}
// Invincibility timer
if (invincibleTicks > 0) {
invincibleTicks--;
}
// Wave logic
waveTimer++;
if (currentWaveAttacks && currentAttackIndex < currentWaveAttacks.length - 1) {
// Time for next attack in this wave?
if (waveTimer % attackInterval === 0) {
currentAttackIndex++;
if (currentWaveAttacks[currentAttackIndex]) {
currentWaveAttacks[currentAttackIndex]();
}
}
}
// If all attacks in this wave have been launched, advance to next wave after waveDuration
if (waveTimer >= waveDuration) {
waveNum++;
score++;
scoreText.setText('WAVES: ' + score);
waveTimer = 0;
spawnWave(waveNum);
}
// After wave 15, allow player to attack sans (enemy)
if (waveNum >= 15) {
// Enable player attack mode
if (!game.playerAttackEnabled) {
game.playerAttackEnabled = true;
// Show a visual cue or instruction (optional)
LK.effects.flashObject(enemy, 0xffff00, 600);
// Add "Attack" button to GUI if not already present
if (!game.attackButton) {
game.attackButton = new Text2('ATTACK', {
size: 120,
fill: 0xff2222,
font: "Impact, Arial Black, Tahoma"
});
game.attackButton.anchor.set(0.5, 0.5);
// Place button at bottom center, above the box
LK.gui.bottom.addChild(game.attackButton);
// Button state
game.attackButton.enabled = true;
// Button handler
game.attackButton.down = function (x, y, obj) {
if (!game.attackButton.enabled) return;
game.attackButton.enabled = false;
// 99999 crit attack!
enemyHP = 0;
enemyHealthBar.width = 0;
// Show crit text
var critText = new Text2('99999 CRIT!', {
size: 160,
fill: 0xff2222,
font: "Impact, Arial Black, Tahoma"
});
critText.anchor.set(0.5, 0.5);
critText.x = enemy.x;
critText.y = enemy.y - 120;
game.addChild(critText);
LK.effects.flashObject(enemy, 0xffffff, 200);
LK.effects.flashScreen(0xffff00, 600);
// Remove all bullets
for (var i = 0; i < enemyBullets.length; i++) {
enemyBullets[i].destroy && enemyBullets[i].destroy();
}
enemyBullets.length = 0;
// Remove button after attack
LK.setTimeout(function () {
if (game.attackButton && game.attackButton.parent) {
game.attackButton.parent.removeChild(game.attackButton);
}
if (critText && critText.parent) {
critText.parent.removeChild(critText);
}
}, 1200);
// Win after short delay
LK.setTimeout(function () {
LK.showYouWin();
}, 900);
};
}
}
// Listen for tap on enemy to attack (legacy, still allow)
if (!enemy._attackHandlerAttached) {
enemy._attackHandlerAttached = true;
enemy.down = function (x, y, obj) {
// Only allow attack if player attack is enabled and enemy is alive
if (game.playerAttackEnabled && enemyHP > 0) {
enemyHP--;
LK.getSound('sans').play();
LK.effects.flashObject(enemy, 0xffffff, 120);
enemyHealthBar.width = (enemyHealthBarWidth - 8) * Math.max(0, enemyHP) / enemyMaxHP;
if (enemyHP <= 0) {
LK.effects.flashScreen(0x00ff00, 1000);
LK.showYouWin();
}
}
};
}
}
// Update lastSoulX and lastSoulY for next frame
lastSoulX = prevSoulX;
lastSoulY = prevSoulY;
};
// Start first wave
spawnWave(waveNum);