User prompt
Sans konuşurken bize yaptıklarımız dan bahsetsin ve biraz alaycı olsun ↪💡 Consider importing and using the following plugins: @upit/tween.v1
User prompt
Arka plana Megalovnia müziğini ekle
User prompt
Sans diyalogalrı saldırı yapmadan önce gelsin ve diyaloglar için konuşma baloncuğu ekle ↪💡 Consider importing and using the following plugins: @upit/tween.v1
User prompt
Saldırıları biraz daha hızlandır,sans bize saldırmadan önce şunları desin"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"desin ve oyun başlasın ↪💡 Consider importing and using the following plugins: @upit/tween.v1
User prompt
Sans karakteri bize saldırmadan önce bize birşeyler desin
User prompt
Sans'ın görünümünü varlıklar kısmına ekle
User prompt
Merhamet düğmesiyle düşman bizi ilk başta affetmesin ama sonra effetsin ↪💡 Consider importing and using the following plugins: @upit/tween.v1
User prompt
Düsman yerine sans'ı koyabilirmiyiz
User prompt
Düşmana saldırdığımız zaman onun kaç canının kaldığını göreliim, düşmanın canı daha fazla olsun,can barını ekranın altına taşıyalım ve ekranın yukarısında savaştığımız düşmanı görelim ↪💡 Consider importing and using the following plugins: @upit/tween.v1
User prompt
Oyun tek bir mermiye deydiğimizde değil can barımız bitince bitsin
User prompt
Oyunda düşman saldırdıktan sonra bizim düşmana saldırabilmemiz için oyuna düşman saldırdıktan sonra kullanabileceğimiz saldırı,eylem,eşya ve merhemet tuşları ekle.saldırı tuşuyla düşmana hasar verelim,eylem tuşuyla düşmanın istatistiklerini görelim,eşya tuşuyla bir eşya seçip canımızı dolduralım ve son olarak merhemet tuşuyla düşmana saldırmadan ona iyi davranarak onla arkadaş olarak kazanalım ↪💡 Consider importing and using the following plugins: @upit/tween.v1
User prompt
Oyunda bize ışın atan, gaster blaster tarzı birşey ve farklı saldırı çeşitleri ekle ↪💡 Consider importing and using the following plugins: @upit/tween.v1
User prompt
Mermiler biraz daha büyük olsun
User prompt
Mobil oyuncular için ekranla kalbi sürükleyebilmemizi sağla
Code edit (1 edits merged)
Please save this source code
User prompt
Soul Dodge - Heart Defense
Initial prompt
Bana Undertale tarzı benzer bir oyun yapabilirmisin
/**** * 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');
;