/****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
var storage = LK.import("@upit/storage.v1");
/****
* Classes
****/
var BossProjectile = Container.expand(function (startX, startY, targetX, targetY, speed) {
var self = Container.call(this);
var projectileGraphics = self.attachAsset('bossProjectile', {
anchorX: 0.5,
anchorY: 0.5
});
self.x = startX;
self.y = startY;
self.radius = 15;
// Calculate direction
var dx = targetX - startX;
var dy = targetY - startY;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance > 0) {
self.velocityX = dx / distance * speed;
self.velocityY = dy / distance * speed;
} else {
self.velocityX = 0;
self.velocityY = speed;
}
self.update = function () {
self.x += self.velocityX;
self.y += self.velocityY;
};
return self;
});
var DamageNumber = Container.expand(function (damage, x, y) {
var self = Container.call(this);
var damageText = new Text2(damage.toString(), {
size: 120,
fill: 0xFF0000
});
damageText.anchor.set(0.5, 0.5);
self.addChild(damageText);
self.x = x;
self.y = y;
self.velocityY = -3;
self.lifeTime = 120; // 2 seconds
self.update = function () {
self.y += self.velocityY;
self.lifeTime--;
// Fade out over time
damageText.alpha = self.lifeTime / 120;
// Scale down slightly over time
var scale = 1 - (120 - self.lifeTime) / 120 * 0.3;
damageText.scaleX = scale;
damageText.scaleY = scale;
};
return self;
});
var DirectionalProjectile = Container.expand(function (fromLeft) {
var self = Container.call(this);
var projectileGraphics = self.attachAsset('square', {
anchorX: 0.5,
anchorY: 0.5
});
self.y = Math.random() * (arenaHeight - 100) + arenaY + 50;
self.radius = 15;
self.speed = 5 + gameSpeed;
if (fromLeft) {
self.x = arenaX - 50;
self.velocityX = self.speed;
} else {
self.x = arenaX + arenaWidth + 50;
self.velocityX = -self.speed;
}
self.lastX = self.x;
self.update = function () {
self.lastX = self.x;
self.x += self.velocityX;
};
return self;
});
var Explosion = Container.expand(function (targetX, targetY) {
var self = Container.call(this);
var explosionGraphics = self.attachAsset('hexagon', {
anchorX: 0.5,
anchorY: 0.5
});
self.x = targetX;
self.y = targetY;
self.radius = 75;
self.lifeTime = 30; // Half second explosion
self.update = function () {
self.lifeTime--;
// Explosion animation
var scale = 1 - self.lifeTime / 30 * 0.5;
explosionGraphics.scaleX = scale;
explosionGraphics.scaleY = scale;
explosionGraphics.alpha = self.lifeTime / 30;
};
return self;
});
var ExplosionWarning = Container.expand(function (targetX, targetY) {
var self = Container.call(this);
var warningGraphics = self.attachAsset('projectile', {
anchorX: 0.5,
anchorY: 0.5
});
warningGraphics.width = 120;
warningGraphics.height = 120;
warningGraphics.tint = 0xFF0000;
warningGraphics.alpha = 0.5;
self.x = targetX;
self.y = targetY;
self.warningTime = 120; // 2 seconds warning
self.radius = 0; // No collision during warning
self.update = function () {
self.warningTime--;
// Pulsing warning effect
warningGraphics.alpha = Math.sin(LK.ticks * 0.5) * 0.3 + 0.5;
warningGraphics.scaleX = 1 + Math.sin(LK.ticks * 0.3) * 0.2;
warningGraphics.scaleY = 1 + Math.sin(LK.ticks * 0.3) * 0.2;
};
return self;
});
var GiantFace = Container.expand(function () {
var self = Container.call(this);
var faceGraphics = self.attachAsset('bossSquare', {
anchorX: 0.5,
anchorY: 0.5
});
self.x = 2048 / 2;
self.y = 2732 / 2;
self.health = 999999999999;
self.maxHealth = 999999999999;
self.visible = false;
self.radius = 100;
self.moveDirection = 1;
self.moveSpeed = 3;
self.attackTimer = 0;
self.disappearTimer = 0;
self.isDisappeared = false;
self.laughTimer = 0;
self.update = function () {
if (self.visible && !self.isDisappeared) {
// Boss movement
self.x += self.moveDirection * self.moveSpeed;
if (self.x <= arenaX + 100 || self.x >= arenaX + arenaWidth - 100) {
self.moveDirection *= -1;
}
// Boss attack timer
self.attackTimer++;
if (self.attackTimer >= 90) {
// Attack every 1.5 seconds
self.attackTimer = 0;
// Launch projectile at player
var bossProjectile = new BossProjectile(self.x, self.y, player.x, player.y, 5);
bossProjectiles.push(bossProjectile);
game.addChild(bossProjectile);
}
// Random boss laugh timer
self.laughTimer++;
if (self.laughTimer >= 300 + Math.random() * 600) {
// Random laugh every 5-15 seconds
self.laughTimer = 0;
LK.getSound('bossLaugh').play();
}
// Disappear timer
self.disappearTimer++;
if (self.disappearTimer >= 600) {
// Disappear every 10 seconds
self.disappearTimer = 0;
self.isDisappeared = true;
self.visible = false;
// Reappear after 3 seconds
LK.setTimeout(function () {
if (inBossFight) {
self.isDisappeared = false;
self.visible = true;
self.x = Math.random() * (arenaWidth - 200) + arenaX + 100;
self.y = Math.random() * (arenaHeight - 200) + arenaY + 100;
}
}, 3000);
}
}
};
return self;
});
var HealingItem = Container.expand(function () {
var self = Container.call(this);
var healingGraphics = self.attachAsset('healingItem', {
anchorX: 0.5,
anchorY: 0.5
});
healingGraphics.tint = 0x00FF00;
self.x = Math.random() * (arenaWidth - 100) + arenaX + 50;
self.y = arenaY - 50;
self.velocityY = 2;
self.radius = 25;
self.lastY = self.y;
self.update = function () {
self.lastY = self.y;
self.y += self.velocityY;
// Gentle floating animation
healingGraphics.rotation += 0.02;
healingGraphics.scaleX = 1 + Math.sin(LK.ticks * 0.1) * 0.1;
healingGraphics.scaleY = 1 + Math.sin(LK.ticks * 0.1) * 0.1;
};
return self;
});
var HorizontalProjectile = Container.expand(function (fromLeft, yPos, speed) {
var self = Container.call(this);
var projectileGraphics = self.attachAsset('projectile', {
anchorX: 0.5,
anchorY: 0.5
});
projectileGraphics.width = 40;
projectileGraphics.height = 20;
if (fromLeft) {
self.x = arenaX - 50;
self.velocityX = speed;
} else {
self.x = arenaX + arenaWidth + 50;
self.velocityX = -speed;
}
self.y = yPos;
self.radius = 15;
self.lastX = self.x;
self.update = function () {
self.lastX = self.x;
self.x += self.velocityX;
};
return self;
});
var Player = Container.expand(function () {
var self = Container.call(this);
var playerGraphics = self.attachAsset('player', {
anchorX: 0.5,
anchorY: 0.5
});
self.radius = 30;
self.speed = 8;
return self;
});
var Projectile = Container.expand(function (startX, startY, targetX, targetY, speed) {
var self = Container.call(this);
var projectileGraphics = self.attachAsset('projectile', {
anchorX: 0.5,
anchorY: 0.5
});
self.x = startX;
self.y = startY;
self.radius = 10;
// Calculate direction
var dx = targetX - startX;
var dy = targetY - startY;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance > 0) {
self.velocityX = dx / distance * speed;
self.velocityY = dy / distance * speed;
} else {
self.velocityX = 0;
self.velocityY = speed;
}
self.update = function () {
self.x += self.velocityX;
self.y += self.velocityY;
};
return self;
});
var Sword = Container.expand(function (targetX) {
var self = Container.call(this);
var swordGraphics = self.attachAsset('sword', {
anchorX: 0.5,
anchorY: 0.5
});
self.x = targetX;
self.y = arenaY - 150;
self.targetY = arenaY + arenaHeight + 100;
self.radius = 20;
self.speed = 0;
self.warningTime = 120; // 2 seconds warning
self.falling = false;
self.lastY = self.y;
swordGraphics.tint = 0xFFFFFF;
self.update = function () {
if (self.warningTime > 0) {
self.warningTime--;
// Flashing warning effect
swordGraphics.alpha = Math.sin(LK.ticks * 0.3) * 0.5 + 0.5;
if (self.warningTime === 0) {
self.falling = true;
self.speed = 8;
swordGraphics.alpha = 1;
LK.getSound('swordFall').play();
}
} else if (self.falling) {
self.lastY = self.y;
self.y += self.speed;
}
};
return self;
});
var VerticalProjectile = Container.expand(function (xPos, speed) {
var self = Container.call(this);
var projectileGraphics = self.attachAsset('projectile', {
anchorX: 0.5,
anchorY: 0.5
});
projectileGraphics.width = 20;
projectileGraphics.height = 40;
self.x = xPos;
self.y = arenaY + arenaHeight + 50;
self.velocityY = -speed;
self.radius = 15;
self.lastY = self.y;
self.update = function () {
self.lastY = self.y;
self.y += self.velocityY;
};
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0x000000
});
/****
* Game Code
****/
// Game variables
var player;
var projectiles = [];
var swords = [];
var horizontalProjectiles = [];
var verticalProjectiles = [];
var healingItems = [];
var directionalProjectiles = [];
var explosionWarnings = [];
var explosions = [];
var survivalTime = 0;
var waveTimer = 0;
var attackPattern = 0;
var gameSpeed = 1;
var isDragging = false;
var playerHealth = 20;
var maxHealth = 20;
var healingTimer = 0;
var playerDamage = 1;
var totalBossDamageDealt = 0;
var baseDamage = 1;
var giantFace;
var fightButton;
var inBossFight = false;
var bossProjectiles = [];
var bossHealthBar;
var bossHealthText;
var showBossHealth = false;
var fightButtonTimer = 0;
var fightButtonHoverCount = 0;
var bossInvoked = false;
var playerColor = 0xFF0000;
var gameStartTime = 0;
var currentDifficulty = 'normal';
var fightTwoButton;
var fightTwoButtonTimer = 0;
var fightTwoButtonVisible = false;
var damageNumbers = [];
// Game state variables
var gameState = 'menu'; // 'menu', 'playing', 'gameOver'
var menuElements = [];
// UI elements
var scoreText = new Text2('Time: 0', {
size: 80,
fill: 0xFFFFFF
});
scoreText.anchor.set(0.5, 0);
LK.gui.top.addChild(scoreText);
// Main menu elements
var titleText = new Text2('UNDERTALE VS CHATORE', {
size: 120,
fill: 0xFFFFFF
});
titleText.anchor.set(0.5, 0.5);
titleText.x = 2048 / 2;
titleText.y = 2732 / 2 - 300;
var heartSymbol = new Text2('♥', {
size: 100,
fill: 0xFF0000
});
heartSymbol.anchor.set(0.5, 0.5);
heartSymbol.x = titleText.x + 400;
heartSymbol.y = titleText.y;
var startButton = new Text2('START GAME', {
size: 80,
fill: 0xFFFF00
});
startButton.anchor.set(0.5, 0.5);
startButton.x = 2048 / 2;
startButton.y = 2732 / 2;
startButton.radius = 150;
var recordsButton = new Text2('RECORDS', {
size: 60,
fill: 0xFF00FF
});
recordsButton.anchor.set(0.5, 0.5);
recordsButton.x = 2048 / 2;
recordsButton.y = 2732 / 2 + 150;
recordsButton.radius = 120;
menuElements.push(titleText, startButton);
// Add determination animation to heart
tween(heartSymbol, {
scaleX: 1.2,
scaleY: 1.2
}, {
duration: 1000,
easing: tween.easeInOut,
onFinish: function onFinish() {
tween(heartSymbol, {
scaleX: 1,
scaleY: 1
}, {
duration: 1000,
easing: tween.easeInOut,
onFinish: function onFinish() {
// Restart the animation cycle
if (gameState === 'menu') {
tween(heartSymbol, {
scaleX: 1.2,
scaleY: 1.2
}, {
duration: 1000,
easing: tween.easeInOut,
onFinish: arguments.callee.caller
});
}
}
});
}
});
var healthText = new Text2('Frisk: 20/20', {
size: 60,
fill: 0xFF0000
});
healthText.anchor.set(0, 0);
healthText.x = 120;
healthText.y = 120;
LK.gui.topLeft.addChild(healthText);
var instructionText = new Text2('Drag to move, avoid white bullets!', {
size: 60,
fill: 0xFFFFFF
});
instructionText.anchor.set(0.5, 0.5);
instructionText.x = 2048 / 2;
instructionText.y = 2732 / 2 + 200;
game.addChild(instructionText);
// Create arena boundaries (visual indicators)
var arenaWidth = 1800;
var arenaHeight = 2200;
var arenaX = (2048 - arenaWidth) / 2;
var arenaY = (2732 - arenaHeight) / 2;
// Initialize player
player = game.addChild(new Player());
player.x = 2048 / 2;
player.y = 2732 / 2;
player.visible = false;
// Load saved player color
playerColor = storage.playerColor || 0xFF0000;
// Show menu initially
function showMenu() {
gameState = 'menu';
// Clear all game elements and reset game state
clearAllProjectiles();
// Reset all timers and variables
survivalTime = 0;
waveTimer = 0;
attackPattern = 0;
gameSpeed = 1;
isDragging = false;
playerHealth = 20;
maxHealth = 20;
healingTimer = 0;
playerDamage = 1;
totalBossDamageDealt = 0;
baseDamage = 1;
inBossFight = false;
bossInvoked = false;
fightButtonTimer = 0;
fightButtonHoverCount = 0;
currentDifficulty = 'normal';
fightTwoButtonTimer = 0;
fightTwoButtonVisible = false;
showBossHealth = false;
// Reset UI elements
if (bossHealthBar) bossHealthBar.visible = false;
if (bossHealthText) bossHealthText.visible = false;
if (giantFace) {
giantFace.visible = false;
giantFace.health = 999999999999;
giantFace.isDisappeared = false;
giantFace.disappearTimer = 0;
giantFace.attackTimer = 0;
giantFace.laughTimer = 0;
}
// Reset player position
player.x = 2048 / 2;
player.y = 2732 / 2;
for (var i = 0; i < menuElements.length; i++) {
game.addChild(menuElements[i]);
}
player.visible = false;
fightButton.visible = false;
scoreText.visible = false;
healthText.visible = false;
LK.playMusic('menuMusic');
}
function hideMenu() {
for (var i = 0; i < menuElements.length; i++) {
if (menuElements[i].parent) {
menuElements[i].parent.removeChild(menuElements[i]);
}
}
player.visible = true;
scoreText.visible = true;
healthText.visible = true;
LK.stopMusic();
}
function showDifficultySelection() {
gameState = 'difficultySelection';
hideMenu();
var difficultyTitle = new Text2('ELIGE NIVEL DE DIFICULTAD', {
size: 100,
fill: 0xFFFFFF
});
difficultyTitle.anchor.set(0.5, 0.5);
difficultyTitle.x = 2048 / 2;
difficultyTitle.y = 400;
game.addChild(difficultyTitle);
var difficulties = [{
name: 'FÁCIL',
health: 120,
color: 0x00FF00
}, {
name: 'NORMAL',
health: 100,
color: 0xFFFF00
}, {
name: 'DIFÍCIL',
health: 20,
color: 0xFF8800
}, {
name: 'HARDCORE',
health: 10,
color: 0xFF0000
}, {
name: 'IMPOSIBLE',
health: 1,
color: 0x8800FF
}];
for (var i = 0; i < difficulties.length; i++) {
var difficultyOption = new Text2(difficulties[i].name + ' (' + difficulties[i].health + ' de vida)', {
size: 70,
fill: difficulties[i].color
});
difficultyOption.anchor.set(0.5, 0.5);
difficultyOption.x = 2048 / 2;
difficultyOption.y = 600 + i * 120;
difficultyOption.radius = 200;
difficultyOption.healthValue = difficulties[i].health;
game.addChild(difficultyOption);
menuElements.push(difficultyOption);
}
var backButton = new Text2('VOLVER', {
size: 60,
fill: 0xFFFFFF
});
backButton.anchor.set(0.5, 0.5);
backButton.x = 2048 / 2;
backButton.y = 1200;
backButton.radius = 100;
backButton.isBackButton = true;
menuElements.push(difficultyTitle, backButton);
game.addChild(backButton);
}
function startGame(selectedHealth) {
gameState = 'playing';
hideMenu();
// Reset game state
survivalTime = 0;
gameStartTime = LK.ticks;
playerHealth = selectedHealth || 20;
maxHealth = selectedHealth || 20;
playerDamage = 1;
gameSpeed = 1;
inBossFight = false;
bossInvoked = false;
totalBossDamageDealt = 0;
playerDamage = baseDamage;
fightButtonTimer = 300 + Math.random() * 600; // Random delay before fight button appears
fightButtonHoverCount = 0;
fightTwoButtonTimer = 0;
fightTwoButtonVisible = false;
// Set difficulty based on health
if (playerHealth >= 120) currentDifficulty = 'easy';else if (playerHealth >= 100) currentDifficulty = 'normal';else if (playerHealth >= 20) currentDifficulty = 'hard';else if (playerHealth >= 10) currentDifficulty = 'hardcore';else currentDifficulty = 'impossible';
giantFace.health = 999999999999;
giantFace.visible = false;
fightButton.visible = false;
fightTwoButton.visible = false;
healthText.setText('Frisk: ' + playerHealth + '/' + maxHealth);
// Reset player appearance completely
player.alpha = 1;
player.scaleX = 1;
player.scaleY = 1;
player.rotation = 0;
player.visible = true;
// Apply player color
var playerGraphics = player.children[0];
if (playerGraphics) {
playerGraphics.tint = playerColor;
}
// Clear all projectiles
clearAllProjectiles();
// Make player visible when starting game
player.visible = true;
}
function showColorSelection() {
gameState = 'colorSelection';
hideMenu();
var colorTitle = new Text2('ELIGE TU COLOR', {
size: 100,
fill: 0xFFFFFF
});
colorTitle.anchor.set(0.5, 0.5);
colorTitle.x = 2048 / 2;
colorTitle.y = 400;
game.addChild(colorTitle);
var colors = [0xFF0000, 0x00FF00, 0x0000FF, 0xFFFF00, 0xFF00FF, 0x00FFFF, 0xFFA500, 0x800080];
var colorNames = ['Rojo', 'Verde', 'Azul', 'Amarillo', 'Magenta', 'Cian', 'Naranja', 'Morado'];
for (var i = 0; i < colors.length; i++) {
var colorOption = new Text2(colorNames[i], {
size: 80,
fill: colors[i]
});
colorOption.anchor.set(0.5, 0.5);
colorOption.x = i % 4 * 400 + 400;
colorOption.y = Math.floor(i / 4) * 150 + 800;
colorOption.radius = 150;
colorOption.colorValue = colors[i];
game.addChild(colorOption);
menuElements.push(colorOption);
}
var backButton = new Text2('VOLVER', {
size: 60,
fill: 0xFFFFFF
});
backButton.anchor.set(0.5, 0.5);
backButton.x = 2048 / 2;
backButton.y = 1400;
backButton.radius = 100;
backButton.isBackButton = true;
menuElements.push(colorTitle, backButton);
game.addChild(backButton);
}
function showRecords() {
gameState = 'records';
hideMenu();
var recordsTitle = new Text2('MEJORES TIEMPOS', {
size: 100,
fill: 0xFFFFFF
});
recordsTitle.anchor.set(0.5, 0.5);
recordsTitle.x = 2048 / 2;
recordsTitle.y = 400;
game.addChild(recordsTitle);
var records = storage.records || [];
records.sort(function (a, b) {
return b - a;
});
for (var i = 0; i < Math.min(5, records.length); i++) {
var recordText = new Text2(i + 1 + '. ' + records[i] + 's', {
size: 80,
fill: 0xFFFF00
});
recordText.anchor.set(0.5, 0.5);
recordText.x = 2048 / 2;
recordText.y = 600 + i * 120;
game.addChild(recordText);
menuElements.push(recordText);
}
if (records.length === 0) {
var noRecordsText = new Text2('No hay records aún', {
size: 80,
fill: 0x888888
});
noRecordsText.anchor.set(0.5, 0.5);
noRecordsText.x = 2048 / 2;
noRecordsText.y = 800;
game.addChild(noRecordsText);
menuElements.push(noRecordsText);
}
var backButton = new Text2('VOLVER', {
size: 60,
fill: 0xFFFFFF
});
backButton.anchor.set(0.5, 0.5);
backButton.x = 2048 / 2;
backButton.y = 1400;
backButton.radius = 100;
backButton.isBackButton = true;
menuElements.push(recordsTitle, backButton);
game.addChild(backButton);
}
function createDamageNumber(damage, x, y) {
// Calculate scaled damage based on total damage dealt to boss
var damageMultiplier = 1 + Math.floor(totalBossDamageDealt / 1000) * 0.5; // Increase by 50% for every 1000 damage dealt
var scaledDamage = Math.floor(damage * damageMultiplier);
// 25% chance for critical damage (much higher damage)
var isCritical = Math.random() < 0.25;
var finalDamage = isCritical ? scaledDamage * 5 : scaledDamage;
var damageNumber = new DamageNumber(finalDamage, x, y);
damageNumbers.push(damageNumber);
game.addChild(damageNumber);
// Track total damage dealt to boss
totalBossDamageDealt += finalDamage;
return finalDamage;
}
function saveRecord(time) {
var records = storage.records || [];
records.push(time);
records.sort(function (a, b) {
return b - a;
});
if (records.length > 10) {
records = records.slice(0, 10);
}
storage.records = records;
}
function clearAllProjectiles() {
// Clear all attack arrays
for (var i = projectiles.length - 1; i >= 0; i--) {
projectiles[i].destroy();
projectiles.splice(i, 1);
}
for (var i = swords.length - 1; i >= 0; i--) {
swords[i].destroy();
swords.splice(i, 1);
}
for (var i = horizontalProjectiles.length - 1; i >= 0; i--) {
horizontalProjectiles[i].destroy();
horizontalProjectiles.splice(i, 1);
}
for (var i = verticalProjectiles.length - 1; i >= 0; i--) {
verticalProjectiles[i].destroy();
verticalProjectiles.splice(i, 1);
}
for (var i = healingItems.length - 1; i >= 0; i--) {
healingItems[i].destroy();
healingItems.splice(i, 1);
}
for (var i = directionalProjectiles.length - 1; i >= 0; i--) {
directionalProjectiles[i].destroy();
directionalProjectiles.splice(i, 1);
}
for (var i = explosionWarnings.length - 1; i >= 0; i--) {
explosionWarnings[i].destroy();
explosionWarnings.splice(i, 1);
}
for (var i = explosions.length - 1; i >= 0; i--) {
explosions[i].destroy();
explosions.splice(i, 1);
}
for (var i = bossProjectiles.length - 1; i >= 0; i--) {
bossProjectiles[i].destroy();
bossProjectiles.splice(i, 1);
}
for (var i = damageNumbers.length - 1; i >= 0; i--) {
damageNumbers[i].destroy();
damageNumbers.splice(i, 1);
}
}
// Initialize giant face
giantFace = game.addChild(new GiantFace());
// Create fight button
fightButton = new Text2('FIGHT', {
size: 120,
fill: 0xFFFF00
});
fightButton.anchor.set(0.5, 0.5);
fightButton.x = 2048 / 2;
fightButton.y = 2732 - 200;
fightButton.radius = 100;
game.addChild(fightButton);
// Create fight two button
fightTwoButton = new Text2('FIGHT TWO', {
size: 100,
fill: 0xFF00FF
});
fightTwoButton.anchor.set(0.5, 0.5);
fightTwoButton.radius = 120;
fightTwoButton.visible = false;
game.addChild(fightTwoButton);
showMenu();
// Create boss health bar
bossHealthBar = new Text2('Chatore: 999999999999', {
size: 50,
fill: 0xFFFF00
});
bossHealthBar.anchor.set(0.5, 0);
bossHealthBar.visible = false;
LK.gui.top.addChild(bossHealthBar);
bossHealthText = new Text2('', {
size: 40,
fill: 0xFF0000
});
bossHealthText.anchor.set(0.5, 0);
bossHealthText.y = 60;
bossHealthText.visible = false;
LK.gui.top.addChild(bossHealthText);
// Attack pattern functions
function spawnLinearAttack() {
var side = Math.floor(Math.random() * 4);
var startX, startY, targetX, targetY;
switch (side) {
case 0:
// Top
startX = Math.random() * arenaWidth + arenaX;
startY = arenaY;
targetX = Math.random() * arenaWidth + arenaX;
targetY = arenaY + arenaHeight;
break;
case 1:
// Right
startX = arenaX + arenaWidth;
startY = Math.random() * arenaHeight + arenaY;
targetX = arenaX;
targetY = Math.random() * arenaHeight + arenaY;
break;
case 2:
// Bottom
startX = Math.random() * arenaWidth + arenaX;
startY = arenaY + arenaHeight;
targetX = Math.random() * arenaWidth + arenaX;
targetY = arenaY;
break;
case 3:
// Left
startX = arenaX;
startY = Math.random() * arenaHeight + arenaY;
targetX = arenaX + arenaWidth;
targetY = Math.random() * arenaHeight + arenaY;
break;
}
var projectile = new Projectile(startX, startY, targetX, targetY, 3 * gameSpeed);
projectiles.push(projectile);
game.addChild(projectile);
}
function spawnCircularAttack() {
var centerX = Math.random() * (arenaWidth - 200) + arenaX + 100;
var centerY = Math.random() * (arenaHeight - 200) + arenaY + 100;
var numProjectiles = 8;
for (var i = 0; i < numProjectiles; i++) {
var angle = i / numProjectiles * Math.PI * 2;
var targetX = centerX + Math.cos(angle) * 1000;
var targetY = centerY + Math.sin(angle) * 1000;
var projectile = new Projectile(centerX, centerY, targetX, targetY, 2.5 * gameSpeed);
projectiles.push(projectile);
game.addChild(projectile);
}
}
function spawnTargetedAttack() {
var side = Math.floor(Math.random() * 4);
var startX, startY;
switch (side) {
case 0:
// Top
startX = Math.random() * arenaWidth + arenaX;
startY = arenaY;
break;
case 1:
// Right
startX = arenaX + arenaWidth;
startY = Math.random() * arenaHeight + arenaY;
break;
case 2:
// Bottom
startX = Math.random() * arenaWidth + arenaX;
startY = arenaY + arenaHeight;
break;
case 3:
// Left
startX = arenaX;
startY = Math.random() * arenaHeight + arenaY;
break;
}
var projectile = new Projectile(startX, startY, player.x, player.y, 4 * gameSpeed);
projectiles.push(projectile);
game.addChild(projectile);
}
function spawnSwordAttack() {
var numSwords = Math.floor(Math.random() * 3) + 2; // 2-4 swords
for (var i = 0; i < numSwords; i++) {
var swordX = Math.random() * (arenaWidth - 80) + arenaX + 40;
var sword = new Sword(swordX);
swords.push(sword);
game.addChild(sword);
}
}
function spawnHorizontalAttack() {
var fromLeft = Math.random() < 0.5;
var yPos = Math.random() * (arenaHeight - 100) + arenaY + 50;
var speed = 4 + gameSpeed;
var projectile = new HorizontalProjectile(fromLeft, yPos, speed);
horizontalProjectiles.push(projectile);
game.addChild(projectile);
}
function spawnVerticalAttack() {
var xPos = Math.random() * (arenaWidth - 100) + arenaX + 50;
var speed = 4 + gameSpeed;
var projectile = new VerticalProjectile(xPos, speed);
verticalProjectiles.push(projectile);
game.addChild(projectile);
}
function spawnDirectionalAttack() {
var fromLeft = Math.random() < 0.5;
var projectile = new DirectionalProjectile(fromLeft);
directionalProjectiles.push(projectile);
game.addChild(projectile);
}
function spawnExplosionAttack() {
var targetX = Math.random() * (arenaWidth - 200) + arenaX + 100;
var targetY = Math.random() * (arenaHeight - 200) + arenaY + 100;
// First spawn warning
var warning = new ExplosionWarning(targetX, targetY);
explosionWarnings.push(warning);
game.addChild(warning);
}
function spawnHealingItem() {
var spawnChance = 0.3;
var numItems = 1;
// When player has low health, increase spawn rate and variety
if (playerHealth < 10) {
spawnChance = 0.8; // Much higher chance
numItems = Math.floor(Math.random() * 3) + 2; // 2-4 items
}
if (Math.random() < spawnChance && playerHealth < maxHealth) {
for (var i = 0; i < numItems; i++) {
var healingItem = new HealingItem();
healingItems.push(healingItem);
game.addChild(healingItem);
}
}
}
// Touch controls
game.down = function (x, y, obj) {
// Handle menu interactions
if (gameState === 'menu') {
var startDx = x - startButton.x;
var startDy = y - startButton.y;
var startDistance = Math.sqrt(startDx * startDx + startDy * startDy);
if (startDistance < startButton.radius) {
showDifficultySelection();
return;
}
return;
}
// Handle color selection interactions
if (gameState === 'colorSelection') {
for (var i = 0; i < menuElements.length; i++) {
var element = menuElements[i];
if (element.colorValue !== undefined) {
var dx = x - element.x;
var dy = y - element.y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance < element.radius) {
playerColor = element.colorValue;
storage.playerColor = playerColor;
showMenu();
return;
}
} else if (element.isBackButton) {
var dx = x - element.x;
var dy = y - element.y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance < element.radius) {
showMenu();
return;
}
}
}
return;
}
// Handle difficulty selection interactions
if (gameState === 'difficultySelection') {
for (var i = 0; i < menuElements.length; i++) {
var element = menuElements[i];
if (element.healthValue !== undefined) {
var dx = x - element.x;
var dy = y - element.y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance < element.radius) {
startGame(element.healthValue);
return;
}
} else if (element.isBackButton) {
var dx = x - element.x;
var dy = y - element.y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance < element.radius) {
showMenu();
return;
}
}
}
return;
}
// Handle records screen interactions
if (gameState === 'records') {
for (var i = 0; i < menuElements.length; i++) {
var element = menuElements[i];
if (element.isBackButton) {
var dx = x - element.x;
var dy = y - element.y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance < element.radius) {
showMenu();
return;
}
}
}
return;
}
if (gameState !== 'playing') return;
// Check if touching fight button
if (fightButton.visible) {
var dx = x - fightButton.x;
var dy = y - fightButton.y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance < fightButton.radius) {
fightButtonHoverCount++;
if (fightButtonHoverCount === 1 && !bossInvoked) {
// First time - start boss fight
inBossFight = true;
bossInvoked = true;
giantFace.visible = true;
fightButton.visible = false;
// Start fight two button timer
fightTwoButtonTimer = 180 + Math.random() * 240; // 3-7 seconds after boss appears
} else if (fightButtonHoverCount === 2) {
// Second time - deal 999 damage to boss
var actualDamage = createDamageNumber(999, giantFace.x, giantFace.y - 100);
giantFace.health -= actualDamage;
if (giantFace.health < 0) giantFace.health = 0;
LK.effects.flashObject(giantFace, 0xff0000, 500);
fightButton.visible = false;
}
return;
}
}
// Check if touching fight two button
if (fightTwoButton.visible) {
var dx2 = x - fightTwoButton.x;
var dy2 = y - fightTwoButton.y;
var distance2 = Math.sqrt(dx2 * dx2 + dy2 * dy2);
if (distance2 < fightTwoButton.radius) {
// Deal 999 damage to boss
var actualDamage = createDamageNumber(999, giantFace.x, giantFace.y - 100);
giantFace.health -= actualDamage;
if (giantFace.health < 0) giantFace.health = 0;
LK.effects.flashObject(giantFace, 0xff0000, 500);
// Move button to random position
fightTwoButton.x = Math.random() * (arenaWidth - 200) + arenaX + 100;
fightTwoButton.y = Math.random() * (arenaHeight - 200) + arenaY + 100;
// Set new timer for next appearance
fightTwoButtonTimer = 300 + Math.random() * 360; // 5-11 seconds
fightTwoButton.visible = false;
return;
}
}
// Check if touching giant face during boss fight
if (inBossFight && giantFace.visible) {
var faceDx = x - giantFace.x;
var faceDy = y - giantFace.y;
var faceDistance = Math.sqrt(faceDx * faceDx + faceDy * faceDy);
if (faceDistance < giantFace.radius) {
// Player collision with boss - boss takes 999 damage, player takes 3
var actualDamage = createDamageNumber(999, giantFace.x, giantFace.y - 100);
giantFace.health -= actualDamage;
playerHealth -= 3;
if (giantFace.health < 0) giantFace.health = 0;
if (playerHealth < 0) playerHealth = 0;
healthText.setText('Frisk: ' + playerHealth + '/' + maxHealth);
LK.effects.flashObject(giantFace, 0xff0000, 200);
LK.effects.flashObject(player, 0xff0000, 500);
LK.getSound('Golpear').play();
// Show boss health bar when damaged
showBossHealth = true;
bossHealthBar.visible = true;
bossHealthText.visible = true;
bossHealthBar.setText('Chatore: ' + giantFace.health.toFixed(0));
bossHealthText.setText('Damage: ' + playerDamage);
// Hide health bar after 3 seconds
LK.setTimeout(function () {
showBossHealth = false;
bossHealthBar.visible = false;
bossHealthText.visible = false;
}, 3000);
// Increase player damage - continues scaling beyond 999
if (playerDamage < 9999) {
if (playerDamage < 100) {
playerDamage++;
} else if (playerDamage < 999) {
playerDamage++;
} else {
// After 999, increase by larger amounts
playerDamage += 10;
}
}
// Check if boss is defeated
if (giantFace.health <= 0) {
// Boss defeat sequence
inBossFight = false;
isDragging = false;
// Focus camera on boss and make it shake
game.setBackgroundColor(0x000000);
LK.effects.flashScreen(0x000000, 500);
// Boss shaking animation
var shakeIntensity = 20;
var shakeTimer = 0;
var shakeInterval = LK.setInterval(function () {
shakeTimer++;
giantFace.x += (Math.random() - 0.5) * shakeIntensity;
giantFace.y += (Math.random() - 0.5) * shakeIntensity;
shakeIntensity *= 0.95; // Reduce shake over time
if (shakeTimer >= 120) {
// 2 seconds of shaking
LK.clearInterval(shakeInterval);
// Boss disappears
LK.getSound('bossDisappear').play();
tween(giantFace, {
alpha: 0,
scaleX: 0,
scaleY: 0
}, {
duration: 500,
easing: tween.easeIn,
onFinish: function onFinish() {
LK.showYouWin();
}
});
}
}, 16); // ~60fps
}
return;
}
}
// Handle game over interactions
if (gameState === 'gameOver') {
// Check if touching back to menu button
if (gameOverElements.length > 2) {
var backToMenuButton = gameOverElements[2];
var againDx = x - backToMenuButton.x;
var againDy = y - backToMenuButton.y;
var againDistance = Math.sqrt(againDx * againDx + againDy * againDy);
if (againDistance < backToMenuButton.radius) {
// Clean up game over screen
for (var i = 0; i < gameOverElements.length; i++) {
if (gameOverElements[i].parent) {
gameOverElements[i].parent.removeChild(gameOverElements[i]);
}
}
gameOverElements = [];
LK.stopMusic();
showMenu();
return;
}
}
return;
}
isDragging = true;
player.x = x;
player.y = y;
// Hide instruction text after first interaction
if (instructionText.parent) {
instructionText.parent.removeChild(instructionText);
}
};
game.move = function (x, y, obj) {
if (gameState === 'playing' && isDragging && !inBossFight) {
// Keep player within arena bounds
player.x = Math.max(arenaX + player.radius, Math.min(arenaX + arenaWidth - player.radius, x));
player.y = Math.max(arenaY + player.radius, Math.min(arenaY + arenaHeight - player.radius, y));
}
};
game.up = function (x, y, obj) {
isDragging = false;
};
// Collision detection
function checkCollision(obj1, obj2) {
var dx = obj1.x - obj2.x;
var dy = obj1.y - obj2.y;
var distance = Math.sqrt(dx * dx + dy * dy);
return distance < obj1.radius + obj2.radius;
}
function takeDamage() {
playerHealth--;
healthText.setText('Frisk: ' + playerHealth + '/' + maxHealth);
LK.getSound('Golpear').play();
LK.effects.flashObject(player, 0xff0000, 500);
// Boss laughs when player takes damage
if (inBossFight && giantFace.visible) {
LK.getSound('bossLaugh').play();
}
if (playerHealth <= 0) {
// Player death sequence
gameState = 'gameOver';
// Stop player movement
isDragging = false;
// Make player break in half and explode
LK.getSound('playerBreak').play();
tween(player, {
scaleX: 0.5,
scaleY: 1.5
}, {
duration: 300,
easing: tween.easeOut
});
tween(player, {
rotation: Math.PI / 4
}, {
duration: 300,
easing: tween.easeOut,
onFinish: function onFinish() {
// Explosion effect
tween(player, {
scaleX: 2,
scaleY: 2,
alpha: 0
}, {
duration: 200,
easing: tween.easeOut,
onFinish: function onFinish() {
// Focus on player position with black screen
game.setBackgroundColor(0x000000);
LK.effects.flashScreen(0x000000, 1000);
// Show game over after animation
LK.setTimeout(function () {
showGameOver();
}, 1000);
}
});
}
});
}
}
function healPlayer() {
if (playerHealth < maxHealth) {
playerHealth = Math.min(playerHealth + 3, maxHealth);
healthText.setText('Frisk: ' + playerHealth + '/' + maxHealth);
LK.getSound('heal').play();
LK.effects.flashObject(player, 0x00ff00, 500);
}
}
function showGameOver() {
// Save survival time record
var finalTime = Math.floor((LK.ticks - gameStartTime) / 60);
saveRecord(finalTime);
LK.playMusic('gameOverMusic');
// Create game over screen
var gameOverBg = new Text2('', {
size: 1,
fill: 0x000000
});
gameOverBg.width = 2048;
gameOverBg.height = 2732;
gameOverBg.x = 0;
gameOverBg.y = 0;
game.addChild(gameOverBg);
var gameOverText = new Text2('Tranquilo Frisk, lo hiciste bien\npero el destino de tu alma esta\nen el juego Frisk.\nMantente determinado.', {
size: 60,
fill: 0xFFFFFF
});
gameOverText.anchor.set(0.5, 0.5);
gameOverText.x = 2048 / 2;
gameOverText.y = 2732 / 2 - 100;
game.addChild(gameOverText);
var backToMenuButton = new Text2('VOLVER AL MENU', {
size: 80,
fill: 0xFFFF00
});
backToMenuButton.anchor.set(0.5, 0.5);
backToMenuButton.x = 2048 / 2;
backToMenuButton.y = 2732 / 2 + 150;
backToMenuButton.radius = 200;
game.addChild(backToMenuButton);
// Store game over elements for cleanup
gameOverElements = [gameOverBg, gameOverText, backToMenuButton];
// Auto redirect to menu after 10 seconds
LK.setTimeout(function () {
// Clean up game over screen
for (var i = 0; i < gameOverElements.length; i++) {
if (gameOverElements[i].parent) {
gameOverElements[i].parent.removeChild(gameOverElements[i]);
}
}
gameOverElements = [];
LK.stopMusic();
showMenu();
}, 10000);
}
var gameOverElements = [];
// Main game loop
game.update = function () {
// Handle game over button click
if (gameState === 'gameOver') {
return; // Don't run game logic during game over
}
if (gameState !== 'playing') {
return; // Don't run game logic during menu
}
// Update fight button timer
if (!bossInvoked && !inBossFight) {
fightButtonTimer--;
if (fightButtonTimer <= 0 && !fightButton.visible) {
fightButton.visible = true;
fightButtonTimer = 600 + Math.random() * 900; // Random reappear delay
}
}
// Update fight two button timer
if (inBossFight && !fightTwoButtonVisible) {
fightTwoButtonTimer--;
if (fightTwoButtonTimer <= 0) {
fightTwoButton.visible = true;
fightTwoButtonVisible = true;
// Position randomly in arena
fightTwoButton.x = Math.random() * (arenaWidth - 200) + arenaX + 100;
fightTwoButton.y = Math.random() * (arenaHeight - 200) + arenaY + 100;
fightTwoButtonTimer = 300 + Math.random() * 360; // Next appearance delay
}
} else if (fightTwoButtonVisible && !fightTwoButton.visible) {
fightTwoButtonVisible = false;
}
// Increase speed if player health is low (only for difficulties after normal)
var currentGameSpeed = gameSpeed;
if (playerHealth < 10 && (currentDifficulty === 'hard' || currentDifficulty === 'hardcore' || currentDifficulty === 'impossible')) {
currentGameSpeed *= 1.5; // Objects appear faster
// But projectiles fall much faster
for (var i = 0; i < projectiles.length; i++) {
projectiles[i].velocityY *= 1.3;
}
for (var i = 0; i < swords.length; i++) {
if (swords[i].falling) {
swords[i].speed *= 1.5;
}
}
}
survivalTime++;
waveTimer++;
// Update survival time display
var seconds = Math.floor(survivalTime / 60);
scoreText.setText('Time: ' + seconds + 's');
LK.setScore(seconds);
// Increase difficulty over time
if (survivalTime % 300 === 0) {
// Every 5 seconds
gameSpeed += 0.1;
}
// Spawn attacks based on patterns
if (inBossFight) {
// More frequent and random attacks during boss fight
// Boss attacks faster when health is low
var bossAttackDelay = 20 + Math.random() * 20;
if (giantFace.health < giantFace.maxHealth * 0.3) {
bossAttackDelay = 10 + Math.random() * 10; // Much faster attacks when boss health < 30%
}
if (waveTimer >= bossAttackDelay) {
waveTimer = 0;
attackPattern = Math.floor(Math.random() * 8);
switch (attackPattern) {
case 0:
spawnLinearAttack();
break;
case 1:
spawnCircularAttack();
break;
case 2:
spawnTargetedAttack();
break;
case 3:
spawnSwordAttack();
break;
case 4:
spawnHorizontalAttack();
break;
case 5:
spawnVerticalAttack();
break;
case 6:
spawnDirectionalAttack();
break;
case 7:
spawnExplosionAttack();
break;
}
}
} else if (waveTimer >= 60 - gameSpeed * 5) {
// Normal attack patterns when not in boss fight
waveTimer = 0;
attackPattern = Math.floor(Math.random() * 8);
switch (attackPattern) {
case 0:
spawnLinearAttack();
break;
case 1:
if (survivalTime > 300) spawnCircularAttack(); // Unlock after 5 seconds
else spawnLinearAttack();
break;
case 2:
if (survivalTime > 600) spawnTargetedAttack(); // Unlock after 10 seconds
else spawnLinearAttack();
break;
case 3:
if (survivalTime > 900) spawnSwordAttack(); // Unlock after 15 seconds
else spawnLinearAttack();
break;
case 4:
if (survivalTime > 1200) spawnHorizontalAttack(); // Unlock after 20 seconds
else spawnLinearAttack();
break;
case 5:
if (survivalTime > 1500) spawnVerticalAttack(); // Unlock after 25 seconds
else spawnLinearAttack();
break;
case 6:
if (survivalTime > 1800) spawnDirectionalAttack(); // Unlock after 30 seconds
else spawnLinearAttack();
break;
case 7:
if (survivalTime > 2100) spawnExplosionAttack(); // Unlock after 35 seconds
else spawnLinearAttack();
break;
}
}
// Spawn healing items occasionally
healingTimer++;
if (healingTimer >= 600) {
// Every 10 seconds
healingTimer = 0;
spawnHealingItem();
}
// Update projectiles
for (var i = projectiles.length - 1; i >= 0; i--) {
var projectile = projectiles[i];
// Check collision with player
if (checkCollision(player, projectile)) {
takeDamage();
projectile.destroy();
projectiles.splice(i, 1);
continue;
}
// Remove projectiles that are off screen
if (projectile.x < -100 || projectile.x > 2148 || projectile.y < -100 || projectile.y > 2832) {
projectile.destroy();
projectiles.splice(i, 1);
}
}
// Update swords
for (var i = swords.length - 1; i >= 0; i--) {
var sword = swords[i];
if (sword.falling && checkCollision(player, sword)) {
takeDamage();
sword.destroy();
swords.splice(i, 1);
continue;
}
if (sword.y > arenaY + arenaHeight + 100) {
sword.destroy();
swords.splice(i, 1);
}
}
// Update horizontal projectiles
for (var i = horizontalProjectiles.length - 1; i >= 0; i--) {
var hProjectile = horizontalProjectiles[i];
if (checkCollision(player, hProjectile)) {
takeDamage();
hProjectile.destroy();
horizontalProjectiles.splice(i, 1);
continue;
}
if (hProjectile.x < arenaX - 100 || hProjectile.x > arenaX + arenaWidth + 100) {
hProjectile.destroy();
horizontalProjectiles.splice(i, 1);
}
}
// Update vertical projectiles
for (var i = verticalProjectiles.length - 1; i >= 0; i--) {
var vProjectile = verticalProjectiles[i];
if (checkCollision(player, vProjectile)) {
takeDamage();
vProjectile.destroy();
verticalProjectiles.splice(i, 1);
continue;
}
if (vProjectile.y < arenaY - 100) {
vProjectile.destroy();
verticalProjectiles.splice(i, 1);
}
}
// Update healing items
for (var i = healingItems.length - 1; i >= 0; i--) {
var healingItem = healingItems[i];
if (checkCollision(player, healingItem)) {
healPlayer();
healingItem.destroy();
healingItems.splice(i, 1);
continue;
}
if (healingItem.y > arenaY + arenaHeight + 100) {
healingItem.destroy();
healingItems.splice(i, 1);
}
}
// Update directional projectiles
for (var i = directionalProjectiles.length - 1; i >= 0; i--) {
var dProjectile = directionalProjectiles[i];
if (checkCollision(player, dProjectile)) {
takeDamage();
dProjectile.destroy();
directionalProjectiles.splice(i, 1);
continue;
}
if (dProjectile.x < arenaX - 100 || dProjectile.x > arenaX + arenaWidth + 100) {
dProjectile.destroy();
directionalProjectiles.splice(i, 1);
}
}
// Update explosion warnings
for (var i = explosionWarnings.length - 1; i >= 0; i--) {
var warning = explosionWarnings[i];
if (warning.warningTime <= 0) {
// Create explosion
var explosion = new Explosion(warning.x, warning.y);
explosions.push(explosion);
game.addChild(explosion);
warning.destroy();
explosionWarnings.splice(i, 1);
}
}
// Update explosions
for (var i = explosions.length - 1; i >= 0; i--) {
var explosion = explosions[i];
if (explosion.lifeTime > 0 && checkCollision(player, explosion)) {
takeDamage();
}
if (explosion.lifeTime <= 0) {
explosion.destroy();
explosions.splice(i, 1);
}
}
// Update damage numbers
for (var i = damageNumbers.length - 1; i >= 0; i--) {
var damageNumber = damageNumbers[i];
if (damageNumber.lifeTime <= 0 || damageNumber.y < -100) {
damageNumber.destroy();
damageNumbers.splice(i, 1);
}
}
// Update boss projectiles
for (var i = bossProjectiles.length - 1; i >= 0; i--) {
var bossProjectile = bossProjectiles[i];
// Check collision with player
if (checkCollision(player, bossProjectile)) {
takeDamage();
bossProjectile.destroy();
bossProjectiles.splice(i, 1);
continue;
}
// Remove projectiles that are off screen
if (bossProjectile.x < -100 || bossProjectile.x > 2148 || bossProjectile.y < -100 || bossProjectile.y > 2832) {
bossProjectile.destroy();
bossProjectiles.splice(i, 1);
}
}
// Play music
if (survivalTime === 1) {
LK.playMusic('battle');
}
}; /****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
var storage = LK.import("@upit/storage.v1");
/****
* Classes
****/
var BossProjectile = Container.expand(function (startX, startY, targetX, targetY, speed) {
var self = Container.call(this);
var projectileGraphics = self.attachAsset('bossProjectile', {
anchorX: 0.5,
anchorY: 0.5
});
self.x = startX;
self.y = startY;
self.radius = 15;
// Calculate direction
var dx = targetX - startX;
var dy = targetY - startY;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance > 0) {
self.velocityX = dx / distance * speed;
self.velocityY = dy / distance * speed;
} else {
self.velocityX = 0;
self.velocityY = speed;
}
self.update = function () {
self.x += self.velocityX;
self.y += self.velocityY;
};
return self;
});
var DamageNumber = Container.expand(function (damage, x, y) {
var self = Container.call(this);
var damageText = new Text2(damage.toString(), {
size: 120,
fill: 0xFF0000
});
damageText.anchor.set(0.5, 0.5);
self.addChild(damageText);
self.x = x;
self.y = y;
self.velocityY = -3;
self.lifeTime = 120; // 2 seconds
self.update = function () {
self.y += self.velocityY;
self.lifeTime--;
// Fade out over time
damageText.alpha = self.lifeTime / 120;
// Scale down slightly over time
var scale = 1 - (120 - self.lifeTime) / 120 * 0.3;
damageText.scaleX = scale;
damageText.scaleY = scale;
};
return self;
});
var DirectionalProjectile = Container.expand(function (fromLeft) {
var self = Container.call(this);
var projectileGraphics = self.attachAsset('square', {
anchorX: 0.5,
anchorY: 0.5
});
self.y = Math.random() * (arenaHeight - 100) + arenaY + 50;
self.radius = 15;
self.speed = 5 + gameSpeed;
if (fromLeft) {
self.x = arenaX - 50;
self.velocityX = self.speed;
} else {
self.x = arenaX + arenaWidth + 50;
self.velocityX = -self.speed;
}
self.lastX = self.x;
self.update = function () {
self.lastX = self.x;
self.x += self.velocityX;
};
return self;
});
var Explosion = Container.expand(function (targetX, targetY) {
var self = Container.call(this);
var explosionGraphics = self.attachAsset('hexagon', {
anchorX: 0.5,
anchorY: 0.5
});
self.x = targetX;
self.y = targetY;
self.radius = 75;
self.lifeTime = 30; // Half second explosion
self.update = function () {
self.lifeTime--;
// Explosion animation
var scale = 1 - self.lifeTime / 30 * 0.5;
explosionGraphics.scaleX = scale;
explosionGraphics.scaleY = scale;
explosionGraphics.alpha = self.lifeTime / 30;
};
return self;
});
var ExplosionWarning = Container.expand(function (targetX, targetY) {
var self = Container.call(this);
var warningGraphics = self.attachAsset('projectile', {
anchorX: 0.5,
anchorY: 0.5
});
warningGraphics.width = 120;
warningGraphics.height = 120;
warningGraphics.tint = 0xFF0000;
warningGraphics.alpha = 0.5;
self.x = targetX;
self.y = targetY;
self.warningTime = 120; // 2 seconds warning
self.radius = 0; // No collision during warning
self.update = function () {
self.warningTime--;
// Pulsing warning effect
warningGraphics.alpha = Math.sin(LK.ticks * 0.5) * 0.3 + 0.5;
warningGraphics.scaleX = 1 + Math.sin(LK.ticks * 0.3) * 0.2;
warningGraphics.scaleY = 1 + Math.sin(LK.ticks * 0.3) * 0.2;
};
return self;
});
var GiantFace = Container.expand(function () {
var self = Container.call(this);
var faceGraphics = self.attachAsset('bossSquare', {
anchorX: 0.5,
anchorY: 0.5
});
self.x = 2048 / 2;
self.y = 2732 / 2;
self.health = 999999999999;
self.maxHealth = 999999999999;
self.visible = false;
self.radius = 100;
self.moveDirection = 1;
self.moveSpeed = 3;
self.attackTimer = 0;
self.disappearTimer = 0;
self.isDisappeared = false;
self.laughTimer = 0;
self.update = function () {
if (self.visible && !self.isDisappeared) {
// Boss movement
self.x += self.moveDirection * self.moveSpeed;
if (self.x <= arenaX + 100 || self.x >= arenaX + arenaWidth - 100) {
self.moveDirection *= -1;
}
// Boss attack timer
self.attackTimer++;
if (self.attackTimer >= 90) {
// Attack every 1.5 seconds
self.attackTimer = 0;
// Launch projectile at player
var bossProjectile = new BossProjectile(self.x, self.y, player.x, player.y, 5);
bossProjectiles.push(bossProjectile);
game.addChild(bossProjectile);
}
// Random boss laugh timer
self.laughTimer++;
if (self.laughTimer >= 300 + Math.random() * 600) {
// Random laugh every 5-15 seconds
self.laughTimer = 0;
LK.getSound('bossLaugh').play();
}
// Disappear timer
self.disappearTimer++;
if (self.disappearTimer >= 600) {
// Disappear every 10 seconds
self.disappearTimer = 0;
self.isDisappeared = true;
self.visible = false;
// Reappear after 3 seconds
LK.setTimeout(function () {
if (inBossFight) {
self.isDisappeared = false;
self.visible = true;
self.x = Math.random() * (arenaWidth - 200) + arenaX + 100;
self.y = Math.random() * (arenaHeight - 200) + arenaY + 100;
}
}, 3000);
}
}
};
return self;
});
var HealingItem = Container.expand(function () {
var self = Container.call(this);
var healingGraphics = self.attachAsset('healingItem', {
anchorX: 0.5,
anchorY: 0.5
});
healingGraphics.tint = 0x00FF00;
self.x = Math.random() * (arenaWidth - 100) + arenaX + 50;
self.y = arenaY - 50;
self.velocityY = 2;
self.radius = 25;
self.lastY = self.y;
self.update = function () {
self.lastY = self.y;
self.y += self.velocityY;
// Gentle floating animation
healingGraphics.rotation += 0.02;
healingGraphics.scaleX = 1 + Math.sin(LK.ticks * 0.1) * 0.1;
healingGraphics.scaleY = 1 + Math.sin(LK.ticks * 0.1) * 0.1;
};
return self;
});
var HorizontalProjectile = Container.expand(function (fromLeft, yPos, speed) {
var self = Container.call(this);
var projectileGraphics = self.attachAsset('projectile', {
anchorX: 0.5,
anchorY: 0.5
});
projectileGraphics.width = 40;
projectileGraphics.height = 20;
if (fromLeft) {
self.x = arenaX - 50;
self.velocityX = speed;
} else {
self.x = arenaX + arenaWidth + 50;
self.velocityX = -speed;
}
self.y = yPos;
self.radius = 15;
self.lastX = self.x;
self.update = function () {
self.lastX = self.x;
self.x += self.velocityX;
};
return self;
});
var Player = Container.expand(function () {
var self = Container.call(this);
var playerGraphics = self.attachAsset('player', {
anchorX: 0.5,
anchorY: 0.5
});
self.radius = 30;
self.speed = 8;
return self;
});
var Projectile = Container.expand(function (startX, startY, targetX, targetY, speed) {
var self = Container.call(this);
var projectileGraphics = self.attachAsset('projectile', {
anchorX: 0.5,
anchorY: 0.5
});
self.x = startX;
self.y = startY;
self.radius = 10;
// Calculate direction
var dx = targetX - startX;
var dy = targetY - startY;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance > 0) {
self.velocityX = dx / distance * speed;
self.velocityY = dy / distance * speed;
} else {
self.velocityX = 0;
self.velocityY = speed;
}
self.update = function () {
self.x += self.velocityX;
self.y += self.velocityY;
};
return self;
});
var Sword = Container.expand(function (targetX) {
var self = Container.call(this);
var swordGraphics = self.attachAsset('sword', {
anchorX: 0.5,
anchorY: 0.5
});
self.x = targetX;
self.y = arenaY - 150;
self.targetY = arenaY + arenaHeight + 100;
self.radius = 20;
self.speed = 0;
self.warningTime = 120; // 2 seconds warning
self.falling = false;
self.lastY = self.y;
swordGraphics.tint = 0xFFFFFF;
self.update = function () {
if (self.warningTime > 0) {
self.warningTime--;
// Flashing warning effect
swordGraphics.alpha = Math.sin(LK.ticks * 0.3) * 0.5 + 0.5;
if (self.warningTime === 0) {
self.falling = true;
self.speed = 8;
swordGraphics.alpha = 1;
LK.getSound('swordFall').play();
}
} else if (self.falling) {
self.lastY = self.y;
self.y += self.speed;
}
};
return self;
});
var VerticalProjectile = Container.expand(function (xPos, speed) {
var self = Container.call(this);
var projectileGraphics = self.attachAsset('projectile', {
anchorX: 0.5,
anchorY: 0.5
});
projectileGraphics.width = 20;
projectileGraphics.height = 40;
self.x = xPos;
self.y = arenaY + arenaHeight + 50;
self.velocityY = -speed;
self.radius = 15;
self.lastY = self.y;
self.update = function () {
self.lastY = self.y;
self.y += self.velocityY;
};
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0x000000
});
/****
* Game Code
****/
// Game variables
var player;
var projectiles = [];
var swords = [];
var horizontalProjectiles = [];
var verticalProjectiles = [];
var healingItems = [];
var directionalProjectiles = [];
var explosionWarnings = [];
var explosions = [];
var survivalTime = 0;
var waveTimer = 0;
var attackPattern = 0;
var gameSpeed = 1;
var isDragging = false;
var playerHealth = 20;
var maxHealth = 20;
var healingTimer = 0;
var playerDamage = 1;
var totalBossDamageDealt = 0;
var baseDamage = 1;
var giantFace;
var fightButton;
var inBossFight = false;
var bossProjectiles = [];
var bossHealthBar;
var bossHealthText;
var showBossHealth = false;
var fightButtonTimer = 0;
var fightButtonHoverCount = 0;
var bossInvoked = false;
var playerColor = 0xFF0000;
var gameStartTime = 0;
var currentDifficulty = 'normal';
var fightTwoButton;
var fightTwoButtonTimer = 0;
var fightTwoButtonVisible = false;
var damageNumbers = [];
// Game state variables
var gameState = 'menu'; // 'menu', 'playing', 'gameOver'
var menuElements = [];
// UI elements
var scoreText = new Text2('Time: 0', {
size: 80,
fill: 0xFFFFFF
});
scoreText.anchor.set(0.5, 0);
LK.gui.top.addChild(scoreText);
// Main menu elements
var titleText = new Text2('UNDERTALE VS CHATORE', {
size: 120,
fill: 0xFFFFFF
});
titleText.anchor.set(0.5, 0.5);
titleText.x = 2048 / 2;
titleText.y = 2732 / 2 - 300;
var heartSymbol = new Text2('♥', {
size: 100,
fill: 0xFF0000
});
heartSymbol.anchor.set(0.5, 0.5);
heartSymbol.x = titleText.x + 400;
heartSymbol.y = titleText.y;
var startButton = new Text2('START GAME', {
size: 80,
fill: 0xFFFF00
});
startButton.anchor.set(0.5, 0.5);
startButton.x = 2048 / 2;
startButton.y = 2732 / 2;
startButton.radius = 150;
var recordsButton = new Text2('RECORDS', {
size: 60,
fill: 0xFF00FF
});
recordsButton.anchor.set(0.5, 0.5);
recordsButton.x = 2048 / 2;
recordsButton.y = 2732 / 2 + 150;
recordsButton.radius = 120;
menuElements.push(titleText, startButton);
// Add determination animation to heart
tween(heartSymbol, {
scaleX: 1.2,
scaleY: 1.2
}, {
duration: 1000,
easing: tween.easeInOut,
onFinish: function onFinish() {
tween(heartSymbol, {
scaleX: 1,
scaleY: 1
}, {
duration: 1000,
easing: tween.easeInOut,
onFinish: function onFinish() {
// Restart the animation cycle
if (gameState === 'menu') {
tween(heartSymbol, {
scaleX: 1.2,
scaleY: 1.2
}, {
duration: 1000,
easing: tween.easeInOut,
onFinish: arguments.callee.caller
});
}
}
});
}
});
var healthText = new Text2('Frisk: 20/20', {
size: 60,
fill: 0xFF0000
});
healthText.anchor.set(0, 0);
healthText.x = 120;
healthText.y = 120;
LK.gui.topLeft.addChild(healthText);
var instructionText = new Text2('Drag to move, avoid white bullets!', {
size: 60,
fill: 0xFFFFFF
});
instructionText.anchor.set(0.5, 0.5);
instructionText.x = 2048 / 2;
instructionText.y = 2732 / 2 + 200;
game.addChild(instructionText);
// Create arena boundaries (visual indicators)
var arenaWidth = 1800;
var arenaHeight = 2200;
var arenaX = (2048 - arenaWidth) / 2;
var arenaY = (2732 - arenaHeight) / 2;
// Initialize player
player = game.addChild(new Player());
player.x = 2048 / 2;
player.y = 2732 / 2;
player.visible = false;
// Load saved player color
playerColor = storage.playerColor || 0xFF0000;
// Show menu initially
function showMenu() {
gameState = 'menu';
// Clear all game elements and reset game state
clearAllProjectiles();
// Reset all timers and variables
survivalTime = 0;
waveTimer = 0;
attackPattern = 0;
gameSpeed = 1;
isDragging = false;
playerHealth = 20;
maxHealth = 20;
healingTimer = 0;
playerDamage = 1;
totalBossDamageDealt = 0;
baseDamage = 1;
inBossFight = false;
bossInvoked = false;
fightButtonTimer = 0;
fightButtonHoverCount = 0;
currentDifficulty = 'normal';
fightTwoButtonTimer = 0;
fightTwoButtonVisible = false;
showBossHealth = false;
// Reset UI elements
if (bossHealthBar) bossHealthBar.visible = false;
if (bossHealthText) bossHealthText.visible = false;
if (giantFace) {
giantFace.visible = false;
giantFace.health = 999999999999;
giantFace.isDisappeared = false;
giantFace.disappearTimer = 0;
giantFace.attackTimer = 0;
giantFace.laughTimer = 0;
}
// Reset player position
player.x = 2048 / 2;
player.y = 2732 / 2;
for (var i = 0; i < menuElements.length; i++) {
game.addChild(menuElements[i]);
}
player.visible = false;
fightButton.visible = false;
scoreText.visible = false;
healthText.visible = false;
LK.playMusic('menuMusic');
}
function hideMenu() {
for (var i = 0; i < menuElements.length; i++) {
if (menuElements[i].parent) {
menuElements[i].parent.removeChild(menuElements[i]);
}
}
player.visible = true;
scoreText.visible = true;
healthText.visible = true;
LK.stopMusic();
}
function showDifficultySelection() {
gameState = 'difficultySelection';
hideMenu();
var difficultyTitle = new Text2('ELIGE NIVEL DE DIFICULTAD', {
size: 100,
fill: 0xFFFFFF
});
difficultyTitle.anchor.set(0.5, 0.5);
difficultyTitle.x = 2048 / 2;
difficultyTitle.y = 400;
game.addChild(difficultyTitle);
var difficulties = [{
name: 'FÁCIL',
health: 120,
color: 0x00FF00
}, {
name: 'NORMAL',
health: 100,
color: 0xFFFF00
}, {
name: 'DIFÍCIL',
health: 20,
color: 0xFF8800
}, {
name: 'HARDCORE',
health: 10,
color: 0xFF0000
}, {
name: 'IMPOSIBLE',
health: 1,
color: 0x8800FF
}];
for (var i = 0; i < difficulties.length; i++) {
var difficultyOption = new Text2(difficulties[i].name + ' (' + difficulties[i].health + ' de vida)', {
size: 70,
fill: difficulties[i].color
});
difficultyOption.anchor.set(0.5, 0.5);
difficultyOption.x = 2048 / 2;
difficultyOption.y = 600 + i * 120;
difficultyOption.radius = 200;
difficultyOption.healthValue = difficulties[i].health;
game.addChild(difficultyOption);
menuElements.push(difficultyOption);
}
var backButton = new Text2('VOLVER', {
size: 60,
fill: 0xFFFFFF
});
backButton.anchor.set(0.5, 0.5);
backButton.x = 2048 / 2;
backButton.y = 1200;
backButton.radius = 100;
backButton.isBackButton = true;
menuElements.push(difficultyTitle, backButton);
game.addChild(backButton);
}
function startGame(selectedHealth) {
gameState = 'playing';
hideMenu();
// Reset game state
survivalTime = 0;
gameStartTime = LK.ticks;
playerHealth = selectedHealth || 20;
maxHealth = selectedHealth || 20;
playerDamage = 1;
gameSpeed = 1;
inBossFight = false;
bossInvoked = false;
totalBossDamageDealt = 0;
playerDamage = baseDamage;
fightButtonTimer = 300 + Math.random() * 600; // Random delay before fight button appears
fightButtonHoverCount = 0;
fightTwoButtonTimer = 0;
fightTwoButtonVisible = false;
// Set difficulty based on health
if (playerHealth >= 120) currentDifficulty = 'easy';else if (playerHealth >= 100) currentDifficulty = 'normal';else if (playerHealth >= 20) currentDifficulty = 'hard';else if (playerHealth >= 10) currentDifficulty = 'hardcore';else currentDifficulty = 'impossible';
giantFace.health = 999999999999;
giantFace.visible = false;
fightButton.visible = false;
fightTwoButton.visible = false;
healthText.setText('Frisk: ' + playerHealth + '/' + maxHealth);
// Reset player appearance completely
player.alpha = 1;
player.scaleX = 1;
player.scaleY = 1;
player.rotation = 0;
player.visible = true;
// Apply player color
var playerGraphics = player.children[0];
if (playerGraphics) {
playerGraphics.tint = playerColor;
}
// Clear all projectiles
clearAllProjectiles();
// Make player visible when starting game
player.visible = true;
}
function showColorSelection() {
gameState = 'colorSelection';
hideMenu();
var colorTitle = new Text2('ELIGE TU COLOR', {
size: 100,
fill: 0xFFFFFF
});
colorTitle.anchor.set(0.5, 0.5);
colorTitle.x = 2048 / 2;
colorTitle.y = 400;
game.addChild(colorTitle);
var colors = [0xFF0000, 0x00FF00, 0x0000FF, 0xFFFF00, 0xFF00FF, 0x00FFFF, 0xFFA500, 0x800080];
var colorNames = ['Rojo', 'Verde', 'Azul', 'Amarillo', 'Magenta', 'Cian', 'Naranja', 'Morado'];
for (var i = 0; i < colors.length; i++) {
var colorOption = new Text2(colorNames[i], {
size: 80,
fill: colors[i]
});
colorOption.anchor.set(0.5, 0.5);
colorOption.x = i % 4 * 400 + 400;
colorOption.y = Math.floor(i / 4) * 150 + 800;
colorOption.radius = 150;
colorOption.colorValue = colors[i];
game.addChild(colorOption);
menuElements.push(colorOption);
}
var backButton = new Text2('VOLVER', {
size: 60,
fill: 0xFFFFFF
});
backButton.anchor.set(0.5, 0.5);
backButton.x = 2048 / 2;
backButton.y = 1400;
backButton.radius = 100;
backButton.isBackButton = true;
menuElements.push(colorTitle, backButton);
game.addChild(backButton);
}
function showRecords() {
gameState = 'records';
hideMenu();
var recordsTitle = new Text2('MEJORES TIEMPOS', {
size: 100,
fill: 0xFFFFFF
});
recordsTitle.anchor.set(0.5, 0.5);
recordsTitle.x = 2048 / 2;
recordsTitle.y = 400;
game.addChild(recordsTitle);
var records = storage.records || [];
records.sort(function (a, b) {
return b - a;
});
for (var i = 0; i < Math.min(5, records.length); i++) {
var recordText = new Text2(i + 1 + '. ' + records[i] + 's', {
size: 80,
fill: 0xFFFF00
});
recordText.anchor.set(0.5, 0.5);
recordText.x = 2048 / 2;
recordText.y = 600 + i * 120;
game.addChild(recordText);
menuElements.push(recordText);
}
if (records.length === 0) {
var noRecordsText = new Text2('No hay records aún', {
size: 80,
fill: 0x888888
});
noRecordsText.anchor.set(0.5, 0.5);
noRecordsText.x = 2048 / 2;
noRecordsText.y = 800;
game.addChild(noRecordsText);
menuElements.push(noRecordsText);
}
var backButton = new Text2('VOLVER', {
size: 60,
fill: 0xFFFFFF
});
backButton.anchor.set(0.5, 0.5);
backButton.x = 2048 / 2;
backButton.y = 1400;
backButton.radius = 100;
backButton.isBackButton = true;
menuElements.push(recordsTitle, backButton);
game.addChild(backButton);
}
function createDamageNumber(damage, x, y) {
// Calculate scaled damage based on total damage dealt to boss
var damageMultiplier = 1 + Math.floor(totalBossDamageDealt / 1000) * 0.5; // Increase by 50% for every 1000 damage dealt
var scaledDamage = Math.floor(damage * damageMultiplier);
// 25% chance for critical damage (much higher damage)
var isCritical = Math.random() < 0.25;
var finalDamage = isCritical ? scaledDamage * 5 : scaledDamage;
var damageNumber = new DamageNumber(finalDamage, x, y);
damageNumbers.push(damageNumber);
game.addChild(damageNumber);
// Track total damage dealt to boss
totalBossDamageDealt += finalDamage;
return finalDamage;
}
function saveRecord(time) {
var records = storage.records || [];
records.push(time);
records.sort(function (a, b) {
return b - a;
});
if (records.length > 10) {
records = records.slice(0, 10);
}
storage.records = records;
}
function clearAllProjectiles() {
// Clear all attack arrays
for (var i = projectiles.length - 1; i >= 0; i--) {
projectiles[i].destroy();
projectiles.splice(i, 1);
}
for (var i = swords.length - 1; i >= 0; i--) {
swords[i].destroy();
swords.splice(i, 1);
}
for (var i = horizontalProjectiles.length - 1; i >= 0; i--) {
horizontalProjectiles[i].destroy();
horizontalProjectiles.splice(i, 1);
}
for (var i = verticalProjectiles.length - 1; i >= 0; i--) {
verticalProjectiles[i].destroy();
verticalProjectiles.splice(i, 1);
}
for (var i = healingItems.length - 1; i >= 0; i--) {
healingItems[i].destroy();
healingItems.splice(i, 1);
}
for (var i = directionalProjectiles.length - 1; i >= 0; i--) {
directionalProjectiles[i].destroy();
directionalProjectiles.splice(i, 1);
}
for (var i = explosionWarnings.length - 1; i >= 0; i--) {
explosionWarnings[i].destroy();
explosionWarnings.splice(i, 1);
}
for (var i = explosions.length - 1; i >= 0; i--) {
explosions[i].destroy();
explosions.splice(i, 1);
}
for (var i = bossProjectiles.length - 1; i >= 0; i--) {
bossProjectiles[i].destroy();
bossProjectiles.splice(i, 1);
}
for (var i = damageNumbers.length - 1; i >= 0; i--) {
damageNumbers[i].destroy();
damageNumbers.splice(i, 1);
}
}
// Initialize giant face
giantFace = game.addChild(new GiantFace());
// Create fight button
fightButton = new Text2('FIGHT', {
size: 120,
fill: 0xFFFF00
});
fightButton.anchor.set(0.5, 0.5);
fightButton.x = 2048 / 2;
fightButton.y = 2732 - 200;
fightButton.radius = 100;
game.addChild(fightButton);
// Create fight two button
fightTwoButton = new Text2('FIGHT TWO', {
size: 100,
fill: 0xFF00FF
});
fightTwoButton.anchor.set(0.5, 0.5);
fightTwoButton.radius = 120;
fightTwoButton.visible = false;
game.addChild(fightTwoButton);
showMenu();
// Create boss health bar
bossHealthBar = new Text2('Chatore: 999999999999', {
size: 50,
fill: 0xFFFF00
});
bossHealthBar.anchor.set(0.5, 0);
bossHealthBar.visible = false;
LK.gui.top.addChild(bossHealthBar);
bossHealthText = new Text2('', {
size: 40,
fill: 0xFF0000
});
bossHealthText.anchor.set(0.5, 0);
bossHealthText.y = 60;
bossHealthText.visible = false;
LK.gui.top.addChild(bossHealthText);
// Attack pattern functions
function spawnLinearAttack() {
var side = Math.floor(Math.random() * 4);
var startX, startY, targetX, targetY;
switch (side) {
case 0:
// Top
startX = Math.random() * arenaWidth + arenaX;
startY = arenaY;
targetX = Math.random() * arenaWidth + arenaX;
targetY = arenaY + arenaHeight;
break;
case 1:
// Right
startX = arenaX + arenaWidth;
startY = Math.random() * arenaHeight + arenaY;
targetX = arenaX;
targetY = Math.random() * arenaHeight + arenaY;
break;
case 2:
// Bottom
startX = Math.random() * arenaWidth + arenaX;
startY = arenaY + arenaHeight;
targetX = Math.random() * arenaWidth + arenaX;
targetY = arenaY;
break;
case 3:
// Left
startX = arenaX;
startY = Math.random() * arenaHeight + arenaY;
targetX = arenaX + arenaWidth;
targetY = Math.random() * arenaHeight + arenaY;
break;
}
var projectile = new Projectile(startX, startY, targetX, targetY, 3 * gameSpeed);
projectiles.push(projectile);
game.addChild(projectile);
}
function spawnCircularAttack() {
var centerX = Math.random() * (arenaWidth - 200) + arenaX + 100;
var centerY = Math.random() * (arenaHeight - 200) + arenaY + 100;
var numProjectiles = 8;
for (var i = 0; i < numProjectiles; i++) {
var angle = i / numProjectiles * Math.PI * 2;
var targetX = centerX + Math.cos(angle) * 1000;
var targetY = centerY + Math.sin(angle) * 1000;
var projectile = new Projectile(centerX, centerY, targetX, targetY, 2.5 * gameSpeed);
projectiles.push(projectile);
game.addChild(projectile);
}
}
function spawnTargetedAttack() {
var side = Math.floor(Math.random() * 4);
var startX, startY;
switch (side) {
case 0:
// Top
startX = Math.random() * arenaWidth + arenaX;
startY = arenaY;
break;
case 1:
// Right
startX = arenaX + arenaWidth;
startY = Math.random() * arenaHeight + arenaY;
break;
case 2:
// Bottom
startX = Math.random() * arenaWidth + arenaX;
startY = arenaY + arenaHeight;
break;
case 3:
// Left
startX = arenaX;
startY = Math.random() * arenaHeight + arenaY;
break;
}
var projectile = new Projectile(startX, startY, player.x, player.y, 4 * gameSpeed);
projectiles.push(projectile);
game.addChild(projectile);
}
function spawnSwordAttack() {
var numSwords = Math.floor(Math.random() * 3) + 2; // 2-4 swords
for (var i = 0; i < numSwords; i++) {
var swordX = Math.random() * (arenaWidth - 80) + arenaX + 40;
var sword = new Sword(swordX);
swords.push(sword);
game.addChild(sword);
}
}
function spawnHorizontalAttack() {
var fromLeft = Math.random() < 0.5;
var yPos = Math.random() * (arenaHeight - 100) + arenaY + 50;
var speed = 4 + gameSpeed;
var projectile = new HorizontalProjectile(fromLeft, yPos, speed);
horizontalProjectiles.push(projectile);
game.addChild(projectile);
}
function spawnVerticalAttack() {
var xPos = Math.random() * (arenaWidth - 100) + arenaX + 50;
var speed = 4 + gameSpeed;
var projectile = new VerticalProjectile(xPos, speed);
verticalProjectiles.push(projectile);
game.addChild(projectile);
}
function spawnDirectionalAttack() {
var fromLeft = Math.random() < 0.5;
var projectile = new DirectionalProjectile(fromLeft);
directionalProjectiles.push(projectile);
game.addChild(projectile);
}
function spawnExplosionAttack() {
var targetX = Math.random() * (arenaWidth - 200) + arenaX + 100;
var targetY = Math.random() * (arenaHeight - 200) + arenaY + 100;
// First spawn warning
var warning = new ExplosionWarning(targetX, targetY);
explosionWarnings.push(warning);
game.addChild(warning);
}
function spawnHealingItem() {
var spawnChance = 0.3;
var numItems = 1;
// When player has low health, increase spawn rate and variety
if (playerHealth < 10) {
spawnChance = 0.8; // Much higher chance
numItems = Math.floor(Math.random() * 3) + 2; // 2-4 items
}
if (Math.random() < spawnChance && playerHealth < maxHealth) {
for (var i = 0; i < numItems; i++) {
var healingItem = new HealingItem();
healingItems.push(healingItem);
game.addChild(healingItem);
}
}
}
// Touch controls
game.down = function (x, y, obj) {
// Handle menu interactions
if (gameState === 'menu') {
var startDx = x - startButton.x;
var startDy = y - startButton.y;
var startDistance = Math.sqrt(startDx * startDx + startDy * startDy);
if (startDistance < startButton.radius) {
showDifficultySelection();
return;
}
return;
}
// Handle color selection interactions
if (gameState === 'colorSelection') {
for (var i = 0; i < menuElements.length; i++) {
var element = menuElements[i];
if (element.colorValue !== undefined) {
var dx = x - element.x;
var dy = y - element.y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance < element.radius) {
playerColor = element.colorValue;
storage.playerColor = playerColor;
showMenu();
return;
}
} else if (element.isBackButton) {
var dx = x - element.x;
var dy = y - element.y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance < element.radius) {
showMenu();
return;
}
}
}
return;
}
// Handle difficulty selection interactions
if (gameState === 'difficultySelection') {
for (var i = 0; i < menuElements.length; i++) {
var element = menuElements[i];
if (element.healthValue !== undefined) {
var dx = x - element.x;
var dy = y - element.y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance < element.radius) {
startGame(element.healthValue);
return;
}
} else if (element.isBackButton) {
var dx = x - element.x;
var dy = y - element.y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance < element.radius) {
showMenu();
return;
}
}
}
return;
}
// Handle records screen interactions
if (gameState === 'records') {
for (var i = 0; i < menuElements.length; i++) {
var element = menuElements[i];
if (element.isBackButton) {
var dx = x - element.x;
var dy = y - element.y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance < element.radius) {
showMenu();
return;
}
}
}
return;
}
if (gameState !== 'playing') return;
// Check if touching fight button
if (fightButton.visible) {
var dx = x - fightButton.x;
var dy = y - fightButton.y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance < fightButton.radius) {
fightButtonHoverCount++;
if (fightButtonHoverCount === 1 && !bossInvoked) {
// First time - start boss fight
inBossFight = true;
bossInvoked = true;
giantFace.visible = true;
fightButton.visible = false;
// Start fight two button timer
fightTwoButtonTimer = 180 + Math.random() * 240; // 3-7 seconds after boss appears
} else if (fightButtonHoverCount === 2) {
// Second time - deal 999 damage to boss
var actualDamage = createDamageNumber(999, giantFace.x, giantFace.y - 100);
giantFace.health -= actualDamage;
if (giantFace.health < 0) giantFace.health = 0;
LK.effects.flashObject(giantFace, 0xff0000, 500);
fightButton.visible = false;
}
return;
}
}
// Check if touching fight two button
if (fightTwoButton.visible) {
var dx2 = x - fightTwoButton.x;
var dy2 = y - fightTwoButton.y;
var distance2 = Math.sqrt(dx2 * dx2 + dy2 * dy2);
if (distance2 < fightTwoButton.radius) {
// Deal 999 damage to boss
var actualDamage = createDamageNumber(999, giantFace.x, giantFace.y - 100);
giantFace.health -= actualDamage;
if (giantFace.health < 0) giantFace.health = 0;
LK.effects.flashObject(giantFace, 0xff0000, 500);
// Move button to random position
fightTwoButton.x = Math.random() * (arenaWidth - 200) + arenaX + 100;
fightTwoButton.y = Math.random() * (arenaHeight - 200) + arenaY + 100;
// Set new timer for next appearance
fightTwoButtonTimer = 300 + Math.random() * 360; // 5-11 seconds
fightTwoButton.visible = false;
return;
}
}
// Check if touching giant face during boss fight
if (inBossFight && giantFace.visible) {
var faceDx = x - giantFace.x;
var faceDy = y - giantFace.y;
var faceDistance = Math.sqrt(faceDx * faceDx + faceDy * faceDy);
if (faceDistance < giantFace.radius) {
// Player collision with boss - boss takes 999 damage, player takes 3
var actualDamage = createDamageNumber(999, giantFace.x, giantFace.y - 100);
giantFace.health -= actualDamage;
playerHealth -= 3;
if (giantFace.health < 0) giantFace.health = 0;
if (playerHealth < 0) playerHealth = 0;
healthText.setText('Frisk: ' + playerHealth + '/' + maxHealth);
LK.effects.flashObject(giantFace, 0xff0000, 200);
LK.effects.flashObject(player, 0xff0000, 500);
LK.getSound('Golpear').play();
// Show boss health bar when damaged
showBossHealth = true;
bossHealthBar.visible = true;
bossHealthText.visible = true;
bossHealthBar.setText('Chatore: ' + giantFace.health.toFixed(0));
bossHealthText.setText('Damage: ' + playerDamage);
// Hide health bar after 3 seconds
LK.setTimeout(function () {
showBossHealth = false;
bossHealthBar.visible = false;
bossHealthText.visible = false;
}, 3000);
// Increase player damage - continues scaling beyond 999
if (playerDamage < 9999) {
if (playerDamage < 100) {
playerDamage++;
} else if (playerDamage < 999) {
playerDamage++;
} else {
// After 999, increase by larger amounts
playerDamage += 10;
}
}
// Check if boss is defeated
if (giantFace.health <= 0) {
// Boss defeat sequence
inBossFight = false;
isDragging = false;
// Focus camera on boss and make it shake
game.setBackgroundColor(0x000000);
LK.effects.flashScreen(0x000000, 500);
// Boss shaking animation
var shakeIntensity = 20;
var shakeTimer = 0;
var shakeInterval = LK.setInterval(function () {
shakeTimer++;
giantFace.x += (Math.random() - 0.5) * shakeIntensity;
giantFace.y += (Math.random() - 0.5) * shakeIntensity;
shakeIntensity *= 0.95; // Reduce shake over time
if (shakeTimer >= 120) {
// 2 seconds of shaking
LK.clearInterval(shakeInterval);
// Boss disappears
LK.getSound('bossDisappear').play();
tween(giantFace, {
alpha: 0,
scaleX: 0,
scaleY: 0
}, {
duration: 500,
easing: tween.easeIn,
onFinish: function onFinish() {
LK.showYouWin();
}
});
}
}, 16); // ~60fps
}
return;
}
}
// Handle game over interactions
if (gameState === 'gameOver') {
// Check if touching back to menu button
if (gameOverElements.length > 2) {
var backToMenuButton = gameOverElements[2];
var againDx = x - backToMenuButton.x;
var againDy = y - backToMenuButton.y;
var againDistance = Math.sqrt(againDx * againDx + againDy * againDy);
if (againDistance < backToMenuButton.radius) {
// Clean up game over screen
for (var i = 0; i < gameOverElements.length; i++) {
if (gameOverElements[i].parent) {
gameOverElements[i].parent.removeChild(gameOverElements[i]);
}
}
gameOverElements = [];
LK.stopMusic();
showMenu();
return;
}
}
return;
}
isDragging = true;
player.x = x;
player.y = y;
// Hide instruction text after first interaction
if (instructionText.parent) {
instructionText.parent.removeChild(instructionText);
}
};
game.move = function (x, y, obj) {
if (gameState === 'playing' && isDragging && !inBossFight) {
// Keep player within arena bounds
player.x = Math.max(arenaX + player.radius, Math.min(arenaX + arenaWidth - player.radius, x));
player.y = Math.max(arenaY + player.radius, Math.min(arenaY + arenaHeight - player.radius, y));
}
};
game.up = function (x, y, obj) {
isDragging = false;
};
// Collision detection
function checkCollision(obj1, obj2) {
var dx = obj1.x - obj2.x;
var dy = obj1.y - obj2.y;
var distance = Math.sqrt(dx * dx + dy * dy);
return distance < obj1.radius + obj2.radius;
}
function takeDamage() {
playerHealth--;
healthText.setText('Frisk: ' + playerHealth + '/' + maxHealth);
LK.getSound('Golpear').play();
LK.effects.flashObject(player, 0xff0000, 500);
// Boss laughs when player takes damage
if (inBossFight && giantFace.visible) {
LK.getSound('bossLaugh').play();
}
if (playerHealth <= 0) {
// Player death sequence
gameState = 'gameOver';
// Stop player movement
isDragging = false;
// Make player break in half and explode
LK.getSound('playerBreak').play();
tween(player, {
scaleX: 0.5,
scaleY: 1.5
}, {
duration: 300,
easing: tween.easeOut
});
tween(player, {
rotation: Math.PI / 4
}, {
duration: 300,
easing: tween.easeOut,
onFinish: function onFinish() {
// Explosion effect
tween(player, {
scaleX: 2,
scaleY: 2,
alpha: 0
}, {
duration: 200,
easing: tween.easeOut,
onFinish: function onFinish() {
// Focus on player position with black screen
game.setBackgroundColor(0x000000);
LK.effects.flashScreen(0x000000, 1000);
// Show game over after animation
LK.setTimeout(function () {
showGameOver();
}, 1000);
}
});
}
});
}
}
function healPlayer() {
if (playerHealth < maxHealth) {
playerHealth = Math.min(playerHealth + 3, maxHealth);
healthText.setText('Frisk: ' + playerHealth + '/' + maxHealth);
LK.getSound('heal').play();
LK.effects.flashObject(player, 0x00ff00, 500);
}
}
function showGameOver() {
// Save survival time record
var finalTime = Math.floor((LK.ticks - gameStartTime) / 60);
saveRecord(finalTime);
LK.playMusic('gameOverMusic');
// Create game over screen
var gameOverBg = new Text2('', {
size: 1,
fill: 0x000000
});
gameOverBg.width = 2048;
gameOverBg.height = 2732;
gameOverBg.x = 0;
gameOverBg.y = 0;
game.addChild(gameOverBg);
var gameOverText = new Text2('Tranquilo Frisk, lo hiciste bien\npero el destino de tu alma esta\nen el juego Frisk.\nMantente determinado.', {
size: 60,
fill: 0xFFFFFF
});
gameOverText.anchor.set(0.5, 0.5);
gameOverText.x = 2048 / 2;
gameOverText.y = 2732 / 2 - 100;
game.addChild(gameOverText);
var backToMenuButton = new Text2('VOLVER AL MENU', {
size: 80,
fill: 0xFFFF00
});
backToMenuButton.anchor.set(0.5, 0.5);
backToMenuButton.x = 2048 / 2;
backToMenuButton.y = 2732 / 2 + 150;
backToMenuButton.radius = 200;
game.addChild(backToMenuButton);
// Store game over elements for cleanup
gameOverElements = [gameOverBg, gameOverText, backToMenuButton];
// Auto redirect to menu after 10 seconds
LK.setTimeout(function () {
// Clean up game over screen
for (var i = 0; i < gameOverElements.length; i++) {
if (gameOverElements[i].parent) {
gameOverElements[i].parent.removeChild(gameOverElements[i]);
}
}
gameOverElements = [];
LK.stopMusic();
showMenu();
}, 10000);
}
var gameOverElements = [];
// Main game loop
game.update = function () {
// Handle game over button click
if (gameState === 'gameOver') {
return; // Don't run game logic during game over
}
if (gameState !== 'playing') {
return; // Don't run game logic during menu
}
// Update fight button timer
if (!bossInvoked && !inBossFight) {
fightButtonTimer--;
if (fightButtonTimer <= 0 && !fightButton.visible) {
fightButton.visible = true;
fightButtonTimer = 600 + Math.random() * 900; // Random reappear delay
}
}
// Update fight two button timer
if (inBossFight && !fightTwoButtonVisible) {
fightTwoButtonTimer--;
if (fightTwoButtonTimer <= 0) {
fightTwoButton.visible = true;
fightTwoButtonVisible = true;
// Position randomly in arena
fightTwoButton.x = Math.random() * (arenaWidth - 200) + arenaX + 100;
fightTwoButton.y = Math.random() * (arenaHeight - 200) + arenaY + 100;
fightTwoButtonTimer = 300 + Math.random() * 360; // Next appearance delay
}
} else if (fightTwoButtonVisible && !fightTwoButton.visible) {
fightTwoButtonVisible = false;
}
// Increase speed if player health is low (only for difficulties after normal)
var currentGameSpeed = gameSpeed;
if (playerHealth < 10 && (currentDifficulty === 'hard' || currentDifficulty === 'hardcore' || currentDifficulty === 'impossible')) {
currentGameSpeed *= 1.5; // Objects appear faster
// But projectiles fall much faster
for (var i = 0; i < projectiles.length; i++) {
projectiles[i].velocityY *= 1.3;
}
for (var i = 0; i < swords.length; i++) {
if (swords[i].falling) {
swords[i].speed *= 1.5;
}
}
}
survivalTime++;
waveTimer++;
// Update survival time display
var seconds = Math.floor(survivalTime / 60);
scoreText.setText('Time: ' + seconds + 's');
LK.setScore(seconds);
// Increase difficulty over time
if (survivalTime % 300 === 0) {
// Every 5 seconds
gameSpeed += 0.1;
}
// Spawn attacks based on patterns
if (inBossFight) {
// More frequent and random attacks during boss fight
// Boss attacks faster when health is low
var bossAttackDelay = 20 + Math.random() * 20;
if (giantFace.health < giantFace.maxHealth * 0.3) {
bossAttackDelay = 10 + Math.random() * 10; // Much faster attacks when boss health < 30%
}
if (waveTimer >= bossAttackDelay) {
waveTimer = 0;
attackPattern = Math.floor(Math.random() * 8);
switch (attackPattern) {
case 0:
spawnLinearAttack();
break;
case 1:
spawnCircularAttack();
break;
case 2:
spawnTargetedAttack();
break;
case 3:
spawnSwordAttack();
break;
case 4:
spawnHorizontalAttack();
break;
case 5:
spawnVerticalAttack();
break;
case 6:
spawnDirectionalAttack();
break;
case 7:
spawnExplosionAttack();
break;
}
}
} else if (waveTimer >= 60 - gameSpeed * 5) {
// Normal attack patterns when not in boss fight
waveTimer = 0;
attackPattern = Math.floor(Math.random() * 8);
switch (attackPattern) {
case 0:
spawnLinearAttack();
break;
case 1:
if (survivalTime > 300) spawnCircularAttack(); // Unlock after 5 seconds
else spawnLinearAttack();
break;
case 2:
if (survivalTime > 600) spawnTargetedAttack(); // Unlock after 10 seconds
else spawnLinearAttack();
break;
case 3:
if (survivalTime > 900) spawnSwordAttack(); // Unlock after 15 seconds
else spawnLinearAttack();
break;
case 4:
if (survivalTime > 1200) spawnHorizontalAttack(); // Unlock after 20 seconds
else spawnLinearAttack();
break;
case 5:
if (survivalTime > 1500) spawnVerticalAttack(); // Unlock after 25 seconds
else spawnLinearAttack();
break;
case 6:
if (survivalTime > 1800) spawnDirectionalAttack(); // Unlock after 30 seconds
else spawnLinearAttack();
break;
case 7:
if (survivalTime > 2100) spawnExplosionAttack(); // Unlock after 35 seconds
else spawnLinearAttack();
break;
}
}
// Spawn healing items occasionally
healingTimer++;
if (healingTimer >= 600) {
// Every 10 seconds
healingTimer = 0;
spawnHealingItem();
}
// Update projectiles
for (var i = projectiles.length - 1; i >= 0; i--) {
var projectile = projectiles[i];
// Check collision with player
if (checkCollision(player, projectile)) {
takeDamage();
projectile.destroy();
projectiles.splice(i, 1);
continue;
}
// Remove projectiles that are off screen
if (projectile.x < -100 || projectile.x > 2148 || projectile.y < -100 || projectile.y > 2832) {
projectile.destroy();
projectiles.splice(i, 1);
}
}
// Update swords
for (var i = swords.length - 1; i >= 0; i--) {
var sword = swords[i];
if (sword.falling && checkCollision(player, sword)) {
takeDamage();
sword.destroy();
swords.splice(i, 1);
continue;
}
if (sword.y > arenaY + arenaHeight + 100) {
sword.destroy();
swords.splice(i, 1);
}
}
// Update horizontal projectiles
for (var i = horizontalProjectiles.length - 1; i >= 0; i--) {
var hProjectile = horizontalProjectiles[i];
if (checkCollision(player, hProjectile)) {
takeDamage();
hProjectile.destroy();
horizontalProjectiles.splice(i, 1);
continue;
}
if (hProjectile.x < arenaX - 100 || hProjectile.x > arenaX + arenaWidth + 100) {
hProjectile.destroy();
horizontalProjectiles.splice(i, 1);
}
}
// Update vertical projectiles
for (var i = verticalProjectiles.length - 1; i >= 0; i--) {
var vProjectile = verticalProjectiles[i];
if (checkCollision(player, vProjectile)) {
takeDamage();
vProjectile.destroy();
verticalProjectiles.splice(i, 1);
continue;
}
if (vProjectile.y < arenaY - 100) {
vProjectile.destroy();
verticalProjectiles.splice(i, 1);
}
}
// Update healing items
for (var i = healingItems.length - 1; i >= 0; i--) {
var healingItem = healingItems[i];
if (checkCollision(player, healingItem)) {
healPlayer();
healingItem.destroy();
healingItems.splice(i, 1);
continue;
}
if (healingItem.y > arenaY + arenaHeight + 100) {
healingItem.destroy();
healingItems.splice(i, 1);
}
}
// Update directional projectiles
for (var i = directionalProjectiles.length - 1; i >= 0; i--) {
var dProjectile = directionalProjectiles[i];
if (checkCollision(player, dProjectile)) {
takeDamage();
dProjectile.destroy();
directionalProjectiles.splice(i, 1);
continue;
}
if (dProjectile.x < arenaX - 100 || dProjectile.x > arenaX + arenaWidth + 100) {
dProjectile.destroy();
directionalProjectiles.splice(i, 1);
}
}
// Update explosion warnings
for (var i = explosionWarnings.length - 1; i >= 0; i--) {
var warning = explosionWarnings[i];
if (warning.warningTime <= 0) {
// Create explosion
var explosion = new Explosion(warning.x, warning.y);
explosions.push(explosion);
game.addChild(explosion);
warning.destroy();
explosionWarnings.splice(i, 1);
}
}
// Update explosions
for (var i = explosions.length - 1; i >= 0; i--) {
var explosion = explosions[i];
if (explosion.lifeTime > 0 && checkCollision(player, explosion)) {
takeDamage();
}
if (explosion.lifeTime <= 0) {
explosion.destroy();
explosions.splice(i, 1);
}
}
// Update damage numbers
for (var i = damageNumbers.length - 1; i >= 0; i--) {
var damageNumber = damageNumbers[i];
if (damageNumber.lifeTime <= 0 || damageNumber.y < -100) {
damageNumber.destroy();
damageNumbers.splice(i, 1);
}
}
// Update boss projectiles
for (var i = bossProjectiles.length - 1; i >= 0; i--) {
var bossProjectile = bossProjectiles[i];
// Check collision with player
if (checkCollision(player, bossProjectile)) {
takeDamage();
bossProjectile.destroy();
bossProjectiles.splice(i, 1);
continue;
}
// Remove projectiles that are off screen
if (bossProjectile.x < -100 || bossProjectile.x > 2148 || bossProjectile.y < -100 || bossProjectile.y > 2832) {
bossProjectile.destroy();
bossProjectiles.splice(i, 1);
}
}
// Play music
if (survivalTime === 1) {
LK.playMusic('battle');
}
};
Un corazón rojo. In-Game asset. 2d. High contrast
Una espada pequeña de color todo blanco. In-Game asset. 2d. High contrast. No shadows
Bola de fuego de color blanco. In-Game asset. 2d. High contrast. No shadows
Un botiquín de color verde con estrellitas. In-Game asset. 2d. High contrast. No shadows
Que la espada este alrevés
Bomba blanca. In-Game asset. 2d. High contrast. No shadows
Cara sonriente malvada de color azul similar ala cara de chara se undertale. In-Game asset. 2d. High contrast. No shadows
Bola de fuego de color blanco y con cara azul. In-Game asset. 2d. High contrast. No shadows