Code edit (8 edits merged)
Please save this source code
User prompt
Please fix the bug: 'Timeout.tick error: howToScene is not defined' in or related to this line: 'howToScene();' Line Number: 1263
Code edit (3 edits merged)
Please save this source code
User prompt
fix problem with click "tap to start" it's not working
Code edit (1 edits merged)
Please save this source code
User prompt
Please fix the bug: 'Uncaught ReferenceError: Player is not defined' in or related to this line: 'player = game.addChild(new Player()); // Stwórz nowego gracza' Line Number: 1103
Code edit (1 edits merged)
Please save this source code
User prompt
Please fix the bug: 'Uncaught TypeError: Cannot set properties of undefined (setting 'alpha')' in or related to this line: 'self.deathsText.alpha = 1;' Line Number: 366
User prompt
Please fix the bug: 'Uncaught ReferenceError: Player is not defined' in or related to this line: 'player = game.addChild(new Player()); // Stwórz nowego gracza' Line Number: 1229
Code edit (1 edits merged)
Please save this source code
User prompt
Please fix the bug: 'Uncaught ReferenceError: Player is not defined' in or related to this line: 'player = game.addChild(new Player()); // Stwórz nowego gracza' Line Number: 1228
Code edit (1 edits merged)
Please save this source code
User prompt
Please fix the bug: 'Uncaught RangeError: Maximum call stack size exceeded' in or related to this line: 'this.handleFakeTutorialInput(); // REMOVED self-recursive call that caused stack overflow' Line Number: 1199
User prompt
Please fix the bug: 'Uncaught ReferenceError: Player is not defined' in or related to this line: 'player = game.addChild(new Player()); // Stwórz nowego gracza' Line Number: 1196
Code edit (4 edits merged)
Please save this source code
User prompt
Please fix the bug: 'Uncaught RangeError: Maximum call stack size exceeded' in or related to this line: 'this.handleFakeTutorialInput(); // REMOVED self-recursive call that caused stack overflow' Line Number: 1293
Code edit (1 edits merged)
Please save this source code
User prompt
add new asset grillMenu
Code edit (1 edits merged)
Please save this source code
User prompt
add asset bossHpbar
Code edit (5 edits merged)
Please save this source code
User prompt
Please fix the bug: 'Uncaught RangeError: Maximum call stack size exceeded' in or related to this line: 'this.handleFakeTutorialInput(); // Wywołaj logikę fałszywej śmierci' Line Number: 1228
User prompt
Please fix the bug: 'Cannot read properties of undefined (reading 'set')' in or related to this line: 'self.bossHealthBarBg.anchor.set(0.5, 0.5); // Ustaw punkt odniesienia na środek' Line Number: 705
User prompt
Please fix the bug: 'Shape is not defined' in or related to this line: 'self.bossHealthBarBg = new Shape({' Line Number: 677
Code edit (1 edits merged)
Please save this source code
/**** * Plugins ****/ var tween = LK.import("@upit/tween.v1"); var storage = LK.import("@upit/storage.v1"); /**** * Classes ****/ // Klasy z stworzylo zloto.txt (bardziej zaawansowana walka z bossem) var Boss = Container.expand(function () { var self = Container.call(this); var bossGraphics = self.attachAsset('boss', { anchorX: 0.5, anchorY: 0.5 }); self.health = 100; // Domyślne zdrowie bossa (nadpisane w startGame) self.maxHealth = 100; // Domyślne max zdrowia bossa (nadpisane w startGame) self.speed = 5; self.attackCooldown = 0; 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 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óć } // Jeśli dotarliśmy tutaj, oznacza to, że możemy rozpocząć nowy wzorzec ataku self.attackPattern = (self.attackPattern + 1) % 3; // Cykl wzorców ataków switch (self.attackPattern) { case 0: self.circleAttack(); break; case 1: self.lineAttack(); break; case 2: self.chargeAttack(); break; } // Ustaw WŁAŚCIWY czas odnowienia dla następnego ataku, PO zainicjowaniu obecnego // Mnożnik prędkości ataków bossa jest już ustawiony w gameState.startGame self.attackCooldown = (90 + Math.floor(Math.random() * 60)) * self.attackSpeedMultiplier; // 1.5-2.5 seconds (90-150 frames) * multiplier }; self.circleAttack = function () { LK.getSound('bossAttack').play(); var center = { x: self.x, y: self.y }; var count = isNewBossPlusMode ? 12 : 8; // W trybie New Boss+ więcej pocisków var radius = 300; var attackLifeTime = isNewBossPlusMode ? 2000 : 3000; // Krótszy czas życia w New Boss+? for (var i = 0; i < count; i++) { var angle = i / count * Math.PI * 2; var posX = center.x + Math.cos(angle) * radius; var posY = center.y + Math.sin(angle) * radius; self.createAttack(posX, posY, attackLifeTime); // Utwórz atak w danej pozycji z czasem życia (skalowane w createAttack) } }; self.lineAttack = function () { LK.getSound('bossAttack').play(); // Tworzy linię ataków od bossa do gracza var targetX = player.x; var targetY = player.y; var count = isNewBossPlusMode ? 8 : 5; // W trybie New Boss+ więcej pocisków w linii var attackLifeTime = isNewBossPlusMode ? 1500 : 2000; // Krótszy czas życia? var delayBetweenAttacks = isNewBossPlusMode ? 100 : 200; // Mniejsze opóźnienie między atakami w linii for (var i = 0; i < count; i++) { var t = i / (count - 1); // Współczynnik interpolacji var posX = self.x + (targetX - self.x) * t; var posY = self.y + (targetY - self.y) * t; var delay = i * delayBetweenAttacks * self.attackSpeedMultiplier; // Opóźnienie dla kolejnych ataków w linii, skalowane LK.setTimeout(function (x, y) { return function () { // Sprawdź, czy boss nadal żyje i gra jest w stanie gry zanim stworzysz atak if (self && !self.dead && gameState.currentState === "game") { self.createAttack(x, y, attackLifeTime); // Utwórz atak po opóźnieniu (czas życia skalowane w createAttack) } }; }(posX, posY), delay); } }; self.chargeAttack = function () { LK.getSound('bossAttack').play(); // Oblicz kierunek do gracza 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; } // Zapisz oryginalną pozycję (z której zaczyna się szarża) var startX = self.x; var startY = self.y; var chargeDistance = isNewBossPlusMode ? 700 : 500; // Dłuższa szarża w New Boss+? var chargeDuration = isNewBossPlusMode ? 600 : 800; // Szybsza szarża w New Boss+? var attacksOnPathCount = isNewBossPlusMode ? 8 : 5; // Więcej ataków na ścieżce szarży? var attackLifeTime = isNewBossPlusMode ? 1000 : 1500; // Krótszy czas życia? // Animacja szarży (boss przesuwa się szybko w kierunku gracza) tween(self, { x: self.x + dx * chargeDistance, // Przesunięcie y: self.y + dy * chargeDistance }, { duration: chargeDuration * self.attackSpeedMultiplier, // Czas trwania szarży, skalowane easing: tween.easeIn, onFinish: function onFinish() { // Sprawdź, czy boss nadal żyje i gra jest w stanie gry if (self && !self.dead && gameState.currentState === "game") { // --- Start Repositioning Phase --- self.repositioning = true; // Ustaw flagę repozycji po zakończeniu szarży // Czas na repozycję (np. 0.5 sekundy), skalowane LK.setTimeout(function () { if (self && !self.dead) { // Upewnij się, że boss nadal istnieje i żyje self.repositioning = false; // Zakończ stan repozycji po opóźnieniu } }, 500 * self.attackSpeedMultiplier); // Opóźnienie skalowane przez mnożnik prędkości ataków // --- End Repositioning Phase --- // Powrót do oryginalnej pozycji (startu szarży) po szarży tween(self, { x: startX, y: startY }, { duration: 1000 * self.attackSpeedMultiplier, // Czas powrotu, skalowane easing: tween.easeOut // Nie potrzebujemy tutaj już onFinish do repozycji, bo obsłużono to powyżej }); // Utwórz ataki wzdłuż ścieżki szarży for (var i = 0; i < attacksOnPathCount; i++) { var t = i / (attacksOnPathCount - 1); var posX = startX + (self.x - startX) * t; var posY = startY + (self.y - startY) * t; self.createAttack(posX, posY, attackLifeTime); // Czas życia skalowane w createAttack } } } }); }; self.createAttack = function (x, y, duration) { // Tworzy wizualne ostrzeżenie ataku (żółty okrąg) var warning = game.addChild(LK.getAsset('attack', { anchorX: 0.5, anchorY: 0.5, x: x, y: y, alpha: 0.3, // Niska przezroczystość na początku tint: 0xFFFF00 // Żółty kolor ostrzeżenia })); // Animacja ostrzeżenia (miganie), skalowana przez mnożnik prędkości var warningDuration = isNewBossPlusMode ? 800 : 1000; // Krótsze ostrzeżenie w New Boss+? tween(warning, { alpha: 0.6 // Zwiększ przezroczystość }, { duration: warningDuration * self.attackSpeedMultiplier, // Skalowany czas trwania easing: tween.easeInOut, onFinish: function onFinish() { // Sprawdź, czy warning nadal istnieje przed zmianą koloru if (warning && !warning.destroyed) { // Zmień kolor na czerwony, sygnalizując faktyczny atak warning.tint = 0xFF0000; // Animacja sygnalizująca gotowość ataku, skalowana przez mnożnik prędkości tween(warning, { alpha: 0.8 // Zwiększ przezroczystość bardziej }, { duration: 200 * self.attackSpeedMultiplier, // Skalowany czas trwania onFinish: function onFinish() { // Sprawdź, czy warning nadal istnieje if (warning && !warning.destroyed) { // Dodaj atak do listy aktywnych ataków bossa (jako obiekt danych) var attack = { x: warning.x, // Pozycja ataku (ze środka) y: warning.y, radius: warning.width / 2, // Promień kolizji (połówka szerokości) visual: warning, // Referencja do obiektu wizualnego lifeTime: duration * self.attackSpeedMultiplier * (60 / 1000) // Czas życia ataku w klatkach, skalowany. Przeliczamy ms na klatki (zakładając 60 FPS) }; self.attacks.push(attack); // !!! USUNIĘTO LK.setTimeout, USUWANIE BĘDZIE TYLKO W BOSS.update() !!! } // if warning exists } }); } // if warning exists } }); }; self.takeDamage = function (amount) { if (self.dead || gameState.currentState !== "game") { // Boss otrzymuje obrażenia tylko w stanie gry return; } self.health -= amount; // Zmniejsz zdrowie // Upewnij się, że zdrowie nie spadnie poniżej zera (chyba że do śmierci) self.health = Math.max(0, self.health); // Wizualne sygnalizowanie otrzymania obrażeń LK.effects.flashObject(self, 0xFFFFFF, 200); // Przejście fazy bossa pri 50% zdrowia (może być wyłączone w New Boss+?) if (!isNewBossPlusMode) { // Tylko w standardowym trybie if (self.health <= self.maxHealth / 2 && self.phase === 1) { self.phase = 2; self.speed += 2; // Boss staje się szybszy (ruch) // Wizualne przejście fazy (np. zmiana koloru) tween(self, { tint: 0xFF3300 // Pomarańczowy/czerwony odcień }, { duration: 1000, easing: tween.easeInOut }); } } // Sprawdzenie, czy boss został pokonany if (self.health <= 0) { self.die(); // Wywołaj funkcję śmierci } // Opcjonalnie: Aktualizuj wizualne wskazanie zdrowia bossa (jeśli istnieje) // ui.updateBossHealth(self.health, self.maxHealth); // Wymagałoby to dodania do klasy UI }; self.die = function () { if (self.dead || gameState.currentState !== "game") { // Boss umiera tylko w stanie gry return; } self.dead = true; // Dźwięk zwycięstwa tylko dla STANDARDOWEGO trybu (jeśli nie New Boss+) if (!isNewBossPlusMode) { LK.getSound('victory').play(); // Dźwięk zwycięstwa w trybie standardowym } // Animacja śmierci bossa (zanikanie i powiększanie) tween(self, { alpha: 0, scaleX: 2, scaleY: 2 }, { duration: 2000, easing: tween.easeOut, onFinish: function onFinish() { // Sprawdź, czy boss nadal istnieje przed zniszczeniem if (self && self.destroy && !self.destroyed) { self.destroy(); // Zniszcz obiekt bossa } // Zwiększ licznik pokonanych bossów TYLKO W STANDARDOWYM TRYBIE LUB MOŻE OSOBNO DLA NEW BOSS+? // Na razie zwiększamy zawsze, ale można to zmienić storage.bossesDefeated = (storage.bossesDefeated || 0) + 1; // Zwiększ licznik pokonanych bossów // !!! Po pokonaniu bossa ZAWSZE przechodzimy do Grill Screena (niezależnie od trybu) !!! gameState.showGrillScreen(); } }); // Zatrzymaj wszystkie timery związane z atakami bossa // Ataki zostaną wyczyszczone w gameState.showGrillScreen clean up }; self.update = function () { var playerRadius; // Declare playerRadius here // Aktualizuj tylko w stanie gry if (gameState.currentState !== "game") { // Jeśli zmieniono stan gry i boss nie umiera, wyczyść pozostałe ataki if (!self.dead && self.attacks) { self.attacks.forEach(function (attack) { if (attack.visual && attack.visual.destroy && !attack.visual.destroyed) { attack.visual.destroy(); } }); self.attacks = []; // Wyczyść tablicę ataków } return; } // Nie aktualizuj jeśli boss jest martwy i animacja śmierci dobiegła końca (obiekt niewidoczny) if (self.dead && self.alpha === 0) { // Gdy animacja śmierci dobiegła końca (obiekt niewidoczny) return; } // Jeśli gracz jest martwy, ale nadal widoczny, pozwól animacji zanikania działać (obsługiwane przez tween) if (self.dead) { return; // Nie wykonuj logiki ruchu i nietykalności jeśli gracz jest martwy } // Obsługa cooldownu turlania if (self.rollCooldown > 0) { self.rollCooldown--; } // Obsługa klatek nietykalności if (self.invulnerable && self.invulnerabilityFrames > 0) { self.invulnerabilityFrames--; // Efekt migotania (zmiana przezroczystości) self.alpha = self.invulnerabilityFrames % 6 > 2 ? 0.5 : 1; // Miga co kilka klatek if (self.invulnerabilityFrames <= 0) { self.invulnerable = false; self.alpha = 1; // Przywróć pełną przezroczystość gdy nietykalność minie } } // Obsługa ruchu podczas turlania - WYKONUJ TYLKO JEŚLI self.rolling JEST TRUE if (self.rolling) { // Jawne sprawdzenie flagi rolling self.x += self.rollDirection.x * self.rollSpeed; self.y += self.rollDirection.y * self.rollSpeed; // !!! NOWA LOGIKA: Sprawdzenie kolizji z bossem podczas turlania i zadawanie obrażeń !!! // Sprawdź tylko jeśli boss istnieje, nie jest martwy i JESZCZE nie zadałeś obrażeń tym turlaniem if (boss && !boss.dead && !self.hasRolledThroughBossThisRoll) { // Uproszczone sprawdzenie kolizji na podstawie odległości między środkami var dx = self.x - boss.x; var dy = self.y - boss.y; var distance = Math.sqrt(dx * dx + dy * dy); // Uproszczony promień kolizji (połówka szerokości/wysokości) var playerCollisionRadius = self.width / 2; // Zakładamy, że kolizja gracza jest okrągła var bossCollisionRadius = boss.width / 2; // Zakładamy, że kolizja bossa jest okrągła // Jeśli odległość między środkami jest mniejsza niż suma promieni kolizji if (distance < playerCollisionRadius + bossCollisionRadius) { // Kolizja wykryta podczas turlania boss.takeDamage(10); // ZADJ 10 OBRAŻEŃ BOSSOWI self.hasRolledThroughBossThisRoll = true; // Oznacz, że zadałeś obrażenia w tym turlaniu // Opcjonalny efekt wizualny/dźwiękowy trafienia bossa turlaniem LK.effects.flashObject(boss, 0x00FF00, 200); // Mignij bossa na zielono // LK.getSound('playerHitBossSound').play(); // Wymagałoby dodania nowego assetu dźwiękowego } } } // Utrzymaj gracza w granicach ekranu (z uwzględnieniem ścian) // Ściany mają szerokość 100, więc gracz (środek) powinien być 100 jednostek od krawędzi mapy self.x = Math.max(100, Math.min(self.x, 2048 - 100)); self.y = Math.max(100, Math.min(self.y, 2732 - 100)); }; return self; }); var Player = Container.expand(function () { var self = Container.call(this); var playerGraphics = self.attachAsset('player', { anchorX: 0.5, anchorY: 0.5 }); // Player properties self.health = 5; // Default health, will be set from storage.maxHearts self.speed = 8; // Movement speed self.rolling = false; // Is player currently rolling self.rollDirection = { x: 0, y: 0 }; // Direction of roll self.rollSpeed = 15; // Speed during roll self.rollCooldown = 0; // Cooldown counter for rolling self.rollDuration = 15; // Frames that roll lasts self.invulnerable = false; // Is player currently invulnerable self.invulnerabilityFrames = 0; // Counter for invulnerability frames self.dead = false; // Is player dead self.rollTimeouts = []; // To store roll-related timeouts self.hasRolledThroughBossThisRoll = false; // Flag to track if damage was dealt during this roll // Clear roll-related timeouts self.clearRollTimeouts = function () { while (self.rollTimeouts.length > 0) { var timeout = self.rollTimeouts.pop(); LK.clearTimeout(timeout); } }; // Initiate a roll in the given direction self.roll = function (direction) { // Don't roll if player is dead, rolling, or on cooldown if (self.dead || self.rolling || self.rollCooldown > 0) { return; } // Set roll state self.rolling = true; self.rollDirection = direction; self.invulnerable = true; self.invulnerabilityFrames = self.rollDuration; self.hasRolledThroughBossThisRoll = false; // Reset damage dealt flag for this roll // Visualize roll with effect var rollEffect = game.addChild(LK.getAsset('roll', { anchorX: 0.5, anchorY: 0.5, x: self.x, y: self.y })); // End roll after duration var rollTimeout = LK.setTimeout(function () { self.rolling = false; self.rollCooldown = 30; // 30 frames cooldown after roll // Remove timeout from array var index = self.rollTimeouts.indexOf(rollTimeout); if (index !== -1) { self.rollTimeouts.splice(index, 1); } // Fade out roll effect tween(rollEffect, { alpha: 0, scaleX: 0.5, scaleY: 0.5 }, { duration: 200, onFinish: function onFinish() { if (rollEffect && rollEffect.destroy) { rollEffect.destroy(); } } }); }, self.rollDuration * (1000 / 60)); // Convert frames to ms // Store timeout for cleanup self.rollTimeouts.push(rollTimeout); }; // Take damage from boss attacks self.takeDamage = function (amount) { // Don't take damage if invulnerable or dead if (self.invulnerable || self.dead || gameState.currentState !== "game") { return; } self.health -= amount; self.health = Math.max(0, self.health); // Visual feedback for damage LK.effects.flashObject(self, 0xFF0000, 200); // Set invulnerability after damage self.invulnerable = true; self.invulnerabilityFrames = 60; // 1 second invulnerability // Check if player died if (self.health <= 0) { self.die(); } }; // Handle player death self.die = function () { if (self.dead || gameState.currentState !== "game") { return; } self.dead = true; // Increase death counter storage.totalDeaths = (storage.totalDeaths || 0) + 1; // Increase max hearts after a certain number of deaths if (storage.totalDeaths % 5 === 0) { storage.maxHearts = (storage.maxHearts || 5) + 1; } // Death animation tween(self, { alpha: 0, scaleX: 0.5, scaleY: 0.5 }, { duration: 1000, easing: tween.easeOut, onFinish: function onFinish() { if (self && !self.destroyed) { self.destroy(); } // Set death as reason for game over gameOverReasonIsDeath = true; // Go to game over gameState.gameOver(true); } }); }; // Update function called every frame self.update = function () { // Don't update if game is not in "game" state if (gameState.currentState !== "game") { return; } // Don't update if player is dead if (self.dead) { return; } // Handle roll cooldown if (self.rollCooldown > 0) { self.rollCooldown--; } // Handle invulnerability frames if (self.invulnerable && self.invulnerabilityFrames > 0) { self.invulnerabilityFrames--; // Flicker effect during invulnerability self.alpha = self.invulnerabilityFrames % 6 > 2 ? 0.5 : 1; if (self.invulnerabilityFrames <= 0) { self.invulnerable = false; self.alpha = 1; } } // Handle rolling movement if (self.rolling) { self.x += self.rollDirection.x * self.rollSpeed; self.y += self.rollDirection.y * self.rollSpeed; // Check collision with boss during roll if (boss && !boss.dead && !self.hasRolledThroughBossThisRoll) { var dx = self.x - boss.x; var dy = self.y - boss.y; var distance = Math.sqrt(dx * dx + dy * dy); var playerCollisionRadius = self.width / 2; var bossCollisionRadius = boss.width / 2; if (distance < playerCollisionRadius + bossCollisionRadius) { boss.takeDamage(10); self.hasRolledThroughBossThisRoll = true; LK.effects.flashObject(boss, 0x00FF00, 200); } } } // Keep player within arena bounds self.x = Math.max(100, Math.min(self.x, 2048 - 100)); self.y = Math.max(100, Math.min(self.y, 2732 - 100)); // Check for collision with boss attacks if (boss && !boss.dead && boss.attacks) { for (var i = 0; i < boss.attacks.length; i++) { var attack = boss.attacks[i]; // Calculate distance between player and attack var dx = self.x - attack.x; var dy = self.y - attack.y; var distance = Math.sqrt(dx * dx + dy * dy); // Player collision radius (half width) var playerRadius = self.width / 2; // Check if player is hit by attack if (distance < playerRadius + attack.radius && !self.invulnerable) { self.takeDamage(1); break; // Only take damage from one attack per frame } } } }; return self; }); // Shape class implementation for creating simple shapes var Shape = Container.expand(function (options) { var self = Container.call(this); options = options || {}; var width = options.width || 100; var height = options.height || 100; var color = options.color || 0xFFFFFF; var shape = options.shape || 'box'; // Create the shape as an asset var asset = self.attachAsset(shape, { anchorX: 0.5, anchorY: 0.5, width: width, height: height, tint: color }); // Set width and height for easier access self.width = width; self.height = height; // Add anchor property to Shape for positioning self.anchor = { set: function set(x, y) { // This mimics the behavior of the anchor.set method self.anchorX = x; self.anchorY = y; } }; return self; }); var UI = Container.expand(function () { var self = Container.call(this); // Create heart containers (wizualizacja zdrowia) self.hearts = []; self.heartContainer = new Container(); // Kontener na ikony serc self.addChild(self.heartContainer); // Title and messages (teksty na ekranach) self.titleText = new Text2("ROLL SOULS", { size: 150, fill: 0xFFFFFF }); self.titleText.anchor.set(0.5, 0.5); self.addChild(self.titleText); self.messageText = new Text2("", { size: 60, fill: 0xFFFFFF }); self.messageText.anchor.set(0.5, 0.5); self.addChild(self.messageText); // Deaths counter (licznik śmierci) self.deathsText = new Text2("Deaths: 0", { size: 40, fill: 0xFFFFFF }); self.deathsText.anchor.set(1, 0); // Wyrównanie do prawego górnego rogu self.deathsText.x = 2048 - 50; // Pozycja X od prawej krawędzi self.deathsText.y = 50; // Pozycja Y od góry self.addChild(self.deathsText); // Tutorial text (teksty tutoriali) self.tutorialText = new Text2("", { size: 50, fill: 0xFFFFFF }); self.tutorialText.anchor.set(0.5, 0); // Wyrównanie do środka na górze self.tutorialText.x = 2048 / 2; // Pozycja X na środku self.tutorialText.y = 200; // Pozycja Y (może być różna w zależności od stanu) self.addChild(self.tutorialText); // --- Timer Text --- self.timerText = new Text2("2:00", { // Początkowe wyświetlanie czasu (2 minuty) size: 60, // Rozmiar czcionki fill: 0xFFFFFF // Biały kolor }); self.timerText.anchor.set(0, 0); // Punkt odniesienia w lewym górnym rogu self.timerText.x = 50; // Pozycja X od lewej krawędzi self.timerText.y = 50; // Pozycja Y od górnej krawędzi self.timerText.alpha = 0; // Domyślnie ukryty self.addChild(self.timerText); // --- Boss Health Bar (wizualizacja zdrowia bossa) --- self.bossHealthBarContainer = new Container(); // Kontener na pasek zdrowia bossa self.bossHealthBarContainer.x = 2048 / 2; // Wyśrodkuj poziomo self.bossHealthBarContainer.y = 150; // Pozycja Y (poniżej timera) self.bossHealthBarContainer.alpha = 0; // Domyślnie ukryty self.addChild(self.bossHealthBarContainer); var barWidth = 800; // Szerokość paska zdrowia (musi być taka sama jak szerokość assetu bossHpbar) var barHeight = 30; // Wysokość paska zdrowia (musi być taka sama jak wysokość assetu bossHpbar) // Tło paska zdrowia bossa (szare) - nadal używamy Shape dla tła self.bossHealthBarBg = new Shape({ width: barWidth, height: barHeight, color: 0x555555, // Szary kolor tła shape: 'box' }); self.bossHealthBarBg.anchor.set(0.5, 0.5); // Ustaw punkt odniesienia na środek self.bossHealthBarContainer.addChild(self.bossHealthBarBg); // Właściwy pasek zdrowia bossa (czerwony) - UŻYWAMY TERAZ NOWEGO ASSETU bossHpbar self.bossHealthBar = self.attachAsset('bossHpbar', { // Użyj assetu 'bossHpbar' anchorX: 0, // Ustaw punkt odniesienia na lewą krawędź (0), środek pionowo (0.5) anchorY: 0.5, x: -barWidth / 2 // Przesuń w lewo o połowę szerokości tła, żeby lewa krawędź paska zdrowia była na środku kontenera (czyli na środku ekranu) // Szerokość i wysokość są brane z assetu, nie ustawiamy ich tutaj jawnie (chyba że chcemy skalować) }); self.bossHealthBarContainer.addChild(self.bossHealthBar); // Aktualizuje wizualizację serc na UI self.updateHearts = function (current, max) { // Usuń istniejące serca while (self.hearts.length > 0) { var heart = self.hearts.pop(); if (heart && heart.destroy) { heart.destroy(); } } self.heartContainer.removeChildren(); // Usuń wizualne obiekty serc // Stwórz nowe ikony serc for (var i = 0; i < max; i++) { var heart = LK.getAsset('heart', { anchorX: 0.5, anchorY: 0.5, x: i * 50, // Pozycja w poziomie (rozstawione co 50 jednostek) y: 0, tint: i < current ? 0xFF0000 : 0x555555 // Czerwone jeśli zdrowie jest >=, szare jeśli < }); self.hearts.push(heart); self.heartContainer.addChild(heart); } // Wyśrodkuj kontener serc w poziomie i ustaw pozycję pionową self.heartContainer.x = (2048 - max * 50) / 2; self.heartContainer.y = 100; // Pozycja pionowa }; // Aktualizuje wizualizację paska zdrowia bossa self.updateBossHealth = function (current, max) { // Oblicz szerokość paska zdrowia na podstawie aktualnego i maksymalnego zdrowia var barWidth = 800; // Całkowita szerokość paska (musi być taka sama jak szerokość assetu bossHpbar) var currentWidth = current / max * barWidth; // Upewnij się, że pasek zdrowia (obiekt sprite) istnieje przed aktualizacją if (self.bossHealthBar) { // Zmień szerokość sprite'a, aby odzwierciedlić aktualne zdrowie self.bossHealthBar.width = currentWidth; // Ustaw widoczność kontenera paska zdrowia bossa (np. pokaż go tylko w stanie gry i gdy boss żyje) // Widoczność jest teraz zarządzana głównie przez positionElements, ale to dodatkowe zabezpieczenie self.bossHealthBarContainer.alpha = gameState.currentState === "game" && boss && !boss.dead ? 1 : 0; } // Upewnij się, że pasek zdrowia tła też jest widoczny, gdy kontener jest widoczny if (self.bossHealthBarBg) { self.bossHealthBarBg.alpha = self.bossHealthBarContainer.alpha; } }; // Wyświetla komunikat na środku ekranu self.showMessage = function (message, duration) { self.messageText.setText(message); self.messageText.alpha = 1; // Ustaw pełną przezroczystość // Wyczyść istniejący timer zanikania, jeśli istnieje if (self.messageTimeout) { LK.clearTimeout(self.messageTimeout); } // Zaplanuj zanikanie po czasie, jeśli duration > 0 if (duration) { self.messageTimeout = LK.setTimeout(function () { tween(self.messageText, { alpha: 0 // Zaniknij do przezroczystości 0 }, { duration: 500 // Czas trwania zanikania }); }, duration); } }; // Wyświetla tekst tutorialu self.showTutorial = function (text) { self.tutorialText.setText(text); self.tutorialText.alpha = 1; // Ustaw pełną przezroczystość }; // Ukrywa tekst tutorialu (zanikając) self.hideTutorial = function () { tween(self.tutorialText, { alpha: 0 // Zaniknij }, { duration: 500 // Czas trwania }); }; // Aktualizuje licznik śmierci self.updateDeathsCounter = function () { self.deathsText.setText("Deaths: " + storage.totalDeaths); }; // Aktualizuje wyświetlanie czasu timera self.updateTimerDisplay = function (seconds) { var minutes = Math.floor(seconds / 60); var remainingSeconds = seconds % 60; // Formatuj sekundy z wiodącym zerem jeśli < 10 var formattedSeconds = remainingSeconds < 10 ? '0' + remainingSeconds : remainingSeconds; self.timerText.setText(minutes + ':' + formattedSeconds); }; // Pozycjonuje elementy UI w zależności od stanu gry // Ta funkcja jest wywoływana przez metody w gameState self.positionElements = function (state) { switch (state) { case "title": // Pozycje dla ekranu tytułowego self.titleText.x = 2048 / 2; self.titleText.y = 800; self.messageText.x = 2048 / 2; self.messageText.y = 1000; // "Tap to Start" self.tutorialText.x = 2048 / 2; self.tutorialText.y = 1200; // "Swipe to Roll - Death is Progress" // Licznik śmierci i timer są pozycjonowane na stałe w UI constructor, zmieniamy tylko ich alpha/widoczność // Ukryj serca i timer na ekranie tytułowym self.heartContainer.alpha = 0; self.timerText.alpha = 0; // Ukryj pasek zdrowia bossa self.bossHealthBarContainer.alpha = 0; break; case "game": // Pozycje dla stanu gry (walka z bossem) self.titleText.alpha = 0; // Ukryj tytuł self.messageText.x = 2048 / 2; self.messageText.y = 1500; // Komunikaty w trakcie gry (np. "YOU DIED" - choć to też w victory/gameOver state) self.tutorialText.x = 2048 / 2; self.tutorialText.y = 200; // Tutorial w trakcie gry ("Swipe to roll away!") // Pokaż serca i timer podczas gry self.heartContainer.alpha = 1; self.timerText.alpha = 1; // Pokaż pasek zdrowia bossa (jeśli boss żyje) // Pasek będzie widoczny tylko w stanie "game" dzięki updateBossHealth i update w game.update self.bossHealthBarContainer.alpha = 1; break; case "grillMenu": // Nowy stan dla Grill Screena self.titleText.alpha = 0; // Ukryj tytuł na Grill Screenie (lub ustaw na "Grill Menu" jeśli chcesz) self.messageText.x = 2048 / 2; // Pozycjonowanie komunikatów/tekstów przycisków na środku self.messageText.y = 500; // Możesz dostosować pozycję self.messageText.setText(""); // Wyczyść domyślny messageText self.tutorialText.alpha = 0; // Ukryj tutorial // Ukryj serca i timer self.heartContainer.alpha = 0; self.timerText.alpha = 0; // Ukryj pasek zdrowia bossa self.bossHealthBarContainer.alpha = 0; break; case "gameOver": // Pozycje dla ekranu Game Over (można użyć tych samych co Victory lub zdefiniować osobno) self.titleText.x = 2048 / 2; self.titleText.y = 800; // Tytuł "YOU DIED" self.messageText.x = 2048 / 2; self.messageText.y = 1000; // Pusty lub inny komunikat self.tutorialText.x = 2048 / 2; self.tutorialText.y = 1200; // Pusty lub "Tap to Restart" // Ukryj serca i timer na ekranie Game Over self.heartContainer.alpha = 0; self.timerText.alpha = 0; // Ukryj pasek zdrowia bossa self.bossHealthBarContainer.alpha = 0; break; case "intro": // Pozycje dla ekranów intro/tutoriali (głównie ukryte elementy UI walki) case "fakeTutorial": case "realTutorial": self.titleText.alpha = 0; // Ukryj tytuł self.messageText.alpha = 0; // Ukryj komunikat self.tutorialText.alpha = 0; // Ukryj tutorial // Licznik śmierci jest zawsze widoczny self.deathsText.x = 2048 - 50; // Upewnij się, że jest w prawym górnym rogu self.deathsText.y = 50; // Ukryj serca i timer self.heartContainer.alpha = 0; self.timerText.alpha = 0; // Ukryj pasek zdrowia bossa self.bossHealthBarContainer.alpha = 0; break; } // Upewnij się, że licznik śmierci jest zawsze widoczny self.deathsText.alpha = 1; }; return self; }); /**** * Initialize Game ****/ var game = new LK.Game({ backgroundColor: 0x111111 // Ciemne tło domyślne }); /**** * Game Code ****/ // Zachowujemy na wypadek użycia w animacjach gracza/UI // Używamy dźwięku zwycięstwa z stworzylo zloto.txt // Przykład koloru, użyje grafiki z upit // Przykład koloru, użyje grafiki z upit // Asset dla tła Grill Screena (dostosuj wymiary jeśli Twoja grafika ma inne) // Zmieniono wymiary przycisku // Twój nowy asset dla paska zdrowia bossa (800x30) // Zachowujemy jako domyślne tło lub na wypadek potrzeby // Globalny kontener na elementy scen intro/tutoriali i Grill Screena (do łatwego czyszczenia) var currentSceneElements = new Container(); // Dodano z souls4.txt i rozbudowano dla Grill Screena // Zmienne gry (z stworzylo zloto.txt) var player; var boss; var ui; var walls = []; // Ściany areny bossa // Dodano zmienną do przechowywania aktywnego tła sceny var currentBackground = null; // Dodana flaga do śledzenia, czy jesteśmy w trybie New Boss+ var isNewBossPlusMode = false; // Dodana flaga do śledzenia przyczyny Game Over (true = gracz zginął, false = czas minął) var gameOverReasonIsDeath = false; // Funkcja do czyszczenia elementów z kontenera currentSceneElements (dla scen intro/tutoriali i Grill Screena) // Nie usuwa tła sceny (currentBackground) ani elementów UI. function clearScene() { // Dodano z souls4.txt i rozbudowano dla Grill Screena // Usuń wszystkie dzieci z kontenera while (currentSceneElements.children.length > 0) { var child = currentSceneElements.children[0]; // Upewnij się, że obiekt istnieje i ma metodę destroy przed wywołaniem if (child && child.destroy) { child.destroy(); // Użyj destroy jeśli dostępne } else { currentSceneElements.removeChild(child); // Fallback } } // Nie resetujemy tutaj game.update ani input handlerów, bo są zarządzane przez gameState } // Obiekt zarządzający stanami gry (z stworzylo zloto.txt, rozbudowany o stany intro, timer i Grill Screen) var gameState = { currentState: "title", // Początkowy stan gry // Zmienne dla timera walki z bossem gameDuration: 120, // Czas trwania walki z bossem w sekundach (2 minuty) - Domyślne remainingTime: 0, // Pozostały czas gameTimerInterval: null, // ID interwału timera // Zmienne dla fałszywego tutorialu fakeTutorialTimerId: null, // ID timera przechodzącego do prawdziwego tutorialu // Zmienne dla przyspieszenia bossa bossSpeedIncreased: false, // Flaga informująca czy prędkość bossa została już zwiększona // Zmienne do śledzenia gestów dotykowych/myszy (dla turlania i tapowania) touchStart: { x: 0, y: 0 }, touchEnd: { x: 0, y: 0 }, // Inicjalizacja gry (wywoływana raz na początku aplikacji) init: function init() { // ZRESETUJ LICZNIK ŚMIERCI I USTAW DOMYŚLNĄ ILOŚĆ SERC NA START APLIKACJI // Resetuj licznik śmierci PRZY KAŻDYM URUCHOMIENIU GRY (załadowaniu strony) storage.totalDeaths = 0; // Ustaw początkową liczbę serc na 5 przy każdym uruchomieniu gry storage.maxHearts = 5; // Zresetuj flagę trybu New Boss+ na początku gry isNewBossPlusMode = false; // Zresetuj flagę przyczyny Game Over gameOverReasonIsDeath = false; // Nie dodajemy tutaj domyślnego tła 'bg'. Tła będą dodawane w metodach scen. game.setBackgroundColor(0x111111); // Ustaw domyślny kolor tła (widoczny np. podczas ładowania assetów) // Stwórz ściany areny bossa (potrzebne w stanie gry, ale stworzone wcześniej) this.createWalls(); // Stwórz UI (ikony serc, teksty tytułów/komunikatów/tutoriali, licznik śmierci, timer, pasek zdrowia bossa) ui = game.addChild(new UI()); // Pozycjonuj UI początkowo dla ekranu tytułowego ui.positionElements("title"); ui.updateDeathsCounter(); // Zaktualizuj wyświetlanie licznika śmierci (powinien być 0) ui.updateHearts(storage.maxHearts, storage.maxHearts); // Zaktualizuj wyświetlanie serc (5 pełnych) // Dodaj kontener na elementy scen intro i Grill Screena do wyświetlania (nad UI) game.addChild(currentSceneElements); // currentSceneElements powinien być dodany po UI, żeby być nad nim // Rozpocznij muzykę w tle LK.playMusic('bgMusic', { fade: { start: 0, end: 0.3, duration: 1000 } }); // Rozpocznij od ekranu tytułowego zarządzanego przez ten obiekt gameState this.showTitleScreen(); }, // Tworzy/odświeża ściany areny bossa createWalls: function createWalls() { // Usuń istniejące ściany, jeśli istnieją walls.forEach(function (wall) { if (wall && wall.destroy) { wall.destroy(); } }); walls = []; // Lewa ściana var leftWall = game.addChild(LK.getAsset('wall', { anchorX: 0, anchorY: 0, x: 0, y: 0 })); walls.push(leftWall); // Prawa ściana var rightWall = game.addChild(LK.getAsset('wall', { anchorX: 0, anchorY: 0, x: 2048 - 100, y: 0 })); walls.push(rightWall); // Górna ściana var topWall = game.addChild(LK.getAsset('floor', { anchorX: 0, anchorY: 0, x: 0, y: 0 })); walls.push(topWall); // Dolna ściana var bottomWall = game.addChild(LK.getAsset('floor', { anchorX: 0, anchorY: 0, x: 0, y: 2732 - 100 })); walls.push(bottomWall); // Upewnij się, że ściany są na odpowiedniej warstwie (pod graczem i bosem) // Domyślnie obiekty są dodawane w kolejności, więc ściany dodane przed graczem i bosem będą pod nimi. // UI i currentSceneElements dodane na końcu w init są na górze. }, // ---------- Metody przejścia między stanami ---------- // Przejście do ekranu tytułowego showTitleScreen: function showTitleScreen() { clearScene(); // Wyczyść elementy poprzedniej sceny (np. z Grill Screena, Victory lub Fake Death) // Upewnij się, że timer fake tutorialu jest wyczyszczony if (this.fakeTutorialTimerId) { LK.clearTimeout(this.fakeTutorialTimerId); this.fakeTutorialTimerId = null; } // Zatrzymaj wszelkie trwające tweeny tła if (currentBackground) { tween.stop(currentBackground); currentBackground.destroy(); // Usuń poprzednie tło currentBackground = null; } this.currentState = "title"; // Ustaw stan na tytuł game.setBackgroundColor(0x1a1a1a); // Ciemne tło bazowe dla tytułu // Dodaj tło ekranu tytułowego (titleBg) currentBackground = game.addChildAt(LK.getAsset('titleBg', { // Dodaj na spód (index 0) anchorX: 0, anchorY: 0, x: 0, y: 0 }), 0); // Dodaj jako pierwsze dziecko gry (na samym spodzie) // Ukryj obiekty gry (gracz, boss) jeśli istnieją if (player) { player.alpha = 0; } if (boss) { boss.alpha = 0; } // Pokaż ściany jako tło menu/intro (jeśli nie mają być widoczne, ustaw alpha na 0) walls.forEach(function (wall) { if (wall) { wall.alpha = 1; } }); ui.positionElements("title"); // Pozycjonuj UI dla stanu tytułowego ui.titleText.setText("ROLL SOULS"); // Upewnij się, że tytuł jest poprawny ui.titleText.alpha = 1; // Pokaż tytuł ui.showMessage("Tap to Start", 0); // Wyświetl komunikat startowy ui.showTutorial("Swipe to Roll - Death is Progress"); // Wyświetl tekst tutorialu (można go ukryć i pokazać dopiero w prawdziwym tutorialu) // Resetuj stan touchGesture na wypadek kliknięcia w poprzednim stanie końcowym this.touchStart = { x: 0, y: 0 }; this.touchEnd = { x: 0, y: 0 }; // --- Dodaj testowy przycisk do Grill Menu --- var grillMenuTestButton = new Container(); grillMenuTestButton.interactive = true; // Spraw, żeby przycisk był interaktywny grillMenuTestButton.x = 2048 / 2; // Wyśrodkuj poziomo grillMenuTestButton.y = 1600; // Ustaw pozycję pionową (np. poniżej innych tekstów) currentSceneElements.addChild(grillMenuTestButton); // Dodaj do kontenera sceny tytułowej var grillButtonBg = LK.getAsset('button_bg', { anchorX: 0.5, anchorY: 0.5 }); grillMenuTestButton.addChild(grillButtonBg); // Dodaj tło przycisku var grillButtonText = new Text2('Go to Grill Menu', { size: 50, fill: 0xFFFFFF }); // Tekst przycisku grillButtonText.anchor.set(0.5, 0.5); grillMenuTestButton.addChild(grillButtonText); // Dodaj tekst do przycisku // Logika przycisku: po kliknięciu przejdź do Grill Menu grillMenuTestButton.down = function () { // Opcjonalnie: wyczyść elementy sceny tytułowej przed przejściem clearScene(); // Przejdź do ekranu Grill Menu gameState.showGrillScreen(); }; // --- Koniec dodawania przycisku testowego --- }, // Przejście do pierwszej sceny intro showIntro: function showIntro() { clearScene(); // Wyczyść elementy poprzedniej sceny (Tytuł) // Upewnij się, że timer fake tutorialu jest wyczyszczony if (this.fakeTutorialTimerId) { LK.clearTimeout(this.fakeTutorialTimerId); this.fakeTutorialTimerId = null; } // Zatrzymaj wszelkie trwające tweeny tła if (currentBackground) { tween.stop(currentBackground); currentBackground.destroy(); // Usuń poprzednie tło currentBackground = null; } this.currentState = "intro"; // Ustaw stan na intro game.setBackgroundColor(0x111111); // Ciemne tło bazowe dla intro // Dodaj tło scen intro (introBg) currentBackground = game.addChildAt(LK.getAsset('introBg', { // Dodaj na spód (index 0) anchorX: 0, anchorY: 0, x: 0, y: 0, scaleX: 1, scaleY: 1 // Upewnij się, że zaczyna w normalnym rozmiarze }), 0); // Zastosuj powolny zoom do tła intro // Zoom IN do 1.1x przez 30 sekund, potem zoom OUT, powtarzaj tween(currentBackground, { scaleX: 1.1, scaleY: 1.1 }, { duration: 30000, // 30 sekund na jeden kierunek zoomu easing: tween.linear, repeat: Infinity, // Powtarzaj w nieskończoność yoyo: true // Zoomuj w tam i z powrotem }); // Ukryj obiekty gry i ściany na czas intro/tutoriali if (player) { player.alpha = 0; } if (boss) { boss.alpha = 0; } walls.forEach(function (wall) { if (wall) { wall.alpha = 0; } }); // Ukryj ściany ui.positionElements("intro"); // Ukryj elementy UI walki (zostaw licznik śmierci) // Tekst pierwszej sceny intro (z souls4.txt) var introText1 = new Text2('From the creators of FromSoftware...', { size: 90, fill: 0xFFFFFF, align: 'center', wordWrap: true, wordWrapWidth: 1800 }); introText1.anchor.set(0.5, 0.5); introText1.x = 2048 / 2; introText1.y = 2732 / 2; currentSceneElements.addChild(introText1); // Dodaj do kontenera, który będzie czyszczony // Timer 1: Zmień tekst po 5 sekundach LK.setTimeout(function () { // Usuń pierwszy tekst if (introText1.parent) { introText1.destroy(); } // Tekst drugiej sceny intro (z souls4.txt) var introText2 = new Text2('...I have no idea. I don’t know them.', { size: 90, fill: 0xFFFFFF, align: 'center', wordWrap: true, wordWrapWidth: 1800 }); introText2.anchor.set(0.5, 0.5); introText2.x = 2048 / 2; introText2.y = 2732 / 2; currentSceneElements.addChild(introText2); // Dodaj do kontenera // Timer 2: Przejdź do fałszywego tutorialu po kolejnych 2 sekundach this.fakeTutorialTimerId = LK.setTimeout(function () { // ZAPISZ ID TIMERA // clearScene() zostanie wywołane na początku następnej metody // Zresetuj ID timera, bo się zakończył gameState.fakeTutorialTimerId = null; // Przejdź do fałszywego tutorialu gameState.showFakeTutorial(); }.bind(this), 2000); // 2 sekundy opóźnienia po wyświetleniu drugiego tekstu. Użyj .bind(this) dla dostępu do this.fakeTutorialTimerId }.bind(this), 5000); // 5 sekund opóźnienia przed wyświetleniem drugiego tekstu. Użyj .bind(this) dla dostępu do this.fakeTutorialTimerId }, // Przejście do sceny fałszywego tutorialu (Press LPM to block) showFakeTutorial: function showFakeTutorial() { clearScene(); // Wyczyść poprzednie elementy (tekst intro) // Tło introBg i jego zoom powinny pozostać, bo nie są w currentSceneElements // Upewnij się, że timer fake tutorialu jest wyczyszczony (na wypadek gdybyśmy tu dotarli inaczej niż przez timer) if (this.fakeTutorialTimerId) { LK.clearTimeout(this.fakeTutorialTimerId); this.fakeTutorialTimerId = null; } this.currentState = "fakeTutorial"; // Ustaw stan na fałszywy tutorial game.setBackgroundColor(0x000000); // Czarne tło - Może chcesz, żeby tło introBg było widoczne? Zmieniam na czarne zgodnie z oryginalnym souls4.txt, ale możesz to zmienić. // Ukryj obiekty gry i ściany if (player) { player.alpha = 0; } if (boss) { boss.alpha = 0; } walls.forEach(function (wall) { if (wall) { wall.alpha = 0; } }); // Tekst fałszywego tutorialu - Zmieniony na "Press LPM to block" var instructionText = new Text2('Press LPM to block.. or don’t press.', { size: 100, fill: 0xFFFFFF, align: 'center', wordWrap: true, wordWrapWidth: 1800 }); instructionText.anchor.set(0.5, 0.5); instructionText.x = 2048 / 2; instructionText.y = 2732 / 2; currentSceneElements.addChild(instructionText); // Timer przechodzący do prawdziwego tutorialu - ZAPISZ JEGO ID this.fakeTutorialTimerId = LK.setTimeout(function () { // clearScene() zostanie wywołane na początku następnej metody // Zresetuj ID timera, bo się zakończył gameState.fakeTutorialTimerId = null; // Przejdź do prawdziwego tutorialu gameState.showRealTutorial(); }, 6000); // 6 sekund }, // Obsługa inputu w stanie fakeTutorial (fałszywa śmierć) handleFakeTutorialInput: function handleFakeTutorialInput() { // Tylko obsłuż input, jeśli jesteśmy w stanie fakeTutorial i timer jeszcze działa if (this.currentState === "fakeTutorial" && this.fakeTutorialTimerId) { this.handleFakeTutorialInput(); // REMOVED self-recursive call that caused stack overflow } // Wyczyść timer, który miał prowadzić do prawdziwego tutorialu LK.clearTimeout(this.fakeTutorialTimerId); this.fakeTutorialTimerId = null; // Zresetuj ID timera // Wyczyść ekran fałszywego tutorialu clearScene(); // Ustaw stan na game over (tymczasowo, żeby logika UI się zgadzała) this.currentState = "gameOver"; // Można użyć dedykowanego stanu 'fakeGameOver' jeśli chcemy inaczej obsłużyć UI // Zatrzymaj wszelkie trwające tweeny tła (np. zoom intro) if (currentBackground) { tween.stop(currentBackground); // Nie usuwamy tła od razu, może chcemy, żeby było widać je za ekranem "YOU DIED" // currentBackground.destroy(); currentBackground = null; } game.setBackgroundColor(0x000000); // Czarne tło bazowe za fake game over // Wyświetl "YOU DIED" na czerwono var diedText = new Text2("YOU DIED", { size: 150, fill: 0xFF0000 // Czerwony kolor }); diedText.anchor.set(0.5, 0.5); diedText.x = 2048 / 2; diedText.y = 800; currentSceneElements.addChild(diedText); // Wyświetl "Did you check the title of the game?" var explanationText = new Text2("Did you check the title of the game?", { size: 60, fill: 0xFFFFFF, // Biały kolor align: 'center', wordWrap: true, wordWrapWidth: 1800 }); explanationText.anchor.set(0.5, 0.5); explanationText.x = 2048 / 2; explanationText.y = 1000; currentSceneElements.addChild(explanationText); // Dodaj przycisk "How to play again" var howToPlayButtonContainer = new Container(); howToPlayButtonContainer.interactive = true; howToPlayButtonContainer.x = 2048 / 2; howToPlayButtonContainer.y = 1300; // Pozycja przycisku currentSceneElements.addChild(howToPlayButtonContainer); var buttonBg = LK.getAsset('button_bg', { anchorX: 0.5, anchorY: 0.5 }); howToPlayButtonContainer.addChild(buttonBg); var buttonText = new Text2('How to play again', { size: 60, fill: 0xFFFFFF }); // Zmniejszono rozmiar czcionki, żeby pasowała do przycisku buttonText.anchor.set(0.5, 0.5); howToPlayButtonContainer.addChild(buttonText); // Akcja przycisku: Przejdź z powrotem do prawdziwego tutorialu howToPlayButtonContainer.down = function () { gameState.showRealTutorial(); // Przejdź do prawdziwego tutorialu }; }, // Przejście do sceny prawdziwego tutorialu showRealTutorial: function showRealTutorial() { clearScene(); // Wyczyść poprzednie elementy (fałszywa śmierć lub fałszywy tutorial) // Tło introBg i jego zoom powinny pozostać // Upewnij się, że timer fake tutorialu jest wyczyszczony if (this.fakeTutorialTimerId) { LK.clearTimeout(this.fakeTutorialTimerId); this.fakeTutorialTimerId = null; } this.currentState = "realTutorial"; // Ustaw stan na prawdziwy tutorial game.setBackgroundColor(0x1a1a1a); // Ciemne tło bazowe - Może chcesz, żeby tło introBg było widoczne? // Ukryj obiekty gry i ściany if (player) { player.alpha = 0; } if (boss) { boss.alpha = 0; } walls.forEach(function (wall) { if (wall) { wall.alpha = 0; } }); var yOffset = 2732 / 3; // Początkowa pozycja Y dla tekstu // Tekst tutorialu (zmodyfikowany do mechaniki stworzylo zloto.txt) var instructionText1 = new Text2('Swipe in a direction to ROLL.', { size: 80, fill: 0xFFFFFF, align: 'center', wordWrap: true, wordWrapWidth: 1800 }); instructionText1.anchor.set(0.5, 0.5); instructionText1.x = 2048 / 2; instructionText1.y = yOffset; currentSceneElements.addChild(instructionText1); var instructionText2 = new Text2('There is a short cooldown after each roll.', { size: 80, fill: 0xFFFFFF, align: 'center', wordWrap: true, wordWrapWidth: 1800 }); instructionText2.anchor.set(0.5, 0.5); instructionText2.x = 2048 / 2; instructionText2.y = yOffset + 150; currentSceneElements.addChild(instructionText2); var instructionText3 = new Text2('That\'s it. That\'s the whole game.', { size: 80, fill: 0xFFFFFF, align: 'center', wordWrap: true, wordWrapWidth: 1800 }); instructionText3.anchor.set(0.5, 0.5); instructionText3.x = 2048 / 2; instructionText3.y = yOffset + 300; currentSceneElements.addChild(instructionText3); // Przycisk "Let's Roll" (używamy button_bg) var rollButtonContainer = new Container(); rollButtonContainer.interactive = true; // Przycisk jest interaktywny rollButtonContainer.x = 2048 / 2; rollButtonContainer.y = yOffset + 600; currentSceneElements.addChild(rollButtonContainer); var buttonBg = LK.getAsset('button_bg', { anchorX: 0.5, anchorY: 0.5 }); rollButtonContainer.addChild(buttonBg); var rollButtonText = new Text2('Let\'s Roll', { size: 60, fill: 0xFFFFFF }); // Zmniejszono rozmiar czcionki, żeby pasowała do przycisku rollButtonText.anchor.set(0.5, 0.5); rollButtonContainer.addChild(rollButtonText); // Akcja przycisku: Rozpocznij walkę z bossem (przejdź do stanu gry) rollButtonContainer.down = function () { // Wyczyść elementy intro przed rozpoczęciem gry clearScene(); // Zatrzymaj tween zoomu tła intro i usuń tło if (currentBackground) { tween.stop(currentBackground); currentBackground.destroy(); currentBackground = null; } // Upewnij się, że tryb New Boss+ jest WYŁĄCZONY przy standardowym starcie z tutorialu isNewBossPlusMode = false; gameState.startGame(); // Wywołaj metodę rozpoczynającą grę (walkę z bossem) }; }, // Przejście do stanu gry (walka z bossem) startGame: function startGame() { // Upewnij się, że timer fake tutorialu jest wyczyszczony if (this.fakeTutorialTimerId) { LK.clearTimeout(this.fakeTutorialTimerId); this.fakeTutorialTimerId = null; } // Zatrzymaj wszelkie trwające tweeny tła if (currentBackground) { tween.stop(currentBackground); currentBackground.destroy(); // Usuń tło intro/tytułu currentBackground = null; } this.currentState = "game"; // Ustaw stan na "game" game.setBackgroundColor(0x111111); // Ciemne tło bazowe dla gry // Pokaż obiekty gry (gracz, boss), ukryte w intro/tutorialu // Upewnij się, że istnieją przed ustawieniem alpha if (player) { player.alpha = 1; } if (boss) { boss.alpha = 1; } // Pokaż ściany areny walls.forEach(function (wall) { if (wall) { wall.alpha = 1; } }); ui.positionElements("game"); // Pozycjonuj UI dla stanu gry (ikony serc, licznik śmierci, timer) // Stwórz gracza (jeśli nie istnieje lub zniszcz poprzedniego) if (player) { player.destroy(); } // Zniszcz poprzedniego gracza jeśli był player = game.addChild(new Player()); // Stwórz nowego gracza // Ustaw początkowe zdrowie gracza na podstawie storage.maxHearts (domyślnie 5 na start aplikacji, może wzrośnie po śmierciach) player.health = storage.maxHearts || 5; // Jawnie zresetuj stan turlania i nietykalności gracza na początku walki player.rolling = false; player.invulnerable = false; player.invulnerabilityFrames = 0; player.rollCooldown = 0; player.clearRollTimeouts(); // Wyczyść timery turlania/nietykalności player.hasRolledThroughBossThisRoll = false; // Zresetuj flagę obrażeń od turlania dla nowego gracza player.x = 2048 / 2; player.y = 2732 / 2 + 400; // Pozycja startowa gracza // Stwórz bossa (jeśli nie istnieje lub zniszcz poprzedniego) if (boss) { boss.destroy(); } // Zniszcz poprzedniego bossa jeśli był boss = game.addChild(new Boss()); // Stwórz nowego bossa boss.x = 2048 / 2; boss.y = 2732 / 2 - 400; // Pozycja startowa bossa // Upewnij się, że boss zaczyna w stanie gotowości do ataku z wyczyszczonymi poprzednimi atakami boss.attackCooldown = 0; // Boss zaatakuje po pierwszym opóźnieniu boss.attacks = []; // Wyczyść wszelkie pozostałości po poprzednich atakach boss.attackSpeedMultiplier = 1; // Ustaw początkowy mnożnik prędkości ataków na 1 (normalny) boss.repositioning = false; // Zresetuj flagę repozycji bossa // !!! Ustaw zdrowie bossa i czas gry W ZALEŻNOŚCI OD TRYBU !!! if (isNewBossPlusMode) { boss.maxHealth = 2000; // New Boss+ ma 2000 HP boss.health = boss.maxHealth; this.gameDuration = 600; // New Boss+ ma 10 minut (600 sekund) // W trybie New Boss+ ataki są szybsze OD POCZĄTKU i NIE MA dodadkowego przyspieszenia po minucie boss.attackSpeedMultiplier = 0.6; // Przykładowy, niższy mnożnik (szybsze ataki) OD POCZĄTKU // Można tu dodać inne modyfikatory dla New Boss+, np. wyższą prędkość ruchu bossa boss.speed = 7; // Przykładowe szybsze poruszanie się bossa } else { // Standardowy boss boss.maxHealth = 200; boss.health = boss.maxHealth; this.gameDuration = 120; // Standardowy czas 2 minuty boss.speed = 5; // Standardowa prędkość poruszania się bossa boss.attackSpeedMultiplier = 1; // Standardowa prędkość ataków } // Aktualizuj UI (wyświetl serca i początkowy komunikat) ui.updateHearts(player.health, storage.maxHearts); // Wyświetl aktualne serca (powinno być 5 lub więcej) ui.showTutorial("Swipe to roll away from attacks!"); // Wyświetl tutorial dla stanu gry ui.updateBossHealth(boss.health, boss.maxHealth); // Zaktualizuj pasek zdrowia bossa z poprawnym HP // --- Timer walki z bossem --- this.remainingTime = this.gameDuration; // Ustaw początkowy czas ZALEŻNIE OD TRYBU ui.updateTimerDisplay(this.remainingTime); // Wyświetl początkowy czas // Rozpocznij interwał timera // Wyczyść poprzedni interwał timera, jeśli istniał (przy restartach gry) if (this.gameTimerInterval) { LK.clearInterval(this.gameTimerInterval); } // Zresetuj flagę przyspieszenia bossa po 1 minucie (dotyczy obu trybów) this.bossSpeedIncreased = false; this.gameTimerInterval = LK.setInterval(function () { // Tylko aktualizuj timer i sprawdzaj warunek zwycięstwa/game over jeśli jesteśmy w stanie gry if (gameState.currentState === "game") { gameState.remainingTime--; // Zmniejsz pozostały czas ui.updateTimerDisplay(gameState.remainingTime); // Zaktualizuj wyświetlanie timera na UI // --- Przyspieszenie bossa - TYLKO W STANDARDOWYM TRYBIE --- if (!isNewBossPlusMode) { // Przyspieszenie tylko w standardowym trybie var accelerationThreshold = gameState.gameDuration - 60; // Przyspieszenie na 60 sekund przed końcem if (gameState.remainingTime <= accelerationThreshold && !gameState.bossSpeedIncreased) { gameState.bossSpeedIncreased = true; // Ustaw flagę if (boss && !boss.dead) { // Upewnij się, że boss istnieje i żyje // Zmniejsz mnożnik prędkości ataków bossa (np. o kolejne 30% -> mnożnik 0.7 * 1 = 0.7) boss.attackSpeedMultiplier *= 0.7; // Przykładowe dalsze przyspieszenie ataków // Opcjonalnie: wyświetl komunikat o przyspieszeniu ui.showMessage("Boss attacks faster!", 2000); // Komunikat } } } // --- Koniec Przyspieszenia bossa --- // Sprawdź warunek końca gry (czas minął) if (gameState.remainingTime <= 0) { LK.clearInterval(gameState.gameTimerInterval); // Stop the timer // Ustaw przyczynę Game Over na "czas minął" gameOverReasonIsDeath = false; // Czas minął, nie śmierć gracza // Przejdź do stanu Game Over (z odpowiednim komunikatem) gameState.gameOver(false); // Przekaż false, bo czas minął } } }, 1000); // Interwał co 1 sekundę (1000 ms) // --- Koniec Timer walki z bossem --- // Zaplanuj rozpoczęcie ataków bossa po krótkim opóźnieniu // Nie potrzebujemy jawnie czyścić timeoutu, bo startAttackPattern ma sprawdzenie stanu gry LK.setTimeout(function () { // Sprawdź, czy nadal jesteśmy w stanie gry gdy timeout minie i czy boss istnieje i żyje if (gameState.currentState === "game" && boss && !boss.dead) { ui.hideTutorial(); // Ukryj tekst tutorialu boss.startAttackPattern(); // Rozpocznij ataki bossa } }, 3000); // 3 sekundy opóźnienia przed pierwszym atakiem bossa }, // !!! NOWA METODA: Przejście do ekranu Grilla po zwycięstwie (pokonaniu bossa) !!! showGrillScreen: function showGrillScreen() { clearScene(); // Wyczyść elementy poprzedniej sceny (gra, ataki, itp.) // Zatrzymaj timer gry, jeśli nadal działa (jeśli wygrana była przez zbicie HP bossa, a nie czasem) LK.clearInterval(this.gameTimerInterval); // Zresetuj flagę przyspieszenia bossa this.bossSpeedIncreased = false; this.currentState = "grillMenu"; // Ustaw nowy stan gry game.setBackgroundColor(0x333333); // Możesz ustawić kolor tła lub pozwolić assetowi to zrobić // Zatrzymaj wszelkie trwające tweeny tła gry i usuń tło gry, jeśli istnieje if (currentBackground) { tween.stop(currentBackground); currentBackground.destroy(); currentBackground = null; } // Dodaj tło dla ekranu Grilla currentBackground = game.addChildAt(LK.getAsset('grillMenu', { // Użyj assetu 'grillMenu' anchorX: 0.5, anchorY: 0.5, // Ustaw środek jako punkt odniesienia x: 2048 / 2, y: 2732 / 2 // Wyśrodkuj na ekranie }), 0); // Dodaj na spód // Ukryj obiekty gry (gracz, boss) jeśli istnieją (choć powinny być zniszczone) if (player) { player.alpha = 0; } if (boss) { boss.alpha = 0; } // Ukryj ściany areny walls.forEach(function (wall) { if (wall) { wall.alpha = 0; } }); // Pozycjonuj elementy UI dla stanu Grill Screena ui.positionElements("grillMenu"); // --- Dodaj przyciski na ekranie Grilla --- var buttonYStart = 1000; // Początkowa pozycja Y dla pierwszego przycisku var buttonYOffset = 150; // Odstęp pionowy między przyciskami // Przycisk "Rest" var restButton = new Container(); restButton.interactive = true; restButton.x = 2048 / 2; restButton.y = buttonYStart; currentSceneElements.addChild(restButton); // Dodaj do kontenera sceny var restButtonBg = LK.getAsset('button_bg', { anchorX: 0.5, anchorY: 0.5 }); restButton.addChild(restButtonBg); var restButtonText = new Text2('Rest', { size: 50, fill: 0xFFFFFF }); restButtonText.anchor.set(0.5, 0.5); restButton.addChild(restButtonText); // Logika przycisku "Rest" restButton.down = function () { // Wyświetl komunikat "Rest in peace" i podziękowanie ui.showMessage("Rest in peace...", 2000); // Zniknie po 2 sekundach LK.setTimeout(function () { ui.showMessage("Thank you for playing!", 3000); // Zniknie po 3 sekundach }, 2500); // Pokaż podziękowanie po chwili // Zaplanuj powrót do ekranu tytułowego po wyświetleniu podziękowań LK.setTimeout(function () { // Upewnij się, że elementy Grill Screena zostały wyczyszczone przed powrotem do tytułu clearScene(); gameState.showTitleScreen(); // Wróć do ekranu tytułowego }, 6000); // Opóźnienie przed powrotem do tytułu }; // Przycisk "Upgrade Roll" var upgradeButton = new Container(); upgradeButton.interactive = true; upgradeButton.x = 2048 / 2; upgradeButton.y = buttonYStart + buttonYOffset; currentSceneElements.addChild(upgradeButton); // Dodaj do kontenera sceny var upgradeButtonBg = LK.getAsset('button_bg', { anchorX: 0.5, anchorY: 0.5 }); upgradeButton.addChild(upgradeButtonBg); var upgradeButtonText = new Text2('Upgrade Roll', { size: 50, fill: 0xFFFFFF }); upgradeButtonText.anchor.set(0.5, 0.5); upgradeButton.addChild(upgradeButtonText); // Logika przycisku "Upgrade Roll" upgradeButton.down = function () { ui.showMessage("Rolling?", 2000); // Wyświetl śmieszny napis // W przyszłości można tu dodać rzeczywistą logikę ulepszenia turlania }; // Przycisk "New Boss+" var newBossButton = new Container(); newBossButton.interactive = true; newBossButton.x = 2048 / 2; newBossButton.y = buttonYStart + buttonYOffset * 2; currentSceneElements.addChild(newBossButton); // Dodaj do kontenera sceny var newBossButtonBg = LK.getAsset('button_bg', { anchorX: 0.5, anchorY: 0.5 }); newBossButton.addChild(newBossButtonBg); var newBossButtonText = new Text2('New Boss+', { size: 50, fill: 0xFFFFFF }); newBossButtonText.anchor.set(0.5, 0.5); newBossButton.addChild(newBossButtonText); // Logika przycisku "New Boss+" newBossButton.down = function () { clearScene(); // Wyczyść przyciski i tło Grilla // Ustaw flagę trybu New Boss+ isNewBossPlusMode = true; // Rozpocznij grę z nowymi parametrami (obsłużone w startGame) gameState.startGame(); }; // --- Koniec dodawania przycisków --- // Ukryj pasek zdrowia bossa na ekranie Grilla ui.updateBossHealth(0, boss ? boss.maxHealth : 200); // Ustaw 0 zdrowia, ukryje pasek }, // Przejście do stanu Game Over (wywoływane przez player.die() lub przez timer) // Przyjmuje argument isDeath: true jeśli gracz zginął, false jeśli czas minął gameOver: function gameOver(isDeath) { this.currentState = "gameOver"; // Ustaw stan na game over // Zatrzymaj timer gry, jeśli nadal działa LK.clearInterval(this.gameTimerInterval); // Upewnij się, że interwał timera jest wyczyszczony // Zresetuj flagę przyspieszenia bossa this.bossSpeedIncreased = false; // Zachowaj reset flagi przyspieszenia // Zatrzymaj wszelkie trwające tweeny tła if (currentBackground) { tween.stop(currentBackground); // Nie usuwamy tła od razu, może chcemy, żeby było widać je za ekranem game over // currentBackground.destroy(); currentBackground = null; } game.setBackgroundColor(0x000000); // Czarne tło bazowe za game over // Ukryj obiekty gry (są już niszczone, ale to dodatkowe zabezpieczenie) // Upewnij się, że istnieją przed ustawieniem alpha if (player) { player.alpha = 0; } if (boss) { boss.alpha = 0; } // Ukryj ściany walls.forEach(function (wall) { if (wall) { wall.alpha = 0; } }); // Ustaw pozycje UI dla ekranu game over ui.positionElements("gameOver"); // !!! Zmodyfikowany komunikat Game Over w zależności od trybu i przyczyny !!! var gameOverMessage = "YOU DIED"; // Domyślny komunikat // Sprawdź, CZY BYLIŚMY W TRYBIE New Boss+ if (isNewBossPlusMode) { // Użyjemy wartości isNewBossPlusMode Zanim ją zmienimy // W trybie New Boss+ komunikat zależy od przyczyny końca gry if (isDeath) { // Jeśli gracz zginął (HP=0) gameOverMessage = "I haven't prepared a scene for this touch the grass"; } else { // Jeśli czas minął gameOverMessage = "Wlaśnie straciles 10 minut życia"; } } else { // W trybie standardowym Game Over oznacza zawsze śmierć gracza (jeśli czas minął, to zwycięstwo) gameOverMessage = "YOU DIED"; } // Wyświetl komunikat Game Over ui.showMessage(gameOverMessage, 0); // Wyświetl na stałe do momentu restartu ui.titleText.setText(gameOverMessage); // Ustaw tekst tytułu ui.titleText.alpha = 1; // Pokaż tytuł ui.showTutorial(""); // Ukryj tutorial // Ukryj pasek zdrowia bossa na ekranie game over ui.updateBossHealth(0, isNewBossPlusMode ? 2000 : 200); // Użyj poprawnego maxHP dla ukrycia/resetu // Zaplanuj restart gry po opóźnieniu (powrót do stanu startGame) LK.setTimeout(function () { // Clean up player and boss (gdyby nie zostały zniszczone wcześniej - np. w player.die/boss.die) if (player) { player.destroy(); player = null; } if (boss) { boss.destroy(); boss = null; } // Wyczyść wszystkie aktywne ataki bossa i ich wizualizacje if (boss && boss.attacks) { // Sprawdź czy boss i jego ataki istniały przed zniszczeniem boss.attacks.forEach(function (attack) { if (attack.visual && attack.visual.destroy && !attack.visual.destroyed) { attack.visual.destroy(); } }); boss.attacks = []; // Wyczyść tablicę ataków } // Usuń tło game over if (currentBackground) { currentBackground.destroy(); currentBackground = null; } // Resetuj UI do stanu tytułowego przed potencjalnym restartem ui.positionElements("title"); // Pozycjonuj UI jak dla tytułu ui.titleText.alpha = 0; // Ukryj tytuł ponownie ui.showMessage(""); // Wyczyść komunikat ui.showTutorial(""); // Wyczyść tutorial ui.updateDeathsCounter(); // Upewnij się, że licznik śmierci jest aktualny ui.updateBossHealth(0, 200); // Zresetuj i ukryj pasek zdrowia bossa // *** ZMIANA TUTAJ - usunięto reset flagi isNewBossPlusMode = false; *** // Flaga isNewBossPlusMode zachowa swoją wartość sprzed wywołania gameOver // Resetuj flagę przyczyny Game Over gameOverReasonIsDeath = false; // Restartuj walkę z bossem gameState.startGame(); // Przejdź z powrotem do stanu gry }, 3000); // 3 sekundy opóźnienia przed restartem }, // Ta metoda zwycięstwa (przez czas w trybie standardowym) przechodzi teraz do Grill Screena victory: function victory() { // Ta metoda jest wywoływana, gdy w STANDARDOWYM TRYBIE czas minie. // Logika przejścia do Grill Screena jest już w gameTimerInterval dla tego przypadku. console.log("Standard victory state - should transition to Grill Screen via timer check."); // Brak kodu tutaj, ponieważ gameTimerInterval obsługuje przejście }, // Obsługa gestów dotykowych/myszy w zależności od stanu gry processTouchGesture: function processTouchGesture() { // --- Obsługa inputu w stanie fakeTutorial (fałszywa śmierć) --- // Jeśli jesteśmy w stanie fakeTutorial i timer do prawdziwego tutorialu jeszcze działa, // każdy input (tapnięcie lub swipe) wywoła fałszywą śmierć. if (this.currentState === "fakeTutorial" && this.fakeTutorialTimerId) { this.handleFakeTutorialInput(); // Wywołaj logikę fałszywej śmierci return; // Zakończ przetwarzanie gestu } // Oblicz kierunek i dystans gestu na podstawie zarejestrowanych pozycji startu i końca dotyku var dx = this.touchEnd.x - this.touchStart.x; var dy = this.touchEnd.y - this.touchStart.y; var distance = Math.sqrt(dx * dx + dy * dy); // --- Obsługa tapowania/gestów w stanach poza grą (z wyjątkiem Grill Menu) --- // Sprawdź, czy to było krótkie tapnięcie (dystans mniejszy niż próg turlania) if (distance < 50) { // Próg turlania to 50 jednostek // Jeśli jest to tapnięcie w stanie tytułowym, przejdź do intro if (this.currentState === "title") { this.showIntro(); // <--- Tap na ekranie tytułowym przechodzi do intro return; // Zakończ przetwarzanie gestu } // Jeśli to tapnięcie w stanach game over lub victory (które teraz też prowadzą do Grill Screena lub restartu) - zignoruj tapnięcia // Restart/przejście do Grill Screena dzieje się automatycznie po Game Over/Victory if (this.currentState === "gameOver") { // Zignoruj tapnięcie na ekranie Game Over, restart jest timerem return; } if (this.currentState === "victory") { // Ta metoda nie jest już wywoływana bezpośrednio, ale zabezpieczenie na wszelki wypadek return; } // Jeśli to tapnięcie w innych stanach (intro, real tutorial), zignoruj lub dodaj logikę jeśli potrzebna // W real tutorialu tapnięcie nie wywoła nic poza przyciskiem "Let's Roll" return; // Zakończ przetwarzanie gestu jeśli to tapnięcie i nie obsłużono stanu } // --- Obsługa turlania w stanie gry --- // Tylko przetwarzaj gesty turlania (swipe) w stanie "game" i gdy gracz nie jest martwy if (this.currentState !== "game" || !player || player.dead) { return; // Zignoruj gesty swipe w innych stanach } // W tym miejscu wiemy, że currentState === "game", gracz żyje, i dystans gestu jest >= 50 (to swipe) // Minimalny dystans swipe dla turlania (już sprawdzony powyżej) // Normalizuj kierunek gestu var direction = { x: dx / distance, y: dy / distance }; // Wykonaj turlanie gracza player.roll(direction); } }; // --- Obsługa inputu (mapowanie zdarzeń LK na metody gameState) --- game.down = function (x, y, obj) { // Zarejestruj początek dotyku/kliknięcia tylko w stanach, które tego wymagają if (gameState.currentState === "title" || gameState.currentState === "game" || gameState.currentState === "fakeTutorial" || gameState.currentState === "realTutorial" || gameState.currentState === "grillMenu") { // Dodano stany tutoriali i grillMenu. Usunięto "victory" bo już nie ma oddzielnego ekranu zwycięstwa. Usunięto "gameOver" bo restart jest timerem. gameState.touchStart.x = x; gameState.touchStart.y = y; // Zresetuj end point na początku gestu gameState.touchEnd.x = x; gameState.touchEnd.y = y; } }; game.up = function (x, y, obj) { // Zarejestruj koniec dotyku/kliknięcia tylko w stanach, które tego wymagają if (gameState.currentState === "title" || gameState.currentState === "game" || gameState.currentState === "fakeTutorial" || gameState.currentState === "realTutorial" || gameState.currentState === "grillMenu") { // Dodano stany tutoriali i grillMenu. Usunięto "victory" bo już nie ma oddzielnego ekranu zwycięstwa. Usunięto "gameOver" bo restart jest timerem. gameState.touchEnd.x = x; gameState.touchEnd.y = y; // Przetwórz zarejestrowany gest (tap lub swipe) gameState.processTouchGesture(); // Wywołaj metodę przetwarzającą gest } }; game.move = function (x, y, obj) { // Śledź aktualną pozycję dotyku/kursora, aby obliczyć dystans gestu na koniec if (gameState.currentState === "game" || gameState.currentState === "title" || gameState.currentState === "fakeTutorial" || gameState.currentState === "realTutorial" || gameState.currentState === "grillMenu") { // Śledź ruch w stanach, gdzie gesty są istotne. Usunięto "victory" i "gameOver". gameState.touchEnd.x = x; gameState.touchEnd.y = y; } }; // --- Główna pętla aktualizacji gry --- game.update = function () { // Aktualizuj UI (np. licznik śmierci) niezależnie od stanu gry if (ui) { ui.updateDeathsCounter(); // Aktualizuj pasek zdrowia bossa w każdej klatce (jeśli boss istnieje) if (boss) { ui.updateBossHealth(boss.health, boss.maxHealth); } else { // Jeśli boss nie istnieje, ukryj pasek zdrowia bossa // Użyj poprawnego maxHP dla ukrycia/resetu w zależności od trybu ui.updateBossHealth(0, isNewBossPlusMode ? 2000 : 200); } // Pozycjonowanie elementów UI jest obsługiwane przez positionElements wywoływane w metodach stanów // Komunikaty i tytuły są obsługiwane przez showMessage/showTutorial // Timer jest aktualizowany w interwale timera i wyświetlany przez updateTimerDisplay } // Wykonaj logikę gry (aktualizacja gracza, bossa, ataków) tylko w stanie "game" if (gameState.currentState === "game") { if (player) { player.update(); // Aktualizacja gracza (ruch, turlanie, nietykalność, kolizja turlania z bossem) } if (boss) { boss.update(); // Aktualizacja bossa (ruch, ataki, kolizje ataków z graczem) } // Aktualizuj wizualizację serc na UI (ma sens tylko w stanie gry) if (player && ui) { ui.updateHearts(player.health, storage.maxHearts); } // Kolizje ataków bossa z graczem i przejścia do stanów game over/victory // są obsługiwane wewnątrz metod update klas Boss i Player, wywołując odpowiednie metody gameState. } // Logika w innych stanach (title, intro, tutoriale, game over, grillMenu) jest obsługiwana przez ich własne metody/przyciski. }; // --- Rozpoczęcie gry --- // Zamiast wywoływania konkretnej sceny, inicjalizujemy obiekt gameState // i wywołujemy jego metodę init(), która ustawia początkowy stan i rozpoczyna od ekranu tytułowego. gameState.init();
===================================================================
--- original.js
+++ change.js
@@ -339,8 +339,196 @@
self.y = Math.max(100, Math.min(self.y, 2732 - 100));
};
return self;
});
+var Player = Container.expand(function () {
+ var self = Container.call(this);
+ var playerGraphics = self.attachAsset('player', {
+ anchorX: 0.5,
+ anchorY: 0.5
+ });
+ // Player properties
+ self.health = 5; // Default health, will be set from storage.maxHearts
+ self.speed = 8; // Movement speed
+ self.rolling = false; // Is player currently rolling
+ self.rollDirection = {
+ x: 0,
+ y: 0
+ }; // Direction of roll
+ self.rollSpeed = 15; // Speed during roll
+ self.rollCooldown = 0; // Cooldown counter for rolling
+ self.rollDuration = 15; // Frames that roll lasts
+ self.invulnerable = false; // Is player currently invulnerable
+ self.invulnerabilityFrames = 0; // Counter for invulnerability frames
+ self.dead = false; // Is player dead
+ self.rollTimeouts = []; // To store roll-related timeouts
+ self.hasRolledThroughBossThisRoll = false; // Flag to track if damage was dealt during this roll
+ // Clear roll-related timeouts
+ self.clearRollTimeouts = function () {
+ while (self.rollTimeouts.length > 0) {
+ var timeout = self.rollTimeouts.pop();
+ LK.clearTimeout(timeout);
+ }
+ };
+ // Initiate a roll in the given direction
+ self.roll = function (direction) {
+ // Don't roll if player is dead, rolling, or on cooldown
+ if (self.dead || self.rolling || self.rollCooldown > 0) {
+ return;
+ }
+ // Set roll state
+ self.rolling = true;
+ self.rollDirection = direction;
+ self.invulnerable = true;
+ self.invulnerabilityFrames = self.rollDuration;
+ self.hasRolledThroughBossThisRoll = false; // Reset damage dealt flag for this roll
+ // Visualize roll with effect
+ var rollEffect = game.addChild(LK.getAsset('roll', {
+ anchorX: 0.5,
+ anchorY: 0.5,
+ x: self.x,
+ y: self.y
+ }));
+ // End roll after duration
+ var rollTimeout = LK.setTimeout(function () {
+ self.rolling = false;
+ self.rollCooldown = 30; // 30 frames cooldown after roll
+ // Remove timeout from array
+ var index = self.rollTimeouts.indexOf(rollTimeout);
+ if (index !== -1) {
+ self.rollTimeouts.splice(index, 1);
+ }
+ // Fade out roll effect
+ tween(rollEffect, {
+ alpha: 0,
+ scaleX: 0.5,
+ scaleY: 0.5
+ }, {
+ duration: 200,
+ onFinish: function onFinish() {
+ if (rollEffect && rollEffect.destroy) {
+ rollEffect.destroy();
+ }
+ }
+ });
+ }, self.rollDuration * (1000 / 60)); // Convert frames to ms
+ // Store timeout for cleanup
+ self.rollTimeouts.push(rollTimeout);
+ };
+ // Take damage from boss attacks
+ self.takeDamage = function (amount) {
+ // Don't take damage if invulnerable or dead
+ if (self.invulnerable || self.dead || gameState.currentState !== "game") {
+ return;
+ }
+ self.health -= amount;
+ self.health = Math.max(0, self.health);
+ // Visual feedback for damage
+ LK.effects.flashObject(self, 0xFF0000, 200);
+ // Set invulnerability after damage
+ self.invulnerable = true;
+ self.invulnerabilityFrames = 60; // 1 second invulnerability
+ // Check if player died
+ if (self.health <= 0) {
+ self.die();
+ }
+ };
+ // Handle player death
+ self.die = function () {
+ if (self.dead || gameState.currentState !== "game") {
+ return;
+ }
+ self.dead = true;
+ // Increase death counter
+ storage.totalDeaths = (storage.totalDeaths || 0) + 1;
+ // Increase max hearts after a certain number of deaths
+ if (storage.totalDeaths % 5 === 0) {
+ storage.maxHearts = (storage.maxHearts || 5) + 1;
+ }
+ // Death animation
+ tween(self, {
+ alpha: 0,
+ scaleX: 0.5,
+ scaleY: 0.5
+ }, {
+ duration: 1000,
+ easing: tween.easeOut,
+ onFinish: function onFinish() {
+ if (self && !self.destroyed) {
+ self.destroy();
+ }
+ // Set death as reason for game over
+ gameOverReasonIsDeath = true;
+ // Go to game over
+ gameState.gameOver(true);
+ }
+ });
+ };
+ // Update function called every frame
+ self.update = function () {
+ // Don't update if game is not in "game" state
+ if (gameState.currentState !== "game") {
+ return;
+ }
+ // Don't update if player is dead
+ if (self.dead) {
+ return;
+ }
+ // Handle roll cooldown
+ if (self.rollCooldown > 0) {
+ self.rollCooldown--;
+ }
+ // Handle invulnerability frames
+ if (self.invulnerable && self.invulnerabilityFrames > 0) {
+ self.invulnerabilityFrames--;
+ // Flicker effect during invulnerability
+ self.alpha = self.invulnerabilityFrames % 6 > 2 ? 0.5 : 1;
+ if (self.invulnerabilityFrames <= 0) {
+ self.invulnerable = false;
+ self.alpha = 1;
+ }
+ }
+ // Handle rolling movement
+ if (self.rolling) {
+ self.x += self.rollDirection.x * self.rollSpeed;
+ self.y += self.rollDirection.y * self.rollSpeed;
+ // Check collision with boss during roll
+ if (boss && !boss.dead && !self.hasRolledThroughBossThisRoll) {
+ var dx = self.x - boss.x;
+ var dy = self.y - boss.y;
+ var distance = Math.sqrt(dx * dx + dy * dy);
+ var playerCollisionRadius = self.width / 2;
+ var bossCollisionRadius = boss.width / 2;
+ if (distance < playerCollisionRadius + bossCollisionRadius) {
+ boss.takeDamage(10);
+ self.hasRolledThroughBossThisRoll = true;
+ LK.effects.flashObject(boss, 0x00FF00, 200);
+ }
+ }
+ }
+ // Keep player within arena bounds
+ self.x = Math.max(100, Math.min(self.x, 2048 - 100));
+ self.y = Math.max(100, Math.min(self.y, 2732 - 100));
+ // Check for collision with boss attacks
+ if (boss && !boss.dead && boss.attacks) {
+ for (var i = 0; i < boss.attacks.length; i++) {
+ var attack = boss.attacks[i];
+ // Calculate distance between player and attack
+ var dx = self.x - attack.x;
+ var dy = self.y - attack.y;
+ var distance = Math.sqrt(dx * dx + dy * dy);
+ // Player collision radius (half width)
+ var playerRadius = self.width / 2;
+ // Check if player is hit by attack
+ if (distance < playerRadius + attack.radius && !self.invulnerable) {
+ self.takeDamage(1);
+ break; // Only take damage from one attack per frame
+ }
+ }
+ }
+ };
+ return self;
+});
// Shape class implementation for creating simple shapes
var Shape = Container.expand(function (options) {
var self = Container.call(this);
options = options || {};