User prompt
Please fix the bug: 'self.addChild is not a function' in or related to this line: 'self.addChild(self.bossGraphics);' Line Number: 657
User prompt
Please fix the bug: 'this.addChildAt is not a function' in or related to this line: 'Container.prototype.addChild.call(self, self.bossGraphics);' Line Number: 657
User prompt
Please fix the bug: 'self.addChild is not a function' in or related to this line: 'self.addChild(self.bossGraphics);' Line Number: 657
User prompt
Please fix the bug: 'this.addChildAt is not a function' in or related to this line: 'Container.prototype.addChild.call(self, self.bossGraphics);' Line Number: 657
User prompt
Please fix the bug: 'self.addChild is not a function' in or related to this line: 'self.addChild(self.bossGraphics);' Line Number: 657
User prompt
Please fix the bug: 'this.addChildAt is not a function' in or related to this line: 'Container.prototype.addChild.call(self, self.bossGraphics);' Line Number: 657
User prompt
Please fix the bug: 'self.addChild is not a function' in or related to this line: 'self.addChild(self.bossGraphics);' Line Number: 657
User prompt
Please fix the bug: 'this.addChildAt is not a function' in or related to this line: 'Container.prototype.addChild.call(self, self.bossGraphics);' Line Number: 657
User prompt
Please fix the bug: 'self.addChild is not a function' in or related to this line: 'self.addChild(self.bossGraphics);' Line Number: 657
User prompt
Please fix the bug: 'this.addChildAt is not a function' in or related to this line: 'Container.prototype.addChild.call(self, self.bossGraphics);' Line Number: 657
User prompt
Please fix the bug: 'self.addChild is not a function' in or related to this line: 'self.addChild(self.bossGraphics);' Line Number: 657
User prompt
Please fix the bug: 'this.addChildAt is not a function' in or related to this line: 'Container.prototype.addChild.call(self, self.bossGraphics);' Line Number: 657
User prompt
Please fix the bug: 'self.addChild is not a function' in or related to this line: 'self.addChild(self.bossGraphics);' Line Number: 657
User prompt
Please fix the bug: 'this.addChildAt is not a function' in or related to this line: 'Container.prototype.addChild.call(self, self.bossGraphics);' Line Number: 657
User prompt
Please fix the bug: 'self.addChild is not a function' in or related to this line: 'self.addChild(self.bossGraphics);' Line Number: 657
User prompt
Please fix the bug: 'this.addChildAt is not a function' in or related to this line: 'Container.prototype.addChild.call(self, self.bossGraphics);' Line Number: 657
User prompt
Please fix the bug: 'self.addChild is not a function' in or related to this line: 'self.addChild(self.bossGraphics);' Line Number: 657
User prompt
Please fix the bug: 'this.addChildAt is not a function' in or related to this line: 'Container.prototype.addChild.call(self, self.bossGraphics);' Line Number: 657
User prompt
Please fix the bug: 'self.addChild is not a function' in or related to this line: 'self.addChild(self.bossGraphics);' Line Number: 657
User prompt
Please fix the bug: 'self.addChild is not a function' in or related to this line: 'Container.prototype.addChild.call(self, self.bossGraphics);' Line Number: 657
User prompt
Please fix the bug: 'self.addChild is not a function' in or related to this line: 'self.addChild(self.bossGraphics);' Line Number: 657
User prompt
Please fix the bug: 'this.addChildAt is not a function' in or related to this line: 'Container.prototype.addChild.call(self, self.bossGraphics);' Line Number: 657
User prompt
Please fix the bug: 'self.addChild is not a function' in or related to this line: 'self.addChild(self.bossGraphics);' Line Number: 657
User prompt
Please fix the bug: 'self.addChild is not a function' in or related to this line: 'self.bossGraphics = self.addChild(LK.getAsset('bossIdle', {' Line Number: 650
User prompt
Please fix the bug: 'self.attachAsset is not a function' in or related to this line: 'self.bossGraphics = self.attachAsset('bossIdle', {' Line Number: 650
/**** * Classes ****/ // End gameState /**** * Player class ****/ var Player = Container.expand(function () { var self = Container.call(this); // Attach player graphic (depends on asset) self.playerGraphics = self.attachAsset('player', { anchorX: 0.5, anchorY: 0.5 }); self.health = 5; // Default player health (will be set by storage.maxHearts in gameState.startGame) self.speed = 8; // Player movement speed self.rollSpeed = 12; // Player roll speed self.rolling = false; self.rollDuration = 30; // Roll duration in frames (0.5 seconds at 60fps) self.rollTimer = 0; self.rollDirectionX = 0; self.rollDirectionY = 0; self.invulnerable = false; // Player invulnerability flag self.invulnerabilityDuration = 60; // Invulnerability duration after rolling/getting hit self.invulnerabilityTimer = 0; self.dead = false; // Create player roll animation (depends on assets) self.createRollAnim = function () { var frames = [LK.getAsset('roll0', {}), LK.getAsset('roll1', {}), LK.getAsset('roll2', {}), LK.getAsset('roll3', {}), LK.getAsset('roll4', {})]; var rollAnim = new SpriteAnimation({ frames: frames, frameDuration: 60, // Roll animation speed loop: true, // Roll animation loops anchorX: 0.5, anchorY: 0.5, x: 0, // Relative position y: 0 // Relative position }); return rollAnim; }; self.rollAnim = null; // Will hold the current roll animation object self.startRoll = function () { if (self.rolling || self.dead || gameState.currentState !== "game") { return; // Cannot roll if already rolling, dead, or not in game state } self.rolling = true; self.rollTimer = self.rollDuration; self.invulnerable = true; // Invulnerable during roll self.invulnerabilityTimer = self.invulnerabilityDuration; // Capture current movement direction for roll self.rollDirectionX = (LK.isDown("left") ? -1 : 0) + (LK.isDown("right") ? 1 : 0); self.rollDirectionY = (LK.isDown("up") ? -1 : 0) + (LK.isDown("down") ? 1 : 0); // Ensure there's a direction to roll, default if no input if (self.rollDirectionX === 0 && self.rollDirectionY === 0) { // Default roll direction (e.g., last movement direction, or down) // For simplicity, let's just prevent roll if no direction pressed. // Or set a default if needed: self.rollDirectionY = 1; // Example: roll down self.rolling = false; // Cancel roll if no direction input self.invulnerable = false; self.invulnerabilityTimer = 0; return; // Exit if no roll direction } // Normalize roll direction vector (optional, but good for consistent speed) var rollMagnitude = Math.sqrt(self.rollDirectionX * self.rollDirectionX + self.rollDirectionY * self.rollDirectionY); if (rollMagnitude > 0) { self.rollDirectionX /= rollMagnitude; self.rollDirectionY /= rollMagnitude; } // Remove idle graphic, add roll animation if (self.playerGraphics && self.playerGraphics.parent === self) { self.playerGraphics.parent.removeChild(self.playerGraphics); } else if (self.playerGraphics && self.playerGraphics.parent) { // Fallback self.playerGraphics.parent.removeChild(self.playerGraphics); } self.playerGraphics = null; self.rollAnim = self.addChild(self.createRollAnim()); // Add roll animation as child self.rollAnim.play(); // Start roll animation }; self.takeDamage = function (amount) { if (self.dead || self.invulnerable || gameState.currentState !== "game") { return; // Cannot take damage if dead, invulnerable, or not in game state } self.health -= amount; self.health = Math.max(0, self.health); // Health cannot go below zero LK.getSound('playerHit').play(); // Play player hit sound LK.effects.flashObject(self, 0xFF0000, 300); // Flash effect on hit self.invulnerable = true; // Become invulnerable after taking damage // Use a separate timer for hit invulnerability if needed, or just reuse roll timer logic // For simplicity, reuse invulnerabilityTimer: self.invulnerabilityTimer = self.invulnerabilityDuration; // Invulnerable for a short period // Update UI hearts immediately // ui.updateHearts(self.health, storage.maxHearts); // This is called in game.update -> ui.updateHearts if (self.health <= 0) { self.die(); // Player dies } }; self.die = function () { if (self.dead) { return; // Already dead } self.dead = true; // Stop all movement/actions self.rolling = false; self.invulnerable = false; // No longer invulnerable once dead // Stop/remove roll animation if active if (self.rollAnim) { self.rollAnim.stop(); if (self.rollAnim.parent === self) { self.rollAnim.parent.removeChild(self.rollAnim); } else if (self.rollAnim.parent) { // Fallback self.rollAnim.parent.removeChild(self.rollAnim); } self.rollAnim.destroy(); self.rollAnim = null; } // Add player death animation or graphic (if any) // For simplicity, just hide or remove the player graphic if (self.playerGraphics && self.playerGraphics.parent === self) { self.playerGraphics.parent.removeChild(self.playerGraphics); } else if (self.playerGraphics && self.playerGraphics.parent) { // Fallback self.playerGraphics.parent.removeChild(self.playerGraphics); } self.playerGraphics = null; // Clear reference // Trigger game over state after a short delay or death animation LK.setTimeout(function () { if (gameState.currentState === "game") { // Only transition if still in game state gameState.gameOver(true); // Call game over function with death reason } }, 1000); // Delay game over screen by 1 second }; self.update = function () { // Update only if in game state if (gameState.currentState !== "game") { // Stop rolling and remove animation if state changes if (self.rolling) { self.rolling = false; if (self.rollAnim) { self.rollAnim.stop(); if (self.rollAnim.parent === self) { self.rollAnim.parent.removeChild(self.rollAnim); } else if (self.rollAnim.parent) { // Fallback self.rollAnim.parent.removeChild(self.rollAnim); } self.rollAnim.destroy(); self.rollAnim = null; } // Add back idle graphic if it was removed if (!self.playerGraphics) { self.playerGraphics = self.addChild(self.attachAsset('player', { anchorX: 0.5, anchorY: 0.5, x: 0, y: 0 // Relative position })); } } return; // Exit update if not in game state } // Do not update if player is dead if (self.dead) { return; } // --- Rolling logic --- if (self.rolling) { self.x += self.rollDirectionX * self.rollSpeed; self.y += self.rollDirectionY * self.rollSpeed; self.rollTimer--; if (self.rollTimer <= 0) { self.rolling = false; // Stop and remove roll animation if (self.rollAnim) { self.rollAnim.stop(); if (self.rollAnim.parent === self) { self.rollAnim.parent.removeChild(self.rollAnim); } else if (self.rollAnim.parent) { // Fallback self.rollAnim.parent.removeChild(self.rollAnim); } self.rollAnim.destroy(); self.rollAnim = null; } // Add back idle graphic if (!self.playerGraphics) { self.playerGraphics = self.addChild(self.attachAsset('player', { anchorX: 0.5, anchorY: 0.5, x: 0, y: 0 // Relative position })); } } } else { // --- Movement logic (only when not rolling) --- var moveX = (LK.isDown("left") ? -1 : 0) + (LK.isDown("right") ? 1 : 0); var moveY = (LK.isDown("up") ? -1 : 0) + (LK.isDown("down") ? 1 : 0); var moveMagnitude = Math.sqrt(moveX * moveX + moveY * moveY); if (moveMagnitude > 0) { moveX /= moveMagnitude; moveY /= moveMagnitude; self.x += moveX * self.speed; self.y += moveY * self.speed; } // No specific idle animation in this example, uses static player graphic } // --- Invulnerability timer --- if (self.invulnerable) { self.invulnerabilityTimer--; if (self.invulnerabilityTimer <= 0) { self.invulnerable = false; } // Optional: add visual effect for invulnerability (e.g., alpha blinking) self.alpha = self.invulnerabilityTimer % 10 < 5 ? 0.5 : 1; // Simple blinking effect } else { self.alpha = 1; // Fully visible when not invulnerable } // --- Boundary checks (Arena borders) --- // Assuming arena borders are at 100px from edge, map 2048x2732 var halfWidth = self.width * self.scaleX / 2; var halfHeight = self.height * self.scaleY / 2; var minX = 100 + halfWidth; var maxX = 2048 - 100 - halfWidth; var minY = 100 + halfHeight; var maxY = 2732 - 100 - halfHeight; self.x = Math.max(minX, Math.min(self.x, maxX)); self.y = Math.max(minY, Math.min(self.y, maxY)); // --- Collision with Boss (only when not invulnerable) --- // Boss collision check is handled in player.update(), NOT boss.update() // It should only happen if player is NOT invulnerable // This collision check is handled in game.update -> player.update() // Let's add the check here if player.update is responsible for it. // Check collision with Boss if boss exists, is not dead, and player is not invulnerable if (boss && !boss.dead && !self.invulnerable) { var dx_b = boss.x - self.x; var dy_b = boss.y - self.y; var distance_b = Math.sqrt(dx_b * dx_b + dy_b * dy_b); var playerRadius_b = self.width / 2; var bossRadius_b = boss.width * boss.scaleX / 2; // Use scaled boss width if (distance_b < playerRadius_b + bossRadius_b) { // Collision detected with Boss body self.takeDamage(1); // Player takes 1 damage } } // Note: Collision with Boss *attacks* is handled in boss.update() }; return self; }); /**** * Initialize Game ****/ // End Player class /**** * UI object ****/ var game = new LK.Game({ backgroundColor: 0x000000 }); /**** * Game Code ****/ /**** * Global variables ****/ var player = null; var boss = null; var ui = null; var storage = null; var isNewBossPlusMode = false; var game = null; var gameContainer = "gameContainer"; // ID elementu HTML, w którym będzie wyświetlana gra var gameOverReasonIsDeath = true; // true jeśli game over z powodu śmierci, false jeśli z powodu czasu w Boss+ /**** * Game state manager ****/ var gameState = function () { var self = {}; self.currentState = "loading"; // Initial state self.changeState = function (newState) { console.log("Changing state from", self.currentState, "to", newState); // Log state changes self.currentState = newState; // Manage visibility of game elements based on state // These position/visibility methods should be in gameState or UI, not individual element updates self.positionElements(); // Call positionElements on state change // Handle specific state transitions if (self.currentState === "title") { // Clear any game elements from previous attempts if necessary if (game) { game.removeChildren(); // Remove all children from the game container // Do not destroy game here, it's handled in createGame } // Ensure player and boss are null when returning to title player = null; boss = null; ui = null; // UI will be recreated in createGame if returning from game over // Ensure attacks are cleared if (boss && boss.attacks) { boss.attacks.forEach(function (attack) { if (attack.visual && attack.visual.destroy && !attack.visual.destroyed) { attack.visual.destroy(); } }); boss.attacks = []; } // If boss object is destroyed, self.attacks check might fail, add a check for that // Display title screen elements // In this simple example, we just show the start button via positionElements } else if (self.currentState === "game") { // Start the game loop when entering game state // The gameLoop is already running based on LK.init settings (60fps) // Player and Boss are created just before changing to 'game' state // Reset player/boss health or properties if necessary (or do it in their constructors) if (player) { player.health = storage.maxHearts; // Reset player health // ui.updateHearts(player.health, storage.maxHearts); // Update UI immediately } if (boss) { boss.health = boss.maxHealth; // Reset boss health // ui.updateBossHealth(boss.health, boss.maxHealth); // Update UI immediately boss.attackCooldown = 60; // Initial delay before first boss attack (1 second at 60fps) boss.stunned = false; // Ensure boss is not stunned boss.repositioning = false; // Ensure boss is not repositioning boss.dead = false; // Ensure boss is not dead boss.phase = 1; // Reset boss phase boss.attackSpeedMultiplier = 1; // Reset attack speed multiplier boss.attacks = []; // Clear any lingering attacks // Reset boss graphic state if needed (e.g., tint) boss.tint = 0xFFFFFF; // Reset tint } } else if (self.currentState === "gameOver") { // Stop game loop? LK manages this, or we can stop updates manually in gameLoop // In this example, gameLoop checks gameState.currentState // Display game over screen elements (handled by positionElements) // Stop boss attacks explicitly if boss exists and has a method for it if (boss) { boss.attackCooldown = 9999; // Prevent new attacks // Ensure existing attacks are cleaned up in boss.update when state changes } // Stop player movement, etc. (handled by player.update checking gameState.currentState) // Clean up game elements from screen? gameState.positionElements might hide them. } else if (self.currentState === "grillScreen") { // Similar cleanup/display logic for grill screen if (boss) { boss.attackCooldown = 9999; // Prevent new attacks } // Ensure existing attacks are cleaned up if (boss && boss.attacks) { boss.attacks.forEach(function (attack) { if (attack.visual && attack.visual.destroy && !attack.visual.destroyed) { attack.visual.destroy(); } }); boss.attacks = []; } // Hide player and boss (handled by positionElements) } }; self.positionElements = function () { // This function manages the visibility and potentially position of game elements based on the current state // It should be called whenever the state changes. var startButton = LK.getObjectById("startButton"); // Przykład pobierania przycisku var bossHpBar = LK.getObjectById("bossHpBar"); var playerHearts = LK.getObjectById("playerHearts"); // Zakładając, że UI ma kontener na serca var gameOverMessage = LK.getObjectById("gameOverMessage"); // Przykład wiadomości game over var grillMenu = LK.getObjectById("grillMenu"); // Przykład menu grill screen // Visibility logic if (startButton) { startButton.visible = self.currentState === "title"; } // Pasek HP bossa widoczny tylko w stanie "game" (lub game over po czasie w Boss+) if (bossHpBar) { bossHpBar.visible = self.currentState === "game" || self.currentState === "gameOver" && !gameOverReasonIsDeath && isNewBossPlusMode; } // Serca gracza widoczne tylko w stanie "game" if (playerHearts) { playerHearts.visible = self.currentState === "game"; } if (gameOverMessage) { gameOverMessage.visible = self.currentState === "gameOver" && gameOverReasonIsDeath; } // Game over tylko ze śmierci if (grillMenu) { grillMenu.visible = self.currentState === "grillScreen" || self.currentState === "gameOver" && !gameOverReasonIsDeath && isNewBossPlusMode; } // Menu grill screen lub game over po czasie // Show player and boss only in game state if (player) { player.visible = self.currentState === "game" || self.currentState === "gameOver" || self.currentState === "grillScreen"; } // Pokaż gracza w game, game over, grill screen if (boss) { boss.visible = self.currentState === "game" || self.currentState === "gameOver" || self.currentState === "grillScreen"; } // Pokaż bossa w game, game over, grill screen }; self.gameOver = function () { var reasonIsDeath = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : true; gameOverReasonIsDeath = reasonIsDeath; // Zapisz przyczynę game over self.changeState("gameOver"); // Wyświetl wiadomość Game Over lub Grill Screen w zależności od przyczyny i trybu (zajmie się tym positionElements) // Tutaj można dodać logikę czyszczenia obiektów, zatrzymania muzyki itp. LK.getSound('playerHit').stop(); // Stop player hit sound if it's playing }; self.showGrillScreen = function () { self.changeState("grillScreen"); // Logic for showing the grill screen }; // Metody zmiany stanu dla przycisków self.startGame = function () { var newBossPlus = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : false; isNewBossPlusMode = newBossPlus; // Set the mode storage.newBossPlusMode = isNewBossPlusMode; // Save mode to storage // Wyczyść poprzednią grę i stwórz nową instancję createGame(); // This function now contains a setTimeout delay // Change state to game is now inside the setTimeout in createGame() // self.changeState("game"); // MOVED INSIDE TIMEOUT IN createGame }; self.returnToTitle = function () { // Clean up game elements before returning to title if (game) { game.removeChildren(); // Remove all display objects from the game container // Destroy player, boss, ui objects explicitly to clear references and updates if (player && player.destroy && !player.destroyed) { player.destroy(); } if (boss && boss.destroy && !boss.destroyed) { boss.destroy(); } if (ui && ui.destroy && !ui.destroyed) { ui.destroy(); } player = null; boss = null; ui = null; // game.destroy(); // Do not destroy game container itself, it's reused } // Clean up any lingering attacks if Boss object was null if (boss && boss.attacks) { // This check is redundant if boss is null, but safe boss.attacks.forEach(function (attack) { if (attack.visual && attack.visual.destroy && !attack.visual.destroyed) { attack.visual.destroy(); } }); boss.attacks = []; } self.changeState("title"); }; return self; }(); // End Player class /**** * UI object ****/ var UI = function () { var self = {}; // Create UI elements (depend on assets/shapes) self.bossHpBar = LK.getAsset('bossHpbar', {}); self.bossHpBar.x = 1024 - self.bossHpBar.width / 2; // Center the bar self.bossHpBar.y = 50; // Position near top self.bossHpBar.visible = false; // Hide initially self.playerHearts = new Container(); // Container for player hearts // Hearts will be added as children to this container in updateHearts self.playerHearts.x = 100; // Position the hearts container self.playerHearts.y = 50; self.playerHearts.visible = false; // Hide initially // Create start button (depends on asset/shape) self.startButton = LK.getAsset('button_bg', {}); // Example shape button self.startButton.x = 1024; // Center the button self.startButton.y = 1366; // Position it self.startButton.anchorX = 0.5; self.startButton.anchorY = 0.5; self.startButton.visible = false; // Hide initially // Add text to start button (depends on font/text rendering) self.startButtonText = new Text2("Start Game", { fill: 0x000000, size: 40, font: "Arial", anchorX: 0.5, anchorY: 0.5 }); self.startButton.addChild(self.startButtonText); // Add text as child of button // Add event listener to start button self.startButton.on("click", function () { if (gameState.currentState === "title") { gameState.startGame(); // Start game in standard mode } }); // Create Boss+ button (depends on asset/shape) self.newBossPlusButton = LK.getAsset('button_bg', {}); // Example shape button self.newBossPlusButton.x = 1024; // Center self.newBossPlusButton.y = 1500; // Position below start self.newBossPlusButton.anchorX = 0.5; self.newBossPlusButton.anchorY = 0.5; self.newBossPlusButton.visible = false; // Add text to Boss+ button self.newBossPlusButtonText = new Text2("NEW BOSS+", { fill: 0x000000, size: 40, font: "Arial", anchorX: 0.5, anchorY: 0.5 }); self.newBossPlusButton.addChild(self.newBossPlusButtonText); // Add event listener to Boss+ button self.newBossPlusButton.on("click", function () { if (gameState.currentState === "title") { gameState.startGame(true); // Start game in Boss+ mode } }); // Create Game Over message (depends on text rendering) self.gameOverMessage = new Text2("Game Over!", { fill: 0xFF0000, size: 80, font: "Arial", anchorX: 0.5, anchorY: 0.5, x: 1024, // Center X y: 1366, // Center Y visible: false // Hide initially }); // Create Grill Screen menu (depends on shape) self.grillMenu = LK.getAsset('grillMenu', { anchorX: 0.5, anchorY: 0.5 }); self.grillMenu.x = 1024; self.grillMenu.y = 1366; self.grillMenu.visible = false; // Hide initially // Add buttons/text to Grill Screen if needed // Add all UI elements to the game container (depth/layering handled by addChild order or setChildIndex) // Order matters for layering: background first, then game objects, then UI on top // Assuming UI should be on top of everything else in the 'game' container // Add UI elements AFTER adding player, boss, etc. in createGame // Or add them here and manage z-index // Let's add them here and assume later added elements are on top by default or managed by setChildIndex self.updateBossHealth = function (currentHp, maxHp) { // Ensure maxHp is at least 1 to prevent division by zero var safeMaxHp = Math.max(1, maxHp); // Calculate width based on health percentage var hpPercentage = currentHp / safeMaxHp; // Assuming original bossHpBar width was 800 self.bossHpBar.scaleX = hpPercentage; // Adjust position if anchor is not 0,0 to keep the bar filling from left // If anchorX is 0.5, the bar scales from the center. Need to adjust x. self.bossHpBar.x = 1024 - self.bossHpBar.width * self.bossHpBar.scaleX / 2; }; self.updateHearts = function (currentHearts, maxHearts) { // Clear existing heart graphics self.playerHearts.removeChildren(); // Add heart graphics based on currentHearts var heartSize = 40; // Match heart asset size var padding = 10; // Spacing between hearts for (var i = 0; i < maxHearts; i++) { var heartGraphic = LK.getShape('heart', {}); // Use heart shape asset // Position hearts horizontally within the container heartGraphic.x = i * (heartSize + padding); heartGraphic.y = 0; // All on the same vertical level // Tint heart based on current health if (i < currentHearts) { heartGraphic.tint = 0xFF0000; // Full heart color (Red) } else { heartGraphic.tint = 0x888888; // Empty heart color (Grayish) } self.playerHearts.addChild(heartGraphic); // Add heart to the container } // Position the container is done in gameState.positionElements or createGame }; // Add UI elements to game container in createGame after player/boss // This is handled in the modified createGame function self.update = function () { // UI elements are mostly passive and updated by boss/player health changes // No continuous update needed for UI elements themselves in this example, // but UI object could have update logic for animations, etc. }; return self; }(); // End UI object /**** * Boss class ****/ var Boss = Container.expand(function () { var self = Container.call(this); // Definicje list klatek dla fireballi - Zdefiniowane raz na początku klasy Boss var circleFrames = [LK.getAsset('fireball0', {}), LK.getAsset('fireball00', {}), LK.getAsset('fireball01', {}), LK.getAsset('fireball02', {}), LK.getAsset('fireball03', {}), LK.getAsset('fireball04', {}), LK.getAsset('fireball05', {}), LK.getAsset('fireball06', {}), LK.getAsset('fireball07', {}), LK.getAsset('fireball08', {}), LK.getAsset('fireball09', {}), LK.getAsset('fireball1', {}), LK.getAsset('fireball10', {}), LK.getAsset('fireball11', {}), LK.getAsset('fireball12', {}), LK.getAsset('fireball13', {}), LK.getAsset('fireball14', {})]; var lineFrames = [LK.getAsset('fireball2', {}), LK.getAsset('fireball3', {}), LK.getAsset('fireball4', {}), LK.getAsset('fireball5', {}), LK.getAsset('fireball6', {}), LK.getAsset('fireball7', {}), LK.getAsset('fireball8', {}), LK.getAsset('fireball9', {}), LK.getAsset('fireball15', {}), LK.getAsset('fireball16', {})]; // *** MODYFIKACJA: Dodaj grafikę bossIdle jako DZIECKO obiektu bossa (self) *** // Używamy LK.getAsset z self.addChild, aby dodać jako dziecko do self self.bossGraphics = self.addChild(LK.getAsset('bossIdle', { // Dziecko self anchorX: 0.5, anchorY: 0.5 // Pozycja relatywna na 0,0 jest domyślna i poprawna przy anchorX/Y 0.5 })); // Dodajemy funkcje animacji // Ta funkcja tworzy obiekt SpriteAnimation dla animacji ataku Bossa (nie fireballi) self.createBossAttackAnim = function () { var frames = [LK.getAsset('bossAttack0', {}), LK.getAsset('bossAttack1', {}), LK.getAsset('bossAttack2', {}), LK.getAsset('bossAttack3', {})]; var bossAttackAnim = new SpriteAnimation({ frames: frames, frameDuration: 100, // Czas trwania klatki animacji ataku Bossa (w ms) loop: false, anchorX: 0.5, anchorY: 0.5, // Pozycja animacji jako dziecka bossa - ustawiamy na 0,0 relatywnie do rodzica (self) x: 0, // MODIFIED y: 0 // MODIFIED }); return bossAttackAnim; }; // Zmieniamy sygnaturę funkcji, przyjmuje teraz typ ataku (do decydowania o grafice Bossa) // Ta funkcja zarządza grafiką Bossa (animacja ataku Bossa lub grafika idle) self.playBossAttackAnim = function (attackType) { // <--- DODANO PARAMETR // Upewnij się, że poprzednia animacja ataku Bossa jest całkowicie usunięta, zanim zaczniemy nową if (self.bossAttackAnim) { self.bossAttackAnim.stop(); // Zatrzymujemy animację // Usuń poprzednią animację z jej rodzica (Bossa) przed zniszczeniem // Sprawdź, czy rodzicem jest self przed próbą usunięcia if (self.bossAttackAnim.parent === self) { // MODIFIED check self.bossAttackAnim.parent.removeChild(self.bossAttackAnim); } else if (self.bossAttackAnim.parent) { // Fallback na wypadek, gdyby rodzic był inny self.bossAttackAnim.parent.removeChild(self.bossAttackAnim); } self.bossAttackAnim.destroy(); // Usuwamy poprzednią animację self.bossAttackAnim = null; // Ustawiamy na null po zniszczeniu } // *** Zmieniona logika: Zmiana grafiki bossa tylko jeśli to NIE jest atak szarży (attackType 2) *** if (attackType !== 2) { // Jeśli to atak koła (0) lub linii (1) // Usuń obecną główną grafikę bossa (idle) jako dziecko bossa // Sprawdź, czy rodzicem jest self przed próbą usunięcia if (self.bossGraphics && self.bossGraphics.parent === self) { // MODIFIED check self.bossGraphics.parent.removeChild(self.bossGraphics); } else if (self.bossGraphics && self.bossGraphics.parent) { // Fallback self.bossGraphics.parent.removeChild(self.bossGraphics); } self.bossGraphics = null; // Wyczyść referencję // Tworzymy nową animację bossa i dodajemy ją JAKO DZIECKO BOSSA self.bossAttackAnim = self.addChild(self.createBossAttackAnim()); // *** MODIFIED: DODANO DO self! *** // Pozycja animacji jako dziecka bossa jest już ustawiona na 0,0 w createBossAttackAnim // Metoda update dla TEJ NOWEJ animacji (definiowana tylko dla ataków 0 i 1) self.bossAttackAnim.update = function () { // Sprawdź, czy ten obiekt animacji jest nadal aktywny self.bossAttackAnim bossa // Użyj 'this' dla obiektu animacji, 'self' dla obiektu Boss if (self.bossAttackAnim !== this || !this.playing || this.frames.length === 0) { // Jeśli już nie jesteśmy aktualną animacją lub nie gramy, zakończ return; } this.frameTimer++; if (this.frameTimer >= this.frameDuration / (1000 / 60)) { // Przelicz ms na klatki gry (przy 60fps) this.frameTimer = 0; this.removeChildren(); // Usuń sprite klatki z kontenera animacji (z obiektu animacji) this.currentFrame++; if (this.currentFrame >= this.frames.length) { // Animacja skończona, wracamy do idle // *** Dodaj grafikę idle z powrotem JAKO DZIECKO BOSSA i ustaw self.bossGraphics *** self.bossGraphics = self.addChild(LK.getAsset('bossIdle', { // *** MODIFIED: DODANO DO self! *** anchorX: 0.5, anchorY: 0.5, x: 0, // Pozycja relatywna y: 0 // Pozycja relatywna })); // *** Usuń obiekt animacji z jego rodzica (Bossa) i zniszcz go *** // Sprawdź, czy rodzicem jest self przed próbą usunięcia if (this.parent === self) { // MODIFIED check this.parent.removeChild(this); // Użyj 'this' dla obiektu animacji } else if (this.parent) { // Fallback this.parent.removeChild(this); } this.destroy(); // Zniszcz obiekt animacji self.bossAttackAnim = null; // Wyczyść referencję w obiekcie bossa } else { this.addChild(this.frames[this.currentFrame]); // Dodaj sprite następnej klatki (do obiektu animacji) } } }; } // Else (attackType === 2, czyli chargeAttack): playBossAttackAnim nic nie robi z grafiką. // chargeAttack sam zadba o ustawienie grafiki 'idle' JAKO DZIECKO BOSSA. }; self.health = 100; // Domyślne zdrowie bossa (nadpisane w startGame) self.maxHealth = 100; // Domyślne max zdrowia bossa (nadpisane w startGame) self.speed = 5; // Prędkość ruchu bossa self.attackCooldown = 0; // Czas do następnego ataku (w klatkach gry) self.attackPattern = 0; self.attacks = []; // Aktywne ataki bossa (obiekty JS z pozycją, promieniem, itp.) self.stunned = false; self.stunDuration = 0; self.dead = false; self.phase = 1; self.attackSpeedMultiplier = 1; // Mnożnik prędkości ataków (1 = normalna, < 1 = szybsza) self.repositioning = false; // Flaga informująca czy boss się przemieszcza po szarży // startAttackPattern pozostaje jak w poprzedniej poprawionej wersji self.startAttackPattern = function () { // !!! Ustaw tymczasowy cooldown na początku, ZANIM sprawdzisz warunki powrotu !!! // To zapobiegnie natychmiastowemu ponownemu wywołaniu, jeśli metoda wróci wcześnie self.attackCooldown = 1; // Ustaw minimalny cooldown (np. 1 klatka) od razu // Sprawdź warunki wczesnego powrotu: boss jest martwy, gra nie jest w stanie "game" LUB boss jest w stanie "repositioning" if (self.dead || gameState.currentState !== "game" || self.repositioning) { // Dodano || self.repositioning return; // Jeśli warunek spełniony, wróć } // Decydujemy O WZORCU ATAKU przed zmianą grafiki bossa self.attackPattern = (self.attackPattern + 1) % 3; // Cykl wzorców ataków (0, 1, 2) // 🔥 Przekazujemy typ ataku do playBossAttackAnim - ta funkcja teraz decyduje CZY zmienić grafikę bossa self.playBossAttackAnim(self.attackPattern); // <--- PRZEKAZUJEMY TYP ATAKU // Następnie wywołujemy właściwą funkcję ataku (chargeAttack itp.) // Logika graficzna dla szarży jest TERAZ W chargeAttack switch (self.attackPattern) { case 0: self.circleAttack(); // Wywołaj atak koła break; case 1: self.lineAttack(); // Wywołaj atak linii break; case 2: self.chargeAttack(); // Wywołaj atak szarży break; } // Ustaw właściwy czas odnowienia dla następnego ataku self.attackCooldown = (90 + Math.floor(Math.random() * 60)) * self.attackSpeedMultiplier; // 1.5-2.5 seconds * multiplier }; // Zmodyfikowana funkcja createAttack - NIE nadpisuje lokalnie spriteAnim.update (jak w poprzedniej próbie) // Kolejność argumentów: x, y, duration (ms), activationFrame (index klatki), frameDurationMs (ms), framesList (opcjonalne) // Logika aktywacji kolizji (isActive) jest w Boss.update self.createAttack = function (x, y, duration, activationFrame, frameDurationMs, framesList) { // Użyj przekazanej framesList, domyślną oryginalną jeśli nie przekazana var frames = framesList || [LK.getAsset('fireball0', {}), LK.getAsset('fireball00', {}), LK.getAsset('fireball01', {}), LK.getAsset('fireball02', {}), LK.getAsset('fireball03', {}), LK.getAsset('fireball04', {}), LK.getAsset('fireball05', {}), LK.getAsset('fireball06', {}), LK.getAsset('fireball07', {}), LK.getAsset('fireball08', {}), LK.getAsset('fireball09', {}), LK.getAsset('fireball1', {}), LK.getAsset('fireball10', {}), LK.getAsset('fireball11', {}), LK.getAsset('fireball12', {}), LK.getAsset('fireball13', {}), LK.getAsset('fireball14', {})]; // console.log("CreateAttack called at:", x, y, " Frames array:", frames); // Debug log - można usunąć po naprawie // if (!frames || frames.length === 0 || frames.some(f => f === null || typeof f === 'undefined' || !f.texture)) { // Debug check - można usunąć po naprawie // console.error("Frames array is empty or contains invalid assets! Attack creation might fail."); // Debug log - można usunąć po naprawie // } var spriteAnim = game.addChild(new SpriteAnimation({ // Wizualizacja fireballa jest dodawana do kontenera 'game' frames: frames, frameDuration: frameDurationMs, // Użyj wartości przekazanej do konstruktora SpriteAnimation loop: false, // Animacja nie zapętla się anchorX: 0.5, anchorY: 0.5, x: x, y: y })); spriteAnim.scaleX = 1.6; // Ręczne skalowanie wizualizacji spriteAnim.scaleY = 1.6; var attack = { // Obiekt logiczny ataku x: x, y: y, radius: 60, // Promień kolizji visual: spriteAnim, // Referencja do wizualizacji (SpriteAnimation) lifeTime: Math.floor(duration * (60 / 1000)), // Czas życia w klatkach gry (przy 60fps) isActive: false, // Flaga aktywności kolizji, domyślnie false activationFrame: activationFrame // Przechowuj activationFrame w obiekcie ataku }; // USUNIĘTO: Lokalna definicja funkcji spriteAnim.update self.attacks.push(attack); // Dodaj obiekt ataku logicznego do tablicy bossa }; // Zmodyfikowana funkcja circleAttack - Z pętlą setTimeout delay (jak w poprzedniej próbie, która dała wiele pocisków) self.circleAttack = function () { LK.getSound('bossAttack').play(); // Odtwórz dźwięk ataku var center = { // Ustaw środek koła na pozycji bossa x: self.x, y: self.y }; var count = isNewBossPlusMode ? 12 : 8; // Liczba pocisków w ataku (więcej w New Boss+) var radius = 300; // Promień koła, na którym rozmieszczone są pociski var attackLifeTime = isNewBossPlusMode ? 2000 : 3000; // Czas życia pocisku w ms (krótszy w New Boss+?) // circleFrames array jest zdefiniowane raz na początku klasy Boss // Pętla tworząca wiele pocisków rozmieszczonych w kole Z MINIMALNYM OPÓŹNIENIEM DLA KAŻDEGO for (var i = 0; i < count; i++) { // Iteruj tyle razy, ile ma być pocisków (count) var angle = i / count * Math.PI * 2; // Oblicz kąt dla bieżącego pocisku (równo rozmieszczone na okręgu) var posX = center.x + Math.cos(angle) * radius; // Oblicz współrzędną X na okręgu var posY = center.y + Math.sin(angle) * radius; // Oblicz współrzędną Y na okręgu // Dodaj minimalne opóźnienie (np. 20ms) DO KAŻDEGO pocisku var delay = i * 20 + 20; // Zapewnij, że nawet pierwszy pocisk ma opóźnienie // Użyj setTimeout do stworzenia każdego pocisku po małym opóźnieniu LK.setTimeout(function (x, y) { // Używamy closure do "zamknięcia" wartości x, y (frames jest globalne/dostępne) return function () { // Sprawdź czy boss nadal istnieje i gra jest w stanie 'game' zanim stworzysz atak po opóźnieniu if (self && !self.dead && gameState.currentState === "game") { // Wywołaj funkcję createAttack dla tego pocisku z zapamiętaną pozycją i parametrami // Kolejność argumentów createAttack: x, y, duration(ms), activationFrame(index), frameDurationMs(ms), framesList // activationFrame = 8 (aktywacja od klatki fireball08) // frameDurationMs = 250ms (przykład prędkości) // framesList = circleFrames (używamy zmiennej zdefiniowanej poza funkcją) self.createAttack(x, y, attackLifeTime, 8, 250, circleFrames); // Używamy 250ms dla frameDurationMs } }; }(posX, posY), delay); // Przekaż aktualne posX, posY do closure } }; // *** START Zmodyfikowana funkcja lineAttack - Używa synchronicznej pętli i wywołuje nową createAttack *** self.lineAttack = function () { LK.getSound('bossAttack').play(); // Odtwórz dźwięk ataku // Tworzy linię ataków if (!player) { return; } var targetX = player.x; var targetY = player.y; var count = isNewBossPlusMode ? 8 : 5; // Liczba pocisków w linii var attackLifeTime = isNewBossPlusMode ? 1500 : 2000; // Czas życia pocisku w ms var delayBetweenAttacks = isNewBossPlusMode ? 100 : 200; // Opóźnienie między pociskami w ms // lineFrames array jest zdefiniowane raz na początku klasy // Pętla tworząca pociski w linii z opóźnieniem (jak w souls5.txt) for (var i = 0; i < count; i++) { var t = i / (count - 1); // Współczynnik interpolacji var posX = self.x + (targetX - self.x) * t; // Interpolowana pozycja X var posY = self.y + (targetY - self.y) * t; // Interpolowana pozycja Y var delay = i * delayBetweenAttacks * self.attackSpeedMultiplier; // Opóźnienie przed stworzeniem pocisku, skalowane LK.setTimeout(function (x, y) { // Używamy closure do przekazania pozycji return function () { // Sprawdź warunki przed stworzeniem ataku po opóźnieniu if (self && !self.dead && gameState.currentState === "game") { // Wywołaj funkcję createAttack // Kolejność argumentów createAttack: x, y, duration(ms), activationFrame(index), frameDurationMs(ms), framesList // activationFrame = 3 (indeks 'fireball5' na liście lineFrames) // frameDurationMs = 100ms (przykład prędkości) // framesList = lineFrames (używamy zmiennej zdefiniowanej poza funkcją) self.createAttack(x, y, attackLifeTime, 3, 100, lineFrames); // Przekaż parametry } }; }(posX, posY), delay); // Przekaż aktualne posX, posY do closure } }; // *** END Zmodyfikowana funkcja lineAttack *** // *** START Zmodyfikowana funkcja chargeAttack - tworzy pociski podczas szarży *** // Ta funkcja zarządza ruchem bossa (szarżą) i tworzy pociski wzdłuż ścieżki szarży self.chargeAttack = function () { LK.getSound('bossAttack').play(); // Odtwórz dźwięk szarży // Upewnij się, że gracz istnieje if (!player) { return; } // Obsługa grafiki bossa podczas szarży (pokazuje grafikę idle) if (self.bossAttackAnim) { self.bossAttackAnim.stop(); if (self.bossAttackAnim.parent === self) { self.bossAttackAnim.parent.removeChild(self.bossAttackAnim); } else if (self.bossAttackAnim.parent) { self.bossAttackAnim.parent.removeChild(self.bossAttackAnim); } self.bossAttackAnim.destroy(); self.bossAttackAnim = null; } if (self.bossGraphics && self.bossGraphics.parent === self) { self.bossGraphics.parent.removeChild(self.bossGraphics); } else if (self.bossGraphics && self.bossGraphics.parent) { self.bossGraphics.parent.removeChild(self.bossGraphics); } self.bossGraphics = self.addChild(LK.getAsset('bossIdle', { anchorX: 0.5, anchorY: 0.5, x: 0, y: 0 })); // Oblicz kierunek do gracza i parametry szarży var dx = player.x - self.x; var dy = player.y - self.y; var distance = Math.sqrt(dx * dx + dy * dy); if (distance > 0) { dx /= distance; dy /= distance; } var startX = self.x; // Pozycja początkowa szarży var startY = self.y; var chargeDistance = isNewBossPlusMode ? 700 : 500; // Dystans szarży var chargeDuration = isNewBossPlusMode ? // Czas trwania szarży 600 : 800; var attacksOnPathCount = isNewBossPlusMode ? 8 : 5; // Liczba pocisków wzdłuż ścieżki var attackLifeTime = isNewBossPlusMode ? 1000 : 1500; // Czas życia pocisku // Tween (animacja ruchu) bossa podczas szarży tween(self, { x: self.x + dx * chargeDistance, y: self.y + dy * chargeDistance }, { duration: chargeDuration * self.attackSpeedMultiplier, // Czas trwania, skalowany easing: tween.easeIn, // Krzywa animacji onFinish: function onFinish() { // Po zakończeniu szarży if (self && !self.dead && gameState.currentState === "game") { // Faza repozycji self.repositioning = true; LK.setTimeout(function () { if (self && !self.dead) { self.repositioning = false; } }, 500 * self.attackSpeedMultiplier); // Tween powrotu do pozycji startowej tween(self, { x: startX, y: startY }, { duration: 1000 * self.attackSpeedMultiplier, easing: tween.easeOut }); // Utworzenie ataków (fireballi) wzdłuż ścieżki szarży // Fireballe ze szarży używają domyślnej listy klatek z createAttack (circleFrames) for (var i = 0; i < attacksOnPathCount; i++) { var t = i / (attacksOnPathCount - 1); // Współczynnik interpolacji var currentChargeX = self.x; // Aktualna pozycja bossa (koniec szarży) var currentChargeY = self.y; var posX = startX + (currentChargeX - startX) * t; // Interpolowana pozycja X var posY = startY + (currentChargeY - startY) * t; // Interpolowana pozycja Y // Utwórz atak // Kolejność argumentów createAttack: x, y, duration(ms), activationFrame(index), frameDurationMs(ms), framesList // activationFrame = 4 (domyślna klatka aktywacji dla tych pocisków) // frameDurationMs = 140ms (domyślna prędkość) // framesList = circleFrames (używamy listy klatek ataku koła) self.createAttack(posX, posY, attackLifeTime, 4, 140, circleFrames); // Przekaż parametry } } } }); }; self.takeDamage = function (amount) { // Funkcja obsługująca otrzymywanie obrażeń przez bossa if (self.dead || gameState.currentState !== "game") { // Boss otrzymuje obrażenia tylko w stanie gry return; } self.health -= amount; // Zmniejsz zdrowie self.health = Math.max(0, self.health); // Upewnij się, że zdrowie nie spadnie poniżej zera LK.effects.flashObject(self, 0xFFFFFF, 200); // Efekt błysku if (!isNewBossPlusMode) { // Przejście fazy bossa przy 50% zdrowia tylko w standardowym trybie if (self.health <= self.maxHealth / 2 && self.phase === 1) { self.phase = 2; // Zmień fazę na 2 self.speed += 2; // Boss staje się szybszy (ruch) self.attackSpeedMultiplier *= 0.8; // Boss atakuje nieco szybciej tween(self, { tint: 0xFF3300 // Pomarańczowy/czerwony odcień }, { duration: 1000, easing: tween.easeInOut }); } } if (self.health <= 0) { self.die(); // Wywołaj funkcję śmierci } // ui.updateBossHealth jest w game.update }; self.die = function () { // Funkcja obsługująca śmierć bossa if (self.dead || gameState.currentState !== "game") { return; } self.dead = true; // Ustaw flagę śmierci if (!isNewBossPlusMode) { // Dźwięk zwycięstwa tylko dla STANDARDOWEGO trybu LK.getSound('victory').play(); } // Wyczyść pozostałe ataki przy śmierci bossa self.attacks.forEach(function (attack) { if (attack.visual && attack.visual.destroy && !attack.visual.destroyed) { attack.visual.destroy(); } }); self.attacks = []; // Wyczyść tablicę ataków logicznych // Animacja śmierci bossa (zanikanie i powiększanie) tween(self, { alpha: 0, scaleX: 2, scaleY: 2 }, { duration: 2000, easing: tween.easeOut, onFinish: function onFinish() { // Po zakończeniu animacji śmierci if (self && self.destroy && !self.destroyed) { self.destroy(); // Zniszcz obiekt bossa } storage.bossesDefeated = (storage.bossesDefeated || 0) + 1; // Zwiększ licznik pokonanych bossów // ZAWSZE przechodzimy do Grill Screena gameState.showGrillScreen(); } }); }; self.update = function () { // Główna metoda aktualizacji bossa // Aktualizuj tylko w stanie gry if (gameState.currentState !== "game") { if (!self.dead && self.attacks) { // Jeśli zmieniono stan i boss nie umiera, wyczyść ataki self.attacks.forEach(function (attack) { if (attack.visual && attack.visual.destroy && !attack.visual.destroyed) { attack.visual.destroy(); } }); self.attacks = []; } return; } // Nie aktualizuj jeśli boss jest martwy (pozwól animacji śmierci działać) if (self.dead) { return; } // --- LOGIKA RUCHU BOSS --- // Poruszaj się w kierunku gracza jeśli spełnione warunki if (!self.repositioning && self.attackCooldown > 30 && player && !player.dead) { var dx = player.x - self.x; var dy = player.y - self.y; var distance = Math.sqrt(dx * dx + dy * dy); var moveSpeed = self.speed; if (distance > 150) { // Poruszaj się tylko jeśli gracz jest dalej niż 150 jednostek var moveX = dx / distance * moveSpeed; var moveY = dy / distance * moveSpeed; var nextX = self.x + moveX; var nextY = self.y + moveY; // Ograniczenia areny var halfWidth = self.width * self.scaleX / 2; var halfHeight = self.height * self.scaleY / 2; var minX = 100 + halfWidth; var maxX = 2048 - 100 - halfWidth; var minY = 100 + halfHeight; var maxY = 2732 - 100 - halfHeight; self.x = Math.max(minX, Math.min(nextX, maxX)); self.y = Math.max(minY, Math.min(nextY, maxY)); // Grafika jako dziecko podąża automatycznie. } } // --- KONIEC LOGIKA RUCHU BOSS --- // --- OBSŁUGA ATAKÓW BOSS (Kolizje i Czas Życia Pocisków) --- // Aktualizuj istniejące ataki bossa (pociski) i sprawdzaj kolizje z graczem for (var i = self.attacks.length - 1; i >= 0; i--) { var attack = self.attacks[i]; // Aktualizuj flagę isActive na podstawie aktualnej klatki visual i przechowywanego activationFrame // Sprawdź czy visual istnieje i ma currentFrame if (attack.visual && typeof attack.visual.currentFrame !== 'undefined') { if (attack.visual.currentFrame >= attack.activationFrame) { attack.isActive = true; // Aktywuj kolizję } else { attack.isActive = false; // Kolizja nieaktywna } } else { attack.isActive = false; } if (attack.lifeTime > 0) { // Zmniejszaj czas życia attack.lifeTime--; } // Sprawdź kolizję z graczem jeśli spełnione warunki if (attack.isActive && player && !player.dead && !player.invulnerable && attack.visual && !attack.visual.destroyed) { var dx_p = player.x - attack.x; var dy_p = player.y - attack.y; var distance_p = Math.sqrt(dx_p * dx_p + dy_p * dy_p); var playerRadius_p = player.width / 2; var attackRadius = attack.radius; if (distance_p < playerRadius_p + attackRadius) { player.takeDamage(1); // Gracz otrzymuje obrażenia if (attack.visual && attack.visual.destroy) { attack.visual.destroy(); } self.attacks.splice(i, 1); // Usuń trafiony atak // break; } } // Usuń atak, jeśli skończył mu się czas życia LUB wizualizacja zniszczona if (attack.lifeTime <= 0 || attack.visual && attack.visual.destroy || attack.visual && attack.visual.destroyed) { // Dodano trzeci warunek dla pewności if (attack.visual && attack.visual.destroy && !attack.visual.destroyed) { attack.visual.destroy(); } self.attacks.splice(i, 1); } } // --- KONIEC OBSŁUGA ATAKÓW BOSS --- if (self.attackCooldown > 0) { // Zmniejszaj cooldown self.attackCooldown--; } // Sprawdź, czy nadszedł czas na nowy atak if (self.attackCooldown <= 0 && !self.repositioning) { self.startAttackPattern(); } }; return self; // Zwróć instancję obiektu Boss })(); // End Boss class /**** * Game creation and loop ****/ // Global variables for game objects // Already defined at the beginning: var player, boss, ui, game, gameContainer, etc. var createGame = function createGame() { // Upewnij się, że poprzednia instancja gry jest posprzątana if (game) { game.destroy(); game = null; } game = new DisplayObjectContainer(); // Stwórz główny kontener gry LK.add(game); // Dodaj kontener gry do frameworka LK // Stwórz i dodaj podstawowe elementy tła od razu (nie zależą od assetów fireballi) gameState.createWalls(); // Tworzy i dodaje ściany gameState.createFloor(); // Tworzy i dodaje podłogę // *** MODYFIKACJA: Dodaj opóźnienie setTimeout przed tworzeniem elementów zależnych od assetów *** // Daje to assetom czas na załadowanie po odpaleniu onInit przez LK.init LK.setTimeout(function () { // *** Kod z oryginalnego createGame, który tworzył elementy zależne od assetów, PRZENIESIONY tutaj *** // Stwórz i dodaj gracza (zależy od assetów) player = new Player(); // Klasa Player prawdopodobnie używa assetów game.addChild(player); // Stwórz i dodaj bossa (mocno zależy od assetów dla grafiki i ataków) boss = new Boss(); // Klasa Boss używa wielu assetów game.addChild(boss); // Stwórz i dodaj elementy UI (niektóre elementy UI mogą używać assetów) ui = new UI(); // Klasa UI prawdopodobnie używa assetów (serca, przyciski, itp.) game.addChild(ui); // Add UI elements to the game container (order matters for layering) // Assuming UI should be on top of game elements game.addChild(ui.bossHpBar); game.addChild(ui.playerHearts); game.addChild(ui.startButton); // Start button is visible in title state game.addChild(ui.newBossPlusButton); // Boss+ button is visible in title state game.addChild(ui.gameOverMessage); // Game over message is hidden initially game.addChild(ui.grillMenu); // Grill menu is hidden initially // Start the game state (enables updates, attacks, etc.) // This was the last line in the original createGame gameState.changeState("title"); // Rozpocznij grę w stanie title console.log("Opóźnione kroki tworzenia gry wykonane."); // Opcjonalny log do potwierdzenia odpalenia timeoutu }, 500); // *** CZAS OPÓŹNIENIA (np. 500ms) - Dostosuj w razie potrzeby *** // To jest opóźnienie heurystyczne. Lepszym rozwiązaniem byłby właściwy mechanizm preładowania, jeśli dostępny. // Oryginalne linie kodu po ustawieniach createGame i przed gameState.changeState są teraz wewnątrz timeoutu // gameState.changeState("title"); // This line is moved inside the timeout }; var gameLoop = function gameLoop() { // Główne aktualizacje gry // LK automatycznie wywołuje update na obiektach dodanych do sceny (game) // Możemy dodać tutaj logikę globalną // Na przykład, aktualizacja UI, która nie jest automatycznie wywoływana na obiektach UI, chyba że UI jest DisplayObjectContainer i dodano je do sceny. // Aktualizuj UI niezależnie od stanu gry, bo jego widoczność i zawartość zależą od stanu/danych // Ale tylko jeśli UI zostało stworzone if (ui) { // UI update logic if needed (currently empty) // ui.update(); // Zakładając, że UI ma metodę update // Aktualizuj pasek zdrowia bossa (jeśli boss istnieje) if (boss) { // ui.updateBossHealth(boss.health, boss.maxHealth); // Ta linia była wcześniej, ale lepiej wywoływać ją po zmianie zdrowia bossa // Zaktualizuj UI HP Bossa w game.update, ale tylko gdy HP bossa > 0 (żeby nie migało przy śmierci) // UI HP Boss aktualizowane jest teraz w takeDamage i die Bossa. // Upewnijmy się, że jest aktualizowane też jeśli boss HP > 0. if (boss.health > 0 || gameState.currentState === "gameOver" && !gameOverReasonIsDeath && isNewBossPlusMode) { ui.updateBossHealth(boss.health, boss.maxHealth); } else { // Jeśli boss martwy i nie jest to game over po czasie, ukryj pasek if (!(gameState.currentState === "gameOver" && !gameOverReasonIsDeath && isNewBossPlusMode)) { ui.updateBossHealth(0, 1); // Ukryj pasek } } } else { // Jeśli boss nie istnieje, ukryj pasek (chyba że to wygrana Boss+ przez czas) if (!(gameState.currentState === "gameOver" && !gameOverReasonIsDeath && isNewBossPlusMode)) { // Domyślne maxHP do ukrycia paska to 1 (uniknięcie dzielenia przez 0) ui.updateBossHealth(0, 1); } } // Aktualizuj serca gracza (jeśli gracz istnieje i UI istnieje) if (player) { ui.updateHearts(player.health, storage.maxHearts); } else if (gameState.currentState !== "game" && gameState.currentState !== "gameOver") { // Ukryj serca poza grą/game over ui.updateHearts(0, storage.maxHearts); } // Pozycjonowanie/widoczność elementów UI jest zarządzana przez gameState.positionElements() w metodach zmiany stanu. } // Logika specyficzna dla stanu gry if (gameState.currentState === "game") { if (player) { player.update(); // Aktualizacja gracza (ruch turlania, nietykalność, kolizja turlania z bossem) } if (boss) { boss.update(); // Aktualizacja bossa (ruch, cooldown, tworzenie/usuwanie ataków, kolizje ataków z graczem) } } // Logika game over else if (gameState.currentState === "gameOver") { // Tutaj można dodać logikę game over, np. animację, możliwość restartu // Aktualizacja player i boss nadal jest wywoływana, ale ich metody update powinny sprawdzać stan gry if (player) { player.update(); } // Nadal aktualizuj gracza (np. animacja śmierci) if (boss) { boss.update(); } // Nadal aktualizuj bossa (np. animacja śmierci) // Można dodać logikę przycisków restartu, powrotu do menu itp. } // Logika grill screen else if (gameState.currentState === "grillScreen") { // Tutaj można dodać logikę grill screen, np. animację, możliwość przejścia dalej if (player) { player.update(); } // Nadal aktualizuj gracza (jeśli widoczny) if (boss) { boss.update(); } // Nadal aktualizuj bossa (jeśli widoczny) // Można dodać logikę przycisków przejścia dalej, powrotu do menu itp. } // Rysowanie sceny (wykonywane automatycznie przez LK po gameLoop) }; // Inicjalizacja gry LK.init(gameContainer, { onInit: function onInit() { storage = LK.localStorage; // Initialize storage here after LK.init // Load or set maxHearts from storage if needed, default to 5 storage.maxHearts = storage.maxHearts || 5; // Default max hearts // Determine game mode (Boss+ or Standard) from storage or default isNewBossPlusMode = storage.newBossPlusMode === true; // Default to false if not in storage createGame(); // Call the createGame function to set up the initial game state and objects // Game loop is set up by LK.init to call gameLoop function repeatedly (e.g., 60 times per second) } });
/****
* Classes
****/
// End gameState
/****
* Player class
****/
var Player = Container.expand(function () {
var self = Container.call(this);
// Attach player graphic (depends on asset)
self.playerGraphics = self.attachAsset('player', {
anchorX: 0.5,
anchorY: 0.5
});
self.health = 5; // Default player health (will be set by storage.maxHearts in gameState.startGame)
self.speed = 8; // Player movement speed
self.rollSpeed = 12; // Player roll speed
self.rolling = false;
self.rollDuration = 30; // Roll duration in frames (0.5 seconds at 60fps)
self.rollTimer = 0;
self.rollDirectionX = 0;
self.rollDirectionY = 0;
self.invulnerable = false; // Player invulnerability flag
self.invulnerabilityDuration = 60; // Invulnerability duration after rolling/getting hit
self.invulnerabilityTimer = 0;
self.dead = false;
// Create player roll animation (depends on assets)
self.createRollAnim = function () {
var frames = [LK.getAsset('roll0', {}), LK.getAsset('roll1', {}), LK.getAsset('roll2', {}), LK.getAsset('roll3', {}), LK.getAsset('roll4', {})];
var rollAnim = new SpriteAnimation({
frames: frames,
frameDuration: 60,
// Roll animation speed
loop: true,
// Roll animation loops
anchorX: 0.5,
anchorY: 0.5,
x: 0,
// Relative position
y: 0 // Relative position
});
return rollAnim;
};
self.rollAnim = null; // Will hold the current roll animation object
self.startRoll = function () {
if (self.rolling || self.dead || gameState.currentState !== "game") {
return; // Cannot roll if already rolling, dead, or not in game state
}
self.rolling = true;
self.rollTimer = self.rollDuration;
self.invulnerable = true; // Invulnerable during roll
self.invulnerabilityTimer = self.invulnerabilityDuration;
// Capture current movement direction for roll
self.rollDirectionX = (LK.isDown("left") ? -1 : 0) + (LK.isDown("right") ? 1 : 0);
self.rollDirectionY = (LK.isDown("up") ? -1 : 0) + (LK.isDown("down") ? 1 : 0);
// Ensure there's a direction to roll, default if no input
if (self.rollDirectionX === 0 && self.rollDirectionY === 0) {
// Default roll direction (e.g., last movement direction, or down)
// For simplicity, let's just prevent roll if no direction pressed.
// Or set a default if needed: self.rollDirectionY = 1; // Example: roll down
self.rolling = false; // Cancel roll if no direction input
self.invulnerable = false;
self.invulnerabilityTimer = 0;
return; // Exit if no roll direction
}
// Normalize roll direction vector (optional, but good for consistent speed)
var rollMagnitude = Math.sqrt(self.rollDirectionX * self.rollDirectionX + self.rollDirectionY * self.rollDirectionY);
if (rollMagnitude > 0) {
self.rollDirectionX /= rollMagnitude;
self.rollDirectionY /= rollMagnitude;
}
// Remove idle graphic, add roll animation
if (self.playerGraphics && self.playerGraphics.parent === self) {
self.playerGraphics.parent.removeChild(self.playerGraphics);
} else if (self.playerGraphics && self.playerGraphics.parent) {
// Fallback
self.playerGraphics.parent.removeChild(self.playerGraphics);
}
self.playerGraphics = null;
self.rollAnim = self.addChild(self.createRollAnim()); // Add roll animation as child
self.rollAnim.play(); // Start roll animation
};
self.takeDamage = function (amount) {
if (self.dead || self.invulnerable || gameState.currentState !== "game") {
return; // Cannot take damage if dead, invulnerable, or not in game state
}
self.health -= amount;
self.health = Math.max(0, self.health); // Health cannot go below zero
LK.getSound('playerHit').play(); // Play player hit sound
LK.effects.flashObject(self, 0xFF0000, 300); // Flash effect on hit
self.invulnerable = true; // Become invulnerable after taking damage
// Use a separate timer for hit invulnerability if needed, or just reuse roll timer logic
// For simplicity, reuse invulnerabilityTimer:
self.invulnerabilityTimer = self.invulnerabilityDuration; // Invulnerable for a short period
// Update UI hearts immediately
// ui.updateHearts(self.health, storage.maxHearts); // This is called in game.update -> ui.updateHearts
if (self.health <= 0) {
self.die(); // Player dies
}
};
self.die = function () {
if (self.dead) {
return; // Already dead
}
self.dead = true;
// Stop all movement/actions
self.rolling = false;
self.invulnerable = false; // No longer invulnerable once dead
// Stop/remove roll animation if active
if (self.rollAnim) {
self.rollAnim.stop();
if (self.rollAnim.parent === self) {
self.rollAnim.parent.removeChild(self.rollAnim);
} else if (self.rollAnim.parent) {
// Fallback
self.rollAnim.parent.removeChild(self.rollAnim);
}
self.rollAnim.destroy();
self.rollAnim = null;
}
// Add player death animation or graphic (if any)
// For simplicity, just hide or remove the player graphic
if (self.playerGraphics && self.playerGraphics.parent === self) {
self.playerGraphics.parent.removeChild(self.playerGraphics);
} else if (self.playerGraphics && self.playerGraphics.parent) {
// Fallback
self.playerGraphics.parent.removeChild(self.playerGraphics);
}
self.playerGraphics = null; // Clear reference
// Trigger game over state after a short delay or death animation
LK.setTimeout(function () {
if (gameState.currentState === "game") {
// Only transition if still in game state
gameState.gameOver(true); // Call game over function with death reason
}
}, 1000); // Delay game over screen by 1 second
};
self.update = function () {
// Update only if in game state
if (gameState.currentState !== "game") {
// Stop rolling and remove animation if state changes
if (self.rolling) {
self.rolling = false;
if (self.rollAnim) {
self.rollAnim.stop();
if (self.rollAnim.parent === self) {
self.rollAnim.parent.removeChild(self.rollAnim);
} else if (self.rollAnim.parent) {
// Fallback
self.rollAnim.parent.removeChild(self.rollAnim);
}
self.rollAnim.destroy();
self.rollAnim = null;
}
// Add back idle graphic if it was removed
if (!self.playerGraphics) {
self.playerGraphics = self.addChild(self.attachAsset('player', {
anchorX: 0.5,
anchorY: 0.5,
x: 0,
y: 0 // Relative position
}));
}
}
return; // Exit update if not in game state
}
// Do not update if player is dead
if (self.dead) {
return;
}
// --- Rolling logic ---
if (self.rolling) {
self.x += self.rollDirectionX * self.rollSpeed;
self.y += self.rollDirectionY * self.rollSpeed;
self.rollTimer--;
if (self.rollTimer <= 0) {
self.rolling = false;
// Stop and remove roll animation
if (self.rollAnim) {
self.rollAnim.stop();
if (self.rollAnim.parent === self) {
self.rollAnim.parent.removeChild(self.rollAnim);
} else if (self.rollAnim.parent) {
// Fallback
self.rollAnim.parent.removeChild(self.rollAnim);
}
self.rollAnim.destroy();
self.rollAnim = null;
}
// Add back idle graphic
if (!self.playerGraphics) {
self.playerGraphics = self.addChild(self.attachAsset('player', {
anchorX: 0.5,
anchorY: 0.5,
x: 0,
y: 0 // Relative position
}));
}
}
} else {
// --- Movement logic (only when not rolling) ---
var moveX = (LK.isDown("left") ? -1 : 0) + (LK.isDown("right") ? 1 : 0);
var moveY = (LK.isDown("up") ? -1 : 0) + (LK.isDown("down") ? 1 : 0);
var moveMagnitude = Math.sqrt(moveX * moveX + moveY * moveY);
if (moveMagnitude > 0) {
moveX /= moveMagnitude;
moveY /= moveMagnitude;
self.x += moveX * self.speed;
self.y += moveY * self.speed;
}
// No specific idle animation in this example, uses static player graphic
}
// --- Invulnerability timer ---
if (self.invulnerable) {
self.invulnerabilityTimer--;
if (self.invulnerabilityTimer <= 0) {
self.invulnerable = false;
}
// Optional: add visual effect for invulnerability (e.g., alpha blinking)
self.alpha = self.invulnerabilityTimer % 10 < 5 ? 0.5 : 1; // Simple blinking effect
} else {
self.alpha = 1; // Fully visible when not invulnerable
}
// --- Boundary checks (Arena borders) ---
// Assuming arena borders are at 100px from edge, map 2048x2732
var halfWidth = self.width * self.scaleX / 2;
var halfHeight = self.height * self.scaleY / 2;
var minX = 100 + halfWidth;
var maxX = 2048 - 100 - halfWidth;
var minY = 100 + halfHeight;
var maxY = 2732 - 100 - halfHeight;
self.x = Math.max(minX, Math.min(self.x, maxX));
self.y = Math.max(minY, Math.min(self.y, maxY));
// --- Collision with Boss (only when not invulnerable) ---
// Boss collision check is handled in player.update(), NOT boss.update()
// It should only happen if player is NOT invulnerable
// This collision check is handled in game.update -> player.update()
// Let's add the check here if player.update is responsible for it.
// Check collision with Boss if boss exists, is not dead, and player is not invulnerable
if (boss && !boss.dead && !self.invulnerable) {
var dx_b = boss.x - self.x;
var dy_b = boss.y - self.y;
var distance_b = Math.sqrt(dx_b * dx_b + dy_b * dy_b);
var playerRadius_b = self.width / 2;
var bossRadius_b = boss.width * boss.scaleX / 2; // Use scaled boss width
if (distance_b < playerRadius_b + bossRadius_b) {
// Collision detected with Boss body
self.takeDamage(1); // Player takes 1 damage
}
}
// Note: Collision with Boss *attacks* is handled in boss.update()
};
return self;
});
/****
* Initialize Game
****/
// End Player class
/****
* UI object
****/
var game = new LK.Game({
backgroundColor: 0x000000
});
/****
* Game Code
****/
/****
* Global variables
****/
var player = null;
var boss = null;
var ui = null;
var storage = null;
var isNewBossPlusMode = false;
var game = null;
var gameContainer = "gameContainer"; // ID elementu HTML, w którym będzie wyświetlana gra
var gameOverReasonIsDeath = true; // true jeśli game over z powodu śmierci, false jeśli z powodu czasu w Boss+
/****
* Game state manager
****/
var gameState = function () {
var self = {};
self.currentState = "loading"; // Initial state
self.changeState = function (newState) {
console.log("Changing state from", self.currentState, "to", newState); // Log state changes
self.currentState = newState;
// Manage visibility of game elements based on state
// These position/visibility methods should be in gameState or UI, not individual element updates
self.positionElements(); // Call positionElements on state change
// Handle specific state transitions
if (self.currentState === "title") {
// Clear any game elements from previous attempts if necessary
if (game) {
game.removeChildren(); // Remove all children from the game container
// Do not destroy game here, it's handled in createGame
}
// Ensure player and boss are null when returning to title
player = null;
boss = null;
ui = null; // UI will be recreated in createGame if returning from game over
// Ensure attacks are cleared
if (boss && boss.attacks) {
boss.attacks.forEach(function (attack) {
if (attack.visual && attack.visual.destroy && !attack.visual.destroyed) {
attack.visual.destroy();
}
});
boss.attacks = [];
}
// If boss object is destroyed, self.attacks check might fail, add a check for that
// Display title screen elements
// In this simple example, we just show the start button via positionElements
} else if (self.currentState === "game") {
// Start the game loop when entering game state
// The gameLoop is already running based on LK.init settings (60fps)
// Player and Boss are created just before changing to 'game' state
// Reset player/boss health or properties if necessary (or do it in their constructors)
if (player) {
player.health = storage.maxHearts; // Reset player health
// ui.updateHearts(player.health, storage.maxHearts); // Update UI immediately
}
if (boss) {
boss.health = boss.maxHealth; // Reset boss health
// ui.updateBossHealth(boss.health, boss.maxHealth); // Update UI immediately
boss.attackCooldown = 60; // Initial delay before first boss attack (1 second at 60fps)
boss.stunned = false; // Ensure boss is not stunned
boss.repositioning = false; // Ensure boss is not repositioning
boss.dead = false; // Ensure boss is not dead
boss.phase = 1; // Reset boss phase
boss.attackSpeedMultiplier = 1; // Reset attack speed multiplier
boss.attacks = []; // Clear any lingering attacks
// Reset boss graphic state if needed (e.g., tint)
boss.tint = 0xFFFFFF; // Reset tint
}
} else if (self.currentState === "gameOver") {
// Stop game loop? LK manages this, or we can stop updates manually in gameLoop
// In this example, gameLoop checks gameState.currentState
// Display game over screen elements (handled by positionElements)
// Stop boss attacks explicitly if boss exists and has a method for it
if (boss) {
boss.attackCooldown = 9999; // Prevent new attacks
// Ensure existing attacks are cleaned up in boss.update when state changes
}
// Stop player movement, etc. (handled by player.update checking gameState.currentState)
// Clean up game elements from screen? gameState.positionElements might hide them.
} else if (self.currentState === "grillScreen") {
// Similar cleanup/display logic for grill screen
if (boss) {
boss.attackCooldown = 9999; // Prevent new attacks
}
// Ensure existing attacks are cleaned up
if (boss && boss.attacks) {
boss.attacks.forEach(function (attack) {
if (attack.visual && attack.visual.destroy && !attack.visual.destroyed) {
attack.visual.destroy();
}
});
boss.attacks = [];
}
// Hide player and boss (handled by positionElements)
}
};
self.positionElements = function () {
// This function manages the visibility and potentially position of game elements based on the current state
// It should be called whenever the state changes.
var startButton = LK.getObjectById("startButton"); // Przykład pobierania przycisku
var bossHpBar = LK.getObjectById("bossHpBar");
var playerHearts = LK.getObjectById("playerHearts"); // Zakładając, że UI ma kontener na serca
var gameOverMessage = LK.getObjectById("gameOverMessage"); // Przykład wiadomości game over
var grillMenu = LK.getObjectById("grillMenu"); // Przykład menu grill screen
// Visibility logic
if (startButton) {
startButton.visible = self.currentState === "title";
}
// Pasek HP bossa widoczny tylko w stanie "game" (lub game over po czasie w Boss+)
if (bossHpBar) {
bossHpBar.visible = self.currentState === "game" || self.currentState === "gameOver" && !gameOverReasonIsDeath && isNewBossPlusMode;
}
// Serca gracza widoczne tylko w stanie "game"
if (playerHearts) {
playerHearts.visible = self.currentState === "game";
}
if (gameOverMessage) {
gameOverMessage.visible = self.currentState === "gameOver" && gameOverReasonIsDeath;
} // Game over tylko ze śmierci
if (grillMenu) {
grillMenu.visible = self.currentState === "grillScreen" || self.currentState === "gameOver" && !gameOverReasonIsDeath && isNewBossPlusMode;
} // Menu grill screen lub game over po czasie
// Show player and boss only in game state
if (player) {
player.visible = self.currentState === "game" || self.currentState === "gameOver" || self.currentState === "grillScreen";
} // Pokaż gracza w game, game over, grill screen
if (boss) {
boss.visible = self.currentState === "game" || self.currentState === "gameOver" || self.currentState === "grillScreen";
} // Pokaż bossa w game, game over, grill screen
};
self.gameOver = function () {
var reasonIsDeath = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : true;
gameOverReasonIsDeath = reasonIsDeath; // Zapisz przyczynę game over
self.changeState("gameOver");
// Wyświetl wiadomość Game Over lub Grill Screen w zależności od przyczyny i trybu (zajmie się tym positionElements)
// Tutaj można dodać logikę czyszczenia obiektów, zatrzymania muzyki itp.
LK.getSound('playerHit').stop(); // Stop player hit sound if it's playing
};
self.showGrillScreen = function () {
self.changeState("grillScreen");
// Logic for showing the grill screen
};
// Metody zmiany stanu dla przycisków
self.startGame = function () {
var newBossPlus = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : false;
isNewBossPlusMode = newBossPlus; // Set the mode
storage.newBossPlusMode = isNewBossPlusMode; // Save mode to storage
// Wyczyść poprzednią grę i stwórz nową instancję
createGame(); // This function now contains a setTimeout delay
// Change state to game is now inside the setTimeout in createGame()
// self.changeState("game"); // MOVED INSIDE TIMEOUT IN createGame
};
self.returnToTitle = function () {
// Clean up game elements before returning to title
if (game) {
game.removeChildren(); // Remove all display objects from the game container
// Destroy player, boss, ui objects explicitly to clear references and updates
if (player && player.destroy && !player.destroyed) {
player.destroy();
}
if (boss && boss.destroy && !boss.destroyed) {
boss.destroy();
}
if (ui && ui.destroy && !ui.destroyed) {
ui.destroy();
}
player = null;
boss = null;
ui = null;
// game.destroy(); // Do not destroy game container itself, it's reused
}
// Clean up any lingering attacks if Boss object was null
if (boss && boss.attacks) {
// This check is redundant if boss is null, but safe
boss.attacks.forEach(function (attack) {
if (attack.visual && attack.visual.destroy && !attack.visual.destroyed) {
attack.visual.destroy();
}
});
boss.attacks = [];
}
self.changeState("title");
};
return self;
}();
// End Player class
/****
* UI object
****/
var UI = function () {
var self = {};
// Create UI elements (depend on assets/shapes)
self.bossHpBar = LK.getAsset('bossHpbar', {});
self.bossHpBar.x = 1024 - self.bossHpBar.width / 2; // Center the bar
self.bossHpBar.y = 50; // Position near top
self.bossHpBar.visible = false; // Hide initially
self.playerHearts = new Container(); // Container for player hearts
// Hearts will be added as children to this container in updateHearts
self.playerHearts.x = 100; // Position the hearts container
self.playerHearts.y = 50;
self.playerHearts.visible = false; // Hide initially
// Create start button (depends on asset/shape)
self.startButton = LK.getAsset('button_bg', {}); // Example shape button
self.startButton.x = 1024; // Center the button
self.startButton.y = 1366; // Position it
self.startButton.anchorX = 0.5;
self.startButton.anchorY = 0.5;
self.startButton.visible = false; // Hide initially
// Add text to start button (depends on font/text rendering)
self.startButtonText = new Text2("Start Game", {
fill: 0x000000,
size: 40,
font: "Arial",
anchorX: 0.5,
anchorY: 0.5
});
self.startButton.addChild(self.startButtonText); // Add text as child of button
// Add event listener to start button
self.startButton.on("click", function () {
if (gameState.currentState === "title") {
gameState.startGame(); // Start game in standard mode
}
});
// Create Boss+ button (depends on asset/shape)
self.newBossPlusButton = LK.getAsset('button_bg', {}); // Example shape button
self.newBossPlusButton.x = 1024; // Center
self.newBossPlusButton.y = 1500; // Position below start
self.newBossPlusButton.anchorX = 0.5;
self.newBossPlusButton.anchorY = 0.5;
self.newBossPlusButton.visible = false;
// Add text to Boss+ button
self.newBossPlusButtonText = new Text2("NEW BOSS+", {
fill: 0x000000,
size: 40,
font: "Arial",
anchorX: 0.5,
anchorY: 0.5
});
self.newBossPlusButton.addChild(self.newBossPlusButtonText);
// Add event listener to Boss+ button
self.newBossPlusButton.on("click", function () {
if (gameState.currentState === "title") {
gameState.startGame(true); // Start game in Boss+ mode
}
});
// Create Game Over message (depends on text rendering)
self.gameOverMessage = new Text2("Game Over!", {
fill: 0xFF0000,
size: 80,
font: "Arial",
anchorX: 0.5,
anchorY: 0.5,
x: 1024,
// Center X
y: 1366,
// Center Y
visible: false // Hide initially
});
// Create Grill Screen menu (depends on shape)
self.grillMenu = LK.getAsset('grillMenu', {
anchorX: 0.5,
anchorY: 0.5
});
self.grillMenu.x = 1024;
self.grillMenu.y = 1366;
self.grillMenu.visible = false; // Hide initially
// Add buttons/text to Grill Screen if needed
// Add all UI elements to the game container (depth/layering handled by addChild order or setChildIndex)
// Order matters for layering: background first, then game objects, then UI on top
// Assuming UI should be on top of everything else in the 'game' container
// Add UI elements AFTER adding player, boss, etc. in createGame
// Or add them here and manage z-index
// Let's add them here and assume later added elements are on top by default or managed by setChildIndex
self.updateBossHealth = function (currentHp, maxHp) {
// Ensure maxHp is at least 1 to prevent division by zero
var safeMaxHp = Math.max(1, maxHp);
// Calculate width based on health percentage
var hpPercentage = currentHp / safeMaxHp;
// Assuming original bossHpBar width was 800
self.bossHpBar.scaleX = hpPercentage;
// Adjust position if anchor is not 0,0 to keep the bar filling from left
// If anchorX is 0.5, the bar scales from the center. Need to adjust x.
self.bossHpBar.x = 1024 - self.bossHpBar.width * self.bossHpBar.scaleX / 2;
};
self.updateHearts = function (currentHearts, maxHearts) {
// Clear existing heart graphics
self.playerHearts.removeChildren();
// Add heart graphics based on currentHearts
var heartSize = 40; // Match heart asset size
var padding = 10; // Spacing between hearts
for (var i = 0; i < maxHearts; i++) {
var heartGraphic = LK.getShape('heart', {}); // Use heart shape asset
// Position hearts horizontally within the container
heartGraphic.x = i * (heartSize + padding);
heartGraphic.y = 0; // All on the same vertical level
// Tint heart based on current health
if (i < currentHearts) {
heartGraphic.tint = 0xFF0000; // Full heart color (Red)
} else {
heartGraphic.tint = 0x888888; // Empty heart color (Grayish)
}
self.playerHearts.addChild(heartGraphic); // Add heart to the container
}
// Position the container is done in gameState.positionElements or createGame
};
// Add UI elements to game container in createGame after player/boss
// This is handled in the modified createGame function
self.update = function () {
// UI elements are mostly passive and updated by boss/player health changes
// No continuous update needed for UI elements themselves in this example,
// but UI object could have update logic for animations, etc.
};
return self;
}(); // End UI object
/****
* Boss class
****/
var Boss = Container.expand(function () {
var self = Container.call(this);
// Definicje list klatek dla fireballi - Zdefiniowane raz na początku klasy Boss
var circleFrames = [LK.getAsset('fireball0', {}), LK.getAsset('fireball00', {}), LK.getAsset('fireball01', {}), LK.getAsset('fireball02', {}), LK.getAsset('fireball03', {}), LK.getAsset('fireball04', {}), LK.getAsset('fireball05', {}), LK.getAsset('fireball06', {}), LK.getAsset('fireball07', {}), LK.getAsset('fireball08', {}), LK.getAsset('fireball09', {}), LK.getAsset('fireball1', {}), LK.getAsset('fireball10', {}), LK.getAsset('fireball11', {}), LK.getAsset('fireball12', {}), LK.getAsset('fireball13', {}), LK.getAsset('fireball14', {})];
var lineFrames = [LK.getAsset('fireball2', {}), LK.getAsset('fireball3', {}), LK.getAsset('fireball4', {}), LK.getAsset('fireball5', {}), LK.getAsset('fireball6', {}), LK.getAsset('fireball7', {}), LK.getAsset('fireball8', {}), LK.getAsset('fireball9', {}), LK.getAsset('fireball15', {}), LK.getAsset('fireball16', {})];
// *** MODYFIKACJA: Dodaj grafikę bossIdle jako DZIECKO obiektu bossa (self) ***
// Używamy LK.getAsset z self.addChild, aby dodać jako dziecko do self
self.bossGraphics = self.addChild(LK.getAsset('bossIdle', {
// Dziecko self
anchorX: 0.5,
anchorY: 0.5
// Pozycja relatywna na 0,0 jest domyślna i poprawna przy anchorX/Y 0.5
}));
// Dodajemy funkcje animacji
// Ta funkcja tworzy obiekt SpriteAnimation dla animacji ataku Bossa (nie fireballi)
self.createBossAttackAnim = function () {
var frames = [LK.getAsset('bossAttack0', {}), LK.getAsset('bossAttack1', {}), LK.getAsset('bossAttack2', {}), LK.getAsset('bossAttack3', {})];
var bossAttackAnim = new SpriteAnimation({
frames: frames,
frameDuration: 100,
// Czas trwania klatki animacji ataku Bossa (w ms)
loop: false,
anchorX: 0.5,
anchorY: 0.5,
// Pozycja animacji jako dziecka bossa - ustawiamy na 0,0 relatywnie do rodzica (self)
x: 0,
// MODIFIED
y: 0 // MODIFIED
});
return bossAttackAnim;
};
// Zmieniamy sygnaturę funkcji, przyjmuje teraz typ ataku (do decydowania o grafice Bossa)
// Ta funkcja zarządza grafiką Bossa (animacja ataku Bossa lub grafika idle)
self.playBossAttackAnim = function (attackType) {
// <--- DODANO PARAMETR
// Upewnij się, że poprzednia animacja ataku Bossa jest całkowicie usunięta, zanim zaczniemy nową
if (self.bossAttackAnim) {
self.bossAttackAnim.stop(); // Zatrzymujemy animację
// Usuń poprzednią animację z jej rodzica (Bossa) przed zniszczeniem
// Sprawdź, czy rodzicem jest self przed próbą usunięcia
if (self.bossAttackAnim.parent === self) {
// MODIFIED check
self.bossAttackAnim.parent.removeChild(self.bossAttackAnim);
} else if (self.bossAttackAnim.parent) {
// Fallback na wypadek, gdyby rodzic był inny
self.bossAttackAnim.parent.removeChild(self.bossAttackAnim);
}
self.bossAttackAnim.destroy(); // Usuwamy poprzednią animację
self.bossAttackAnim = null; // Ustawiamy na null po zniszczeniu
}
// *** Zmieniona logika: Zmiana grafiki bossa tylko jeśli to NIE jest atak szarży (attackType 2) ***
if (attackType !== 2) {
// Jeśli to atak koła (0) lub linii (1)
// Usuń obecną główną grafikę bossa (idle) jako dziecko bossa
// Sprawdź, czy rodzicem jest self przed próbą usunięcia
if (self.bossGraphics && self.bossGraphics.parent === self) {
// MODIFIED check
self.bossGraphics.parent.removeChild(self.bossGraphics);
} else if (self.bossGraphics && self.bossGraphics.parent) {
// Fallback
self.bossGraphics.parent.removeChild(self.bossGraphics);
}
self.bossGraphics = null; // Wyczyść referencję
// Tworzymy nową animację bossa i dodajemy ją JAKO DZIECKO BOSSA
self.bossAttackAnim = self.addChild(self.createBossAttackAnim()); // *** MODIFIED: DODANO DO self! ***
// Pozycja animacji jako dziecka bossa jest już ustawiona na 0,0 w createBossAttackAnim
// Metoda update dla TEJ NOWEJ animacji (definiowana tylko dla ataków 0 i 1)
self.bossAttackAnim.update = function () {
// Sprawdź, czy ten obiekt animacji jest nadal aktywny self.bossAttackAnim bossa
// Użyj 'this' dla obiektu animacji, 'self' dla obiektu Boss
if (self.bossAttackAnim !== this || !this.playing || this.frames.length === 0) {
// Jeśli już nie jesteśmy aktualną animacją lub nie gramy, zakończ
return;
}
this.frameTimer++;
if (this.frameTimer >= this.frameDuration / (1000 / 60)) {
// Przelicz ms na klatki gry (przy 60fps)
this.frameTimer = 0;
this.removeChildren(); // Usuń sprite klatki z kontenera animacji (z obiektu animacji)
this.currentFrame++;
if (this.currentFrame >= this.frames.length) {
// Animacja skończona, wracamy do idle
// *** Dodaj grafikę idle z powrotem JAKO DZIECKO BOSSA i ustaw self.bossGraphics ***
self.bossGraphics = self.addChild(LK.getAsset('bossIdle', {
// *** MODIFIED: DODANO DO self! ***
anchorX: 0.5,
anchorY: 0.5,
x: 0,
// Pozycja relatywna
y: 0 // Pozycja relatywna
}));
// *** Usuń obiekt animacji z jego rodzica (Bossa) i zniszcz go ***
// Sprawdź, czy rodzicem jest self przed próbą usunięcia
if (this.parent === self) {
// MODIFIED check
this.parent.removeChild(this); // Użyj 'this' dla obiektu animacji
} else if (this.parent) {
// Fallback
this.parent.removeChild(this);
}
this.destroy(); // Zniszcz obiekt animacji
self.bossAttackAnim = null; // Wyczyść referencję w obiekcie bossa
} else {
this.addChild(this.frames[this.currentFrame]); // Dodaj sprite następnej klatki (do obiektu animacji)
}
}
};
}
// Else (attackType === 2, czyli chargeAttack): playBossAttackAnim nic nie robi z grafiką.
// chargeAttack sam zadba o ustawienie grafiki 'idle' JAKO DZIECKO BOSSA.
};
self.health = 100; // Domyślne zdrowie bossa (nadpisane w startGame)
self.maxHealth = 100; // Domyślne max zdrowia bossa (nadpisane w startGame)
self.speed = 5; // Prędkość ruchu bossa
self.attackCooldown = 0; // Czas do następnego ataku (w klatkach gry)
self.attackPattern = 0;
self.attacks = []; // Aktywne ataki bossa (obiekty JS z pozycją, promieniem, itp.)
self.stunned = false;
self.stunDuration = 0;
self.dead = false;
self.phase = 1;
self.attackSpeedMultiplier = 1; // Mnożnik prędkości ataków (1 = normalna, < 1 = szybsza)
self.repositioning = false; // Flaga informująca czy boss się przemieszcza po szarży
// startAttackPattern pozostaje jak w poprzedniej poprawionej wersji
self.startAttackPattern = function () {
// !!! Ustaw tymczasowy cooldown na początku, ZANIM sprawdzisz warunki powrotu !!!
// To zapobiegnie natychmiastowemu ponownemu wywołaniu, jeśli metoda wróci wcześnie
self.attackCooldown = 1;
// Ustaw minimalny cooldown (np. 1 klatka) od razu
// Sprawdź warunki wczesnego powrotu: boss jest martwy, gra nie jest w stanie "game" LUB boss jest w stanie "repositioning"
if (self.dead || gameState.currentState !== "game" || self.repositioning) {
// Dodano || self.repositioning
return; // Jeśli warunek spełniony, wróć
}
// Decydujemy O WZORCU ATAKU przed zmianą grafiki bossa
self.attackPattern = (self.attackPattern + 1) % 3; // Cykl wzorców ataków (0, 1, 2)
// 🔥 Przekazujemy typ ataku do playBossAttackAnim - ta funkcja teraz decyduje CZY zmienić grafikę bossa
self.playBossAttackAnim(self.attackPattern); // <--- PRZEKAZUJEMY TYP ATAKU
// Następnie wywołujemy właściwą funkcję ataku (chargeAttack itp.)
// Logika graficzna dla szarży jest TERAZ W chargeAttack
switch (self.attackPattern) {
case 0:
self.circleAttack(); // Wywołaj atak koła
break;
case 1:
self.lineAttack(); // Wywołaj atak linii
break;
case 2:
self.chargeAttack(); // Wywołaj atak szarży
break;
}
// Ustaw właściwy czas odnowienia dla następnego ataku
self.attackCooldown = (90 + Math.floor(Math.random() * 60)) * self.attackSpeedMultiplier; // 1.5-2.5 seconds * multiplier
};
// Zmodyfikowana funkcja createAttack - NIE nadpisuje lokalnie spriteAnim.update (jak w poprzedniej próbie)
// Kolejność argumentów: x, y, duration (ms), activationFrame (index klatki), frameDurationMs (ms), framesList (opcjonalne)
// Logika aktywacji kolizji (isActive) jest w Boss.update
self.createAttack = function (x, y, duration, activationFrame, frameDurationMs, framesList) {
// Użyj przekazanej framesList, domyślną oryginalną jeśli nie przekazana
var frames = framesList || [LK.getAsset('fireball0', {}), LK.getAsset('fireball00', {}), LK.getAsset('fireball01', {}), LK.getAsset('fireball02', {}), LK.getAsset('fireball03', {}), LK.getAsset('fireball04', {}), LK.getAsset('fireball05', {}), LK.getAsset('fireball06', {}), LK.getAsset('fireball07', {}), LK.getAsset('fireball08', {}), LK.getAsset('fireball09', {}), LK.getAsset('fireball1', {}), LK.getAsset('fireball10', {}), LK.getAsset('fireball11', {}), LK.getAsset('fireball12', {}), LK.getAsset('fireball13', {}), LK.getAsset('fireball14', {})];
// console.log("CreateAttack called at:", x, y, " Frames array:", frames); // Debug log - można usunąć po naprawie
// if (!frames || frames.length === 0 || frames.some(f => f === null || typeof f === 'undefined' || !f.texture)) { // Debug check - można usunąć po naprawie
// console.error("Frames array is empty or contains invalid assets! Attack creation might fail."); // Debug log - można usunąć po naprawie
// }
var spriteAnim = game.addChild(new SpriteAnimation({
// Wizualizacja fireballa jest dodawana do kontenera 'game'
frames: frames,
frameDuration: frameDurationMs,
// Użyj wartości przekazanej do konstruktora SpriteAnimation
loop: false,
// Animacja nie zapętla się
anchorX: 0.5,
anchorY: 0.5,
x: x,
y: y
}));
spriteAnim.scaleX = 1.6; // Ręczne skalowanie wizualizacji
spriteAnim.scaleY = 1.6;
var attack = {
// Obiekt logiczny ataku
x: x,
y: y,
radius: 60,
// Promień kolizji
visual: spriteAnim,
// Referencja do wizualizacji (SpriteAnimation)
lifeTime: Math.floor(duration * (60 / 1000)),
// Czas życia w klatkach gry (przy 60fps)
isActive: false,
// Flaga aktywności kolizji, domyślnie false
activationFrame: activationFrame // Przechowuj activationFrame w obiekcie ataku
};
// USUNIĘTO: Lokalna definicja funkcji spriteAnim.update
self.attacks.push(attack); // Dodaj obiekt ataku logicznego do tablicy bossa
};
// Zmodyfikowana funkcja circleAttack - Z pętlą setTimeout delay (jak w poprzedniej próbie, która dała wiele pocisków)
self.circleAttack = function () {
LK.getSound('bossAttack').play(); // Odtwórz dźwięk ataku
var center = {
// Ustaw środek koła na pozycji bossa
x: self.x,
y: self.y
};
var count = isNewBossPlusMode ? 12 : 8; // Liczba pocisków w ataku (więcej w New Boss+)
var radius = 300; // Promień koła, na którym rozmieszczone są pociski
var attackLifeTime = isNewBossPlusMode ? 2000 : 3000; // Czas życia pocisku w ms (krótszy w New Boss+?)
// circleFrames array jest zdefiniowane raz na początku klasy Boss
// Pętla tworząca wiele pocisków rozmieszczonych w kole Z MINIMALNYM OPÓŹNIENIEM DLA KAŻDEGO
for (var i = 0; i < count; i++) {
// Iteruj tyle razy, ile ma być pocisków (count)
var angle = i / count * Math.PI * 2; // Oblicz kąt dla bieżącego pocisku (równo rozmieszczone na okręgu)
var posX = center.x + Math.cos(angle) * radius; // Oblicz współrzędną X na okręgu
var posY = center.y + Math.sin(angle) * radius; // Oblicz współrzędną Y na okręgu
// Dodaj minimalne opóźnienie (np. 20ms) DO KAŻDEGO pocisku
var delay = i * 20 + 20; // Zapewnij, że nawet pierwszy pocisk ma opóźnienie
// Użyj setTimeout do stworzenia każdego pocisku po małym opóźnieniu
LK.setTimeout(function (x, y) {
// Używamy closure do "zamknięcia" wartości x, y (frames jest globalne/dostępne)
return function () {
// Sprawdź czy boss nadal istnieje i gra jest w stanie 'game' zanim stworzysz atak po opóźnieniu
if (self && !self.dead && gameState.currentState === "game") {
// Wywołaj funkcję createAttack dla tego pocisku z zapamiętaną pozycją i parametrami
// Kolejność argumentów createAttack: x, y, duration(ms), activationFrame(index), frameDurationMs(ms), framesList
// activationFrame = 8 (aktywacja od klatki fireball08)
// frameDurationMs = 250ms (przykład prędkości)
// framesList = circleFrames (używamy zmiennej zdefiniowanej poza funkcją)
self.createAttack(x, y, attackLifeTime, 8, 250, circleFrames); // Używamy 250ms dla frameDurationMs
}
};
}(posX, posY), delay); // Przekaż aktualne posX, posY do closure
}
};
// *** START Zmodyfikowana funkcja lineAttack - Używa synchronicznej pętli i wywołuje nową createAttack ***
self.lineAttack = function () {
LK.getSound('bossAttack').play(); // Odtwórz dźwięk ataku
// Tworzy linię ataków
if (!player) {
return;
}
var targetX = player.x;
var targetY = player.y;
var count = isNewBossPlusMode ? 8 : 5; // Liczba pocisków w linii
var attackLifeTime = isNewBossPlusMode ? 1500 : 2000; // Czas życia pocisku w ms
var delayBetweenAttacks = isNewBossPlusMode ? 100 : 200; // Opóźnienie między pociskami w ms
// lineFrames array jest zdefiniowane raz na początku klasy
// Pętla tworząca pociski w linii z opóźnieniem (jak w souls5.txt)
for (var i = 0; i < count; i++) {
var t = i / (count - 1); // Współczynnik interpolacji
var posX = self.x + (targetX - self.x) * t; // Interpolowana pozycja X
var posY = self.y + (targetY - self.y) * t; // Interpolowana pozycja Y
var delay = i * delayBetweenAttacks * self.attackSpeedMultiplier; // Opóźnienie przed stworzeniem pocisku, skalowane
LK.setTimeout(function (x, y) {
// Używamy closure do przekazania pozycji
return function () {
// Sprawdź warunki przed stworzeniem ataku po opóźnieniu
if (self && !self.dead && gameState.currentState === "game") {
// Wywołaj funkcję createAttack
// Kolejność argumentów createAttack: x, y, duration(ms), activationFrame(index), frameDurationMs(ms), framesList
// activationFrame = 3 (indeks 'fireball5' na liście lineFrames)
// frameDurationMs = 100ms (przykład prędkości)
// framesList = lineFrames (używamy zmiennej zdefiniowanej poza funkcją)
self.createAttack(x, y, attackLifeTime, 3, 100, lineFrames); // Przekaż parametry
}
};
}(posX, posY), delay); // Przekaż aktualne posX, posY do closure
}
};
// *** END Zmodyfikowana funkcja lineAttack ***
// *** START Zmodyfikowana funkcja chargeAttack - tworzy pociski podczas szarży ***
// Ta funkcja zarządza ruchem bossa (szarżą) i tworzy pociski wzdłuż ścieżki szarży
self.chargeAttack = function () {
LK.getSound('bossAttack').play(); // Odtwórz dźwięk szarży
// Upewnij się, że gracz istnieje
if (!player) {
return;
}
// Obsługa grafiki bossa podczas szarży (pokazuje grafikę idle)
if (self.bossAttackAnim) {
self.bossAttackAnim.stop();
if (self.bossAttackAnim.parent === self) {
self.bossAttackAnim.parent.removeChild(self.bossAttackAnim);
} else if (self.bossAttackAnim.parent) {
self.bossAttackAnim.parent.removeChild(self.bossAttackAnim);
}
self.bossAttackAnim.destroy();
self.bossAttackAnim = null;
}
if (self.bossGraphics && self.bossGraphics.parent === self) {
self.bossGraphics.parent.removeChild(self.bossGraphics);
} else if (self.bossGraphics && self.bossGraphics.parent) {
self.bossGraphics.parent.removeChild(self.bossGraphics);
}
self.bossGraphics = self.addChild(LK.getAsset('bossIdle', {
anchorX: 0.5,
anchorY: 0.5,
x: 0,
y: 0
}));
// Oblicz kierunek do gracza i parametry szarży
var dx = player.x - self.x;
var dy = player.y - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance > 0) {
dx /= distance;
dy /= distance;
}
var startX = self.x; // Pozycja początkowa szarży
var startY = self.y;
var chargeDistance = isNewBossPlusMode ? 700 : 500; // Dystans szarży
var chargeDuration = isNewBossPlusMode ?
// Czas trwania szarży
600 : 800;
var attacksOnPathCount = isNewBossPlusMode ? 8 : 5; // Liczba pocisków wzdłuż ścieżki
var attackLifeTime = isNewBossPlusMode ? 1000 : 1500; // Czas życia pocisku
// Tween (animacja ruchu) bossa podczas szarży
tween(self, {
x: self.x + dx * chargeDistance,
y: self.y + dy * chargeDistance
}, {
duration: chargeDuration * self.attackSpeedMultiplier,
// Czas trwania, skalowany
easing: tween.easeIn,
// Krzywa animacji
onFinish: function onFinish() {
// Po zakończeniu szarży
if (self && !self.dead && gameState.currentState === "game") {
// Faza repozycji
self.repositioning = true;
LK.setTimeout(function () {
if (self && !self.dead) {
self.repositioning = false;
}
}, 500 * self.attackSpeedMultiplier);
// Tween powrotu do pozycji startowej
tween(self, {
x: startX,
y: startY
}, {
duration: 1000 * self.attackSpeedMultiplier,
easing: tween.easeOut
});
// Utworzenie ataków (fireballi) wzdłuż ścieżki szarży
// Fireballe ze szarży używają domyślnej listy klatek z createAttack (circleFrames)
for (var i = 0; i < attacksOnPathCount; i++) {
var t = i / (attacksOnPathCount - 1); // Współczynnik interpolacji
var currentChargeX = self.x; // Aktualna pozycja bossa (koniec szarży)
var currentChargeY = self.y;
var posX = startX + (currentChargeX - startX) * t; // Interpolowana pozycja X
var posY = startY + (currentChargeY - startY) * t; // Interpolowana pozycja Y
// Utwórz atak
// Kolejność argumentów createAttack: x, y, duration(ms), activationFrame(index), frameDurationMs(ms), framesList
// activationFrame = 4 (domyślna klatka aktywacji dla tych pocisków)
// frameDurationMs = 140ms (domyślna prędkość)
// framesList = circleFrames (używamy listy klatek ataku koła)
self.createAttack(posX, posY, attackLifeTime, 4, 140, circleFrames); // Przekaż parametry
}
}
}
});
};
self.takeDamage = function (amount) {
// Funkcja obsługująca otrzymywanie obrażeń przez bossa
if (self.dead || gameState.currentState !== "game") {
// Boss otrzymuje obrażenia tylko w stanie gry
return;
}
self.health -= amount; // Zmniejsz zdrowie
self.health = Math.max(0, self.health); // Upewnij się, że zdrowie nie spadnie poniżej zera
LK.effects.flashObject(self, 0xFFFFFF, 200); // Efekt błysku
if (!isNewBossPlusMode) {
// Przejście fazy bossa przy 50% zdrowia tylko w standardowym trybie
if (self.health <= self.maxHealth / 2 && self.phase === 1) {
self.phase = 2; // Zmień fazę na 2
self.speed += 2; // Boss staje się szybszy (ruch)
self.attackSpeedMultiplier *= 0.8; // Boss atakuje nieco szybciej
tween(self, {
tint: 0xFF3300 // Pomarańczowy/czerwony odcień
}, {
duration: 1000,
easing: tween.easeInOut
});
}
}
if (self.health <= 0) {
self.die(); // Wywołaj funkcję śmierci
}
// ui.updateBossHealth jest w game.update
};
self.die = function () {
// Funkcja obsługująca śmierć bossa
if (self.dead || gameState.currentState !== "game") {
return;
}
self.dead = true; // Ustaw flagę śmierci
if (!isNewBossPlusMode) {
// Dźwięk zwycięstwa tylko dla STANDARDOWEGO trybu
LK.getSound('victory').play();
}
// Wyczyść pozostałe ataki przy śmierci bossa
self.attacks.forEach(function (attack) {
if (attack.visual && attack.visual.destroy && !attack.visual.destroyed) {
attack.visual.destroy();
}
});
self.attacks = []; // Wyczyść tablicę ataków logicznych
// Animacja śmierci bossa (zanikanie i powiększanie)
tween(self, {
alpha: 0,
scaleX: 2,
scaleY: 2
}, {
duration: 2000,
easing: tween.easeOut,
onFinish: function onFinish() {
// Po zakończeniu animacji śmierci
if (self && self.destroy && !self.destroyed) {
self.destroy(); // Zniszcz obiekt bossa
}
storage.bossesDefeated = (storage.bossesDefeated || 0) + 1; // Zwiększ licznik pokonanych bossów
// ZAWSZE przechodzimy do Grill Screena
gameState.showGrillScreen();
}
});
};
self.update = function () {
// Główna metoda aktualizacji bossa
// Aktualizuj tylko w stanie gry
if (gameState.currentState !== "game") {
if (!self.dead && self.attacks) {
// Jeśli zmieniono stan i boss nie umiera, wyczyść ataki
self.attacks.forEach(function (attack) {
if (attack.visual && attack.visual.destroy && !attack.visual.destroyed) {
attack.visual.destroy();
}
});
self.attacks = [];
}
return;
}
// Nie aktualizuj jeśli boss jest martwy (pozwól animacji śmierci działać)
if (self.dead) {
return;
}
// --- LOGIKA RUCHU BOSS ---
// Poruszaj się w kierunku gracza jeśli spełnione warunki
if (!self.repositioning && self.attackCooldown > 30 && player && !player.dead) {
var dx = player.x - self.x;
var dy = player.y - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
var moveSpeed = self.speed;
if (distance > 150) {
// Poruszaj się tylko jeśli gracz jest dalej niż 150 jednostek
var moveX = dx / distance * moveSpeed;
var moveY = dy / distance * moveSpeed;
var nextX = self.x + moveX;
var nextY = self.y + moveY;
// Ograniczenia areny
var halfWidth = self.width * self.scaleX / 2;
var halfHeight = self.height * self.scaleY / 2;
var minX = 100 + halfWidth;
var maxX = 2048 - 100 - halfWidth;
var minY = 100 + halfHeight;
var maxY = 2732 - 100 - halfHeight;
self.x = Math.max(minX, Math.min(nextX, maxX));
self.y = Math.max(minY, Math.min(nextY, maxY));
// Grafika jako dziecko podąża automatycznie.
}
}
// --- KONIEC LOGIKA RUCHU BOSS ---
// --- OBSŁUGA ATAKÓW BOSS (Kolizje i Czas Życia Pocisków) ---
// Aktualizuj istniejące ataki bossa (pociski) i sprawdzaj kolizje z graczem
for (var i = self.attacks.length - 1; i >= 0; i--) {
var attack = self.attacks[i];
// Aktualizuj flagę isActive na podstawie aktualnej klatki visual i przechowywanego activationFrame
// Sprawdź czy visual istnieje i ma currentFrame
if (attack.visual && typeof attack.visual.currentFrame !== 'undefined') {
if (attack.visual.currentFrame >= attack.activationFrame) {
attack.isActive = true; // Aktywuj kolizję
} else {
attack.isActive = false; // Kolizja nieaktywna
}
} else {
attack.isActive = false;
}
if (attack.lifeTime > 0) {
// Zmniejszaj czas życia
attack.lifeTime--;
}
// Sprawdź kolizję z graczem jeśli spełnione warunki
if (attack.isActive && player && !player.dead && !player.invulnerable && attack.visual && !attack.visual.destroyed) {
var dx_p = player.x - attack.x;
var dy_p = player.y - attack.y;
var distance_p = Math.sqrt(dx_p * dx_p + dy_p * dy_p);
var playerRadius_p = player.width / 2;
var attackRadius = attack.radius;
if (distance_p < playerRadius_p + attackRadius) {
player.takeDamage(1); // Gracz otrzymuje obrażenia
if (attack.visual && attack.visual.destroy) {
attack.visual.destroy();
}
self.attacks.splice(i, 1); // Usuń trafiony atak
// break;
}
}
// Usuń atak, jeśli skończył mu się czas życia LUB wizualizacja zniszczona
if (attack.lifeTime <= 0 || attack.visual && attack.visual.destroy || attack.visual && attack.visual.destroyed) {
// Dodano trzeci warunek dla pewności
if (attack.visual && attack.visual.destroy && !attack.visual.destroyed) {
attack.visual.destroy();
}
self.attacks.splice(i, 1);
}
}
// --- KONIEC OBSŁUGA ATAKÓW BOSS ---
if (self.attackCooldown > 0) {
// Zmniejszaj cooldown
self.attackCooldown--;
}
// Sprawdź, czy nadszedł czas na nowy atak
if (self.attackCooldown <= 0 && !self.repositioning) {
self.startAttackPattern();
}
};
return self; // Zwróć instancję obiektu Boss
})(); // End Boss class
/****
* Game creation and loop
****/
// Global variables for game objects
// Already defined at the beginning: var player, boss, ui, game, gameContainer, etc.
var createGame = function createGame() {
// Upewnij się, że poprzednia instancja gry jest posprzątana
if (game) {
game.destroy();
game = null;
}
game = new DisplayObjectContainer(); // Stwórz główny kontener gry
LK.add(game); // Dodaj kontener gry do frameworka LK
// Stwórz i dodaj podstawowe elementy tła od razu (nie zależą od assetów fireballi)
gameState.createWalls(); // Tworzy i dodaje ściany
gameState.createFloor(); // Tworzy i dodaje podłogę
// *** MODYFIKACJA: Dodaj opóźnienie setTimeout przed tworzeniem elementów zależnych od assetów ***
// Daje to assetom czas na załadowanie po odpaleniu onInit przez LK.init
LK.setTimeout(function () {
// *** Kod z oryginalnego createGame, który tworzył elementy zależne od assetów, PRZENIESIONY tutaj ***
// Stwórz i dodaj gracza (zależy od assetów)
player = new Player(); // Klasa Player prawdopodobnie używa assetów
game.addChild(player);
// Stwórz i dodaj bossa (mocno zależy od assetów dla grafiki i ataków)
boss = new Boss(); // Klasa Boss używa wielu assetów
game.addChild(boss);
// Stwórz i dodaj elementy UI (niektóre elementy UI mogą używać assetów)
ui = new UI(); // Klasa UI prawdopodobnie używa assetów (serca, przyciski, itp.)
game.addChild(ui);
// Add UI elements to the game container (order matters for layering)
// Assuming UI should be on top of game elements
game.addChild(ui.bossHpBar);
game.addChild(ui.playerHearts);
game.addChild(ui.startButton); // Start button is visible in title state
game.addChild(ui.newBossPlusButton); // Boss+ button is visible in title state
game.addChild(ui.gameOverMessage); // Game over message is hidden initially
game.addChild(ui.grillMenu); // Grill menu is hidden initially
// Start the game state (enables updates, attacks, etc.)
// This was the last line in the original createGame
gameState.changeState("title"); // Rozpocznij grę w stanie title
console.log("Opóźnione kroki tworzenia gry wykonane."); // Opcjonalny log do potwierdzenia odpalenia timeoutu
}, 500); // *** CZAS OPÓŹNIENIA (np. 500ms) - Dostosuj w razie potrzeby ***
// To jest opóźnienie heurystyczne. Lepszym rozwiązaniem byłby właściwy mechanizm preładowania, jeśli dostępny.
// Oryginalne linie kodu po ustawieniach createGame i przed gameState.changeState są teraz wewnątrz timeoutu
// gameState.changeState("title"); // This line is moved inside the timeout
};
var gameLoop = function gameLoop() {
// Główne aktualizacje gry
// LK automatycznie wywołuje update na obiektach dodanych do sceny (game)
// Możemy dodać tutaj logikę globalną
// Na przykład, aktualizacja UI, która nie jest automatycznie wywoływana na obiektach UI, chyba że UI jest DisplayObjectContainer i dodano je do sceny.
// Aktualizuj UI niezależnie od stanu gry, bo jego widoczność i zawartość zależą od stanu/danych
// Ale tylko jeśli UI zostało stworzone
if (ui) {
// UI update logic if needed (currently empty)
// ui.update(); // Zakładając, że UI ma metodę update
// Aktualizuj pasek zdrowia bossa (jeśli boss istnieje)
if (boss) {
// ui.updateBossHealth(boss.health, boss.maxHealth); // Ta linia była wcześniej, ale lepiej wywoływać ją po zmianie zdrowia bossa
// Zaktualizuj UI HP Bossa w game.update, ale tylko gdy HP bossa > 0 (żeby nie migało przy śmierci)
// UI HP Boss aktualizowane jest teraz w takeDamage i die Bossa.
// Upewnijmy się, że jest aktualizowane też jeśli boss HP > 0.
if (boss.health > 0 || gameState.currentState === "gameOver" && !gameOverReasonIsDeath && isNewBossPlusMode) {
ui.updateBossHealth(boss.health, boss.maxHealth);
} else {
// Jeśli boss martwy i nie jest to game over po czasie, ukryj pasek
if (!(gameState.currentState === "gameOver" && !gameOverReasonIsDeath && isNewBossPlusMode)) {
ui.updateBossHealth(0, 1); // Ukryj pasek
}
}
} else {
// Jeśli boss nie istnieje, ukryj pasek (chyba że to wygrana Boss+ przez czas)
if (!(gameState.currentState === "gameOver" && !gameOverReasonIsDeath && isNewBossPlusMode)) {
// Domyślne maxHP do ukrycia paska to 1 (uniknięcie dzielenia przez 0)
ui.updateBossHealth(0, 1);
}
}
// Aktualizuj serca gracza (jeśli gracz istnieje i UI istnieje)
if (player) {
ui.updateHearts(player.health, storage.maxHearts);
} else if (gameState.currentState !== "game" && gameState.currentState !== "gameOver") {
// Ukryj serca poza grą/game over
ui.updateHearts(0, storage.maxHearts);
}
// Pozycjonowanie/widoczność elementów UI jest zarządzana przez gameState.positionElements() w metodach zmiany stanu.
}
// Logika specyficzna dla stanu gry
if (gameState.currentState === "game") {
if (player) {
player.update(); // Aktualizacja gracza (ruch turlania, nietykalność, kolizja turlania z bossem)
}
if (boss) {
boss.update(); // Aktualizacja bossa (ruch, cooldown, tworzenie/usuwanie ataków, kolizje ataków z graczem)
}
}
// Logika game over
else if (gameState.currentState === "gameOver") {
// Tutaj można dodać logikę game over, np. animację, możliwość restartu
// Aktualizacja player i boss nadal jest wywoływana, ale ich metody update powinny sprawdzać stan gry
if (player) {
player.update();
} // Nadal aktualizuj gracza (np. animacja śmierci)
if (boss) {
boss.update();
} // Nadal aktualizuj bossa (np. animacja śmierci)
// Można dodać logikę przycisków restartu, powrotu do menu itp.
}
// Logika grill screen
else if (gameState.currentState === "grillScreen") {
// Tutaj można dodać logikę grill screen, np. animację, możliwość przejścia dalej
if (player) {
player.update();
} // Nadal aktualizuj gracza (jeśli widoczny)
if (boss) {
boss.update();
} // Nadal aktualizuj bossa (jeśli widoczny)
// Można dodać logikę przycisków przejścia dalej, powrotu do menu itp.
}
// Rysowanie sceny (wykonywane automatycznie przez LK po gameLoop)
};
// Inicjalizacja gry
LK.init(gameContainer, {
onInit: function onInit() {
storage = LK.localStorage; // Initialize storage here after LK.init
// Load or set maxHearts from storage if needed, default to 5
storage.maxHearts = storage.maxHearts || 5; // Default max hearts
// Determine game mode (Boss+ or Standard) from storage or default
isNewBossPlusMode = storage.newBossPlusMode === true; // Default to false if not in storage
createGame(); // Call the createGame function to set up the initial game state and objects
// Game loop is set up by LK.init to call gameLoop function repeatedly (e.g., 60 times per second)
}
});