User prompt
okey, ahora haremos 2 cosas, primero, ha que para la opcion "check" en el boton de "ACT", una vez el jugador vuelva a la fase de combate, que desaparzca, y 2, haz que el 4to boton no haga absolutamente nada
User prompt
Please fix the bug: 'Timeout.tick error: textElement.setText is not a function' in or related to this line: 'textElement.setText(itemNames[itemIdx]);' Line Number: 1269
User prompt
Okey, ahora hagamos la función del botón "ITEM", en este botón, se van a encontrar 4 opciones, la opción 1 hará que el jugador recupere 5 de vida, la opción 2 hará que cure 2 de vida la opción 3 hará que cure 7, y la opción 4 restaurará la vida máxima del jugador, estos objetos una vez se usen una vez, la opción desaparecerá y no se podrá volver a usar 3 turnos después ↪💡 Consider importing and using the following plugins: @upit/tween.v1
User prompt
Ahora haz que el ataque cyan aparezca con mas frecuencia, cuando aparezca, que solo pueda aparecer "enemy" y no el gaster blaster, cuando el ataque cyan desaparezca, que la aparición de los gaster blaster vuelva a ser normal ↪💡 Consider importing and using the following plugins: @upit/tween.v1
User prompt
Haz que si el jugador toca laserbeam, aparezcan un "enemy" invisible justo en las mismas coordenadas que está el jugador, y que esta variación de "enemy" haga el doble de daño que uno normal (esta variación solo aparecerá si el jugador toca *laserbeam")
Code edit (1 edits merged)
Please save this source code
User prompt
Haz que si el jugador toca "laserbeam" piedra 2 de vida
User prompt
Haz que el gaster blaster quite 2 de vida al jugador SOLO si lo toca ↪💡 Consider importing and using the following plugins: @upit/tween.v1
User prompt
Sigue sin quitar vida aaaaaaaaaaaaa, ↪💡 Consider importing and using the following plugins: @upit/tween.v1
User prompt
Sabes que? Vuelve a programar todo el gaster blaster desde cero, tienes que programarlo para que funcione EXACTMAENTE IGUAL al gaster blaster original de undertale ↪💡 Consider importing and using the following plugins: @upit/tween.v1
User prompt
Please fix the bug: 'ReferenceError: angleTolerrance is not defined' in or related to this line: 'if (angleDifference <= angleTolerrance && distance <= maxRange) {' Line Number: 1532
User prompt
Ahh ..sigue sin funcionar, y ya se me acabaron las ideas...tienes alguna idea? (Hablame en español)
User prompt
Sigue sin funcionar
User prompt
Al tocar el láser, hace el camera shake indicando que hizo daño, peero no quita vida al jugador, al tocar el láser del gaster blaster, tiene que quitar 2 de vida al jugador al tocarlo ↪💡 Consider importing and using the following plugins: @upit/tween.v1
User prompt
Sigue sin funcionar...ehhh,...¿Tienes alguna idea para arreglarlo?
User prompt
El gaster blaster no hace daño al tocar el láser
User prompt
Funciona bien, peeero quiero cambiar y arreglar algunas cosas, 1: la hitbox de el gaster blaster al disparar está mal hecha, debe hacer daño si el jugador toca el láser, el jugador debe recibir 2 de daño al tocar el láser, segundo, haz que el Sprite del gaster blaster mire a la última posición que detecto del jugador, similar a como se comporta "enemy", dirigiéndose mirando al jugador a la última posición del jugador de los últimos 0.2 segundossegundos (esto solo es visual y no afecta la trayectoria de disparo del gaster blaster) ↪💡 Consider importing and using the following plugins: @upit/tween.v1
User prompt
Funciona bien, peeero quiero cambiar y arreglar algunas cosas, 1: la hitbox de el gaster blaster al disparar está mal hecha, debe hacer daño si el jugador toca el láser, el jugador debe recibir 2 de daño al tocar el láser, segundo, haz que el Sprite del gaster blaster mire a la última posición que detecto del jugador, similar a como se comporta "enemy", dirigiéndose mirando al jugador a la última posicion del jugador de hace 0.2 segundos (esto solo es visual y no afecta el disparo del gaster blaster) ↪💡 Consider importing and using the following plugins: @upit/tween.v1
User prompt
Dale, intenta que sea igual o lo más parecido a un gaster blaster de undertale ↪💡 Consider importing and using the following plugins: @upit/tween.v1
User prompt
Si porfa ↪💡 Consider importing and using the following plugins: @upit/tween.v1, @upit/storage.v1
User prompt
Haz dos cosas, edita el texto para que diga "Sans 1 ATK 1 DEF el enemigo más fácil, solo puede recibir uno de daño, no puede seguir esquivando para siempre, sigue atacando" y que aparezca el texto durante 5 segundos, desaparezca pasado ese tiempo, y vuelva a iniciar la fase de combate ↪💡 Consider importing and using the following plugins: @upit/tween.v1
User prompt
Haz que en el apartado de ACT, la opción de talk y de check estén separadas, que al darle a la opción de "check" aparezca un texto en la caja de combate que diga "Sans 1 ATK 1 DEF el enemigo más fácil, solo puede recibir uno de daño ↪💡 Consider importing and using the following plugins: @upit/tween.v1
User prompt
Dos cosas, 1: haz que la caja de combate al adaptarse a una forma rectangular,que no se vuelva más pequeña 2, facilita el movimiento por el menú de acciones, si el jugador da click una vez a un botón, que se teleporte a ese botón como ahora, pero si vuelve a tocar un botón en el que ya está, que se ejecute su acción correspondiente ↪💡 Consider importing and using the following plugins: @upit/tween.v1
User prompt
Tres cosas, 1: haz que la caja de combate al adaptarse a una forma rectangular, que mantenga las dimensiones verticalmente (que no se vuelva más pequeña) y 2, facilita el movimiento por el menú de acciones, si el jugador da click una vez a un botón, que se teleporte a ese botón como ahora, pero si vuelve a tocar un botón en el que ya está, que se ejecute su acción correspondiente ↪💡 Consider importing and using the following plugins: @upit/tween.v1
User prompt
Hagamos esto, en el juego original, el menú tanto de ataque como de opciones se centra en la caja de combate, está misma cambia de dimensiones para tener forma rectangular, dentro de esta misma se ven todas las opciones, tanto la de ACT, como la de ITEM y la de MERCY (haremos ITEM y MERCY en un momento), haz que la caja de combate con una transición limpia cambie su forma a una rectangular, y que en esa se centren los botones de check y talk, una vez se vuelva a la fase de combate, la caja de combate regresará a su forma original ↪💡 Consider importing and using the following plugins: @upit/tween.v1
/****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
/****
* Classes
****/
var Character = Container.expand(function () {
var self = Container.call(this);
var characterGraphics = self.attachAsset('character', {
anchorX: 0.5,
anchorY: 0.5
});
// Target position for smooth movement
self.targetX = 0;
self.targetY = 0;
// Movement speed factor for smooth following
self.followSpeed = 0.15;
// Square boundary limits (centered square area)
var squareSize = 1200;
var centerX = 2048 / 2;
var centerY = 2732 / 2;
self.boundaryLeft = centerX - squareSize / 2;
self.boundaryRight = centerX + squareSize / 2;
self.boundaryTop = centerY - squareSize / 2;
self.boundaryBottom = centerY + squareSize / 2;
var characterRadius = 60;
self.update = function () {
// Calculate next position
var deltaX = self.targetX - self.x;
var deltaY = self.targetY - self.y;
var nextX = self.x + deltaX * self.followSpeed;
var nextY = self.y + deltaY * self.followSpeed;
// Only apply boundary constraints during playing state, not during menu selection
if (gameState === 'playing') {
// Check wall collisions and prevent movement beyond boundaries
if (nextX - characterRadius < self.boundaryLeft) {
nextX = self.boundaryLeft + characterRadius;
}
if (nextX + characterRadius > self.boundaryRight) {
nextX = self.boundaryRight - characterRadius;
}
if (nextY - characterRadius < self.boundaryTop) {
nextY = self.boundaryTop + characterRadius;
}
if (nextY + characterRadius > self.boundaryBottom) {
nextY = self.boundaryBottom - characterRadius;
}
}
// Apply the constrained position
self.x = nextX;
self.y = nextY;
};
// Method to set target position
self.setTarget = function (x, y) {
self.targetX = x;
self.targetY = y;
};
return self;
});
var CyanAreaAttack = Container.expand(function () {
var self = Container.call(this);
var attackGraphics = self.attachAsset('cyanAreaAttack', {
anchorX: 0,
anchorY: 0.5
});
self.duration = 0;
self.maxDuration = 3000; // 3 seconds
self.isActive = false;
self.startX = -600; // Start from left side of combat box
self.endX = 1200; // End at right side of combat box
self.speed = 5 + difficultyLevel * 0.5; // Movement speed increases with difficulty
// Start with low alpha and fade in
attackGraphics.alpha = 0;
// Position at left edge initially
self.x = self.startX;
self.update = function () {
self.duration += 16.67;
// Move from left to right across combat box
if (self.duration > 500 && self.x < self.endX + 600) {
// Move until completely off screen
self.x += self.speed;
}
// Fade in effect at the beginning
if (self.duration < 500) {
attackGraphics.alpha = self.duration / 500 * 0.6; // Max alpha of 0.6
} else if (self.x < self.endX + 600) {
// Stay visible and active while crossing combat box
attackGraphics.alpha = 0.6;
self.isActive = true;
} else {
// Mark as finished when completely crossed
self.isActive = false;
attackGraphics.alpha = 0;
}
};
return self;
});
var Enemy = Container.expand(function () {
var self = Container.call(this);
var enemyGraphics = self.attachAsset('enemy', {
anchorX: 0.5,
anchorY: 0.5
});
self.speed = 8 + difficultyLevel * 0.3;
self.targetX = 0;
self.targetY = 0;
self.isInView = false; // Track if enemy is in player's view
self.positionLocked = false; // Track if position is locked
self.lockedX = 0; // Locked X position
self.lockedY = 0; // Locked Y position
self.update = function () {
// Check if enemy is in view (within combat box boundaries)
var wasInView = self.isInView;
self.isInView = self.x >= boundaryLeft && self.x <= boundaryRight && self.y >= boundaryTop && self.y <= boundaryBottom;
// Lock position when entering view for the first time
if (!wasInView && self.isInView && !self.positionLocked) {
self.positionLocked = true;
self.lockedX = self.x;
self.lockedY = self.y;
}
// If direction is locked, continue moving in that direction
if (self.positionLocked) {
var deltaX = self.targetX - self.lockedX;
var deltaY = self.targetY - self.lockedY;
// Continue moving in the same direction as when we locked
if (Math.abs(deltaX) > 5) {
self.x += deltaX > 0 ? self.speed : -self.speed;
}
if (Math.abs(deltaY) > 5) {
self.y += deltaY > 0 ? self.speed : -self.speed;
}
} else {
// Follow player until we appear in view
var deltaX = self.targetX - self.x;
var deltaY = self.targetY - self.y;
if (Math.abs(deltaX) > 5) {
self.x += deltaX > 0 ? self.speed : -self.speed;
}
if (Math.abs(deltaY) > 5) {
self.y += deltaY > 0 ? self.speed : -self.speed;
}
}
// Add rotation effect (visual only, doesn't affect movement)
enemyGraphics.rotation += 0.1;
};
self.setTarget = function (x, y) {
self.targetX = x;
self.targetY = y;
};
return self;
});
var LaserAttack = Container.expand(function () {
var self = Container.call(this);
var chargerGraphics = self.attachAsset('laserCharger', {
anchorX: 0.5,
anchorY: 0.5
});
self.targetX = 0;
self.targetY = 0;
self.state = 'charging'; // 'charging', 'firing', 'finished'
self.chargeTime = 0;
self.fireTime = 0;
self.laserBeam = null;
self.hasDealtDamage = false;
self.update = function () {
if (self.state === 'charging') {
self.chargeTime += 16.67;
// Charging animation - pulsing effect
chargerGraphics.alpha = 0.5 + 0.5 * Math.sin(self.chargeTime * 0.01);
if (self.chargeTime >= 1200) {
// 1.2 seconds
self.state = 'firing';
self.fireLaser();
}
} else if (self.state === 'firing') {
self.fireTime += 16.67;
if (self.fireTime >= 3000) {
// 3 seconds
self.state = 'finished';
}
}
};
self.fireLaser = function () {
// Hide charger
chargerGraphics.alpha = 0;
// Create laser beam
self.laserBeam = self.addChild(LK.getAsset('laserAttack', {
anchorX: 0,
anchorY: 0.5
}));
// Position laser to fire across the screen toward target
var angle = Math.atan2(self.targetY - self.y, self.targetX - self.x);
self.laserBeam.rotation = angle;
self.laserBeam.x = 0;
self.laserBeam.y = 0;
};
self.setTarget = function (x, y) {
self.targetX = x;
self.targetY = y;
};
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0x000000
});
/****
* Game Code
****/
// Game state management
var gameState = 'menu'; // 'menu', 'playing', 'menuSelection', or 'actSubmenu'
var debugMode = false; // Debug mode for invulnerability
var holdTimer = 0; // Timer for tracking button hold duration
var menuSelectionTimer = 0; // Timer for menu selection phase
var selectedButtonIndex = 0; // Current selected button (0=FIGHT, 1=ACT, 2=ITEM, 3=MERCY)
var isInMenuSelection = false; // Flag to track if we're in menu selection mode
var actSubmenuIndex = 0; // Current selected ACT submenu option (0=Check, 1=Talk)
var talkEffectActive = false; // Flag to track if Talk effect is active for current turn
var attackSpeedModifier = 1.0; // Speed modifier for attacks (0.95 when Talk is used)
var menuContainer = game.addChild(new Container());
// Create menu elements
var logo = menuContainer.addChild(LK.getAsset('breathingSprite3', {
anchorX: 0.5,
anchorY: 0.5
}));
logo.x = 2048 / 2;
logo.y = 2732 / 2 - 250;
// Create play button
var playButton = menuContainer.addChild(LK.getAsset('playButton', {
anchorX: 0.5,
anchorY: 0.5
}));
playButton.x = 2048 / 2;
playButton.y = 2732 / 2 + 100;
// Play button text
var playText = menuContainer.addChild(new Text2('JUGAR', {
size: 70,
fill: 0xFFFFFF
}));
playText.anchor.set(0.5, 0.5);
playText.x = playButton.x;
playText.y = playButton.y;
// Debug mode text (initially hidden)
var debugText = new Text2('Debug Mode: True', {
size: 40,
fill: 0xFFFFFF
});
debugText.anchor.set(0, 0);
debugText.x = 120; // Avoid the top-left menu icon area
debugText.y = 50;
debugText.visible = false;
game.addChild(debugText);
// Play button interaction
playButton.down = function (x, y, obj) {
// Reset hold timer and start counting
holdTimer = 0;
// Flash button on press
tween(playButton, {
tint: 0xffffff
}, {
duration: 100,
onFinish: function onFinish() {
tween(playButton, {
tint: 0xffffff
}, {
duration: 100
});
}
});
};
playButton.up = function (x, y, obj) {
// Check if button was held for 7 seconds (7000ms)
if (holdTimer >= 7000) {
debugMode = true;
console.log("Debug mode activated!");
}
// Start the game
gameState = 'playing';
menuContainer.visible = false;
// Show debug text if debug mode is active
if (debugMode) {
debugText.visible = true;
}
// Show game elements
leftWall.visible = true;
rightWall.visible = true;
topWall.visible = true;
bottomWall.visible = true;
character.visible = true;
healthBarBackground.visible = true;
healthLabel.visible = true;
breathingSprite1.visible = true;
breathingSprite2.visible = true;
for (var i = 0; i < healthSegments.length; i++) {
healthSegments[i].visible = true;
}
// Show menu buttons
for (var j = 0; j < menuButtons.length; j++) {
menuButtons[j].visible = true;
}
};
// Create visual boundary walls
var squareSize = 1200;
var centerX = 2048 / 2;
var centerY = 2732 / 2;
var boundaryLeft = centerX - squareSize / 2;
var boundaryRight = centerX + squareSize / 2;
var boundaryTop = centerY - squareSize / 2;
var boundaryBottom = centerY + squareSize / 2;
// Left wall
var leftWall = game.addChild(LK.getAsset('wall', {
anchorX: 0.5,
anchorY: 0.5
}));
leftWall.x = boundaryLeft;
leftWall.y = centerY;
leftWall.visible = false;
// Right wall
var rightWall = game.addChild(LK.getAsset('wall', {
anchorX: 0.5,
anchorY: 0.5
}));
rightWall.x = boundaryRight;
rightWall.y = centerY;
rightWall.visible = false;
// Top wall
var topWall = game.addChild(LK.getAsset('wallHorizontal', {
anchorX: 0.5,
anchorY: 0.5
}));
topWall.x = centerX;
topWall.y = boundaryTop;
topWall.visible = false;
// Bottom wall
var bottomWall = game.addChild(LK.getAsset('wallHorizontal', {
anchorX: 0.5,
anchorY: 0.5
}));
bottomWall.x = centerX;
bottomWall.y = boundaryBottom;
bottomWall.visible = false;
// Create the character at center of screen
var character = game.addChild(new Character());
character.x = 2048 / 2;
character.y = 2732 / 2;
character.visible = false;
// Set initial target position to character's starting position
character.setTarget(character.x, character.y);
// Handle mouse/touch movement
game.move = function (x, y, obj) {
if (gameState === 'playing') {
// Set character target to mouse position
character.setTarget(x, y);
} else if (gameState === 'menuSelection') {
// During menu selection, check which button was clicked
var buttonClicked = -1;
for (var i = 0; i < menuButtons.length; i++) {
var button = menuButtons[i];
var buttonLeft = button.x - buttonWidth / 2;
var buttonRight = button.x + buttonWidth / 2;
var buttonTop = button.y - buttonHeight / 2;
var buttonBottom = button.y + buttonHeight / 2;
if (x >= buttonLeft && x <= buttonRight && y >= buttonTop && y <= buttonBottom) {
buttonClicked = i;
break;
}
}
// If a button was clicked, move character to it or activate it
if (buttonClicked !== -1) {
if (buttonClicked === selectedButtonIndex) {
// Same button clicked - activate it
activateMenuButton(buttonClicked);
} else {
// Different button - teleport character to it
selectedButtonIndex = buttonClicked;
var targetButton = menuButtons[selectedButtonIndex];
character.setTarget(targetButton.x, targetButton.y);
character.x = targetButton.x;
character.y = targetButton.y;
}
}
} else if (gameState === 'actSubmenu') {
// During ACT submenu, check which submenu button was clicked
var actButtonClicked = -1;
for (var j = 0; j < actSubmenuButtons.length; j++) {
var actButton = actSubmenuButtons[j];
var actButtonLeft = actButton.x - buttonWidth / 2;
var actButtonRight = actButton.x + buttonWidth / 2;
var actButtonTop = actButton.y - buttonHeight / 2;
var actButtonBottom = actButton.y + buttonHeight / 2;
if (x >= actButtonLeft && x <= actButtonRight && y >= actButtonTop && y <= actButtonBottom) {
actButtonClicked = j;
break;
}
}
// If an ACT button was clicked, move character to it or activate it
if (actButtonClicked !== -1) {
if (actButtonClicked === actSubmenuIndex) {
// Same button clicked - activate it
activateActSubmenuButton(actButtonClicked);
} else {
// Different button - teleport character to it
actSubmenuIndex = actButtonClicked;
var targetActButton = actSubmenuButtons[actSubmenuIndex];
character.setTarget(targetActButton.x, targetActButton.y);
character.x = targetActButton.x;
character.y = targetActButton.y;
}
}
}
};
// Handle mouse/touch down - also update position immediately
game.down = function (x, y, obj) {
if (gameState === 'playing') {
// Set character target to touch/click position
character.setTarget(x, y);
}
};
// Create health bar interface below the square
var healthBarY = boundaryBottom + 100;
// Health bar background
var healthBarBackground = game.addChild(LK.getAsset('healthBarBg', {
anchorX: 0.5,
anchorY: 0.5
}));
healthBarBackground.x = centerX;
healthBarBackground.y = healthBarY;
healthBarBackground.visible = false;
// Health bar segments (10 parts)
var healthSegments = [];
var segmentWidth = 36;
var segmentSpacing = 2;
var totalWidth = (segmentWidth + segmentSpacing) * 10 - segmentSpacing;
var startX = centerX - totalWidth / 2;
for (var i = 0; i < 10; i++) {
var segment = game.addChild(LK.getAsset('healthSegment', {
anchorX: 0,
anchorY: 0.5
}));
segment.x = startX + i * (segmentWidth + segmentSpacing);
segment.y = healthBarY;
segment.tint = 0xffff00; // Yellow by default
segment.visible = false;
healthSegments.push(segment);
}
// Health bar label
var healthLabel = new Text2('VIDA', {
size: 30,
fill: 0xFFFFFF
});
healthLabel.anchor.set(0.5, 1);
healthLabel.x = centerX;
healthLabel.y = healthBarY - 30;
healthLabel.visible = false;
game.addChild(healthLabel);
// Health variables
var maxHealth = 100;
var currentHealth = 100;
// Invulnerability system
var isInvulnerable = false;
var invulnerabilityDuration = 3000; // 3 seconds
// Function to update health bar segments
function updateHealthBar() {
var healthSegmentsRemaining = Math.ceil(currentHealth / 10);
for (var i = 0; i < 10; i++) {
if (i < healthSegmentsRemaining) {
healthSegments[i].tint = 0xffff00; // Yellow for remaining health
} else {
healthSegments[i].tint = 0xff0000; // Red for lost health
}
}
}
// Initial health bar update
updateHealthBar();
// Create breathing sprites above combat box
var breathingSprite1 = game.addChild(LK.getAsset('breathingSprite1', {
anchorX: 0.5,
anchorY: 0.5
}));
breathingSprite1.x = centerX;
breathingSprite1.y = boundaryTop - 300;
breathingSprite1.visible = false;
var breathingSprite2 = game.addChild(LK.getAsset('breathingSprite2', {
anchorX: 0.5,
anchorY: 0.5
}));
breathingSprite2.x = centerX;
// Position sprite2 directly on top of sprite1 to form a tower
breathingSprite2.y = breathingSprite1.y - (breathingSprite1.height / 2 + breathingSprite2.height / 2) + 200;
breathingSprite2.visible = false;
// Start breathing animation for both sprites
function startBreathingAnimation() {
// Animate first sprite
tween(breathingSprite1, {
y: breathingSprite1.y - 15
}, {
duration: 1500,
easing: tween.easeInOut,
onFinish: function onFinish() {
tween(breathingSprite1, {
y: breathingSprite1.y + 15
}, {
duration: 1500,
easing: tween.easeInOut,
onFinish: startBreathingAnimation
});
}
});
// Animate second sprite with slight delay
LK.setTimeout(function () {
tween(breathingSprite2, {
y: breathingSprite2.y - 12
}, {
duration: 1600,
easing: tween.easeInOut,
onFinish: function onFinish() {
tween(breathingSprite2, {
y: breathingSprite2.y + 12
}, {
duration: 1600,
easing: tween.easeInOut
});
}
});
}, 200);
}
startBreathingAnimation();
// Player position tracking for laser attacks and enemy targeting
var playerPositions = [];
var maxStoredPositions = 12; // Store 12 positions for ~0.2 seconds at 60fps
// Enemy management
var enemies = [];
// Laser attack management
var laserAttacks = [];
// Cyan area attack management
var cyanAreaAttacks = [];
// Attack pattern system
var attackPatternTimer = 0;
var nextAttackTime = 1000; // First attack after 1 second
var difficultyLevel = 1;
var difficultyTimer = 0;
var difficultyIncreaseInterval = 30000; // Increase difficulty every 30 seconds
// Player movement tracking
var lastPlayerX = character.x;
var lastPlayerY = character.y;
var isPlayerMoving = false;
// Function to spawn enemy at random position outside the square
function spawnEnemy() {
// Capture player position from 0.2 seconds ago (12 ticks at 60fps)
var targetPlayerPos = {
x: character.x,
y: character.y
};
if (playerPositions.length > 0) {
// Use oldest stored position as it represents roughly 0.2 seconds ago
targetPlayerPos = playerPositions[0];
}
var enemy = new Enemy();
// Spawn at random position outside the boundary square
var side = Math.floor(Math.random() * 4);
switch (side) {
case 0:
// Top
enemy.x = boundaryLeft + Math.random() * squareSize;
enemy.y = boundaryTop - 100;
break;
case 1:
// Right
enemy.x = boundaryRight + 100;
enemy.y = boundaryTop + Math.random() * squareSize;
break;
case 2:
// Bottom
enemy.x = boundaryLeft + Math.random() * squareSize;
enemy.y = boundaryBottom + 100;
break;
case 3:
// Left
enemy.x = boundaryLeft - 100;
enemy.y = boundaryTop + Math.random() * squareSize;
break;
}
enemy.setTarget(targetPlayerPos.x, targetPlayerPos.y);
enemies.push(enemy);
game.addChild(enemy);
}
// Function to spawn laser attack targeting player's previous positions
function spawnLaserAttack() {
if (playerPositions.length === 0) return;
// Choose a random previous position to target
var targetIndex = Math.floor(Math.random() * playerPositions.length);
var targetPos = playerPositions[targetIndex];
var laserAttack = new LaserAttack();
// Position laser attack outside the combat box
var side = Math.floor(Math.random() * 4);
switch (side) {
case 0:
// Top
laserAttack.x = boundaryLeft + Math.random() * squareSize;
laserAttack.y = boundaryTop - 150;
break;
case 1:
// Right
laserAttack.x = boundaryRight + 150;
laserAttack.y = boundaryTop + Math.random() * squareSize;
break;
case 2:
// Bottom
laserAttack.x = boundaryLeft + Math.random() * squareSize;
laserAttack.y = boundaryBottom + 150;
break;
case 3:
// Left
laserAttack.x = boundaryLeft - 150;
laserAttack.y = boundaryTop + Math.random() * squareSize;
break;
}
laserAttack.setTarget(targetPos.x, targetPos.y);
laserAttacks.push(laserAttack);
game.addChild(laserAttack);
}
// Function to spawn cyan area attack covering the entire combat box
function spawnCyanAreaAttack() {
var cyanAttack = new CyanAreaAttack();
cyanAttack.x = boundaryLeft - 600; // Start from left side
cyanAttack.y = centerY; // Center vertically in combat box
cyanAreaAttacks.push(cyanAttack);
game.addChild(cyanAttack);
}
// Function to generate random attack patterns
function generateAttackPattern() {
// Base probability for each attack type (adjusted by difficulty)
var enemyChance = 0.4 + difficultyLevel * 0.1;
var laserChance = 0.3 + difficultyLevel * 0.05;
var cyanChance = 0.2 + difficultyLevel * 0.02;
// Ensure we don't overwhelm the player
if (enemies.length > 5) enemyChance = 0;
if (laserAttacks.length > 2) laserChance = 0;
if (cyanAreaAttacks.length > 1) cyanChance = 0;
// Random attack selection
var attackRoll = Math.random();
var attacksSpawned = 0;
// Spawn enemies
if (attackRoll < enemyChance) {
var enemyCount = Math.min(1 + Math.floor(Math.random() * difficultyLevel), 3);
for (var i = 0; i < enemyCount; i++) {
spawnEnemy();
attacksSpawned++;
}
}
// Spawn laser attack (only if player has moved)
if (Math.random() < laserChance && playerPositions.length > 0) {
spawnLaserAttack();
attacksSpawned++;
}
// Spawn cyan area attack (less frequent)
if (Math.random() < cyanChance) {
// Check if player has space to dodge
var playerNearEdge = character.x < boundaryLeft + 300 || character.x > boundaryRight - 300;
if (!playerNearEdge || Math.random() < 0.3) {
spawnCyanAreaAttack();
attacksSpawned++;
}
}
// Calculate next attack time based on difficulty and current attacks
var baseInterval = 2000 - difficultyLevel * 100; // Decrease time between patterns
var randomVariance = Math.random() * 1000;
// Apply talk effect speed modifier (make attacks slower)
if (talkEffectActive) {
baseInterval = baseInterval / attackSpeedModifier; // Increase interval by 5%
}
// Ensure minimum time between attacks for fairness
nextAttackTime = Math.max(800, baseInterval - randomVariance);
// If no attacks were spawned, try again sooner
if (attacksSpawned === 0) {
nextAttackTime = 500;
}
}
// Function to damage player
function damagePlayer(damage) {
if (debugMode) {
console.log("Debug: Damage blocked - " + (damage || 10));
return; // No damage in debug mode
}
if (damage === undefined) damage = 10; // Default 10% damage
currentHealth = Math.max(0, currentHealth - damage);
updateHealthBar();
// Camera shake effect for 1.2 seconds
var originalX = game.x;
var originalY = game.y;
var shakeIntensity = 10;
var shakeDuration = 1200; // 1.2 seconds
var shakeStartTime = Date.now();
function shakeCamera() {
var elapsed = Date.now() - shakeStartTime;
if (elapsed < shakeDuration) {
game.x = originalX + (Math.random() - 0.5) * shakeIntensity;
game.y = originalY + (Math.random() - 0.5) * shakeIntensity;
LK.setTimeout(shakeCamera, 16); // ~60fps
} else {
game.x = originalX;
game.y = originalY;
}
}
shakeCamera();
// Activate invulnerability
isInvulnerable = true;
// Flash character red when damaged and add blinking effect during invulnerability
tween(character, {
tint: 0xff0000
}, {
duration: 200,
onFinish: function onFinish() {
tween(character, {
tint: 0xffffff
}, {
duration: 200,
onFinish: function onFinish() {
// Start blinking effect for remaining invulnerability time
var blinkCount = 0;
var maxBlinks = 12; // Blink for about 2.4 seconds (remaining invulnerability time)
function blink() {
if (blinkCount < maxBlinks && isInvulnerable) {
character.alpha = character.alpha === 1 ? 0.3 : 1;
blinkCount++;
LK.setTimeout(blink, 200);
} else {
character.alpha = 1; // Ensure character is fully visible
}
}
blink();
}
});
}
});
// Set timeout to end invulnerability
LK.setTimeout(function () {
isInvulnerable = false;
character.alpha = 1; // Ensure character is fully visible
}, invulnerabilityDuration);
// Check game over - when all 10 segments are red (health is 0)
if (currentHealth <= 0) {
LK.showGameOver();
}
}
// Function to activate menu button based on index
function activateMenuButton(buttonIndex) {
switch (buttonIndex) {
case 0:
// FIGHT
console.log("FIGHT activated!");
// Sans dodge animation - random left or right movement
var dodgeDirection = Math.random() < 0.5 ? -1 : 1; // -1 for left, 1 for right
var dodgeDistance = 200; // Distance to dodge
var originalSprite1X = breathingSprite1.x;
var originalSprite2X = breathingSprite2.x;
var targetSprite1X = breathingSprite1.x + dodgeDistance * dodgeDirection;
var targetSprite2X = breathingSprite2.x + dodgeDistance * dodgeDirection;
// Animate both breathing sprites to dodge
tween(breathingSprite1, {
x: targetSprite1X
}, {
duration: 200,
easing: tween.easeOut
});
tween(breathingSprite2, {
x: targetSprite2X
}, {
duration: 200,
easing: tween.easeOut,
onFinish: function onFinish() {
// Return to original position after 0.75 seconds
LK.setTimeout(function () {
tween(breathingSprite1, {
x: originalSprite1X
}, {
duration: 300,
easing: tween.easeInOut
});
tween(breathingSprite2, {
x: originalSprite2X
}, {
duration: 300,
easing: tween.easeInOut,
onFinish: function onFinish() {
// Return to combat after the complete animation sequence
exitMenuSelection();
}
});
}, 750);
}
});
break;
case 1:
// ACT
console.log("ACT activated!");
// Transform combat box to rectangular shape
transformCombatBoxToRectangle();
// Enter ACT submenu
gameState = 'actSubmenu';
actSubmenuIndex = 0; // Start at Check option
// Reposition ACT submenu buttons to center of rectangular combat box
LK.setTimeout(function () {
// Position buttons in center of the rectangular combat box
actSubmenuButtons[0].x = centerX - 150; // Check button
actSubmenuButtons[0].y = centerY;
actSubmenuButtons[1].x = centerX + 150; // Talk button
actSubmenuButtons[1].y = centerY;
// Update text positions to match buttons
actSubmenuContainer.children[2].x = actSubmenuButtons[0].x; // Check text
actSubmenuContainer.children[2].y = actSubmenuButtons[0].y;
actSubmenuContainer.children[3].x = actSubmenuButtons[1].x; // Talk text
actSubmenuContainer.children[3].y = actSubmenuButtons[1].y;
// Show ACT submenu buttons
for (var k = 0; k < actSubmenuButtons.length; k++) {
actSubmenuButtons[k].visible = true;
actSubmenuContainer.children[k + actSubmenuButtons.length].visible = true; // Show text labels
}
// Move character to Check button
var targetActButton = actSubmenuButtons[actSubmenuIndex];
character.setTarget(targetActButton.x, targetActButton.y);
character.x = targetActButton.x;
character.y = targetActButton.y;
}, 300); // Wait for combat box transformation to complete
break;
case 2:
// ITEM
console.log("ITEM activated!");
// TODO: Implement ITEM mechanics
break;
case 3:
// MERCY
console.log("MERCY activated!");
// TODO: Implement MERCY mechanics
break;
}
}
// Function to activate ACT submenu button
function activateActSubmenuButton(buttonIndex) {
switch (buttonIndex) {
case 0:
// Check
console.log("Check activated!");
// Hide ACT submenu
for (var m = 0; m < actSubmenuButtons.length; m++) {
actSubmenuButtons[m].visible = false;
actSubmenuContainer.children[m + actSubmenuButtons.length].visible = false; // Hide text labels
}
// Show Sans statistics (simulate showing stats briefly)
console.log("Sans - ATK: 1 DEF: 1 HP: ??? - The easiest enemy. Can only deal 1 damage.");
// Return to combat after showing stats
LK.setTimeout(function () {
exitMenuSelection();
}, 1500); // Show stats for 1.5 seconds
break;
case 1:
// Talk
console.log("Talk activated!");
// Activate talk effect for this turn only
talkEffectActive = true;
attackSpeedModifier = 0.95; // 5% slower attacks
console.log("You talked to Sans. His attacks will be 5% slower this turn.");
// Hide ACT submenu
for (var n = 0; n < actSubmenuButtons.length; n++) {
actSubmenuButtons[n].visible = false;
actSubmenuContainer.children[n + actSubmenuButtons.length].visible = false; // Hide text labels
}
// Return to combat
exitMenuSelection();
break;
}
}
// Function to transform combat box to rectangular shape for menus
function transformCombatBoxToRectangle() {
// New rectangular dimensions
var rectWidth = 800;
var rectHeight = 200;
var rectCenterX = centerX;
var rectCenterY = centerY;
// Calculate new wall positions
var rectLeft = rectCenterX - rectWidth / 2;
var rectRight = rectCenterX + rectWidth / 2;
var rectTop = rectCenterY - rectHeight / 2;
var rectBottom = rectCenterY + rectHeight / 2;
// Animate walls to new rectangular positions
tween(leftWall, {
x: rectLeft,
y: rectCenterY,
scaleY: rectHeight / 1200
}, {
duration: 300,
easing: tween.easeInOut
});
tween(rightWall, {
x: rectRight,
y: rectCenterY,
scaleY: rectHeight / 1200
}, {
duration: 300,
easing: tween.easeInOut
});
tween(topWall, {
x: rectCenterX,
y: rectTop,
scaleX: rectWidth / 1200
}, {
duration: 300,
easing: tween.easeInOut
});
tween(bottomWall, {
x: rectCenterX,
y: rectBottom,
scaleX: rectWidth / 1200
}, {
duration: 300,
easing: tween.easeInOut
});
// Update character boundaries for rectangular area
character.boundaryLeft = rectLeft;
character.boundaryRight = rectRight;
character.boundaryTop = rectTop;
character.boundaryBottom = rectBottom;
}
// Function to restore combat box to original square shape
function restoreCombatBoxToSquare() {
// Animate walls back to original square positions
tween(leftWall, {
x: boundaryLeft,
y: centerY,
scaleY: 1
}, {
duration: 300,
easing: tween.easeInOut
});
tween(rightWall, {
x: boundaryRight,
y: centerY,
scaleY: 1
}, {
duration: 300,
easing: tween.easeInOut
});
tween(topWall, {
x: centerX,
y: boundaryTop,
scaleX: 1
}, {
duration: 300,
easing: tween.easeInOut
});
tween(bottomWall, {
x: centerX,
y: boundaryBottom,
scaleX: 1
}, {
duration: 300,
easing: tween.easeInOut
});
// Restore character boundaries to original square
character.boundaryLeft = boundaryLeft;
character.boundaryRight = boundaryRight;
character.boundaryTop = boundaryTop;
character.boundaryBottom = boundaryBottom;
}
// Function to exit menu selection and return to combat
function exitMenuSelection() {
// Start restoring combat box to square shape
restoreCombatBoxToSquare();
gameState = 'playing';
isInMenuSelection = false;
menuSelectionTimer = 0;
// Reset talk effect for next turn
talkEffectActive = false;
attackSpeedModifier = 1.0;
// Hide ACT submenu if it was visible
for (var p = 0; p < actSubmenuButtons.length; p++) {
actSubmenuButtons[p].visible = false;
if (actSubmenuContainer.children[p + actSubmenuButtons.length]) {
actSubmenuContainer.children[p + actSubmenuButtons.length].visible = false;
}
}
// Move character back to center of combat box
character.setTarget(centerX, centerY);
}
// Create menu below combat box
var menuY = boundaryBottom + 200;
var gameMenuContainer = game.addChild(new Container());
// Create 4 menu buttons as rectangles
var buttonWidth = 300;
var buttonHeight = 120;
var buttonSpacing = 60;
var totalMenuWidth = buttonWidth * 4 + buttonSpacing * 3;
var menuStartX = centerX - totalMenuWidth / 2;
var menuButtons = [];
var buttonAssets = ['menuButton1', 'menuButton2', 'menuButton3', 'menuButton4'];
for (var b = 0; b < 4; b++) {
var button = gameMenuContainer.addChild(LK.getAsset(buttonAssets[b], {
anchorX: 0.5,
anchorY: 0.5
}));
button.x = menuStartX + b * (buttonWidth + buttonSpacing) + buttonWidth / 2;
button.y = menuY;
button.visible = false; // Hide initially
menuButtons.push(button);
}
// Create ACT submenu elements
var actSubmenuContainer = game.addChild(new Container());
var actSubmenuButtons = [];
var actSubmenuY = menuY + 150;
var actButtonLabels = ['Check', 'Talk'];
// Create Check and Talk buttons for ACT submenu
for (var actB = 0; actB < 2; actB++) {
var actButton = actSubmenuContainer.addChild(LK.getAsset('menuButton2', {
anchorX: 0.5,
anchorY: 0.5
}));
actButton.x = centerX - 200 + actB * 400; // Position side by side
actButton.y = actSubmenuY;
actButton.visible = false;
actSubmenuButtons.push(actButton);
// Add text labels for ACT buttons
var actButtonText = actSubmenuContainer.addChild(new Text2(actButtonLabels[actB], {
size: 40,
fill: 0xFFFFFF
}));
actButtonText.anchor.set(0.5, 0.5);
actButtonText.x = actButton.x;
actButtonText.y = actButton.y;
actButtonText.visible = false;
}
// Main game update loop
game.update = function () {
// Update hold timer when in menu state (increment by ~16.67ms per frame at 60fps)
if (gameState === 'menu') {
holdTimer += 16.67;
}
// Handle menu selection timer - activate menu selection after 45 seconds
if (gameState === 'playing' && !isInMenuSelection) {
menuSelectionTimer += 16.67;
if (menuSelectionTimer >= 45000) {
// 45 seconds
// Enter menu selection mode
gameState = 'menuSelection';
isInMenuSelection = true;
selectedButtonIndex = 0; // Start at FIGHT button
// Move character to FIGHT button position
var targetButton = menuButtons[selectedButtonIndex];
character.setTarget(targetButton.x, targetButton.y);
character.x = targetButton.x;
character.y = targetButton.y;
// Reset timer for next menu cycle
menuSelectionTimer = 0;
console.log("Menu selection activated! Click buttons to navigate or activate.");
}
}
// Only update game logic when playing (not in menu or menu selection)
if (gameState !== 'playing') return;
// Track player movement
var deltaX = character.x - lastPlayerX;
var deltaY = character.y - lastPlayerY;
var movementThreshold = 1; // Minimum movement to consider as "moving"
isPlayerMoving = Math.abs(deltaX) > movementThreshold || Math.abs(deltaY) > movementThreshold;
lastPlayerX = character.x;
lastPlayerY = character.y;
// Track player positions for laser attacks
if (LK.ticks % 10 === 0) {
// Store position every 10 ticks
playerPositions.push({
x: character.x,
y: character.y
});
if (playerPositions.length > maxStoredPositions) {
playerPositions.shift(); // Remove oldest position
}
}
// Attack pattern generation
if (gameState === 'playing') {
attackPatternTimer += 16.67;
if (attackPatternTimer >= nextAttackTime) {
generateAttackPattern();
attackPatternTimer = 0;
}
}
// Difficulty progression
if (gameState === 'playing') {
difficultyTimer += 16.67;
if (difficultyTimer >= difficultyIncreaseInterval) {
difficultyLevel = Math.min(difficultyLevel + 1, 5); // Max difficulty level 5
difficultyTimer = 0;
}
}
// Update cyan area attacks and check collisions
for (var k = cyanAreaAttacks.length - 1; k >= 0; k--) {
var cyanAttack = cyanAreaAttacks[k];
// Check collision with player only if player is moving and attack is active
if (cyanAttack.isActive && cyanAttack.intersects(character) && isPlayerMoving && !isInvulnerable) {
damagePlayer(10); // 10% damage
}
// Remove finished cyan area attacks when they've crossed the combat box or are off-screen
var isOffScreen = cyanAttack.x >= boundaryRight + 600 || cyanAttack.x <= boundaryLeft - 600;
if (cyanAttack.x >= cyanAttack.endX + 600 || isOffScreen) {
cyanAttack.destroy();
cyanAreaAttacks.splice(k, 1);
}
}
// Update laser attacks and check collisions
for (var j = laserAttacks.length - 1; j >= 0; j--) {
var laserAttack = laserAttacks[j];
// Check collision with player during firing state
if (laserAttack.state === 'firing' && laserAttack.laserBeam && !laserAttack.hasDealtDamage) {
// Calculate if character is actually intersecting with the laser beam
var characterBounds = {
left: character.x - 60,
right: character.x + 60,
top: character.y - 60,
bottom: character.y + 60
};
var laserBounds = {
left: laserAttack.x + laserAttack.laserBeam.x,
right: laserAttack.x + laserAttack.laserBeam.x + laserAttack.laserBeam.width,
top: laserAttack.y + laserAttack.laserBeam.y - 20,
bottom: laserAttack.y + laserAttack.laserBeam.y + 20
};
// Check if character bounds overlap with laser bounds
var isIntersecting = characterBounds.right > laserBounds.left && characterBounds.left < laserBounds.right && characterBounds.bottom > laserBounds.top && characterBounds.top < laserBounds.bottom;
if (isIntersecting && !isInvulnerable) {
damagePlayer(20); // 20% damage
laserAttack.hasDealtDamage = true;
}
}
// Remove finished laser attacks
if (laserAttack.state === 'finished') {
laserAttack.destroy();
laserAttacks.splice(j, 1);
}
}
// Update enemies and check collisions
for (var i = enemies.length - 1; i >= 0; i--) {
var enemy = enemies[i];
// Update enemy target to current player position if not locked
if (!enemy.positionLocked) {
enemy.setTarget(character.x, character.y);
}
// Check if enemy is outside visible area and remove it
var isOutOfBounds = enemy.x < boundaryLeft - 200 || enemy.x > boundaryRight + 200 || enemy.y < boundaryTop - 200 || enemy.y > boundaryBottom + 200;
if (isOutOfBounds) {
enemy.destroy();
enemies.splice(i, 1);
continue;
}
// Check collision with player
if (enemy.intersects(character)) {
if (!isInvulnerable) {
damagePlayer();
}
// Destroy enemy regardless of invulnerability state
enemy.destroy();
enemies.splice(i, 1);
}
}
}; /****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
/****
* Classes
****/
var Character = Container.expand(function () {
var self = Container.call(this);
var characterGraphics = self.attachAsset('character', {
anchorX: 0.5,
anchorY: 0.5
});
// Target position for smooth movement
self.targetX = 0;
self.targetY = 0;
// Movement speed factor for smooth following
self.followSpeed = 0.15;
// Square boundary limits (centered square area)
var squareSize = 1200;
var centerX = 2048 / 2;
var centerY = 2732 / 2;
self.boundaryLeft = centerX - squareSize / 2;
self.boundaryRight = centerX + squareSize / 2;
self.boundaryTop = centerY - squareSize / 2;
self.boundaryBottom = centerY + squareSize / 2;
var characterRadius = 60;
self.update = function () {
// Calculate next position
var deltaX = self.targetX - self.x;
var deltaY = self.targetY - self.y;
var nextX = self.x + deltaX * self.followSpeed;
var nextY = self.y + deltaY * self.followSpeed;
// Only apply boundary constraints during playing state, not during menu selection
if (gameState === 'playing') {
// Check wall collisions and prevent movement beyond boundaries
if (nextX - characterRadius < self.boundaryLeft) {
nextX = self.boundaryLeft + characterRadius;
}
if (nextX + characterRadius > self.boundaryRight) {
nextX = self.boundaryRight - characterRadius;
}
if (nextY - characterRadius < self.boundaryTop) {
nextY = self.boundaryTop + characterRadius;
}
if (nextY + characterRadius > self.boundaryBottom) {
nextY = self.boundaryBottom - characterRadius;
}
}
// Apply the constrained position
self.x = nextX;
self.y = nextY;
};
// Method to set target position
self.setTarget = function (x, y) {
self.targetX = x;
self.targetY = y;
};
return self;
});
var CyanAreaAttack = Container.expand(function () {
var self = Container.call(this);
var attackGraphics = self.attachAsset('cyanAreaAttack', {
anchorX: 0,
anchorY: 0.5
});
self.duration = 0;
self.maxDuration = 3000; // 3 seconds
self.isActive = false;
self.startX = -600; // Start from left side of combat box
self.endX = 1200; // End at right side of combat box
self.speed = 5 + difficultyLevel * 0.5; // Movement speed increases with difficulty
// Start with low alpha and fade in
attackGraphics.alpha = 0;
// Position at left edge initially
self.x = self.startX;
self.update = function () {
self.duration += 16.67;
// Move from left to right across combat box
if (self.duration > 500 && self.x < self.endX + 600) {
// Move until completely off screen
self.x += self.speed;
}
// Fade in effect at the beginning
if (self.duration < 500) {
attackGraphics.alpha = self.duration / 500 * 0.6; // Max alpha of 0.6
} else if (self.x < self.endX + 600) {
// Stay visible and active while crossing combat box
attackGraphics.alpha = 0.6;
self.isActive = true;
} else {
// Mark as finished when completely crossed
self.isActive = false;
attackGraphics.alpha = 0;
}
};
return self;
});
var Enemy = Container.expand(function () {
var self = Container.call(this);
var enemyGraphics = self.attachAsset('enemy', {
anchorX: 0.5,
anchorY: 0.5
});
self.speed = 8 + difficultyLevel * 0.3;
self.targetX = 0;
self.targetY = 0;
self.isInView = false; // Track if enemy is in player's view
self.positionLocked = false; // Track if position is locked
self.lockedX = 0; // Locked X position
self.lockedY = 0; // Locked Y position
self.update = function () {
// Check if enemy is in view (within combat box boundaries)
var wasInView = self.isInView;
self.isInView = self.x >= boundaryLeft && self.x <= boundaryRight && self.y >= boundaryTop && self.y <= boundaryBottom;
// Lock position when entering view for the first time
if (!wasInView && self.isInView && !self.positionLocked) {
self.positionLocked = true;
self.lockedX = self.x;
self.lockedY = self.y;
}
// If direction is locked, continue moving in that direction
if (self.positionLocked) {
var deltaX = self.targetX - self.lockedX;
var deltaY = self.targetY - self.lockedY;
// Continue moving in the same direction as when we locked
if (Math.abs(deltaX) > 5) {
self.x += deltaX > 0 ? self.speed : -self.speed;
}
if (Math.abs(deltaY) > 5) {
self.y += deltaY > 0 ? self.speed : -self.speed;
}
} else {
// Follow player until we appear in view
var deltaX = self.targetX - self.x;
var deltaY = self.targetY - self.y;
if (Math.abs(deltaX) > 5) {
self.x += deltaX > 0 ? self.speed : -self.speed;
}
if (Math.abs(deltaY) > 5) {
self.y += deltaY > 0 ? self.speed : -self.speed;
}
}
// Add rotation effect (visual only, doesn't affect movement)
enemyGraphics.rotation += 0.1;
};
self.setTarget = function (x, y) {
self.targetX = x;
self.targetY = y;
};
return self;
});
var LaserAttack = Container.expand(function () {
var self = Container.call(this);
var chargerGraphics = self.attachAsset('laserCharger', {
anchorX: 0.5,
anchorY: 0.5
});
self.targetX = 0;
self.targetY = 0;
self.state = 'charging'; // 'charging', 'firing', 'finished'
self.chargeTime = 0;
self.fireTime = 0;
self.laserBeam = null;
self.hasDealtDamage = false;
self.update = function () {
if (self.state === 'charging') {
self.chargeTime += 16.67;
// Charging animation - pulsing effect
chargerGraphics.alpha = 0.5 + 0.5 * Math.sin(self.chargeTime * 0.01);
if (self.chargeTime >= 1200) {
// 1.2 seconds
self.state = 'firing';
self.fireLaser();
}
} else if (self.state === 'firing') {
self.fireTime += 16.67;
if (self.fireTime >= 3000) {
// 3 seconds
self.state = 'finished';
}
}
};
self.fireLaser = function () {
// Hide charger
chargerGraphics.alpha = 0;
// Create laser beam
self.laserBeam = self.addChild(LK.getAsset('laserAttack', {
anchorX: 0,
anchorY: 0.5
}));
// Position laser to fire across the screen toward target
var angle = Math.atan2(self.targetY - self.y, self.targetX - self.x);
self.laserBeam.rotation = angle;
self.laserBeam.x = 0;
self.laserBeam.y = 0;
};
self.setTarget = function (x, y) {
self.targetX = x;
self.targetY = y;
};
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0x000000
});
/****
* Game Code
****/
// Game state management
var gameState = 'menu'; // 'menu', 'playing', 'menuSelection', or 'actSubmenu'
var debugMode = false; // Debug mode for invulnerability
var holdTimer = 0; // Timer for tracking button hold duration
var menuSelectionTimer = 0; // Timer for menu selection phase
var selectedButtonIndex = 0; // Current selected button (0=FIGHT, 1=ACT, 2=ITEM, 3=MERCY)
var isInMenuSelection = false; // Flag to track if we're in menu selection mode
var actSubmenuIndex = 0; // Current selected ACT submenu option (0=Check, 1=Talk)
var talkEffectActive = false; // Flag to track if Talk effect is active for current turn
var attackSpeedModifier = 1.0; // Speed modifier for attacks (0.95 when Talk is used)
var menuContainer = game.addChild(new Container());
// Create menu elements
var logo = menuContainer.addChild(LK.getAsset('breathingSprite3', {
anchorX: 0.5,
anchorY: 0.5
}));
logo.x = 2048 / 2;
logo.y = 2732 / 2 - 250;
// Create play button
var playButton = menuContainer.addChild(LK.getAsset('playButton', {
anchorX: 0.5,
anchorY: 0.5
}));
playButton.x = 2048 / 2;
playButton.y = 2732 / 2 + 100;
// Play button text
var playText = menuContainer.addChild(new Text2('JUGAR', {
size: 70,
fill: 0xFFFFFF
}));
playText.anchor.set(0.5, 0.5);
playText.x = playButton.x;
playText.y = playButton.y;
// Debug mode text (initially hidden)
var debugText = new Text2('Debug Mode: True', {
size: 40,
fill: 0xFFFFFF
});
debugText.anchor.set(0, 0);
debugText.x = 120; // Avoid the top-left menu icon area
debugText.y = 50;
debugText.visible = false;
game.addChild(debugText);
// Play button interaction
playButton.down = function (x, y, obj) {
// Reset hold timer and start counting
holdTimer = 0;
// Flash button on press
tween(playButton, {
tint: 0xffffff
}, {
duration: 100,
onFinish: function onFinish() {
tween(playButton, {
tint: 0xffffff
}, {
duration: 100
});
}
});
};
playButton.up = function (x, y, obj) {
// Check if button was held for 7 seconds (7000ms)
if (holdTimer >= 7000) {
debugMode = true;
console.log("Debug mode activated!");
}
// Start the game
gameState = 'playing';
menuContainer.visible = false;
// Show debug text if debug mode is active
if (debugMode) {
debugText.visible = true;
}
// Show game elements
leftWall.visible = true;
rightWall.visible = true;
topWall.visible = true;
bottomWall.visible = true;
character.visible = true;
healthBarBackground.visible = true;
healthLabel.visible = true;
breathingSprite1.visible = true;
breathingSprite2.visible = true;
for (var i = 0; i < healthSegments.length; i++) {
healthSegments[i].visible = true;
}
// Show menu buttons
for (var j = 0; j < menuButtons.length; j++) {
menuButtons[j].visible = true;
}
};
// Create visual boundary walls
var squareSize = 1200;
var centerX = 2048 / 2;
var centerY = 2732 / 2;
var boundaryLeft = centerX - squareSize / 2;
var boundaryRight = centerX + squareSize / 2;
var boundaryTop = centerY - squareSize / 2;
var boundaryBottom = centerY + squareSize / 2;
// Left wall
var leftWall = game.addChild(LK.getAsset('wall', {
anchorX: 0.5,
anchorY: 0.5
}));
leftWall.x = boundaryLeft;
leftWall.y = centerY;
leftWall.visible = false;
// Right wall
var rightWall = game.addChild(LK.getAsset('wall', {
anchorX: 0.5,
anchorY: 0.5
}));
rightWall.x = boundaryRight;
rightWall.y = centerY;
rightWall.visible = false;
// Top wall
var topWall = game.addChild(LK.getAsset('wallHorizontal', {
anchorX: 0.5,
anchorY: 0.5
}));
topWall.x = centerX;
topWall.y = boundaryTop;
topWall.visible = false;
// Bottom wall
var bottomWall = game.addChild(LK.getAsset('wallHorizontal', {
anchorX: 0.5,
anchorY: 0.5
}));
bottomWall.x = centerX;
bottomWall.y = boundaryBottom;
bottomWall.visible = false;
// Create the character at center of screen
var character = game.addChild(new Character());
character.x = 2048 / 2;
character.y = 2732 / 2;
character.visible = false;
// Set initial target position to character's starting position
character.setTarget(character.x, character.y);
// Handle mouse/touch movement
game.move = function (x, y, obj) {
if (gameState === 'playing') {
// Set character target to mouse position
character.setTarget(x, y);
} else if (gameState === 'menuSelection') {
// During menu selection, check which button was clicked
var buttonClicked = -1;
for (var i = 0; i < menuButtons.length; i++) {
var button = menuButtons[i];
var buttonLeft = button.x - buttonWidth / 2;
var buttonRight = button.x + buttonWidth / 2;
var buttonTop = button.y - buttonHeight / 2;
var buttonBottom = button.y + buttonHeight / 2;
if (x >= buttonLeft && x <= buttonRight && y >= buttonTop && y <= buttonBottom) {
buttonClicked = i;
break;
}
}
// If a button was clicked, move character to it or activate it
if (buttonClicked !== -1) {
if (buttonClicked === selectedButtonIndex) {
// Same button clicked - activate it
activateMenuButton(buttonClicked);
} else {
// Different button - teleport character to it
selectedButtonIndex = buttonClicked;
var targetButton = menuButtons[selectedButtonIndex];
character.setTarget(targetButton.x, targetButton.y);
character.x = targetButton.x;
character.y = targetButton.y;
}
}
} else if (gameState === 'actSubmenu') {
// During ACT submenu, check which submenu button was clicked
var actButtonClicked = -1;
for (var j = 0; j < actSubmenuButtons.length; j++) {
var actButton = actSubmenuButtons[j];
var actButtonLeft = actButton.x - buttonWidth / 2;
var actButtonRight = actButton.x + buttonWidth / 2;
var actButtonTop = actButton.y - buttonHeight / 2;
var actButtonBottom = actButton.y + buttonHeight / 2;
if (x >= actButtonLeft && x <= actButtonRight && y >= actButtonTop && y <= actButtonBottom) {
actButtonClicked = j;
break;
}
}
// If an ACT button was clicked, move character to it or activate it
if (actButtonClicked !== -1) {
if (actButtonClicked === actSubmenuIndex) {
// Same button clicked - activate it
activateActSubmenuButton(actButtonClicked);
} else {
// Different button - teleport character to it
actSubmenuIndex = actButtonClicked;
var targetActButton = actSubmenuButtons[actSubmenuIndex];
character.setTarget(targetActButton.x, targetActButton.y);
character.x = targetActButton.x;
character.y = targetActButton.y;
}
}
}
};
// Handle mouse/touch down - also update position immediately
game.down = function (x, y, obj) {
if (gameState === 'playing') {
// Set character target to touch/click position
character.setTarget(x, y);
}
};
// Create health bar interface below the square
var healthBarY = boundaryBottom + 100;
// Health bar background
var healthBarBackground = game.addChild(LK.getAsset('healthBarBg', {
anchorX: 0.5,
anchorY: 0.5
}));
healthBarBackground.x = centerX;
healthBarBackground.y = healthBarY;
healthBarBackground.visible = false;
// Health bar segments (10 parts)
var healthSegments = [];
var segmentWidth = 36;
var segmentSpacing = 2;
var totalWidth = (segmentWidth + segmentSpacing) * 10 - segmentSpacing;
var startX = centerX - totalWidth / 2;
for (var i = 0; i < 10; i++) {
var segment = game.addChild(LK.getAsset('healthSegment', {
anchorX: 0,
anchorY: 0.5
}));
segment.x = startX + i * (segmentWidth + segmentSpacing);
segment.y = healthBarY;
segment.tint = 0xffff00; // Yellow by default
segment.visible = false;
healthSegments.push(segment);
}
// Health bar label
var healthLabel = new Text2('VIDA', {
size: 30,
fill: 0xFFFFFF
});
healthLabel.anchor.set(0.5, 1);
healthLabel.x = centerX;
healthLabel.y = healthBarY - 30;
healthLabel.visible = false;
game.addChild(healthLabel);
// Health variables
var maxHealth = 100;
var currentHealth = 100;
// Invulnerability system
var isInvulnerable = false;
var invulnerabilityDuration = 3000; // 3 seconds
// Function to update health bar segments
function updateHealthBar() {
var healthSegmentsRemaining = Math.ceil(currentHealth / 10);
for (var i = 0; i < 10; i++) {
if (i < healthSegmentsRemaining) {
healthSegments[i].tint = 0xffff00; // Yellow for remaining health
} else {
healthSegments[i].tint = 0xff0000; // Red for lost health
}
}
}
// Initial health bar update
updateHealthBar();
// Create breathing sprites above combat box
var breathingSprite1 = game.addChild(LK.getAsset('breathingSprite1', {
anchorX: 0.5,
anchorY: 0.5
}));
breathingSprite1.x = centerX;
breathingSprite1.y = boundaryTop - 300;
breathingSprite1.visible = false;
var breathingSprite2 = game.addChild(LK.getAsset('breathingSprite2', {
anchorX: 0.5,
anchorY: 0.5
}));
breathingSprite2.x = centerX;
// Position sprite2 directly on top of sprite1 to form a tower
breathingSprite2.y = breathingSprite1.y - (breathingSprite1.height / 2 + breathingSprite2.height / 2) + 200;
breathingSprite2.visible = false;
// Start breathing animation for both sprites
function startBreathingAnimation() {
// Animate first sprite
tween(breathingSprite1, {
y: breathingSprite1.y - 15
}, {
duration: 1500,
easing: tween.easeInOut,
onFinish: function onFinish() {
tween(breathingSprite1, {
y: breathingSprite1.y + 15
}, {
duration: 1500,
easing: tween.easeInOut,
onFinish: startBreathingAnimation
});
}
});
// Animate second sprite with slight delay
LK.setTimeout(function () {
tween(breathingSprite2, {
y: breathingSprite2.y - 12
}, {
duration: 1600,
easing: tween.easeInOut,
onFinish: function onFinish() {
tween(breathingSprite2, {
y: breathingSprite2.y + 12
}, {
duration: 1600,
easing: tween.easeInOut
});
}
});
}, 200);
}
startBreathingAnimation();
// Player position tracking for laser attacks and enemy targeting
var playerPositions = [];
var maxStoredPositions = 12; // Store 12 positions for ~0.2 seconds at 60fps
// Enemy management
var enemies = [];
// Laser attack management
var laserAttacks = [];
// Cyan area attack management
var cyanAreaAttacks = [];
// Attack pattern system
var attackPatternTimer = 0;
var nextAttackTime = 1000; // First attack after 1 second
var difficultyLevel = 1;
var difficultyTimer = 0;
var difficultyIncreaseInterval = 30000; // Increase difficulty every 30 seconds
// Player movement tracking
var lastPlayerX = character.x;
var lastPlayerY = character.y;
var isPlayerMoving = false;
// Function to spawn enemy at random position outside the square
function spawnEnemy() {
// Capture player position from 0.2 seconds ago (12 ticks at 60fps)
var targetPlayerPos = {
x: character.x,
y: character.y
};
if (playerPositions.length > 0) {
// Use oldest stored position as it represents roughly 0.2 seconds ago
targetPlayerPos = playerPositions[0];
}
var enemy = new Enemy();
// Spawn at random position outside the boundary square
var side = Math.floor(Math.random() * 4);
switch (side) {
case 0:
// Top
enemy.x = boundaryLeft + Math.random() * squareSize;
enemy.y = boundaryTop - 100;
break;
case 1:
// Right
enemy.x = boundaryRight + 100;
enemy.y = boundaryTop + Math.random() * squareSize;
break;
case 2:
// Bottom
enemy.x = boundaryLeft + Math.random() * squareSize;
enemy.y = boundaryBottom + 100;
break;
case 3:
// Left
enemy.x = boundaryLeft - 100;
enemy.y = boundaryTop + Math.random() * squareSize;
break;
}
enemy.setTarget(targetPlayerPos.x, targetPlayerPos.y);
enemies.push(enemy);
game.addChild(enemy);
}
// Function to spawn laser attack targeting player's previous positions
function spawnLaserAttack() {
if (playerPositions.length === 0) return;
// Choose a random previous position to target
var targetIndex = Math.floor(Math.random() * playerPositions.length);
var targetPos = playerPositions[targetIndex];
var laserAttack = new LaserAttack();
// Position laser attack outside the combat box
var side = Math.floor(Math.random() * 4);
switch (side) {
case 0:
// Top
laserAttack.x = boundaryLeft + Math.random() * squareSize;
laserAttack.y = boundaryTop - 150;
break;
case 1:
// Right
laserAttack.x = boundaryRight + 150;
laserAttack.y = boundaryTop + Math.random() * squareSize;
break;
case 2:
// Bottom
laserAttack.x = boundaryLeft + Math.random() * squareSize;
laserAttack.y = boundaryBottom + 150;
break;
case 3:
// Left
laserAttack.x = boundaryLeft - 150;
laserAttack.y = boundaryTop + Math.random() * squareSize;
break;
}
laserAttack.setTarget(targetPos.x, targetPos.y);
laserAttacks.push(laserAttack);
game.addChild(laserAttack);
}
// Function to spawn cyan area attack covering the entire combat box
function spawnCyanAreaAttack() {
var cyanAttack = new CyanAreaAttack();
cyanAttack.x = boundaryLeft - 600; // Start from left side
cyanAttack.y = centerY; // Center vertically in combat box
cyanAreaAttacks.push(cyanAttack);
game.addChild(cyanAttack);
}
// Function to generate random attack patterns
function generateAttackPattern() {
// Base probability for each attack type (adjusted by difficulty)
var enemyChance = 0.4 + difficultyLevel * 0.1;
var laserChance = 0.3 + difficultyLevel * 0.05;
var cyanChance = 0.2 + difficultyLevel * 0.02;
// Ensure we don't overwhelm the player
if (enemies.length > 5) enemyChance = 0;
if (laserAttacks.length > 2) laserChance = 0;
if (cyanAreaAttacks.length > 1) cyanChance = 0;
// Random attack selection
var attackRoll = Math.random();
var attacksSpawned = 0;
// Spawn enemies
if (attackRoll < enemyChance) {
var enemyCount = Math.min(1 + Math.floor(Math.random() * difficultyLevel), 3);
for (var i = 0; i < enemyCount; i++) {
spawnEnemy();
attacksSpawned++;
}
}
// Spawn laser attack (only if player has moved)
if (Math.random() < laserChance && playerPositions.length > 0) {
spawnLaserAttack();
attacksSpawned++;
}
// Spawn cyan area attack (less frequent)
if (Math.random() < cyanChance) {
// Check if player has space to dodge
var playerNearEdge = character.x < boundaryLeft + 300 || character.x > boundaryRight - 300;
if (!playerNearEdge || Math.random() < 0.3) {
spawnCyanAreaAttack();
attacksSpawned++;
}
}
// Calculate next attack time based on difficulty and current attacks
var baseInterval = 2000 - difficultyLevel * 100; // Decrease time between patterns
var randomVariance = Math.random() * 1000;
// Apply talk effect speed modifier (make attacks slower)
if (talkEffectActive) {
baseInterval = baseInterval / attackSpeedModifier; // Increase interval by 5%
}
// Ensure minimum time between attacks for fairness
nextAttackTime = Math.max(800, baseInterval - randomVariance);
// If no attacks were spawned, try again sooner
if (attacksSpawned === 0) {
nextAttackTime = 500;
}
}
// Function to damage player
function damagePlayer(damage) {
if (debugMode) {
console.log("Debug: Damage blocked - " + (damage || 10));
return; // No damage in debug mode
}
if (damage === undefined) damage = 10; // Default 10% damage
currentHealth = Math.max(0, currentHealth - damage);
updateHealthBar();
// Camera shake effect for 1.2 seconds
var originalX = game.x;
var originalY = game.y;
var shakeIntensity = 10;
var shakeDuration = 1200; // 1.2 seconds
var shakeStartTime = Date.now();
function shakeCamera() {
var elapsed = Date.now() - shakeStartTime;
if (elapsed < shakeDuration) {
game.x = originalX + (Math.random() - 0.5) * shakeIntensity;
game.y = originalY + (Math.random() - 0.5) * shakeIntensity;
LK.setTimeout(shakeCamera, 16); // ~60fps
} else {
game.x = originalX;
game.y = originalY;
}
}
shakeCamera();
// Activate invulnerability
isInvulnerable = true;
// Flash character red when damaged and add blinking effect during invulnerability
tween(character, {
tint: 0xff0000
}, {
duration: 200,
onFinish: function onFinish() {
tween(character, {
tint: 0xffffff
}, {
duration: 200,
onFinish: function onFinish() {
// Start blinking effect for remaining invulnerability time
var blinkCount = 0;
var maxBlinks = 12; // Blink for about 2.4 seconds (remaining invulnerability time)
function blink() {
if (blinkCount < maxBlinks && isInvulnerable) {
character.alpha = character.alpha === 1 ? 0.3 : 1;
blinkCount++;
LK.setTimeout(blink, 200);
} else {
character.alpha = 1; // Ensure character is fully visible
}
}
blink();
}
});
}
});
// Set timeout to end invulnerability
LK.setTimeout(function () {
isInvulnerable = false;
character.alpha = 1; // Ensure character is fully visible
}, invulnerabilityDuration);
// Check game over - when all 10 segments are red (health is 0)
if (currentHealth <= 0) {
LK.showGameOver();
}
}
// Function to activate menu button based on index
function activateMenuButton(buttonIndex) {
switch (buttonIndex) {
case 0:
// FIGHT
console.log("FIGHT activated!");
// Sans dodge animation - random left or right movement
var dodgeDirection = Math.random() < 0.5 ? -1 : 1; // -1 for left, 1 for right
var dodgeDistance = 200; // Distance to dodge
var originalSprite1X = breathingSprite1.x;
var originalSprite2X = breathingSprite2.x;
var targetSprite1X = breathingSprite1.x + dodgeDistance * dodgeDirection;
var targetSprite2X = breathingSprite2.x + dodgeDistance * dodgeDirection;
// Animate both breathing sprites to dodge
tween(breathingSprite1, {
x: targetSprite1X
}, {
duration: 200,
easing: tween.easeOut
});
tween(breathingSprite2, {
x: targetSprite2X
}, {
duration: 200,
easing: tween.easeOut,
onFinish: function onFinish() {
// Return to original position after 0.75 seconds
LK.setTimeout(function () {
tween(breathingSprite1, {
x: originalSprite1X
}, {
duration: 300,
easing: tween.easeInOut
});
tween(breathingSprite2, {
x: originalSprite2X
}, {
duration: 300,
easing: tween.easeInOut,
onFinish: function onFinish() {
// Return to combat after the complete animation sequence
exitMenuSelection();
}
});
}, 750);
}
});
break;
case 1:
// ACT
console.log("ACT activated!");
// Transform combat box to rectangular shape
transformCombatBoxToRectangle();
// Enter ACT submenu
gameState = 'actSubmenu';
actSubmenuIndex = 0; // Start at Check option
// Reposition ACT submenu buttons to center of rectangular combat box
LK.setTimeout(function () {
// Position buttons in center of the rectangular combat box
actSubmenuButtons[0].x = centerX - 150; // Check button
actSubmenuButtons[0].y = centerY;
actSubmenuButtons[1].x = centerX + 150; // Talk button
actSubmenuButtons[1].y = centerY;
// Update text positions to match buttons
actSubmenuContainer.children[2].x = actSubmenuButtons[0].x; // Check text
actSubmenuContainer.children[2].y = actSubmenuButtons[0].y;
actSubmenuContainer.children[3].x = actSubmenuButtons[1].x; // Talk text
actSubmenuContainer.children[3].y = actSubmenuButtons[1].y;
// Show ACT submenu buttons
for (var k = 0; k < actSubmenuButtons.length; k++) {
actSubmenuButtons[k].visible = true;
actSubmenuContainer.children[k + actSubmenuButtons.length].visible = true; // Show text labels
}
// Move character to Check button
var targetActButton = actSubmenuButtons[actSubmenuIndex];
character.setTarget(targetActButton.x, targetActButton.y);
character.x = targetActButton.x;
character.y = targetActButton.y;
}, 300); // Wait for combat box transformation to complete
break;
case 2:
// ITEM
console.log("ITEM activated!");
// TODO: Implement ITEM mechanics
break;
case 3:
// MERCY
console.log("MERCY activated!");
// TODO: Implement MERCY mechanics
break;
}
}
// Function to activate ACT submenu button
function activateActSubmenuButton(buttonIndex) {
switch (buttonIndex) {
case 0:
// Check
console.log("Check activated!");
// Hide ACT submenu
for (var m = 0; m < actSubmenuButtons.length; m++) {
actSubmenuButtons[m].visible = false;
actSubmenuContainer.children[m + actSubmenuButtons.length].visible = false; // Hide text labels
}
// Show Sans statistics (simulate showing stats briefly)
console.log("Sans - ATK: 1 DEF: 1 HP: ??? - The easiest enemy. Can only deal 1 damage.");
// Return to combat after showing stats
LK.setTimeout(function () {
exitMenuSelection();
}, 1500); // Show stats for 1.5 seconds
break;
case 1:
// Talk
console.log("Talk activated!");
// Activate talk effect for this turn only
talkEffectActive = true;
attackSpeedModifier = 0.95; // 5% slower attacks
console.log("You talked to Sans. His attacks will be 5% slower this turn.");
// Hide ACT submenu
for (var n = 0; n < actSubmenuButtons.length; n++) {
actSubmenuButtons[n].visible = false;
actSubmenuContainer.children[n + actSubmenuButtons.length].visible = false; // Hide text labels
}
// Return to combat
exitMenuSelection();
break;
}
}
// Function to transform combat box to rectangular shape for menus
function transformCombatBoxToRectangle() {
// New rectangular dimensions
var rectWidth = 800;
var rectHeight = 200;
var rectCenterX = centerX;
var rectCenterY = centerY;
// Calculate new wall positions
var rectLeft = rectCenterX - rectWidth / 2;
var rectRight = rectCenterX + rectWidth / 2;
var rectTop = rectCenterY - rectHeight / 2;
var rectBottom = rectCenterY + rectHeight / 2;
// Animate walls to new rectangular positions
tween(leftWall, {
x: rectLeft,
y: rectCenterY,
scaleY: rectHeight / 1200
}, {
duration: 300,
easing: tween.easeInOut
});
tween(rightWall, {
x: rectRight,
y: rectCenterY,
scaleY: rectHeight / 1200
}, {
duration: 300,
easing: tween.easeInOut
});
tween(topWall, {
x: rectCenterX,
y: rectTop,
scaleX: rectWidth / 1200
}, {
duration: 300,
easing: tween.easeInOut
});
tween(bottomWall, {
x: rectCenterX,
y: rectBottom,
scaleX: rectWidth / 1200
}, {
duration: 300,
easing: tween.easeInOut
});
// Update character boundaries for rectangular area
character.boundaryLeft = rectLeft;
character.boundaryRight = rectRight;
character.boundaryTop = rectTop;
character.boundaryBottom = rectBottom;
}
// Function to restore combat box to original square shape
function restoreCombatBoxToSquare() {
// Animate walls back to original square positions
tween(leftWall, {
x: boundaryLeft,
y: centerY,
scaleY: 1
}, {
duration: 300,
easing: tween.easeInOut
});
tween(rightWall, {
x: boundaryRight,
y: centerY,
scaleY: 1
}, {
duration: 300,
easing: tween.easeInOut
});
tween(topWall, {
x: centerX,
y: boundaryTop,
scaleX: 1
}, {
duration: 300,
easing: tween.easeInOut
});
tween(bottomWall, {
x: centerX,
y: boundaryBottom,
scaleX: 1
}, {
duration: 300,
easing: tween.easeInOut
});
// Restore character boundaries to original square
character.boundaryLeft = boundaryLeft;
character.boundaryRight = boundaryRight;
character.boundaryTop = boundaryTop;
character.boundaryBottom = boundaryBottom;
}
// Function to exit menu selection and return to combat
function exitMenuSelection() {
// Start restoring combat box to square shape
restoreCombatBoxToSquare();
gameState = 'playing';
isInMenuSelection = false;
menuSelectionTimer = 0;
// Reset talk effect for next turn
talkEffectActive = false;
attackSpeedModifier = 1.0;
// Hide ACT submenu if it was visible
for (var p = 0; p < actSubmenuButtons.length; p++) {
actSubmenuButtons[p].visible = false;
if (actSubmenuContainer.children[p + actSubmenuButtons.length]) {
actSubmenuContainer.children[p + actSubmenuButtons.length].visible = false;
}
}
// Move character back to center of combat box
character.setTarget(centerX, centerY);
}
// Create menu below combat box
var menuY = boundaryBottom + 200;
var gameMenuContainer = game.addChild(new Container());
// Create 4 menu buttons as rectangles
var buttonWidth = 300;
var buttonHeight = 120;
var buttonSpacing = 60;
var totalMenuWidth = buttonWidth * 4 + buttonSpacing * 3;
var menuStartX = centerX - totalMenuWidth / 2;
var menuButtons = [];
var buttonAssets = ['menuButton1', 'menuButton2', 'menuButton3', 'menuButton4'];
for (var b = 0; b < 4; b++) {
var button = gameMenuContainer.addChild(LK.getAsset(buttonAssets[b], {
anchorX: 0.5,
anchorY: 0.5
}));
button.x = menuStartX + b * (buttonWidth + buttonSpacing) + buttonWidth / 2;
button.y = menuY;
button.visible = false; // Hide initially
menuButtons.push(button);
}
// Create ACT submenu elements
var actSubmenuContainer = game.addChild(new Container());
var actSubmenuButtons = [];
var actSubmenuY = menuY + 150;
var actButtonLabels = ['Check', 'Talk'];
// Create Check and Talk buttons for ACT submenu
for (var actB = 0; actB < 2; actB++) {
var actButton = actSubmenuContainer.addChild(LK.getAsset('menuButton2', {
anchorX: 0.5,
anchorY: 0.5
}));
actButton.x = centerX - 200 + actB * 400; // Position side by side
actButton.y = actSubmenuY;
actButton.visible = false;
actSubmenuButtons.push(actButton);
// Add text labels for ACT buttons
var actButtonText = actSubmenuContainer.addChild(new Text2(actButtonLabels[actB], {
size: 40,
fill: 0xFFFFFF
}));
actButtonText.anchor.set(0.5, 0.5);
actButtonText.x = actButton.x;
actButtonText.y = actButton.y;
actButtonText.visible = false;
}
// Main game update loop
game.update = function () {
// Update hold timer when in menu state (increment by ~16.67ms per frame at 60fps)
if (gameState === 'menu') {
holdTimer += 16.67;
}
// Handle menu selection timer - activate menu selection after 45 seconds
if (gameState === 'playing' && !isInMenuSelection) {
menuSelectionTimer += 16.67;
if (menuSelectionTimer >= 45000) {
// 45 seconds
// Enter menu selection mode
gameState = 'menuSelection';
isInMenuSelection = true;
selectedButtonIndex = 0; // Start at FIGHT button
// Move character to FIGHT button position
var targetButton = menuButtons[selectedButtonIndex];
character.setTarget(targetButton.x, targetButton.y);
character.x = targetButton.x;
character.y = targetButton.y;
// Reset timer for next menu cycle
menuSelectionTimer = 0;
console.log("Menu selection activated! Click buttons to navigate or activate.");
}
}
// Only update game logic when playing (not in menu or menu selection)
if (gameState !== 'playing') return;
// Track player movement
var deltaX = character.x - lastPlayerX;
var deltaY = character.y - lastPlayerY;
var movementThreshold = 1; // Minimum movement to consider as "moving"
isPlayerMoving = Math.abs(deltaX) > movementThreshold || Math.abs(deltaY) > movementThreshold;
lastPlayerX = character.x;
lastPlayerY = character.y;
// Track player positions for laser attacks
if (LK.ticks % 10 === 0) {
// Store position every 10 ticks
playerPositions.push({
x: character.x,
y: character.y
});
if (playerPositions.length > maxStoredPositions) {
playerPositions.shift(); // Remove oldest position
}
}
// Attack pattern generation
if (gameState === 'playing') {
attackPatternTimer += 16.67;
if (attackPatternTimer >= nextAttackTime) {
generateAttackPattern();
attackPatternTimer = 0;
}
}
// Difficulty progression
if (gameState === 'playing') {
difficultyTimer += 16.67;
if (difficultyTimer >= difficultyIncreaseInterval) {
difficultyLevel = Math.min(difficultyLevel + 1, 5); // Max difficulty level 5
difficultyTimer = 0;
}
}
// Update cyan area attacks and check collisions
for (var k = cyanAreaAttacks.length - 1; k >= 0; k--) {
var cyanAttack = cyanAreaAttacks[k];
// Check collision with player only if player is moving and attack is active
if (cyanAttack.isActive && cyanAttack.intersects(character) && isPlayerMoving && !isInvulnerable) {
damagePlayer(10); // 10% damage
}
// Remove finished cyan area attacks when they've crossed the combat box or are off-screen
var isOffScreen = cyanAttack.x >= boundaryRight + 600 || cyanAttack.x <= boundaryLeft - 600;
if (cyanAttack.x >= cyanAttack.endX + 600 || isOffScreen) {
cyanAttack.destroy();
cyanAreaAttacks.splice(k, 1);
}
}
// Update laser attacks and check collisions
for (var j = laserAttacks.length - 1; j >= 0; j--) {
var laserAttack = laserAttacks[j];
// Check collision with player during firing state
if (laserAttack.state === 'firing' && laserAttack.laserBeam && !laserAttack.hasDealtDamage) {
// Calculate if character is actually intersecting with the laser beam
var characterBounds = {
left: character.x - 60,
right: character.x + 60,
top: character.y - 60,
bottom: character.y + 60
};
var laserBounds = {
left: laserAttack.x + laserAttack.laserBeam.x,
right: laserAttack.x + laserAttack.laserBeam.x + laserAttack.laserBeam.width,
top: laserAttack.y + laserAttack.laserBeam.y - 20,
bottom: laserAttack.y + laserAttack.laserBeam.y + 20
};
// Check if character bounds overlap with laser bounds
var isIntersecting = characterBounds.right > laserBounds.left && characterBounds.left < laserBounds.right && characterBounds.bottom > laserBounds.top && characterBounds.top < laserBounds.bottom;
if (isIntersecting && !isInvulnerable) {
damagePlayer(20); // 20% damage
laserAttack.hasDealtDamage = true;
}
}
// Remove finished laser attacks
if (laserAttack.state === 'finished') {
laserAttack.destroy();
laserAttacks.splice(j, 1);
}
}
// Update enemies and check collisions
for (var i = enemies.length - 1; i >= 0; i--) {
var enemy = enemies[i];
// Update enemy target to current player position if not locked
if (!enemy.positionLocked) {
enemy.setTarget(character.x, character.y);
}
// Check if enemy is outside visible area and remove it
var isOutOfBounds = enemy.x < boundaryLeft - 200 || enemy.x > boundaryRight + 200 || enemy.y < boundaryTop - 200 || enemy.y > boundaryBottom + 200;
if (isOutOfBounds) {
enemy.destroy();
enemies.splice(i, 1);
continue;
}
// Check collision with player
if (enemy.intersects(character)) {
if (!isInvulnerable) {
damagePlayer();
}
// Destroy enemy regardless of invulnerability state
enemy.destroy();
enemies.splice(i, 1);
}
}
};