/**** * Plugins ****/ var tween = LK.import("@upit/tween.v1"); /**** * Classes ****/ var ActionButton = Container.expand(function (label, action) { var self = Container.call(this); var buttonGraphics = self.attachAsset('actionButton', { anchorX: 0.5, anchorY: 0.5, alpha: 0.8 }); var buttonText = new Text2(label, { size: 60, fill: 0xFFFFFF }); buttonText.anchor.set(0.5, 0.5); self.addChild(buttonText); self.action = action; self.down = function (x, y, obj) { if (isActionPhase) { tween(buttonGraphics, { scaleX: 0.9, scaleY: 0.9 }, { duration: 100, onFinish: function onFinish() { tween(buttonGraphics, { scaleX: 1, scaleY: 1 }, { duration: 100 }); } }); self.action(); } }; return self; }); var BlueBullet = Container.expand(function () { var self = Container.call(this); var bulletGraphics = self.attachAsset('blueBullet', { anchorX: 0.5, anchorY: 0.5, tint: 0x0099ff }); self.speedX = 0; self.speedY = 0; self.lifetime = 0; self.maxLifetime = 360; self.rotationSpeed = 0.1; self.lastHeartMoving = false; self.update = function () { self.x += self.speedX; self.y += self.speedY; self.lifetime++; // Rotate blue bullets bulletGraphics.rotation += self.rotationSpeed; // Slight homing behavior towards center var centerX = 1024; var centerY = 1366; var dx = centerX - self.x; var dy = centerY - self.y; var distance = Math.sqrt(dx * dx + dy * dy); if (distance > 50) { self.speedX += dx / distance * 0.05; self.speedY += dy / distance * 0.05; } // Cap speed var speed = Math.sqrt(self.speedX * self.speedX + self.speedY * self.speedY); if (speed > 6) { self.speedX = self.speedX / speed * 6; self.speedY = self.speedY / speed * 6; } if (self.lifetime > self.maxLifetime || self.x < -100 || self.x > 2148 || self.y < -100 || self.y > 2832) { self.markForDestroy = true; } }; return self; }); var Bullet = Container.expand(function () { var self = Container.call(this); var bulletGraphics = self.attachAsset('bullet', { anchorX: 0.5, anchorY: 0.5 }); self.speedX = 0; self.speedY = 0; self.lifetime = 0; self.maxLifetime = 300; // 5 seconds at 60fps self.update = function () { self.x += self.speedX; self.y += self.speedY; self.lifetime++; // Remove if too old or off screen if (self.lifetime > self.maxLifetime || self.x < -100 || self.x > 2148 || self.y < -100 || self.y > 2832) { self.markForDestroy = true; } }; return self; }); var Gasterblaster = Container.expand(function () { var self = Container.call(this); var blasterGraphics = self.attachAsset('Gasterblaster', { anchorX: 0.5, anchorY: 0.5, scaleX: 1.5, scaleY: 1.5 }); self.fireTimer = 0; self.fireInterval = 180; // Fire every 3 seconds self.isActive = true; self.orbitAngle = 0; self.orbitRadius = 750; self.orbitSpeed = 0.025; self.targetDirection = 'horizontal'; // Default laser direction self.update = function () { if (!self.isActive) return; // Fire laser periodically self.fireTimer++; if (self.fireTimer >= self.fireInterval) { self.fireLaser(); self.fireTimer = 0; // Randomize next fire interval self.fireInterval = 150 + Math.random() * 90; } }; self.fireLaser = function () { var laser = new LaserAttack(); // Choose random laser direction var directions = ['horizontal', 'vertical', 'diagonal1', 'diagonal2']; laser.direction = directions[Math.floor(Math.random() * directions.length)]; // Position laser based on direction if (laser.direction === 'horizontal') { laser.x = 1024; laser.y = 666 + Math.random() * 1400; // Random Y within battle area laser.rotation = 0; // Position Gasterblaster at left edge of laser self.x = 324 - 100; // Position outside battle box on left self.y = laser.y; blasterGraphics.rotation = 0; // Face right for horizontal laser } else if (laser.direction === 'vertical') { laser.x = 324 + Math.random() * 1400; // Random X within battle area laser.y = 1366; laser.rotation = Math.PI / 2; // Position Gasterblaster at top edge of laser self.x = laser.x; self.y = 666 - 100; // Position outside battle box on top blasterGraphics.rotation = Math.PI / 2; // Face down for vertical laser } else if (laser.direction === 'diagonal1') { laser.x = 1024; laser.y = 1366; laser.rotation = Math.PI / 4; // Position Gasterblaster at top-left for diagonal laser self.x = 324 - 100; self.y = 666 - 100; blasterGraphics.rotation = Math.PI / 4; // Face diagonal } else if (laser.direction === 'diagonal2') { laser.x = 1024; laser.y = 1366; laser.rotation = -Math.PI / 4; // Position Gasterblaster at top-right for diagonal laser self.x = 1724 + 100; self.y = 666 - 100; blasterGraphics.rotation = -Math.PI / 4; // Face diagonal } // Charging animation - glow effect tween(blasterGraphics, { tint: 0xffffff, scaleX: 2, scaleY: 2 }, { duration: 400, easing: tween.easeOut, onFinish: function onFinish() { // Return to normal after charging tween(blasterGraphics, { tint: 0x345073, scaleX: 1.5, scaleY: 1.5 }, { duration: 200 }); } }); laser.startWarning(); laserAttacks.push(laser); game.addChild(laser); }; return self; }); var Heart = Container.expand(function () { var self = Container.call(this); var heartGraphics = self.attachAsset('heart', { anchorX: 0.5, anchorY: 0.5 }); // Rotate to make it look like a heart heartGraphics.rotation = Math.PI / 4; self.speed = 8; self.isDragging = false; return self; }); var LaserAttack = Container.expand(function () { var self = Container.call(this); // Warning phase graphics var warningGraphics = self.attachAsset('laserWarning', { anchorX: 0.5, anchorY: 0.5, alpha: 0 }); // Actual laser beam graphics var laserGraphics = self.attachAsset('laserBeam', { anchorX: 0.5, anchorY: 0.5, alpha: 0 }); self.phase = 'warning'; // warning, firing, done self.warningDuration = 60; // 1 second warning self.firingDuration = 90; // 1.5 seconds firing self.timer = 0; self.direction = 'horizontal'; // horizontal or vertical self.isActive = false; self.startWarning = function () { self.isActive = true; self.phase = 'warning'; self.timer = 0; // Animate warning appearing tween(warningGraphics, { alpha: 0.6 }, { duration: 300, easing: tween.easeInOut }); // Pulsing warning effect var _pulseWarning = function pulseWarning() { if (self.phase === 'warning') { tween(warningGraphics, { alpha: 0.3 }, { duration: 400, easing: tween.easeInOut, onFinish: function onFinish() { tween(warningGraphics, { alpha: 0.6 }, { duration: 400, easing: tween.easeInOut, onFinish: _pulseWarning }); } }); } }; _pulseWarning(); }; self.startFiring = function () { self.phase = 'firing'; self.timer = 0; // Hide warning, show laser tween(warningGraphics, { alpha: 0 }, { duration: 100 }); tween(laserGraphics, { alpha: 1 }, { duration: 100 }); // Laser intensity effect tween(laserGraphics, { scaleY: 1.2 }, { duration: 200, easing: tween.easeOut, onFinish: function onFinish() { tween(laserGraphics, { scaleY: 1 }, { duration: 200, easing: tween.easeIn }); } }); }; self.update = function () { if (!self.isActive) return; self.timer++; if (self.phase === 'warning' && self.timer >= self.warningDuration) { self.startFiring(); } else if (self.phase === 'firing' && self.timer >= self.firingDuration) { self.phase = 'done'; tween(laserGraphics, { alpha: 0 }, { duration: 300 }); self.markForDestroy = true; } // Check collision during firing phase if (self.phase === 'firing' && heart) { var collision = false; if (self.direction === 'horizontal') { // Check if heart is within laser beam vertically var beamTop = self.y - laserGraphics.height / 2; var beamBottom = self.y + laserGraphics.height / 2; if (heart.y >= beamTop && heart.y <= beamBottom) { collision = true; } } else if (self.direction === 'vertical') { // Vertical laser var beamLeft = self.x - laserGraphics.width / 2; var beamRight = self.x + laserGraphics.width / 2; if (heart.x >= beamLeft && heart.x <= beamRight) { collision = true; } } else if (self.direction === 'diagonal1') { // Diagonal laser (top-left to bottom-right) var dx = heart.x - self.x; var dy = heart.y - self.y; var distanceFromLine = Math.abs(dx - dy) / Math.sqrt(2); if (distanceFromLine <= laserGraphics.height / 2) { collision = true; } } else if (self.direction === 'diagonal2') { // Diagonal laser (top-right to bottom-left) var dx = heart.x - self.x; var dy = heart.y - self.y; var distanceFromLine = Math.abs(dx + dy) / Math.sqrt(2); if (distanceFromLine <= laserGraphics.height / 2) { collision = true; } } else if (self.direction === 'focused') { // Focused narrow laser beam from drone var dx = heart.x - self.x; var dy = heart.y - self.y; var heartDistance = Math.sqrt(dx * dx + dy * dy); var heartAngle = Math.atan2(dy, dx); var angleDiff = Math.abs(heartAngle - self.targetAngle); // Normalize angle difference to 0-PI range if (angleDiff > Math.PI) angleDiff = 2 * Math.PI - angleDiff; // Check if heart is within narrow beam width and reasonable distance var beamWidth = laserGraphics.height / 2; var perpendicularDistance = heartDistance * Math.sin(angleDiff); if (perpendicularDistance <= beamWidth && heartDistance <= 700) { collision = true; } } if (collision) { LK.getSound('hit').play(); LK.effects.flashScreen(0xff0000, 500); playerHealth -= 20; if (playerHealth <= 0) { playerHealth = 0; updateHealthBars(); LK.showGameOver(); } else { updateHealthBars(); } self.phase = 'done'; tween(laserGraphics, { alpha: 0 }, { duration: 300 }); self.markForDestroy = true; } } }; return self; }); var LaserDrone = Container.expand(function () { var self = Container.call(this); // Create drone visual - a simple geometric shape var droneGraphics = self.attachAsset('bullet', { anchorX: 0.5, anchorY: 0.5, scaleX: 1.5, scaleY: 1.5, tint: 0x4444ff }); // Drone properties self.sansRef = null; self.fireTimer = 0; self.fireInterval = 180; // Fire every 3 seconds self.isActive = true; self.orbitAngle = 0; self.orbitRadius = 150; self.orbitSpeed = 0.02; self.setSansReference = function (sansDisplayObject) { self.sansRef = sansDisplayObject; }; self.update = function () { if (!self.isActive || !self.sansRef) return; // Orbit around battle box perimeter instead of Sans self.orbitAngle += self.orbitSpeed; var battleBoxX = 1024; var battleBoxY = 1366; var battleBoxRadius = 750; // Orbit around the battle box edge self.x = battleBoxX + Math.cos(self.orbitAngle) * battleBoxRadius; self.y = battleBoxY + Math.sin(self.orbitAngle) * battleBoxRadius; // Fire laser periodically self.fireTimer++; if (self.fireTimer >= self.fireInterval) { self.fireLaser(); self.fireTimer = 0; // Randomize next fire interval slightly self.fireInterval = 150 + Math.random() * 60; } }; self.fireLaser = function () { if (!self.sansRef) return; // Flash drone when firing tween(droneGraphics, { tint: 0xffffff }, { duration: 200, onFinish: function onFinish() { tween(droneGraphics, { tint: 0x4444ff }, { duration: 300 }); } }); var laser = new LaserAttack(); // Fire narrow focused laser towards center of battle box var battleBoxCenterX = 1024; var battleBoxCenterY = 1366; // Calculate angle from drone to battle box center var dx = battleBoxCenterX - self.x; var dy = battleBoxCenterY - self.y; var angle = Math.atan2(dy, dx); // Position laser from drone mouth towards center laser.direction = 'focused'; // New focused direction laser.x = self.x; laser.y = self.y; laser.rotation = angle; // Store target direction for collision detection laser.targetAngle = angle; laser.startWarning(); laserAttacks.push(laser); game.addChild(laser); }; return self; }); var LaserEntity = Container.expand(function () { var self = Container.call(this); var entityGraphics = self.attachAsset('bullet', { anchorX: 0.5, anchorY: 0.5, scaleX: 2, scaleY: 2, tint: 0xff4444 }); self.fireTimer = 0; self.fireInterval = 120; // Fire every 2 seconds self.isActive = true; self.orbitAngle = 0; self.orbitSpeed = 0.03; self.update = function () { if (!self.isActive) return; // Orbit around battle area self.orbitAngle += self.orbitSpeed; var centerX = 1024; var centerY = 1366; var radius = 800; self.x = centerX + Math.cos(self.orbitAngle) * radius; self.y = centerY + Math.sin(self.orbitAngle) * radius; // Fire laser periodically self.fireTimer++; if (self.fireTimer >= self.fireInterval) { self.fireLaser(); self.fireTimer = 0; } }; self.fireLaser = function () { // Flash when firing tween(entityGraphics, { tint: 0xffffff }, { duration: 150, onFinish: function onFinish() { tween(entityGraphics, { tint: 0xff4444 }, { duration: 200 }); } }); var laser = new LaserAttack(); laser.direction = 'horizontal'; laser.x = 1024; laser.y = self.y; laser.startWarning(); laserAttacks.push(laser); game.addChild(laser); }; return self; }); var OrangeBullet = Container.expand(function () { var self = Container.call(this); var bulletGraphics = self.attachAsset('orangeBullet', { anchorX: 0.5, anchorY: 0.5, tint: 0xff6600 }); self.speedX = 0; self.speedY = 0; self.lifetime = 0; self.maxLifetime = 360; self.rotationSpeed = 0.1; self.lastHeartMoving = false; self.update = function () { self.x += self.speedX; self.y += self.speedY; self.lifetime++; // Rotate orange bullets bulletGraphics.rotation += self.rotationSpeed; // Slight homing behavior towards center var centerX = 1024; var centerY = 1366; var dx = centerX - self.x; var dy = centerY - self.y; var distance = Math.sqrt(dx * dx + dy * dy); if (distance > 50) { self.speedX += dx / distance * 0.05; self.speedY += dy / distance * 0.05; } // Cap speed var speed = Math.sqrt(self.speedX * self.speedX + self.speedY * self.speedY); if (speed > 6) { self.speedX = self.speedX / speed * 6; self.speedY = self.speedY / speed * 6; } if (self.lifetime > self.maxLifetime || self.x < -100 || self.x > 2148 || self.y < -100 || self.y > 2832) { self.markForDestroy = true; } }; return self; }); var SpeechBubble = Container.expand(function (message) { var self = Container.call(this); // Create speech bubble background var bubbleWidth = Math.min(1400, message.length * 25 + 200); var bubbleHeight = 300; var bubbleBg = LK.getAsset('actionButton', { anchorX: 0.5, anchorY: 0.5, scaleX: bubbleWidth / 400, scaleY: bubbleHeight / 150, tint: 0xffffff, alpha: 0.9 }); self.addChild(bubbleBg); // Create speech bubble border var bubbleBorder = LK.getAsset('actionButton', { anchorX: 0.5, anchorY: 0.5, scaleX: (bubbleWidth + 20) / 400, scaleY: (bubbleHeight + 20) / 150, tint: 0x000000, alpha: 0.8 }); self.addChild(bubbleBorder); self.addChild(bubbleBg); // Create speech bubble tail (pointing down to Sans) var tailWidth = 40; var tailHeight = 60; var tail = LK.getAsset('actionButton', { anchorX: 0.5, anchorY: 0, scaleX: tailWidth / 400, scaleY: tailHeight / 150, tint: 0xffffff, alpha: 0.9, y: bubbleHeight / 2, rotation: Math.PI / 4 }); self.addChild(tail); // Create message text var messageText = new Text2(message, { size: 35, fill: 0x000000, font: "'GillSans-Bold',Impact,'Arial Black',Tahoma" }); messageText.anchor.set(0.5, 0.5); self.addChild(messageText); return self; }); var SpiralBullet = Container.expand(function () { var self = Container.call(this); var bulletGraphics = self.attachAsset('bullet', { anchorX: 0.5, anchorY: 0.5 }); self.centerX = 1024; self.centerY = 1366; self.angle = 0; self.radius = 50; self.radiusSpeed = 2; self.angleSpeed = 0.1; self.lifetime = 0; self.maxLifetime = 400; self.update = function () { self.lifetime++; self.angle += self.angleSpeed; self.radius += self.radiusSpeed; self.x = self.centerX + Math.cos(self.angle) * self.radius; self.y = self.centerY + Math.sin(self.angle) * self.radius; if (self.lifetime > self.maxLifetime || self.radius > 800) { self.markForDestroy = true; } }; return self; }); /**** * Initialize Game ****/ var game = new LK.Game({ backgroundColor: 0x000000 }); /**** * Game Code ****/ // Game state variables var heart; var bullets = []; var blueBullets = []; var orangeBullets = []; var laserAttacks = []; var spiralBullets = []; var lastHeartX = 0; var lastHeartY = 0; var isHeartMoving = false; var battleBox; var isDragging = false; var dragOffsetX = 0; var dragOffsetY = 0; var waveTimer = 0; var currentWave = 1; var waveSpawnTimer = 0; var gameTime = 0; var isActionPhase = false; var actionTimer = 0; var playerHealth = 150; var maxHealth = 150; var itemUses = 3; var enemyHealth = 1; var maxEnemyHealth = 200; var mercyAttempts = 0; var maxMercyAttempts = 3; var isShowingDialogue = false; var dialogueTimer = 0; var currentDialogue = null; // Create UI elements var scoreText = new Text2('Score: 0', { size: 80, fill: 0xFFFFFF }); scoreText.anchor.set(0.5, 0); LK.gui.top.addChild(scoreText); var waveText = new Text2('Wave 1', { size: 60, fill: 0xFFFF00 }); waveText.anchor.set(0, 0); waveText.x = 50; waveText.y = 200; LK.gui.topLeft.addChild(waveText); // Action phase UI var actionPhaseText = new Text2('Wybierz akcję!', { size: 80, fill: 0xFFFF00 }); actionPhaseText.anchor.set(0.5, 0.5); actionPhaseText.x = 1024; actionPhaseText.y = 300; actionPhaseText.alpha = 0; // Health bars - positioned next to action buttons var playerHealthBarBg = LK.getAsset('healthBarBg', { anchorX: 0, anchorY: 0.5, x: 100, y: 2200, alpha: 1 }); var playerHealthBar = LK.getAsset('healthBar', { anchorX: 0, anchorY: 0.5, x: 100, y: 2200, alpha: 1 }); var playerHealthText = new Text2('HP: 100/100', { size: 50, fill: 0xFFFFFF }); playerHealthText.anchor.set(0, 0.5); playerHealthText.x = 520; playerHealthText.y = 2200; var enemyHealthBarBg = LK.getAsset('healthBarBg', { anchorX: 0, anchorY: 0.5, x: 1500, y: 100, alpha: 1 }); var enemyHealthBar = LK.getAsset('healthBar', { anchorX: 0, anchorY: 0.5, x: 1500, y: 100, tint: 0xff0000, alpha: 1 }); var enemyHealthText = new Text2('Sans HP: 200/200', { size: 50, fill: 0xFFFFFF }); enemyHealthText.anchor.set(1, 0.5); enemyHealthText.x = 1900; enemyHealthText.y = 100; // Enemy display at top - Sans (positioned in background) - larger size var enemyDisplay = LK.getAsset('sans', { anchorX: 0.5, anchorY: 0.5, x: 1024, y: 250, scaleX: 1.5, scaleY: 1.5 }); // Sans dodging variables var sansDodgeTimer = 0; var sansDodgeCooldown = 0; var sansOriginalX = 1024; // Add Sans to game background first so dialogue appears on top game.addChild(enemyDisplay); LK.gui.addChild(playerHealthBarBg); LK.gui.addChild(playerHealthBar); LK.gui.addChild(playerHealthText); LK.gui.addChild(enemyHealthBarBg); LK.gui.addChild(enemyHealthBar); LK.gui.addChild(enemyHealthText); // Action buttons var attackButton = new ActionButton('SALDIRI', function () { // Check if Sans is tired (after wave 15) or should dodge var isTired = currentWave > 15; var shouldDodge = !isTired && sansDodgeCooldown === 0; // Always dodge if not tired and cooldown ready var damage = 0; var damageText; if (shouldDodge) { // Sans dodges - no damage taken // Choose random dodge direction var dodgeDirection = Math.random() < 0.5 ? -1 : 1; var dodgeDistance = 200 + Math.random() * 150; var newX = sansOriginalX + dodgeDirection * dodgeDistance; // Keep Sans within screen bounds newX = Math.max(200, Math.min(1848, newX)); // Dodge animation - quick movement tween(enemyDisplay, { x: newX }, { duration: 400, easing: tween.easeOut, onFinish: function onFinish() { // Return to original position after a delay LK.setTimeout(function () { tween(enemyDisplay, { x: sansOriginalX }, { duration: 800, easing: tween.easeInOut }); }, 1000); } }); // Set cooldown to prevent constant dodging sansDodgeCooldown = 60; // Shorter cooldown for more frequent dodging // Show dodge message if (currentWave <= 15) { damageText = new Text2('MISS!\nSans kaçtı!', { size: 60, fill: 0xffff00 }); } else { damageText = new Text2('MISS!\nSans yorgun ama hala kaçıyor!', { size: 60, fill: 0xffff00 }); } } else if (isTired) { // Sans is tired after wave 15 - can now take damage damage = Math.floor(Math.random() * 20) + 10; enemyHealth -= damage; if (enemyHealth <= 0) { enemyHealth = 0; LK.showYouWin(); } // Show damage dealt and remaining health damageText = new Text2('-' + damage + ' HP!\nSans yoruldu! Kalan Can: ' + enemyHealth + '/' + maxEnemyHealth, { size: 60, fill: 0xff4444 }); // Show Sans getting tired with visual effect tween(enemyDisplay, { alpha: 0.7, scaleX: 1.3, scaleY: 1.3 }, { duration: 300, onFinish: function onFinish() { tween(enemyDisplay, { alpha: 1, scaleX: 1.5, scaleY: 1.5 }, { duration: 300 }); } }); } else { // Before wave 15 - Sans always dodges successfully damageText = new Text2('MISS!\nSans çok hızlı!', { size: 60, fill: 0xffff00 }); } damageText.anchor.set(0.5, 0.5); damageText.x = 1024; damageText.y = 400; game.addChild(damageText); tween(damageText, { y: damageText.y - 100, alpha: 0 }, { duration: 2000, onFinish: function onFinish() { damageText.destroy(); } }); updateHealthBars(); endActionPhase(); }); var actButton = new ActionButton('EYLEM', function () { // Act action - show enemy stats var statsText = new Text2('Sans İstatistikleri:\nSaldırı: 15\nSavunma: 8\nHP: ' + enemyHealth + '/' + maxEnemyHealth + '\n*Seni yargılıyor...', { size: 50, fill: 0xFFFFFF }); statsText.anchor.set(0.5, 0.5); statsText.x = 1024; statsText.y = 1366; game.addChild(statsText); tween(statsText, { alpha: 0 }, { duration: 3000, onFinish: function onFinish() { statsText.destroy(); } }); endActionPhase(); }); var itemButton = new ActionButton('EŞYA', function () { // Item action - heal player if (itemUses > 0 && playerHealth < maxHealth) { playerHealth = Math.min(maxHealth, playerHealth + 30); itemUses--; var healText = new Text2('+30 HP!\nKalan kullanım: ' + itemUses, { size: 60, fill: 0x00ff00 }); healText.anchor.set(0.5, 0.5); healText.x = heart.x; healText.y = heart.y - 50; game.addChild(healText); tween(healText, { y: healText.y - 100, alpha: 0 }, { duration: 2000, onFinish: function onFinish() { healText.destroy(); } }); updateHealthBars(); } endActionPhase(); }); var mercyButton = new ActionButton('MERHAMET', function () { mercyAttempts++; var mercyText; if (mercyAttempts < maxMercyAttempts) { // Sans doesn't forgive yet var responses = ['*Sans sana şüpheyle bakıyor.\n*Henüz affetmeye hazır değil.', '*Sans hala kararlı görünüyor.\n*Biraz daha sabır gerekiyor.', '*Sans\'ın gözlerinde değişim var...\n*Ama henüz yeterli değil.']; mercyText = new Text2(responses[mercyAttempts - 1] + '\n\n(' + mercyAttempts + '/' + maxMercyAttempts + ' deneme)', { size: 60, fill: 0xffff00 }); } else { // Sans finally forgives mercyText = new Text2('*Sans derin bir nefes alıyor...\n*"tamam, belki sen farklısın."\n*Sans seni affetti!\nKazandın!', { size: 70, fill: 0x00ff00 }); } mercyText.anchor.set(0.5, 0.5); mercyText.x = 1024; mercyText.y = 1366; game.addChild(mercyText); if (mercyAttempts >= maxMercyAttempts) { LK.setTimeout(function () { LK.showYouWin(); }, 3000); } else { tween(mercyText, { alpha: 0 }, { duration: 3000, onFinish: function onFinish() { mercyText.destroy(); } }); } endActionPhase(); }); attackButton.x = 512; attackButton.y = 2400; actButton.x = 1024; actButton.y = 2400; itemButton.x = 1536; itemButton.y = 2400; mercyButton.x = 1024; mercyButton.y = 2600; game.addChild(actionPhaseText); game.addChild(attackButton); game.addChild(actButton); game.addChild(itemButton); game.addChild(mercyButton); // Initially hide action elements attackButton.alpha = 0; actButton.alpha = 0; itemButton.alpha = 0; mercyButton.alpha = 0; // Sans dialogue messages var sansDialogues = ["bugün güzel bir gün,kuşlar cıvıldıyor, çiçekler açıyor,böyle günlerde senin gibi çocuklar, CEHENNEMDE YANMALI", "*hmm... yaptıklarını hatırlıyorum.", "*kaç tane masumun canını aldın acaba?", "*o gülümseme yüzünden silinmiyor, değil mi?", "*gerçekten pişman olduğunu sanıyor musun?", "*haha... bu çok komik.", "*seni affetmemi mi bekliyorsun?", "*yaptıklarının hesabını vereceksin.", "*her şeyi hatırlıyorum, her şeyi.", "*bu sefer farklı olacağını mı sanıyorsun?", "*sen sadece bir katilsin, başka bir şey değil.", "*devam ediyorsun... neden?", "*artık sıkıldım bu oyundan.", "*belki de... artık yeter.", "*huff... huff... yoruldum.", "*artık kaçamıyorum... son şansın bu."]; // Create battle box (play area boundary) battleBox = game.addChild(LK.getAsset('battleBox', { anchorX: 0.5, anchorY: 0.5, x: 1024, y: 1366, alpha: 0.1 })); // Create heart heart = game.addChild(new Heart()); heart.x = 1024; heart.y = 1366; // Create multiple Gasterblasters var gasterblasters = []; for (var gb = 0; gb < 3; gb++) { var gasterblaster = new Gasterblaster(); gasterblaster.isActive = false; // Start deactivated until wave 4 gasterblaster.fireInterval = 180 + gb * 60; // Stagger firing times gasterblasters.push(gasterblaster); game.addChild(gasterblaster); } // Touch/drag handlers game.down = function (x, y, obj) { // Allow dragging from anywhere on screen for mobile isDragging = true; dragOffsetX = x - heart.x; dragOffsetY = y - heart.y; }; game.move = function (x, y, obj) { if (isDragging) { var newX = x - dragOffsetX; var newY = y - dragOffsetY; // Constrain to battle box var boxLeft = battleBox.x - battleBox.width / 2 + 30; var boxRight = battleBox.x + battleBox.width / 2 - 30; var boxTop = battleBox.y - battleBox.height / 2 + 30; var boxBottom = battleBox.y + battleBox.height / 2 - 30; heart.x = Math.max(boxLeft, Math.min(boxRight, newX)); heart.y = Math.max(boxTop, Math.min(boxBottom, newY)); } }; game.up = function (x, y, obj) { isDragging = false; }; // Spawn patterns function spawnStraightBullets() { var numBullets = 4 + Math.floor(currentWave / 2); var side = Math.floor(Math.random() * 4); // 0=top, 1=right, 2=bottom, 3=left for (var i = 0; i < numBullets; i++) { var bullet = new Bullet(); var speed = 5 + currentWave * 0.8; switch (side) { case 0: // Top bullet.x = 324 + 1400 / numBullets * i; bullet.y = 566; bullet.speedY = speed; break; case 1: // Right bullet.x = 1724; bullet.y = 666 + 1400 / numBullets * i; bullet.speedX = -speed; break; case 2: // Bottom bullet.x = 324 + 1400 / numBullets * i; bullet.y = 2066; bullet.speedY = -speed; break; case 3: // Left bullet.x = 324; bullet.y = 666 + 1400 / numBullets * i; bullet.speedX = speed; break; } bullets.push(bullet); game.addChild(bullet); } } function spawnConvergingBullets() { var numBullets = 8 + currentWave; var centerX = heart.x; var centerY = heart.y; for (var i = 0; i < numBullets; i++) { var angle = Math.PI * 2 * i / numBullets; var distance = 800; var bullet = new Bullet(); bullet.x = centerX + Math.cos(angle) * distance; bullet.y = centerY + Math.sin(angle) * distance; var speed = 4 + currentWave * 0.6; bullet.speedX = -Math.cos(angle) * speed; bullet.speedY = -Math.sin(angle) * speed; bullets.push(bullet); game.addChild(bullet); } } function spawnColoredBullets() { var numBullets = 3 + Math.floor(currentWave / 3); for (var i = 0; i < numBullets; i++) { // Randomly choose blue or orange bullet var isBlue = Math.random() < 0.5; var bullet = isBlue ? new BlueBullet() : new OrangeBullet(); // Spawn from random edge var edge = Math.floor(Math.random() * 4); switch (edge) { case 0: // Top bullet.x = 324 + Math.random() * 1400; bullet.y = 466; break; case 1: // Right bullet.x = 1824; bullet.y = 566 + Math.random() * 1400; break; case 2: // Bottom bullet.x = 324 + Math.random() * 1400; bullet.y = 2166; break; case 3: // Left bullet.x = 224; bullet.y = 566 + Math.random() * 1400; break; } // Initial speed towards center with some randomness var dx = 1024 - bullet.x; var dy = 1366 - bullet.y; var distance = Math.sqrt(dx * dx + dy * dy); var speed = 3 + currentWave * 0.4; bullet.speedX = dx / distance * speed + (Math.random() - 0.5) * 2; bullet.speedY = dy / distance * speed + (Math.random() - 0.5) * 2; if (isBlue) { blueBullets.push(bullet); } else { orangeBullets.push(bullet); } game.addChild(bullet); } } function spawnLaserAttack() { // Gasterblasters now handle laser spawning automatically // This function triggers a random active Gasterblaster to fire var activeBlasters = []; for (var gb = 0; gb < gasterblasters.length; gb++) { if (gasterblasters[gb].isActive) { activeBlasters.push(gasterblasters[gb]); } } if (activeBlasters.length > 0) { var randomBlaster = activeBlasters[Math.floor(Math.random() * activeBlasters.length)]; randomBlaster.fireLaser(); } } function spawnSpiralAttack() { var numSpirals = 2 + Math.floor(currentWave / 4); for (var i = 0; i < numSpirals; i++) { var spiral = new SpiralBullet(); spiral.angle = Math.PI * 2 * i / numSpirals; spiral.angleSpeed = 0.08 + Math.random() * 0.04; spiral.radiusSpeed = 1.5 + Math.random() * 1; spiralBullets.push(spiral); game.addChild(spiral); } } function showSansDialogue(message, callback) { isShowingDialogue = true; dialogueTimer = 0; currentDialogue = new SpeechBubble(message); currentDialogue.x = 1024; currentDialogue.y = 450; // Position above Sans currentDialogue.alpha = 0; currentDialogue.scaleX = 0.3; currentDialogue.scaleY = 0.3; game.addChild(currentDialogue); // Fade in dialogue with bounce effect tween(currentDialogue, { alpha: 1, scaleX: 1, scaleY: 1 }, { duration: 500, easing: tween.bounceOut, onFinish: function onFinish() { // Keep dialogue visible for 3 seconds LK.setTimeout(function () { // Fade out dialogue tween(currentDialogue, { alpha: 0, scaleX: 0.8, scaleY: 0.8 }, { duration: 500, easing: tween.easeIn, onFinish: function onFinish() { currentDialogue.destroy(); currentDialogue = null; isShowingDialogue = false; if (callback) callback(); } }); }, 3000); } }); } function startActionPhase() { isActionPhase = true; actionTimer = 0; // Stop all enemy attacks for (var i = bullets.length - 1; i >= 0; i--) { bullets[i].destroy(); } bullets = []; for (var j = blueBullets.length - 1; j >= 0; j--) { blueBullets[j].destroy(); } blueBullets = []; for (var k = orangeBullets.length - 1; k >= 0; k--) { orangeBullets[k].destroy(); } orangeBullets = []; for (var l = laserAttacks.length - 1; l >= 0; l--) { laserAttacks[l].destroy(); } laserAttacks = []; for (var s = spiralBullets.length - 1; s >= 0; s--) { spiralBullets[s].destroy(); } spiralBullets = []; // Show action UI tween(actionPhaseText, { alpha: 1 }, { duration: 500 }); tween(attackButton, { alpha: 1 }, { duration: 500 }); tween(actButton, { alpha: 1 }, { duration: 500 }); tween(itemButton, { alpha: itemUses > 0 ? 1 : 0.3 }, { duration: 500 }); tween(mercyButton, { alpha: 1 }, { duration: 500 }); } function endActionPhase() { isActionPhase = false; waveSpawnTimer = 0; // Hide action UI tween(actionPhaseText, { alpha: 0 }, { duration: 500 }); tween(attackButton, { alpha: 0 }, { duration: 500 }); tween(actButton, { alpha: 0 }, { duration: 500 }); tween(itemButton, { alpha: 0 }, { duration: 500 }); tween(mercyButton, { alpha: 0 }, { duration: 500 }); } function updateHealthBars() { var playerHealthPercent = playerHealth / maxHealth; var enemyHealthPercent = enemyHealth / maxEnemyHealth; tween(playerHealthBar, { scaleX: playerHealthPercent }, { duration: 300 }); tween(enemyHealthBar, { scaleX: enemyHealthPercent }, { duration: 300 }); playerHealthText.setText('HP: ' + playerHealth + '/' + maxHealth); enemyHealthText.setText('Sans HP: ' + enemyHealth + '/' + maxEnemyHealth); } // Main game update game.update = function () { gameTime++; // Don't update game during dialogue if (isShowingDialogue) { return; } if (isActionPhase) { actionTimer++; // Auto-end action phase after 10 seconds if no action taken if (actionTimer > 600) { endActionPhase(); } return; } waveTimer++; waveSpawnTimer++; // Update score LK.setScore(Math.floor(gameTime / 60)); scoreText.setText('Score: ' + LK.getScore()); // Wave progression and action phase trigger if (waveTimer > 600 && !isShowingDialogue) { // Show Sans dialogue before action phase var dialogueIndex = Math.min(currentWave - 1, sansDialogues.length - 1); var message = sansDialogues[dialogueIndex]; showSansDialogue(message, function () { // 10 seconds per wave, then action phase startActionPhase(); waveTimer = 0; currentWave++; waveText.setText('Wave ' + currentWave); // Flash effect for new wave LK.effects.flashScreen(0x444444, 500); }); return; } // Spawn bullets based on wave and timing if (waveSpawnTimer > 60 - currentWave * 3) { // Faster spawning each wave waveSpawnTimer = 0; var pattern = Math.floor(Math.random() * 5); if (currentWave < 3) pattern = 0; // Only straight bullets early else if (currentWave < 4) pattern = Math.floor(Math.random() * 3); // No lasers/spirals before wave 4 else if (currentWave < 6) pattern = Math.floor(Math.random() * 4); // Include lasers from wave 4 switch (pattern) { case 0: spawnStraightBullets(); break; case 1: spawnConvergingBullets(); break; case 2: spawnColoredBullets(); break; case 3: spawnLaserAttack(); break; case 4: spawnSpiralAttack(); break; } } // Sans dodging cooldown management if (sansDodgeCooldown > 0) { sansDodgeCooldown--; } // Activate Gasterblasters from wave 4 onwards if (currentWave >= 4) { for (var gb = 0; gb < gasterblasters.length; gb++) { if (!gasterblasters[gb].isActive) { gasterblasters[gb].isActive = true; } } } // Update Gasterblasters for (var gb = 0; gb < gasterblasters.length; gb++) { if (gasterblasters[gb].isActive) { gasterblasters[gb].update(); } } // Update and check bullet collisions for (var i = bullets.length - 1; i >= 0; i--) { var bullet = bullets[i]; if (bullet.markForDestroy) { bullet.destroy(); bullets.splice(i, 1); continue; } // Collision with heart if (bullet.intersects(heart)) { LK.getSound('hit').play(); LK.effects.flashScreen(0xff0000, 500); playerHealth -= 10; if (playerHealth <= 0) { playerHealth = 0; updateHealthBars(); LK.showGameOver(); return; } updateHealthBars(); bullet.destroy(); bullets.splice(i, 1); continue; } } // Track heart movement var currentHeartMoving = heart.x !== lastHeartX || heart.y !== lastHeartY; isHeartMoving = currentHeartMoving; lastHeartX = heart.x; lastHeartY = heart.y; // Update and check blue bullet collisions (safe when stationary) for (var j = blueBullets.length - 1; j >= 0; j--) { var blueBullet = blueBullets[j]; if (blueBullet.markForDestroy) { blueBullet.destroy(); blueBullets.splice(j, 1); continue; } // Collision with heart - only damages if heart is moving if (blueBullet.intersects(heart) && isHeartMoving) { LK.getSound('hit').play(); LK.effects.flashScreen(0xff0000, 500); playerHealth -= 15; if (playerHealth <= 0) { playerHealth = 0; updateHealthBars(); LK.showGameOver(); return; } updateHealthBars(); blueBullet.destroy(); blueBullets.splice(j, 1); continue; } } // Update and check orange bullet collisions (safe when moving) for (var o = orangeBullets.length - 1; o >= 0; o--) { var orangeBullet = orangeBullets[o]; if (orangeBullet.markForDestroy) { orangeBullet.destroy(); orangeBullets.splice(o, 1); continue; } // Collision with heart - only damages if heart is stationary if (orangeBullet.intersects(heart) && !isHeartMoving) { LK.getSound('hit').play(); LK.effects.flashScreen(0xff0000, 500); playerHealth -= 15; if (playerHealth <= 0) { playerHealth = 0; updateHealthBars(); LK.showGameOver(); return; } updateHealthBars(); orangeBullet.destroy(); orangeBullets.splice(o, 1); continue; } } // Update and check laser attacks for (var l = laserAttacks.length - 1; l >= 0; l--) { var laser = laserAttacks[l]; if (laser.markForDestroy) { laser.destroy(); laserAttacks.splice(l, 1); continue; } } // Update and check spiral bullet collisions for (var s = spiralBullets.length - 1; s >= 0; s--) { var spiral = spiralBullets[s]; if (spiral.markForDestroy) { spiral.destroy(); spiralBullets.splice(s, 1); continue; } // Collision with heart if (spiral.intersects(heart)) { LK.getSound('hit').play(); LK.effects.flashScreen(0xff0000, 500); playerHealth -= 12; if (playerHealth <= 0) { playerHealth = 0; updateHealthBars(); LK.showGameOver(); return; } updateHealthBars(); spiral.destroy(); spiralBullets.splice(s, 1); continue; } } // Play dodge sound occasionally for close calls if (gameTime % 180 === 0) { var closeCalls = 0; for (var k = 0; k < bullets.length; k++) { var dist = Math.sqrt(Math.pow(bullets[k].x - heart.x, 2) + Math.pow(bullets[k].y - heart.y, 2)); if (dist < 100) closeCalls++; } if (closeCalls > 2) { LK.getSound('dodge').play(); } } }; // Initialize health bars updateHealthBars(); // Start background music LK.playMusic('megalovania'); ;
/****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
/****
* Classes
****/
var ActionButton = Container.expand(function (label, action) {
var self = Container.call(this);
var buttonGraphics = self.attachAsset('actionButton', {
anchorX: 0.5,
anchorY: 0.5,
alpha: 0.8
});
var buttonText = new Text2(label, {
size: 60,
fill: 0xFFFFFF
});
buttonText.anchor.set(0.5, 0.5);
self.addChild(buttonText);
self.action = action;
self.down = function (x, y, obj) {
if (isActionPhase) {
tween(buttonGraphics, {
scaleX: 0.9,
scaleY: 0.9
}, {
duration: 100,
onFinish: function onFinish() {
tween(buttonGraphics, {
scaleX: 1,
scaleY: 1
}, {
duration: 100
});
}
});
self.action();
}
};
return self;
});
var BlueBullet = Container.expand(function () {
var self = Container.call(this);
var bulletGraphics = self.attachAsset('blueBullet', {
anchorX: 0.5,
anchorY: 0.5,
tint: 0x0099ff
});
self.speedX = 0;
self.speedY = 0;
self.lifetime = 0;
self.maxLifetime = 360;
self.rotationSpeed = 0.1;
self.lastHeartMoving = false;
self.update = function () {
self.x += self.speedX;
self.y += self.speedY;
self.lifetime++;
// Rotate blue bullets
bulletGraphics.rotation += self.rotationSpeed;
// Slight homing behavior towards center
var centerX = 1024;
var centerY = 1366;
var dx = centerX - self.x;
var dy = centerY - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance > 50) {
self.speedX += dx / distance * 0.05;
self.speedY += dy / distance * 0.05;
}
// Cap speed
var speed = Math.sqrt(self.speedX * self.speedX + self.speedY * self.speedY);
if (speed > 6) {
self.speedX = self.speedX / speed * 6;
self.speedY = self.speedY / speed * 6;
}
if (self.lifetime > self.maxLifetime || self.x < -100 || self.x > 2148 || self.y < -100 || self.y > 2832) {
self.markForDestroy = true;
}
};
return self;
});
var Bullet = Container.expand(function () {
var self = Container.call(this);
var bulletGraphics = self.attachAsset('bullet', {
anchorX: 0.5,
anchorY: 0.5
});
self.speedX = 0;
self.speedY = 0;
self.lifetime = 0;
self.maxLifetime = 300; // 5 seconds at 60fps
self.update = function () {
self.x += self.speedX;
self.y += self.speedY;
self.lifetime++;
// Remove if too old or off screen
if (self.lifetime > self.maxLifetime || self.x < -100 || self.x > 2148 || self.y < -100 || self.y > 2832) {
self.markForDestroy = true;
}
};
return self;
});
var Gasterblaster = Container.expand(function () {
var self = Container.call(this);
var blasterGraphics = self.attachAsset('Gasterblaster', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 1.5,
scaleY: 1.5
});
self.fireTimer = 0;
self.fireInterval = 180; // Fire every 3 seconds
self.isActive = true;
self.orbitAngle = 0;
self.orbitRadius = 750;
self.orbitSpeed = 0.025;
self.targetDirection = 'horizontal'; // Default laser direction
self.update = function () {
if (!self.isActive) return;
// Fire laser periodically
self.fireTimer++;
if (self.fireTimer >= self.fireInterval) {
self.fireLaser();
self.fireTimer = 0;
// Randomize next fire interval
self.fireInterval = 150 + Math.random() * 90;
}
};
self.fireLaser = function () {
var laser = new LaserAttack();
// Choose random laser direction
var directions = ['horizontal', 'vertical', 'diagonal1', 'diagonal2'];
laser.direction = directions[Math.floor(Math.random() * directions.length)];
// Position laser based on direction
if (laser.direction === 'horizontal') {
laser.x = 1024;
laser.y = 666 + Math.random() * 1400; // Random Y within battle area
laser.rotation = 0;
// Position Gasterblaster at left edge of laser
self.x = 324 - 100; // Position outside battle box on left
self.y = laser.y;
blasterGraphics.rotation = 0; // Face right for horizontal laser
} else if (laser.direction === 'vertical') {
laser.x = 324 + Math.random() * 1400; // Random X within battle area
laser.y = 1366;
laser.rotation = Math.PI / 2;
// Position Gasterblaster at top edge of laser
self.x = laser.x;
self.y = 666 - 100; // Position outside battle box on top
blasterGraphics.rotation = Math.PI / 2; // Face down for vertical laser
} else if (laser.direction === 'diagonal1') {
laser.x = 1024;
laser.y = 1366;
laser.rotation = Math.PI / 4;
// Position Gasterblaster at top-left for diagonal laser
self.x = 324 - 100;
self.y = 666 - 100;
blasterGraphics.rotation = Math.PI / 4; // Face diagonal
} else if (laser.direction === 'diagonal2') {
laser.x = 1024;
laser.y = 1366;
laser.rotation = -Math.PI / 4;
// Position Gasterblaster at top-right for diagonal laser
self.x = 1724 + 100;
self.y = 666 - 100;
blasterGraphics.rotation = -Math.PI / 4; // Face diagonal
}
// Charging animation - glow effect
tween(blasterGraphics, {
tint: 0xffffff,
scaleX: 2,
scaleY: 2
}, {
duration: 400,
easing: tween.easeOut,
onFinish: function onFinish() {
// Return to normal after charging
tween(blasterGraphics, {
tint: 0x345073,
scaleX: 1.5,
scaleY: 1.5
}, {
duration: 200
});
}
});
laser.startWarning();
laserAttacks.push(laser);
game.addChild(laser);
};
return self;
});
var Heart = Container.expand(function () {
var self = Container.call(this);
var heartGraphics = self.attachAsset('heart', {
anchorX: 0.5,
anchorY: 0.5
});
// Rotate to make it look like a heart
heartGraphics.rotation = Math.PI / 4;
self.speed = 8;
self.isDragging = false;
return self;
});
var LaserAttack = Container.expand(function () {
var self = Container.call(this);
// Warning phase graphics
var warningGraphics = self.attachAsset('laserWarning', {
anchorX: 0.5,
anchorY: 0.5,
alpha: 0
});
// Actual laser beam graphics
var laserGraphics = self.attachAsset('laserBeam', {
anchorX: 0.5,
anchorY: 0.5,
alpha: 0
});
self.phase = 'warning'; // warning, firing, done
self.warningDuration = 60; // 1 second warning
self.firingDuration = 90; // 1.5 seconds firing
self.timer = 0;
self.direction = 'horizontal'; // horizontal or vertical
self.isActive = false;
self.startWarning = function () {
self.isActive = true;
self.phase = 'warning';
self.timer = 0;
// Animate warning appearing
tween(warningGraphics, {
alpha: 0.6
}, {
duration: 300,
easing: tween.easeInOut
});
// Pulsing warning effect
var _pulseWarning = function pulseWarning() {
if (self.phase === 'warning') {
tween(warningGraphics, {
alpha: 0.3
}, {
duration: 400,
easing: tween.easeInOut,
onFinish: function onFinish() {
tween(warningGraphics, {
alpha: 0.6
}, {
duration: 400,
easing: tween.easeInOut,
onFinish: _pulseWarning
});
}
});
}
};
_pulseWarning();
};
self.startFiring = function () {
self.phase = 'firing';
self.timer = 0;
// Hide warning, show laser
tween(warningGraphics, {
alpha: 0
}, {
duration: 100
});
tween(laserGraphics, {
alpha: 1
}, {
duration: 100
});
// Laser intensity effect
tween(laserGraphics, {
scaleY: 1.2
}, {
duration: 200,
easing: tween.easeOut,
onFinish: function onFinish() {
tween(laserGraphics, {
scaleY: 1
}, {
duration: 200,
easing: tween.easeIn
});
}
});
};
self.update = function () {
if (!self.isActive) return;
self.timer++;
if (self.phase === 'warning' && self.timer >= self.warningDuration) {
self.startFiring();
} else if (self.phase === 'firing' && self.timer >= self.firingDuration) {
self.phase = 'done';
tween(laserGraphics, {
alpha: 0
}, {
duration: 300
});
self.markForDestroy = true;
}
// Check collision during firing phase
if (self.phase === 'firing' && heart) {
var collision = false;
if (self.direction === 'horizontal') {
// Check if heart is within laser beam vertically
var beamTop = self.y - laserGraphics.height / 2;
var beamBottom = self.y + laserGraphics.height / 2;
if (heart.y >= beamTop && heart.y <= beamBottom) {
collision = true;
}
} else if (self.direction === 'vertical') {
// Vertical laser
var beamLeft = self.x - laserGraphics.width / 2;
var beamRight = self.x + laserGraphics.width / 2;
if (heart.x >= beamLeft && heart.x <= beamRight) {
collision = true;
}
} else if (self.direction === 'diagonal1') {
// Diagonal laser (top-left to bottom-right)
var dx = heart.x - self.x;
var dy = heart.y - self.y;
var distanceFromLine = Math.abs(dx - dy) / Math.sqrt(2);
if (distanceFromLine <= laserGraphics.height / 2) {
collision = true;
}
} else if (self.direction === 'diagonal2') {
// Diagonal laser (top-right to bottom-left)
var dx = heart.x - self.x;
var dy = heart.y - self.y;
var distanceFromLine = Math.abs(dx + dy) / Math.sqrt(2);
if (distanceFromLine <= laserGraphics.height / 2) {
collision = true;
}
} else if (self.direction === 'focused') {
// Focused narrow laser beam from drone
var dx = heart.x - self.x;
var dy = heart.y - self.y;
var heartDistance = Math.sqrt(dx * dx + dy * dy);
var heartAngle = Math.atan2(dy, dx);
var angleDiff = Math.abs(heartAngle - self.targetAngle);
// Normalize angle difference to 0-PI range
if (angleDiff > Math.PI) angleDiff = 2 * Math.PI - angleDiff;
// Check if heart is within narrow beam width and reasonable distance
var beamWidth = laserGraphics.height / 2;
var perpendicularDistance = heartDistance * Math.sin(angleDiff);
if (perpendicularDistance <= beamWidth && heartDistance <= 700) {
collision = true;
}
}
if (collision) {
LK.getSound('hit').play();
LK.effects.flashScreen(0xff0000, 500);
playerHealth -= 20;
if (playerHealth <= 0) {
playerHealth = 0;
updateHealthBars();
LK.showGameOver();
} else {
updateHealthBars();
}
self.phase = 'done';
tween(laserGraphics, {
alpha: 0
}, {
duration: 300
});
self.markForDestroy = true;
}
}
};
return self;
});
var LaserDrone = Container.expand(function () {
var self = Container.call(this);
// Create drone visual - a simple geometric shape
var droneGraphics = self.attachAsset('bullet', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 1.5,
scaleY: 1.5,
tint: 0x4444ff
});
// Drone properties
self.sansRef = null;
self.fireTimer = 0;
self.fireInterval = 180; // Fire every 3 seconds
self.isActive = true;
self.orbitAngle = 0;
self.orbitRadius = 150;
self.orbitSpeed = 0.02;
self.setSansReference = function (sansDisplayObject) {
self.sansRef = sansDisplayObject;
};
self.update = function () {
if (!self.isActive || !self.sansRef) return;
// Orbit around battle box perimeter instead of Sans
self.orbitAngle += self.orbitSpeed;
var battleBoxX = 1024;
var battleBoxY = 1366;
var battleBoxRadius = 750; // Orbit around the battle box edge
self.x = battleBoxX + Math.cos(self.orbitAngle) * battleBoxRadius;
self.y = battleBoxY + Math.sin(self.orbitAngle) * battleBoxRadius;
// Fire laser periodically
self.fireTimer++;
if (self.fireTimer >= self.fireInterval) {
self.fireLaser();
self.fireTimer = 0;
// Randomize next fire interval slightly
self.fireInterval = 150 + Math.random() * 60;
}
};
self.fireLaser = function () {
if (!self.sansRef) return;
// Flash drone when firing
tween(droneGraphics, {
tint: 0xffffff
}, {
duration: 200,
onFinish: function onFinish() {
tween(droneGraphics, {
tint: 0x4444ff
}, {
duration: 300
});
}
});
var laser = new LaserAttack();
// Fire narrow focused laser towards center of battle box
var battleBoxCenterX = 1024;
var battleBoxCenterY = 1366;
// Calculate angle from drone to battle box center
var dx = battleBoxCenterX - self.x;
var dy = battleBoxCenterY - self.y;
var angle = Math.atan2(dy, dx);
// Position laser from drone mouth towards center
laser.direction = 'focused'; // New focused direction
laser.x = self.x;
laser.y = self.y;
laser.rotation = angle;
// Store target direction for collision detection
laser.targetAngle = angle;
laser.startWarning();
laserAttacks.push(laser);
game.addChild(laser);
};
return self;
});
var LaserEntity = Container.expand(function () {
var self = Container.call(this);
var entityGraphics = self.attachAsset('bullet', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 2,
scaleY: 2,
tint: 0xff4444
});
self.fireTimer = 0;
self.fireInterval = 120; // Fire every 2 seconds
self.isActive = true;
self.orbitAngle = 0;
self.orbitSpeed = 0.03;
self.update = function () {
if (!self.isActive) return;
// Orbit around battle area
self.orbitAngle += self.orbitSpeed;
var centerX = 1024;
var centerY = 1366;
var radius = 800;
self.x = centerX + Math.cos(self.orbitAngle) * radius;
self.y = centerY + Math.sin(self.orbitAngle) * radius;
// Fire laser periodically
self.fireTimer++;
if (self.fireTimer >= self.fireInterval) {
self.fireLaser();
self.fireTimer = 0;
}
};
self.fireLaser = function () {
// Flash when firing
tween(entityGraphics, {
tint: 0xffffff
}, {
duration: 150,
onFinish: function onFinish() {
tween(entityGraphics, {
tint: 0xff4444
}, {
duration: 200
});
}
});
var laser = new LaserAttack();
laser.direction = 'horizontal';
laser.x = 1024;
laser.y = self.y;
laser.startWarning();
laserAttacks.push(laser);
game.addChild(laser);
};
return self;
});
var OrangeBullet = Container.expand(function () {
var self = Container.call(this);
var bulletGraphics = self.attachAsset('orangeBullet', {
anchorX: 0.5,
anchorY: 0.5,
tint: 0xff6600
});
self.speedX = 0;
self.speedY = 0;
self.lifetime = 0;
self.maxLifetime = 360;
self.rotationSpeed = 0.1;
self.lastHeartMoving = false;
self.update = function () {
self.x += self.speedX;
self.y += self.speedY;
self.lifetime++;
// Rotate orange bullets
bulletGraphics.rotation += self.rotationSpeed;
// Slight homing behavior towards center
var centerX = 1024;
var centerY = 1366;
var dx = centerX - self.x;
var dy = centerY - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance > 50) {
self.speedX += dx / distance * 0.05;
self.speedY += dy / distance * 0.05;
}
// Cap speed
var speed = Math.sqrt(self.speedX * self.speedX + self.speedY * self.speedY);
if (speed > 6) {
self.speedX = self.speedX / speed * 6;
self.speedY = self.speedY / speed * 6;
}
if (self.lifetime > self.maxLifetime || self.x < -100 || self.x > 2148 || self.y < -100 || self.y > 2832) {
self.markForDestroy = true;
}
};
return self;
});
var SpeechBubble = Container.expand(function (message) {
var self = Container.call(this);
// Create speech bubble background
var bubbleWidth = Math.min(1400, message.length * 25 + 200);
var bubbleHeight = 300;
var bubbleBg = LK.getAsset('actionButton', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: bubbleWidth / 400,
scaleY: bubbleHeight / 150,
tint: 0xffffff,
alpha: 0.9
});
self.addChild(bubbleBg);
// Create speech bubble border
var bubbleBorder = LK.getAsset('actionButton', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: (bubbleWidth + 20) / 400,
scaleY: (bubbleHeight + 20) / 150,
tint: 0x000000,
alpha: 0.8
});
self.addChild(bubbleBorder);
self.addChild(bubbleBg);
// Create speech bubble tail (pointing down to Sans)
var tailWidth = 40;
var tailHeight = 60;
var tail = LK.getAsset('actionButton', {
anchorX: 0.5,
anchorY: 0,
scaleX: tailWidth / 400,
scaleY: tailHeight / 150,
tint: 0xffffff,
alpha: 0.9,
y: bubbleHeight / 2,
rotation: Math.PI / 4
});
self.addChild(tail);
// Create message text
var messageText = new Text2(message, {
size: 35,
fill: 0x000000,
font: "'GillSans-Bold',Impact,'Arial Black',Tahoma"
});
messageText.anchor.set(0.5, 0.5);
self.addChild(messageText);
return self;
});
var SpiralBullet = Container.expand(function () {
var self = Container.call(this);
var bulletGraphics = self.attachAsset('bullet', {
anchorX: 0.5,
anchorY: 0.5
});
self.centerX = 1024;
self.centerY = 1366;
self.angle = 0;
self.radius = 50;
self.radiusSpeed = 2;
self.angleSpeed = 0.1;
self.lifetime = 0;
self.maxLifetime = 400;
self.update = function () {
self.lifetime++;
self.angle += self.angleSpeed;
self.radius += self.radiusSpeed;
self.x = self.centerX + Math.cos(self.angle) * self.radius;
self.y = self.centerY + Math.sin(self.angle) * self.radius;
if (self.lifetime > self.maxLifetime || self.radius > 800) {
self.markForDestroy = true;
}
};
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0x000000
});
/****
* Game Code
****/
// Game state variables
var heart;
var bullets = [];
var blueBullets = [];
var orangeBullets = [];
var laserAttacks = [];
var spiralBullets = [];
var lastHeartX = 0;
var lastHeartY = 0;
var isHeartMoving = false;
var battleBox;
var isDragging = false;
var dragOffsetX = 0;
var dragOffsetY = 0;
var waveTimer = 0;
var currentWave = 1;
var waveSpawnTimer = 0;
var gameTime = 0;
var isActionPhase = false;
var actionTimer = 0;
var playerHealth = 150;
var maxHealth = 150;
var itemUses = 3;
var enemyHealth = 1;
var maxEnemyHealth = 200;
var mercyAttempts = 0;
var maxMercyAttempts = 3;
var isShowingDialogue = false;
var dialogueTimer = 0;
var currentDialogue = null;
// Create UI elements
var scoreText = new Text2('Score: 0', {
size: 80,
fill: 0xFFFFFF
});
scoreText.anchor.set(0.5, 0);
LK.gui.top.addChild(scoreText);
var waveText = new Text2('Wave 1', {
size: 60,
fill: 0xFFFF00
});
waveText.anchor.set(0, 0);
waveText.x = 50;
waveText.y = 200;
LK.gui.topLeft.addChild(waveText);
// Action phase UI
var actionPhaseText = new Text2('Wybierz akcję!', {
size: 80,
fill: 0xFFFF00
});
actionPhaseText.anchor.set(0.5, 0.5);
actionPhaseText.x = 1024;
actionPhaseText.y = 300;
actionPhaseText.alpha = 0;
// Health bars - positioned next to action buttons
var playerHealthBarBg = LK.getAsset('healthBarBg', {
anchorX: 0,
anchorY: 0.5,
x: 100,
y: 2200,
alpha: 1
});
var playerHealthBar = LK.getAsset('healthBar', {
anchorX: 0,
anchorY: 0.5,
x: 100,
y: 2200,
alpha: 1
});
var playerHealthText = new Text2('HP: 100/100', {
size: 50,
fill: 0xFFFFFF
});
playerHealthText.anchor.set(0, 0.5);
playerHealthText.x = 520;
playerHealthText.y = 2200;
var enemyHealthBarBg = LK.getAsset('healthBarBg', {
anchorX: 0,
anchorY: 0.5,
x: 1500,
y: 100,
alpha: 1
});
var enemyHealthBar = LK.getAsset('healthBar', {
anchorX: 0,
anchorY: 0.5,
x: 1500,
y: 100,
tint: 0xff0000,
alpha: 1
});
var enemyHealthText = new Text2('Sans HP: 200/200', {
size: 50,
fill: 0xFFFFFF
});
enemyHealthText.anchor.set(1, 0.5);
enemyHealthText.x = 1900;
enemyHealthText.y = 100;
// Enemy display at top - Sans (positioned in background) - larger size
var enemyDisplay = LK.getAsset('sans', {
anchorX: 0.5,
anchorY: 0.5,
x: 1024,
y: 250,
scaleX: 1.5,
scaleY: 1.5
});
// Sans dodging variables
var sansDodgeTimer = 0;
var sansDodgeCooldown = 0;
var sansOriginalX = 1024;
// Add Sans to game background first so dialogue appears on top
game.addChild(enemyDisplay);
LK.gui.addChild(playerHealthBarBg);
LK.gui.addChild(playerHealthBar);
LK.gui.addChild(playerHealthText);
LK.gui.addChild(enemyHealthBarBg);
LK.gui.addChild(enemyHealthBar);
LK.gui.addChild(enemyHealthText);
// Action buttons
var attackButton = new ActionButton('SALDIRI', function () {
// Check if Sans is tired (after wave 15) or should dodge
var isTired = currentWave > 15;
var shouldDodge = !isTired && sansDodgeCooldown === 0; // Always dodge if not tired and cooldown ready
var damage = 0;
var damageText;
if (shouldDodge) {
// Sans dodges - no damage taken
// Choose random dodge direction
var dodgeDirection = Math.random() < 0.5 ? -1 : 1;
var dodgeDistance = 200 + Math.random() * 150;
var newX = sansOriginalX + dodgeDirection * dodgeDistance;
// Keep Sans within screen bounds
newX = Math.max(200, Math.min(1848, newX));
// Dodge animation - quick movement
tween(enemyDisplay, {
x: newX
}, {
duration: 400,
easing: tween.easeOut,
onFinish: function onFinish() {
// Return to original position after a delay
LK.setTimeout(function () {
tween(enemyDisplay, {
x: sansOriginalX
}, {
duration: 800,
easing: tween.easeInOut
});
}, 1000);
}
});
// Set cooldown to prevent constant dodging
sansDodgeCooldown = 60; // Shorter cooldown for more frequent dodging
// Show dodge message
if (currentWave <= 15) {
damageText = new Text2('MISS!\nSans kaçtı!', {
size: 60,
fill: 0xffff00
});
} else {
damageText = new Text2('MISS!\nSans yorgun ama hala kaçıyor!', {
size: 60,
fill: 0xffff00
});
}
} else if (isTired) {
// Sans is tired after wave 15 - can now take damage
damage = Math.floor(Math.random() * 20) + 10;
enemyHealth -= damage;
if (enemyHealth <= 0) {
enemyHealth = 0;
LK.showYouWin();
}
// Show damage dealt and remaining health
damageText = new Text2('-' + damage + ' HP!\nSans yoruldu! Kalan Can: ' + enemyHealth + '/' + maxEnemyHealth, {
size: 60,
fill: 0xff4444
});
// Show Sans getting tired with visual effect
tween(enemyDisplay, {
alpha: 0.7,
scaleX: 1.3,
scaleY: 1.3
}, {
duration: 300,
onFinish: function onFinish() {
tween(enemyDisplay, {
alpha: 1,
scaleX: 1.5,
scaleY: 1.5
}, {
duration: 300
});
}
});
} else {
// Before wave 15 - Sans always dodges successfully
damageText = new Text2('MISS!\nSans çok hızlı!', {
size: 60,
fill: 0xffff00
});
}
damageText.anchor.set(0.5, 0.5);
damageText.x = 1024;
damageText.y = 400;
game.addChild(damageText);
tween(damageText, {
y: damageText.y - 100,
alpha: 0
}, {
duration: 2000,
onFinish: function onFinish() {
damageText.destroy();
}
});
updateHealthBars();
endActionPhase();
});
var actButton = new ActionButton('EYLEM', function () {
// Act action - show enemy stats
var statsText = new Text2('Sans İstatistikleri:\nSaldırı: 15\nSavunma: 8\nHP: ' + enemyHealth + '/' + maxEnemyHealth + '\n*Seni yargılıyor...', {
size: 50,
fill: 0xFFFFFF
});
statsText.anchor.set(0.5, 0.5);
statsText.x = 1024;
statsText.y = 1366;
game.addChild(statsText);
tween(statsText, {
alpha: 0
}, {
duration: 3000,
onFinish: function onFinish() {
statsText.destroy();
}
});
endActionPhase();
});
var itemButton = new ActionButton('EŞYA', function () {
// Item action - heal player
if (itemUses > 0 && playerHealth < maxHealth) {
playerHealth = Math.min(maxHealth, playerHealth + 30);
itemUses--;
var healText = new Text2('+30 HP!\nKalan kullanım: ' + itemUses, {
size: 60,
fill: 0x00ff00
});
healText.anchor.set(0.5, 0.5);
healText.x = heart.x;
healText.y = heart.y - 50;
game.addChild(healText);
tween(healText, {
y: healText.y - 100,
alpha: 0
}, {
duration: 2000,
onFinish: function onFinish() {
healText.destroy();
}
});
updateHealthBars();
}
endActionPhase();
});
var mercyButton = new ActionButton('MERHAMET', function () {
mercyAttempts++;
var mercyText;
if (mercyAttempts < maxMercyAttempts) {
// Sans doesn't forgive yet
var responses = ['*Sans sana şüpheyle bakıyor.\n*Henüz affetmeye hazır değil.', '*Sans hala kararlı görünüyor.\n*Biraz daha sabır gerekiyor.', '*Sans\'ın gözlerinde değişim var...\n*Ama henüz yeterli değil.'];
mercyText = new Text2(responses[mercyAttempts - 1] + '\n\n(' + mercyAttempts + '/' + maxMercyAttempts + ' deneme)', {
size: 60,
fill: 0xffff00
});
} else {
// Sans finally forgives
mercyText = new Text2('*Sans derin bir nefes alıyor...\n*"tamam, belki sen farklısın."\n*Sans seni affetti!\nKazandın!', {
size: 70,
fill: 0x00ff00
});
}
mercyText.anchor.set(0.5, 0.5);
mercyText.x = 1024;
mercyText.y = 1366;
game.addChild(mercyText);
if (mercyAttempts >= maxMercyAttempts) {
LK.setTimeout(function () {
LK.showYouWin();
}, 3000);
} else {
tween(mercyText, {
alpha: 0
}, {
duration: 3000,
onFinish: function onFinish() {
mercyText.destroy();
}
});
}
endActionPhase();
});
attackButton.x = 512;
attackButton.y = 2400;
actButton.x = 1024;
actButton.y = 2400;
itemButton.x = 1536;
itemButton.y = 2400;
mercyButton.x = 1024;
mercyButton.y = 2600;
game.addChild(actionPhaseText);
game.addChild(attackButton);
game.addChild(actButton);
game.addChild(itemButton);
game.addChild(mercyButton);
// Initially hide action elements
attackButton.alpha = 0;
actButton.alpha = 0;
itemButton.alpha = 0;
mercyButton.alpha = 0;
// Sans dialogue messages
var sansDialogues = ["bugün güzel bir gün,kuşlar cıvıldıyor, çiçekler açıyor,böyle günlerde senin gibi çocuklar, CEHENNEMDE YANMALI", "*hmm... yaptıklarını hatırlıyorum.", "*kaç tane masumun canını aldın acaba?", "*o gülümseme yüzünden silinmiyor, değil mi?", "*gerçekten pişman olduğunu sanıyor musun?", "*haha... bu çok komik.", "*seni affetmemi mi bekliyorsun?", "*yaptıklarının hesabını vereceksin.", "*her şeyi hatırlıyorum, her şeyi.", "*bu sefer farklı olacağını mı sanıyorsun?", "*sen sadece bir katilsin, başka bir şey değil.", "*devam ediyorsun... neden?", "*artık sıkıldım bu oyundan.", "*belki de... artık yeter.", "*huff... huff... yoruldum.", "*artık kaçamıyorum... son şansın bu."];
// Create battle box (play area boundary)
battleBox = game.addChild(LK.getAsset('battleBox', {
anchorX: 0.5,
anchorY: 0.5,
x: 1024,
y: 1366,
alpha: 0.1
}));
// Create heart
heart = game.addChild(new Heart());
heart.x = 1024;
heart.y = 1366;
// Create multiple Gasterblasters
var gasterblasters = [];
for (var gb = 0; gb < 3; gb++) {
var gasterblaster = new Gasterblaster();
gasterblaster.isActive = false; // Start deactivated until wave 4
gasterblaster.fireInterval = 180 + gb * 60; // Stagger firing times
gasterblasters.push(gasterblaster);
game.addChild(gasterblaster);
}
// Touch/drag handlers
game.down = function (x, y, obj) {
// Allow dragging from anywhere on screen for mobile
isDragging = true;
dragOffsetX = x - heart.x;
dragOffsetY = y - heart.y;
};
game.move = function (x, y, obj) {
if (isDragging) {
var newX = x - dragOffsetX;
var newY = y - dragOffsetY;
// Constrain to battle box
var boxLeft = battleBox.x - battleBox.width / 2 + 30;
var boxRight = battleBox.x + battleBox.width / 2 - 30;
var boxTop = battleBox.y - battleBox.height / 2 + 30;
var boxBottom = battleBox.y + battleBox.height / 2 - 30;
heart.x = Math.max(boxLeft, Math.min(boxRight, newX));
heart.y = Math.max(boxTop, Math.min(boxBottom, newY));
}
};
game.up = function (x, y, obj) {
isDragging = false;
};
// Spawn patterns
function spawnStraightBullets() {
var numBullets = 4 + Math.floor(currentWave / 2);
var side = Math.floor(Math.random() * 4); // 0=top, 1=right, 2=bottom, 3=left
for (var i = 0; i < numBullets; i++) {
var bullet = new Bullet();
var speed = 5 + currentWave * 0.8;
switch (side) {
case 0:
// Top
bullet.x = 324 + 1400 / numBullets * i;
bullet.y = 566;
bullet.speedY = speed;
break;
case 1:
// Right
bullet.x = 1724;
bullet.y = 666 + 1400 / numBullets * i;
bullet.speedX = -speed;
break;
case 2:
// Bottom
bullet.x = 324 + 1400 / numBullets * i;
bullet.y = 2066;
bullet.speedY = -speed;
break;
case 3:
// Left
bullet.x = 324;
bullet.y = 666 + 1400 / numBullets * i;
bullet.speedX = speed;
break;
}
bullets.push(bullet);
game.addChild(bullet);
}
}
function spawnConvergingBullets() {
var numBullets = 8 + currentWave;
var centerX = heart.x;
var centerY = heart.y;
for (var i = 0; i < numBullets; i++) {
var angle = Math.PI * 2 * i / numBullets;
var distance = 800;
var bullet = new Bullet();
bullet.x = centerX + Math.cos(angle) * distance;
bullet.y = centerY + Math.sin(angle) * distance;
var speed = 4 + currentWave * 0.6;
bullet.speedX = -Math.cos(angle) * speed;
bullet.speedY = -Math.sin(angle) * speed;
bullets.push(bullet);
game.addChild(bullet);
}
}
function spawnColoredBullets() {
var numBullets = 3 + Math.floor(currentWave / 3);
for (var i = 0; i < numBullets; i++) {
// Randomly choose blue or orange bullet
var isBlue = Math.random() < 0.5;
var bullet = isBlue ? new BlueBullet() : new OrangeBullet();
// Spawn from random edge
var edge = Math.floor(Math.random() * 4);
switch (edge) {
case 0:
// Top
bullet.x = 324 + Math.random() * 1400;
bullet.y = 466;
break;
case 1:
// Right
bullet.x = 1824;
bullet.y = 566 + Math.random() * 1400;
break;
case 2:
// Bottom
bullet.x = 324 + Math.random() * 1400;
bullet.y = 2166;
break;
case 3:
// Left
bullet.x = 224;
bullet.y = 566 + Math.random() * 1400;
break;
}
// Initial speed towards center with some randomness
var dx = 1024 - bullet.x;
var dy = 1366 - bullet.y;
var distance = Math.sqrt(dx * dx + dy * dy);
var speed = 3 + currentWave * 0.4;
bullet.speedX = dx / distance * speed + (Math.random() - 0.5) * 2;
bullet.speedY = dy / distance * speed + (Math.random() - 0.5) * 2;
if (isBlue) {
blueBullets.push(bullet);
} else {
orangeBullets.push(bullet);
}
game.addChild(bullet);
}
}
function spawnLaserAttack() {
// Gasterblasters now handle laser spawning automatically
// This function triggers a random active Gasterblaster to fire
var activeBlasters = [];
for (var gb = 0; gb < gasterblasters.length; gb++) {
if (gasterblasters[gb].isActive) {
activeBlasters.push(gasterblasters[gb]);
}
}
if (activeBlasters.length > 0) {
var randomBlaster = activeBlasters[Math.floor(Math.random() * activeBlasters.length)];
randomBlaster.fireLaser();
}
}
function spawnSpiralAttack() {
var numSpirals = 2 + Math.floor(currentWave / 4);
for (var i = 0; i < numSpirals; i++) {
var spiral = new SpiralBullet();
spiral.angle = Math.PI * 2 * i / numSpirals;
spiral.angleSpeed = 0.08 + Math.random() * 0.04;
spiral.radiusSpeed = 1.5 + Math.random() * 1;
spiralBullets.push(spiral);
game.addChild(spiral);
}
}
function showSansDialogue(message, callback) {
isShowingDialogue = true;
dialogueTimer = 0;
currentDialogue = new SpeechBubble(message);
currentDialogue.x = 1024;
currentDialogue.y = 450; // Position above Sans
currentDialogue.alpha = 0;
currentDialogue.scaleX = 0.3;
currentDialogue.scaleY = 0.3;
game.addChild(currentDialogue);
// Fade in dialogue with bounce effect
tween(currentDialogue, {
alpha: 1,
scaleX: 1,
scaleY: 1
}, {
duration: 500,
easing: tween.bounceOut,
onFinish: function onFinish() {
// Keep dialogue visible for 3 seconds
LK.setTimeout(function () {
// Fade out dialogue
tween(currentDialogue, {
alpha: 0,
scaleX: 0.8,
scaleY: 0.8
}, {
duration: 500,
easing: tween.easeIn,
onFinish: function onFinish() {
currentDialogue.destroy();
currentDialogue = null;
isShowingDialogue = false;
if (callback) callback();
}
});
}, 3000);
}
});
}
function startActionPhase() {
isActionPhase = true;
actionTimer = 0;
// Stop all enemy attacks
for (var i = bullets.length - 1; i >= 0; i--) {
bullets[i].destroy();
}
bullets = [];
for (var j = blueBullets.length - 1; j >= 0; j--) {
blueBullets[j].destroy();
}
blueBullets = [];
for (var k = orangeBullets.length - 1; k >= 0; k--) {
orangeBullets[k].destroy();
}
orangeBullets = [];
for (var l = laserAttacks.length - 1; l >= 0; l--) {
laserAttacks[l].destroy();
}
laserAttacks = [];
for (var s = spiralBullets.length - 1; s >= 0; s--) {
spiralBullets[s].destroy();
}
spiralBullets = [];
// Show action UI
tween(actionPhaseText, {
alpha: 1
}, {
duration: 500
});
tween(attackButton, {
alpha: 1
}, {
duration: 500
});
tween(actButton, {
alpha: 1
}, {
duration: 500
});
tween(itemButton, {
alpha: itemUses > 0 ? 1 : 0.3
}, {
duration: 500
});
tween(mercyButton, {
alpha: 1
}, {
duration: 500
});
}
function endActionPhase() {
isActionPhase = false;
waveSpawnTimer = 0;
// Hide action UI
tween(actionPhaseText, {
alpha: 0
}, {
duration: 500
});
tween(attackButton, {
alpha: 0
}, {
duration: 500
});
tween(actButton, {
alpha: 0
}, {
duration: 500
});
tween(itemButton, {
alpha: 0
}, {
duration: 500
});
tween(mercyButton, {
alpha: 0
}, {
duration: 500
});
}
function updateHealthBars() {
var playerHealthPercent = playerHealth / maxHealth;
var enemyHealthPercent = enemyHealth / maxEnemyHealth;
tween(playerHealthBar, {
scaleX: playerHealthPercent
}, {
duration: 300
});
tween(enemyHealthBar, {
scaleX: enemyHealthPercent
}, {
duration: 300
});
playerHealthText.setText('HP: ' + playerHealth + '/' + maxHealth);
enemyHealthText.setText('Sans HP: ' + enemyHealth + '/' + maxEnemyHealth);
}
// Main game update
game.update = function () {
gameTime++;
// Don't update game during dialogue
if (isShowingDialogue) {
return;
}
if (isActionPhase) {
actionTimer++;
// Auto-end action phase after 10 seconds if no action taken
if (actionTimer > 600) {
endActionPhase();
}
return;
}
waveTimer++;
waveSpawnTimer++;
// Update score
LK.setScore(Math.floor(gameTime / 60));
scoreText.setText('Score: ' + LK.getScore());
// Wave progression and action phase trigger
if (waveTimer > 600 && !isShowingDialogue) {
// Show Sans dialogue before action phase
var dialogueIndex = Math.min(currentWave - 1, sansDialogues.length - 1);
var message = sansDialogues[dialogueIndex];
showSansDialogue(message, function () {
// 10 seconds per wave, then action phase
startActionPhase();
waveTimer = 0;
currentWave++;
waveText.setText('Wave ' + currentWave);
// Flash effect for new wave
LK.effects.flashScreen(0x444444, 500);
});
return;
}
// Spawn bullets based on wave and timing
if (waveSpawnTimer > 60 - currentWave * 3) {
// Faster spawning each wave
waveSpawnTimer = 0;
var pattern = Math.floor(Math.random() * 5);
if (currentWave < 3) pattern = 0; // Only straight bullets early
else if (currentWave < 4) pattern = Math.floor(Math.random() * 3); // No lasers/spirals before wave 4
else if (currentWave < 6) pattern = Math.floor(Math.random() * 4); // Include lasers from wave 4
switch (pattern) {
case 0:
spawnStraightBullets();
break;
case 1:
spawnConvergingBullets();
break;
case 2:
spawnColoredBullets();
break;
case 3:
spawnLaserAttack();
break;
case 4:
spawnSpiralAttack();
break;
}
}
// Sans dodging cooldown management
if (sansDodgeCooldown > 0) {
sansDodgeCooldown--;
}
// Activate Gasterblasters from wave 4 onwards
if (currentWave >= 4) {
for (var gb = 0; gb < gasterblasters.length; gb++) {
if (!gasterblasters[gb].isActive) {
gasterblasters[gb].isActive = true;
}
}
}
// Update Gasterblasters
for (var gb = 0; gb < gasterblasters.length; gb++) {
if (gasterblasters[gb].isActive) {
gasterblasters[gb].update();
}
}
// Update and check bullet collisions
for (var i = bullets.length - 1; i >= 0; i--) {
var bullet = bullets[i];
if (bullet.markForDestroy) {
bullet.destroy();
bullets.splice(i, 1);
continue;
}
// Collision with heart
if (bullet.intersects(heart)) {
LK.getSound('hit').play();
LK.effects.flashScreen(0xff0000, 500);
playerHealth -= 10;
if (playerHealth <= 0) {
playerHealth = 0;
updateHealthBars();
LK.showGameOver();
return;
}
updateHealthBars();
bullet.destroy();
bullets.splice(i, 1);
continue;
}
}
// Track heart movement
var currentHeartMoving = heart.x !== lastHeartX || heart.y !== lastHeartY;
isHeartMoving = currentHeartMoving;
lastHeartX = heart.x;
lastHeartY = heart.y;
// Update and check blue bullet collisions (safe when stationary)
for (var j = blueBullets.length - 1; j >= 0; j--) {
var blueBullet = blueBullets[j];
if (blueBullet.markForDestroy) {
blueBullet.destroy();
blueBullets.splice(j, 1);
continue;
}
// Collision with heart - only damages if heart is moving
if (blueBullet.intersects(heart) && isHeartMoving) {
LK.getSound('hit').play();
LK.effects.flashScreen(0xff0000, 500);
playerHealth -= 15;
if (playerHealth <= 0) {
playerHealth = 0;
updateHealthBars();
LK.showGameOver();
return;
}
updateHealthBars();
blueBullet.destroy();
blueBullets.splice(j, 1);
continue;
}
}
// Update and check orange bullet collisions (safe when moving)
for (var o = orangeBullets.length - 1; o >= 0; o--) {
var orangeBullet = orangeBullets[o];
if (orangeBullet.markForDestroy) {
orangeBullet.destroy();
orangeBullets.splice(o, 1);
continue;
}
// Collision with heart - only damages if heart is stationary
if (orangeBullet.intersects(heart) && !isHeartMoving) {
LK.getSound('hit').play();
LK.effects.flashScreen(0xff0000, 500);
playerHealth -= 15;
if (playerHealth <= 0) {
playerHealth = 0;
updateHealthBars();
LK.showGameOver();
return;
}
updateHealthBars();
orangeBullet.destroy();
orangeBullets.splice(o, 1);
continue;
}
}
// Update and check laser attacks
for (var l = laserAttacks.length - 1; l >= 0; l--) {
var laser = laserAttacks[l];
if (laser.markForDestroy) {
laser.destroy();
laserAttacks.splice(l, 1);
continue;
}
}
// Update and check spiral bullet collisions
for (var s = spiralBullets.length - 1; s >= 0; s--) {
var spiral = spiralBullets[s];
if (spiral.markForDestroy) {
spiral.destroy();
spiralBullets.splice(s, 1);
continue;
}
// Collision with heart
if (spiral.intersects(heart)) {
LK.getSound('hit').play();
LK.effects.flashScreen(0xff0000, 500);
playerHealth -= 12;
if (playerHealth <= 0) {
playerHealth = 0;
updateHealthBars();
LK.showGameOver();
return;
}
updateHealthBars();
spiral.destroy();
spiralBullets.splice(s, 1);
continue;
}
}
// Play dodge sound occasionally for close calls
if (gameTime % 180 === 0) {
var closeCalls = 0;
for (var k = 0; k < bullets.length; k++) {
var dist = Math.sqrt(Math.pow(bullets[k].x - heart.x, 2) + Math.pow(bullets[k].y - heart.y, 2));
if (dist < 100) closeCalls++;
}
if (closeCalls > 2) {
LK.getSound('dodge').play();
}
}
};
// Initialize health bars
updateHealthBars();
// Start background music
LK.playMusic('megalovania');
;