Code edit (3 edits merged)
Please save this source code
User prompt
add new asset creditsbg
Code edit (14 edits merged)
Please save this source code
User prompt
add new asset buttoncredits
Code edit (1 edits merged)
Please save this source code
Code edit (1 edits merged)
Please save this source code
User prompt
add new assets fireballnew1 fireballnew2 fireballnew3 fireballnew4
Code edit (1 edits merged)
Please save this source code
Code edit (1 edits merged)
Please save this source code
Code edit (1 edits merged)
Please save this source code
Code edit (6 edits merged)
Please save this source code
User prompt
add new assets rmattack2_explode_7 to rmattack2_explode_10
Code edit (7 edits merged)
Please save this source code
User prompt
add new asset rmattack4_parent_3
Code edit (1 edits merged)
Please save this source code
Code edit (1 edits merged)
Please save this source code
User prompt
add new assets rmattack4_parent_0 rmattack4_parent_1 rmattack4_parent_2
Code edit (12 edits merged)
Please save this source code
User prompt
add new assets rmattack1_4 rmattack1_5 rmattack1_6
Code edit (1 edits merged)
Please save this source code
Code edit (12 edits merged)
Please save this source code
User prompt
add new assets rmattack3_strike_0 rmattack3_strike_1 rmattack3_strike_2 rmattack3_strike_3 rmattack3_strike_4
User prompt
add new assets rmattack3_warning_0 rmattack3_warning_1 rmattack3_warning_2 rmattack3_warning_3 rmattack3_warning_4
Code edit (1 edits merged)
Please save this source code
User prompt
add new assets rmattack2_explode_0 rmattack2_explode_1 rmattack2_explode_2 rmattack2_explode_3 rmattack2_explode_4 rmattack2_explode_5 rmattack2_explode_6
/**** * 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) // Klasy z stworzylo zloto.txt (bardziej zaawansowana walka z bossem) var Boss = Container.expand(function () { var self = Container.call(this); // Definicje list klatek dla fireballi - NIE używane w starej metodzie createAttack, ale mogą być potrzebne dla grafiki bossa lub jeśli zdecydujesz się wrócić do SpriteAnimation // Zachowujemy definicje na wypadek przyszłych zmian var circleFrames = [LK.getAsset('fireball0', { anchorX: 0.5, anchorY: 0.5 }), LK.getAsset('fireball00', { anchorX: 0.5, anchorY: 0.5 }), LK.getAsset('fireball01', { anchorX: 0.5, anchorY: 0.5 }), LK.getAsset('fireball02', { anchorX: 0.5, anchorY: 0.5 }), LK.getAsset('fireball03', { anchorX: 0.5, anchorY: 0.5 }), LK.getAsset('fireball04', { anchorX: 0.5, anchorY: 0.5 }), LK.getAsset('fireball05', { anchorX: 0.5, anchorY: 0.5 }), LK.getAsset('fireball06', { anchorX: 0.5, anchorY: 0.5 }), LK.getAsset('fireball07', { anchorX: 0.5, anchorY: 0.5 }), LK.getAsset('fireball08', { anchorX: 0.5, anchorY: 0.5 }), LK.getAsset('fireball09', { anchorX: 0.5, anchorY: 0.5 }), LK.getAsset('fireball1', { anchorX: 0.5, anchorY: 0.5 }), LK.getAsset('fireball10', { anchorX: 0.5, anchorY: 0.5 }), LK.getAsset('fireball11', { anchorX: 0.5, anchorY: 0.5 }), LK.getAsset('fireball12', { anchorX: 0.5, anchorY: 0.5 }), LK.getAsset('fireball13', { anchorX: 0.5, anchorY: 0.5 }), LK.getAsset('fireball14', { anchorX: 0.5, anchorY: 0.5 })]; var lineFrames = [LK.getAsset('fireball2', { anchorX: 0.5, anchorY: 0.5 }), LK.getAsset('fireball3', { anchorX: 0.5, anchorY: 0.5 }), LK.getAsset('fireball4', { anchorX: 0.5, anchorY: 0.5 }), LK.getAsset('fireball5', { anchorX: 0.5, anchorY: 0.5 }), LK.getAsset('fireball6', { anchorX: 0.5, anchorY: 0.5 }), LK.getAsset('fireball7', { anchorX: 0.5, anchorY: 0.5 }), LK.getAsset('fireball8', { anchorX: 0.5, anchorY: 0.5 }), LK.getAsset('fireball9', { anchorX: 0.5, anchorY: 0.5 }), LK.getAsset('fireball15', { anchorX: 0.5, anchorY: 0.5 }), LK.getAsset('fireball16', { anchorX: 0.5, anchorY: 0.5 })]; // Używamy attachAsset, który już dodaje jako dziecko i ustawia pozycję relatywną na podstawie anchor. self.bossGraphics = self.addChild(LK.getAsset('bossIdle', { anchorX: 0.5, anchorY: 0.5, x: 0, y: 0 })); self.bossGraphics.scaleX = 2; self.bossGraphics.scaleY = 2; // Dodajemy funkcje animacji bossa (NIE ataków fireballi) // 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, loop: false, anchorX: 0.5, anchorY: 0.5, x: 0, y: 0 }); // <<< DODAJ TO NATYCHMIAST PO STWORZENIU animacji bossAttackAnim.scaleX = 2; bossAttackAnim.scaleY = 2; return bossAttackAnim; }; // Ta funkcja zarządza grafiką Bossa (animacja ataku Bossa lub grafika idle) self.playBossAttackAnim = function (attackType) { // 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 if (self.bossAttackAnim.parent === self) { 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 if (self.bossGraphics && self.bossGraphics.parent === self) { 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()); // 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', { anchorX: 0.5, anchorY: 0.5, x: 0, y: 0 })); self.bossGraphics.scaleX = 2; self.bossGraphics.scaleY = 2; // *** Usuń obiekt animacji z jego rodzica (Bossa) i zniszcz go *** if (this.parent === self) { 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. }; // *** STAROŚĆ WRACA! // Stara funkcja createAttack (przyjmuje x, y, duration) *** self.createAttack = function (x, y, duration, type) { var frames = []; if (type === 'circle') { frames = [LK.getAsset('fireball0', { anchorX: 0.5, anchorY: 0.5 }), LK.getAsset('fireball00', { anchorX: 0.5, anchorY: 0.5 }), LK.getAsset('fireball01', { anchorX: 0.5, anchorY: 0.5 }), LK.getAsset('fireball02', { anchorX: 0.5, anchorY: 0.5 }), LK.getAsset('fireball03', { anchorX: 0.5, anchorY: 0.5 }), LK.getAsset('fireball04', { anchorX: 0.5, anchorY: 0.5 }), LK.getAsset('fireball05', { anchorX: 0.5, anchorY: 0.5 }), LK.getAsset('fireball06', { anchorX: 0.5, anchorY: 0.5 }), LK.getAsset('fireball07', { anchorX: 0.5, anchorY: 0.5 }), LK.getAsset('fireball08', { anchorX: 0.5, anchorY: 0.5 }), LK.getAsset('fireball09', { anchorX: 0.5, anchorY: 0.5 }), LK.getAsset('fireball1', { anchorX: 0.5, anchorY: 0.5 }), LK.getAsset('fireball10', { anchorX: 0.5, anchorY: 0.5 }), LK.getAsset('fireball11', { anchorX: 0.5, anchorY: 0.5 }), LK.getAsset('fireball12', { anchorX: 0.5, anchorY: 0.5 }), LK.getAsset('fireball13', { anchorX: 0.5, anchorY: 0.5 }), LK.getAsset('fireball14', { anchorX: 0.5, anchorY: 0.5 }), LK.getAsset('fireballnew1', { anchorX: 0.5, anchorY: 0.5 }), LK.getAsset('fireballnew2', { anchorX: 0.5, anchorY: 0.5 }), LK.getAsset('fireballnew3', { anchorX: 0.5, anchorY: 0.5 }), LK.getAsset('fireballnew4', { anchorX: 0.5, anchorY: 0.5 })]; } else if (type === 'line') { frames = [LK.getAsset('fireball2', { anchorX: 0.5, anchorY: 0.5 }), LK.getAsset('fireball3', { anchorX: 0.5, anchorY: 0.5 }), LK.getAsset('fireball4', { anchorX: 0.5, anchorY: 0.5 }), LK.getAsset('fireball5', { anchorX: 0.5, anchorY: 0.5 }), LK.getAsset('fireball6', { anchorX: 0.5, anchorY: 0.5 }), LK.getAsset('fireball7', { anchorX: 0.5, anchorY: 0.5 }), LK.getAsset('fireball8', { anchorX: 0.5, anchorY: 0.5 }), LK.getAsset('fireball9', { anchorX: 0.5, anchorY: 0.5 }), LK.getAsset('fireball15', { anchorX: 0.5, anchorY: 0.5 }), LK.getAsset('fireball16', { anchorX: 0.5, anchorY: 0.5 })]; } var currentFrameDuration = 100; // Używamy zmiennej dla czasu klatki var spriteAnim = game.addChild(new SpriteAnimation({ frames: frames, frameDuration: currentFrameDuration, // Użycie zmiennej // czas w ms jednej klatki // Twój komentarz zostawiony loop: false, // Zmienione na false anchorX: 0.5, anchorY: 0.5, x: x, y: y })); spriteAnim.scaleX = 1.6; spriteAnim.scaleY = 1.6; var attackData = { x: x, y: y, radius: 60, visual: spriteAnim, lifeTime: Math.floor(duration / (1000 / 60)), // Czas życia logiki ataku isActive: true }; self.attacks.push(attackData); // Oblicz rzeczywisty czas trwania animacji wizualnej var animationDuration = frames.length * currentFrameDuration; LK.setTimeout(function () { // Callback niszczący animację i usuwający dane ataku var index = self.attacks.indexOf(attackData); if (index !== -1) { self.attacks.splice(index, 1); } if (spriteAnim && !spriteAnim.destroyed) { spriteAnim.destroy(); } }, animationDuration); // Użyj obliczonego 'animationDuration' ZAMIAST 'duration' }; // *** STAROŚĆ WRACA! Stara funkcja circleAttack *** self.circleAttack = function () { LK.getSound('bossAttack').play(); var center = { x: self.x, y: self.y }; var count = isNewBossPlusMode ? 12 : 8; var radius = 300; var attackLifeTime = isNewBossPlusMode ? 2000 : 3000; for (var i = 0; i < count; i++) { var angle = i / count * Math.PI * 2; var x = center.x + Math.cos(angle) * radius; var y = center.y + Math.sin(angle) * radius; self.createAttack(x, y, attackLifeTime, 'circle'); // DODANE 'circle' } }; // Zmodyfikowana funkcja lineAttack, teraz używa starej sygnatury createAttack self.lineAttack = function () { LK.getSound('bossAttack').play(); if (!player) { return; } var targetX = player.x; var targetY = player.y; var count = isNewBossPlusMode ? 8 : 5; var attackLifeTime = isNewBossPlusMode ? 1500 : 2000; var delayBetweenAttacks = isNewBossPlusMode ? 100 : 200; for (var i = 0; i < count; i++) { var t = i / (count - 1); var posX = self.x + (targetX - self.x) * t; var posY = self.y + (targetY - self.y) * t; var delay = i * delayBetweenAttacks * self.attackSpeedMultiplier; LK.setTimeout(function (x, y) { return function () { if (self && !self.dead && gameState.currentState === "game") { self.createAttack(x, y, attackLifeTime, 'line'); // DODANE 'line' } }; }(posX, posY), delay); } }; // Zmodyfikowana funkcja chargeAttack, teraz używa starej sygnatury createAttack 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) - to pozostaje z nowszego kodu, jest ok. if (self.bossAttackAnim) { self.bossAttackAnim.stop(); if (self.bossAttackAnim.parent === self) { 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(); self.bossAttackAnim = null; } if (self.bossGraphics && self.bossGraphics.parent === self) { self.bossGraphics.parent.removeChild(self.bossGraphics); } else if (self.bossGraphics && self.bossGraphics.parent) { // Fallback self.bossGraphics.parent.removeChild(self.bossGraphics); } self.bossGraphics = self.addChild(LK.getAsset('bossIdle', { anchorX: 0.5, anchorY: 0.5, x: 0, y: 0 })); self.bossGraphics.scaleX = 2; self.bossGraphics.scaleY = 2; // 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; } // Zapisz pozycję startową szarży PRZED tweenem var startX = self.x; var startY = self.y; var chargeDistance = isNewBossPlusMode ? 700 : 500; // Dystans szarży var chargeDuration = isNewBossPlusMode ? // Czas trwania szarży 600 : 800; var attacksOnPathCount = isNewBossPlusMode ? // Liczba pocisków wzdłuż ścieżki 8 : 5; 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() { // Funkcja po zakończeniu szarży if (self && !self.dead && gameState.currentState === "game") { // *** USUNIĘTO LOGIKĘ POWROTU NA POZYCJĘ STARTOWĄ *** // Usunięto: self.repositioning = true; // Usunięto: LK.setTimeout(...); // Usunięto: tween(self, { x: startX, y: startY }, { duration: 1000 * self.attackSpeedMultiplier, easing: tween.easeOut }); // Utworzenie ataków (fireballi) wzdłuż ścieżki szarży (pozostawiamy, bo to część ataku) // Pociski pojawiają się wzdłuż ścieżki od startX/startY do miejsca, gdzie boss zakończył szarżę (aktualne self.x/self.y) 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 // Wywołaj funkcję createAttack z 3 argumentami (stara sygnatura) self.createAttack(posX, posY, attackLifeTime, 'line'); // Używamy typu 'line' dla pocisków szarży } } } }); }; 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(); } }); }; // Update method called every frame // Update method called every frame self.update = function () { // Główna metoda aktualizacji bossa // Aktualizuj tylko w stanie gry if (gameState.currentState !== "game" && gameState.currentState !== "rollMaster") { if (self.rolling) { self.rolling = false; if (self.idleAnimationSprite && !self.idleAnimationSprite.destroyed) { self.idleAnimationSprite.visible = true; self.idleAnimationSprite.play(); } } 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 // Usunięto warunek !self.repositioning, ponieważ boss nie wchodzi już w ten stan po szarży if (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 = 300 + 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) --- // W starej metodzie wizualizacja ataku jest zarządzana przez tween i setTimeout w createAttack. // Obiekt logiczny ataku w self.attacks zawiera flagę isActive i jest usuwany przez setTimeout lub poniżej. // Nie aktualizujemy tutaj attack.visual.update ani isActive na podstawie klatek. // 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]; // Czas życia w klatkach był zarządzany w starej metodzie w createAttack timeout. // Tutaj można dodać opcjonalne zmniejszanie lifeTime dla pewności, jeśli attackData.lifeTime jest używane gdzie indziej, // ale główny mechanizm usuwania jest w setTimeout w createAttack. // If (attack.lifeTime > 0) { attack.lifeTime--; } // Sprawdź kolizję z graczem jeśli spełnione warunki // Flaga isActive powinna być już ustawiona na true w createAttack (stara metoda) 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; // Zakładamy promień kolizji gracza var attackRadius = attack.radius; // Promień kolizji ataku z attackData if (distance_p < playerRadius_p + attackRadius) { player.takeDamage(1); // Gracz otrzymuje obrażenia // Usuń wizualizację i obiekt logiczny natychmiast po trafieniu if (attack.visual && attack.visual.destroy) { attack.visual.destroy(); } self.attacks.splice(i, 1); // Usuń trafiony atak logiczny // break; // Opcjonalnie: zatrzymaj sprawdzanie kolizji dla tej klatki po trafieniu gracza } } // Usuń atak z listy, jeśli jego wizualizacja została zniszczona (np. przez timeout w createAttack lub powyżej) // Obiekt logiczny powinien też zostać usunięty przez timeout w createAttack, ale to dodatkowe sprawdzenie nie zaszkodzi. if (attack.visual && attack.visual.destroyed) { 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 // Usunięto warunek !self.repositioning if (self.attackCooldown <= 0) { self.startAttackPattern(); } // startAttackPattern jest teraz definiowane wyżej, nie tutaj w update }; self.startAttackPattern = function () { // Resetuj cooldown ataku self.attackCooldown = isNewBossPlusMode ? 150 : 200; // Wybierz losowy typ ataku var attackType = Math.floor(Math.random() * 3); // 0, 1 lub 2 // Odtwórz animację ataku bossa self.playBossAttackAnim(attackType); // Wykonaj atak odpowiedniego typu if (attackType === 0) { // Atak kołowy self.circleAttack(); } else if (attackType === 1) { // Atak liniowy self.lineAttack(); } else { // Atak szarży self.chargeAttack(); } }; return self; // Zwróć instancję obiektu Boss }); // Zamykająca klamra dla Container.expand klasy Boss var Player = Container.expand(function () { var self = Container.call(this); // --- Animacja Idle --- var idleFrames = [LK.getAsset('player', { anchorX: 0.5, anchorY: 0.5 }), LK.getAsset('player1', { anchorX: 0.5, anchorY: 0.5 }), LK.getAsset('player2', { anchorX: 0.5, anchorY: 0.5 })]; self.idleAnimationSprite = new SpriteAnimation({ frames: idleFrames, frameDuration: 400, loop: true, anchorX: 0.5, anchorY: 0.5, x: 0, y: 0 }); self.addChild(self.idleAnimationSprite); self.idleAnimationSprite.play(); // --- Właściwości Gracza --- self.health = 5; // Domyślne zdrowie self.speed = 8; // Prędkość chodzenia self.rolling = false; // Flaga uniku self.rollDirection = { x: 0, y: 0 }; // Kierunek uniku self.rollSpeed = 20; // STAŁA prędkość podczas uniku self.rollDuration = 300; // DOMYŚLNY czas trwania uniku w ms (używany jako fallback) self.rollCooldown = 0; // Cooldown uniku (w klatkach) self.invulnerable = false; // Flaga nietykalności self.invulnerabilityFrames = 0; // Pozostałe klatki nietykalności podczas uniku // <-- NOWA WŁAŚCIWOŚĆ: Domyślna liczba klatek nietykalności --> self.defaultInvulnerabilityFrames = 30; // Domyślna liczba klatek dla domyślnego czasu 300ms // <-- KONIEC NOWEJ WŁAŚCIWOŚCI --> self.postHitInvulnerabilityTimer = null; // Timer nietykalności po trafieniu self.dead = false; // Flaga śmierci self.rollTimeoutId = null; // ID timera końca uniku self.invulnerabilityTimeoutId = null; // Nieużywane? self.rollAnimationInterval = null; // ID interwału animacji uniku self.hasRolledThroughBossThisRoll = false; // Flaga trafienia bossa podczas uniku // --- Funkcje Pomocnicze --- self.clearRollTimeouts = function () { if (self.rollTimeoutId) { LK.clearTimeout(self.rollTimeoutId); self.rollTimeoutId = null; } if (self.rollAnimationInterval) { LK.clearInterval(self.rollAnimationInterval); self.rollAnimationInterval = null; } if (self.postHitInvulnerabilityTimer) { LK.clearTimeout(self.postHitInvulnerabilityTimer); self.postHitInvulnerabilityTimer = null; } }; // --- Mechanika Uniku (Roll) --- // <-- ZMIANA: Funkcja roll przyjmuje teraz 'duration' --> self.roll = function (direction, duration) { // Dodano argument 'duration' if (!self.rolling && self.rollCooldown <= 0 && !self.dead) { var targetScaleX = direction.x >= 0 || direction.x === 0 ? 1 : -1; // <-- ZMIANA: Użyj przekazanego czasu trwania lub domyślnego --> var currentRollDuration = duration || self.rollDuration; // Użyj duration z argumentu, lub domyślnego 300ms // <-- KONIEC ZMIANY --> self.rolling = true; self.rollDirection = direction; self.rollCooldown = 45; // Cooldown stały // <-- ZMIANA: Skaluj klatki nietykalności proporcjonalnie do czasu trwania uniku --> var durationRatio = currentRollDuration / self.rollDuration; // Oblicz stosunek czasów self.invulnerabilityFrames = Math.round(self.defaultInvulnerabilityFrames * durationRatio); // Ustaw klatki proporcjonalnie // <-- KONIEC ZMIANY --> self.hasRolledThroughBossThisRoll = false; // Ukryj animację idle if (self.idleAnimationSprite && !self.idleAnimationSprite.destroyed) { self.idleAnimationSprite.stop(); self.idleAnimationSprite.visible = false; } // Animacja turlania (logika bez zmian) var rollFrames = ['roll', 'roll0', 'roll1', 'roll2']; var currentFrame = 0; if (self.rollAnimationInterval) { LK.clearInterval(self.rollAnimationInterval); } var rollAnimationSprite = null; if (self && !self.destroyed) { rollAnimationSprite = self.addChild(LK.getAsset(rollFrames[currentFrame], { anchorX: 0.5, anchorY: 0.5, x: 0, y: 0, scaleX: targetScaleX })); } self.rollAnimationInterval = LK.setInterval(function () { if (!self || self.destroyed) { LK.clearInterval(self.rollAnimationInterval); self.rollAnimationInterval = null; return; } if (rollAnimationSprite && rollAnimationSprite.destroy) { rollAnimationSprite.destroy(); rollAnimationSprite = null; } currentFrame = (currentFrame + 1) % rollFrames.length; if (self && !self.destroyed) { rollAnimationSprite = self.addChild(LK.getAsset(rollFrames[currentFrame], { anchorX: 0.5, anchorY: 0.5, x: 0, y: 0, scaleX: targetScaleX })); } }, 70); // Koniec uniku po OBLICZONYM czasie if (self.rollTimeoutId) { LK.clearTimeout(self.rollTimeoutId); } // <-- ZMIANA: Użyj currentRollDuration w setTimeout --> self.rollTimeoutId = LK.setTimeout(function () { if (!self || self.destroyed) { return; } self.rolling = false; // Zatrzymaj i usuń animację turlania if (self.rollAnimationInterval) { LK.clearInterval(self.rollAnimationInterval); self.rollAnimationInterval = null; } if (rollAnimationSprite && rollAnimationSprite.destroy) { rollAnimationSprite.destroy(); rollAnimationSprite = null; } // Animacja wstawania (logika bez zmian) var standUpFrames = ['roll3', 'roll4']; var standUpFrame = 0; var standUpSprite = null; if (self && !self.destroyed) { standUpSprite = self.addChild(LK.getAsset(standUpFrames[standUpFrame], { anchorX: 0.5, anchorY: 0.5, x: 0, y: 0, scaleX: targetScaleX })); } var standUpInterval = LK.setInterval(function () { if (!self || self.destroyed) { LK.clearInterval(standUpInterval); return; } if (standUpSprite && standUpSprite.destroy) { standUpSprite.destroy(); standUpSprite = null; } standUpFrame++; if (standUpFrame < standUpFrames.length) { if (self && !self.destroyed) { standUpSprite = self.addChild(LK.getAsset(standUpFrames[standUpFrame], { anchorX: 0.5, anchorY: 0.5, x: 0, y: 0, scaleX: targetScaleX })); } } else { LK.clearInterval(standUpInterval); standUpInterval = null; if (standUpSprite && standUpSprite.destroy) { standUpSprite.destroy(); standUpSprite = null; } // Pokaż znowu animację idle if (self.idleAnimationSprite && !self.idleAnimationSprite.destroyed) { self.idleAnimationSprite.scaleX = targetScaleX; // Zachowaj kierunek self.idleAnimationSprite.visible = true; self.idleAnimationSprite.play(); } } }, 100); self.rollTimeoutId = null; }, currentRollDuration); // <--- UŻYJ OBLICZONEGO CZASU TRWANIA! // <-- KONIEC ZMIANY --> } }; // Koniec funkcji self.roll // --- Otrzymywanie Obrażeń (bez zmian) --- self.takeDamage = function (amount) { if (!self.invulnerable && !self.dead) { self.health -= amount; LK.effects.flashObject(self, 0xFF0000, 200); if (self.health <= 0) { self.health = 0; self.die(); return; } // Logika nietykalności PO TRAFIENIU self.invulnerable = true; if (self.postHitInvulnerabilityTimer) { LK.clearTimeout(self.postHitInvulnerabilityTimer); self.postHitInvulnerabilityTimer = null; } self.postHitInvulnerabilityTimer = LK.setTimeout(function () { if (!self || self.destroyed) { return; } self.invulnerable = false; // Reset alpha var currentVisualSprite = null; if (self.rolling && self.children.length > 0) { currentVisualSprite = self.children[0]; } else if (self.idleAnimationSprite && self.idleAnimationSprite.visible && self.idleAnimationSprite.children.length > 0) { currentVisualSprite = self.idleAnimationSprite.children[0]; } if (currentVisualSprite) { currentVisualSprite.alpha = 1; } self.postHitInvulnerabilityTimer = null; }, 2000); } }; // --- Śmierć (bez zmian) --- self.die = function () { if (self.dead) { return; } self.dead = true; storage.totalDeaths = (storage.totalDeaths || 0) + 1; if (!storage.maxHearts || storage.maxHearts < 5) { storage.maxHearts = 5; } if (storage.maxHearts < 10) { storage.maxHearts = storage.maxHearts + 1; } self.clearRollTimeouts(); tween(self, { alpha: 0, scaleX: 2, scaleY: 2 }, { duration: 1000, easing: tween.easeOut, onFinish: function onFinish() { if (self && self.destroy && !self.destroyed) { self.destroy(); } gameOverReasonIsDeath = true; if (typeof gameState !== 'undefined' && typeof gameState.gameOver === 'function') { gameState.gameOver(true); } else { console.error("Nie można wywołać gameState.gameOver() z Player.die"); } } }); }; // --- Aktualizacja (Update) (bez zmian w logice, tylko upewniamy się, że używa self.rollSpeed) --- self.update = function () { if (gameState.currentState !== "game" && gameState.currentState !== "rollMaster") { if (self.rolling) { self.rolling = false; if (self.idleAnimationSprite && !self.idleAnimationSprite.destroyed) { self.idleAnimationSprite.visible = true; self.idleAnimationSprite.play(); } } self.clearRollTimeouts(); return; } if (self.dead) { return; } if (self.rollCooldown > 0) { self.rollCooldown--; } // Logika mrugania var currentVisualSprite = null; if (self.rolling && self.children.length > 0) { currentVisualSprite = self.children[0]; } else if (self.idleAnimationSprite && self.idleAnimationSprite.visible && self.idleAnimationSprite.children.length > 0) { currentVisualSprite = self.idleAnimationSprite.children[0]; } if (self.rolling) { // Nietykalność podczas uniku if (self.invulnerabilityFrames > 0) { self.invulnerabilityFrames--; if (currentVisualSprite) { currentVisualSprite.alpha = self.invulnerabilityFrames % 4 > 1 ? 0.3 : 1; } } else { if (currentVisualSprite) { currentVisualSprite.alpha = 1; } } // Ruch podczas uniku (używa self.rollSpeed) var rollDx = self.rollDirection.x * self.rollSpeed; // Prędkość jest stała var rollDy = self.rollDirection.y * self.rollSpeed; var nextX = self.x + rollDx; var nextY = self.y + rollDy; // Ograniczenia mapy i kolizja z bossem var halfWidth = self.width / 2; var halfHeight = self.height / 2; if (currentVisualSprite) { halfWidth = currentVisualSprite.width / 2 * currentVisualSprite.scaleX; halfHeight = currentVisualSprite.height / 2 * currentVisualSprite.scaleY; } var minX = 100 + halfWidth; var maxX = 2048 - 100 - halfWidth; var minY = 300 + halfHeight; var maxY = 2732 - 100 - halfHeight; self.x = Math.max(minX, Math.min(nextX, maxX)); self.y = Math.max(minY, Math.min(nextY, maxY)); if (boss && !boss.dead && !self.hasRolledThroughBossThisRoll) { var dx_b = self.x - boss.x; var dy_b = self.y - boss.y; var distance_b = Math.sqrt(dx_b * dx_b + dy_b * dy_b); var playerCollisionRadius = self.width / 2; if (currentVisualSprite) { playerCollisionRadius = Math.max(currentVisualSprite.width, currentVisualSprite.height) / 2 * currentVisualSprite.scaleX; } var hitboxMultiplier = 1.2; // Zwiększ hitbox o 20% (zmień tę wartość) var bossCollisionRadius = boss.width * boss.scaleX / 2 * hitboxMultiplier; if (distance_b < playerCollisionRadius + bossCollisionRadius) { boss.takeDamage(10); self.hasRolledThroughBossThisRoll = true; LK.effects.flashObject(boss, 0x00FF00, 200); } } } else if (self.invulnerable && !self.rolling) { // Mruganie po trafieniu if (currentVisualSprite) { currentVisualSprite.alpha = Math.floor(Date.now() / 100) % 4 > 1 ? 0.3 : 1; } } else { // Normalny stan if (currentVisualSprite && currentVisualSprite.alpha !== 1) { currentVisualSprite.alpha = 1; } // Normalny ruch gracza var targetScaleX = self.idleAnimationSprite.scaleX; if (gameState.isInputActive && (gameState.currentState === "game" || gameState.currentState === "rollMaster") && player && !player.dead) { var targetX = gameState.currentInputPos.x; var targetY = gameState.currentInputPos.y; var dx_m = targetX - self.x; var dy_m = targetY - self.y; var distance_m = Math.sqrt(dx_m * dx_m + dy_m * dy_m); if (distance_m > 10) { var normalizedX = dx_m / distance_m; var normalizedY = dy_m / distance_m; var moveSpeed = 3; // Prędkość chodzenia - można by użyć self.speed? self.x += normalizedX * moveSpeed; self.y += normalizedY * moveSpeed; if (normalizedX > 0.1) { targetScaleX = 1; } else if (normalizedX < -0.1) { targetScaleX = -1; } } } if (self.idleAnimationSprite && !self.idleAnimationSprite.destroyed) { self.idleAnimationSprite.scaleX = targetScaleX; } } // Aktualizacja animacji idle if (self.idleAnimationSprite && self.idleAnimationSprite.visible && !self.idleAnimationSprite.destroyed && self.idleAnimationSprite.update) { self.idleAnimationSprite.update(); } }; // Koniec funkcji self.update return self; }); // <--- To zamyka Container.expand dla gracza var Shape = Container.expand(function (options) { var self = Container.call(this); // <--- Tutaj zaczyna się Shape // ... 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 SpriteAnimation = Container.expand(function (options) { var self = Container.call(this); // Initialize with default values options = options || {}; self.frames = options.frames || []; self.frameDuration = options.frameDuration || 120; // Default 100ms per frame self.loop = options.loop !== undefined ? options.loop : true; self.currentFrame = 0; self.frameTimer = 0; self.playing = true; // Set initial position and anchor if provided if (options.x !== undefined) { self.x = options.x; } if (options.y !== undefined) { self.y = options.y; } // Handle anchor options if (options.anchorX !== undefined || options.anchorY !== undefined) { var anchorX = options.anchorX !== undefined ? options.anchorX : 0; var anchorY = options.anchorY !== undefined ? options.anchorY : 0; // Apply anchor to all frames self.frames.forEach(function (frame) { if (frame && frame.anchor) { frame.anchor.set(anchorX, anchorY); } }); } // Add the first frame to display initially if (self.frames.length > 0) { self.removeChildren(); var firstFrame = self.frames[self.currentFrame]; if (firstFrame && firstFrame.anchor) { firstFrame.anchor.set(options.anchorX || 0, options.anchorY || 0); } self.addChild(firstFrame); } // Animation update method self.update = function () { if (!self.playing || self.frames.length === 0) { return; } self.frameTimer++; if (self.frameTimer >= self.frameDuration / (1000 / 60)) { self.frameTimer = 0; if (self.children.length > 0) { self.removeChild(self.children[0]); // Usuwamy tylko 1 aktualną klatkę } self.currentFrame++; if (self.currentFrame >= self.frames.length) { if (self.loop) { self.currentFrame = 0; } else { self.currentFrame = self.frames.length - 1; self.playing = false; } } self.addChild(self.frames[self.currentFrame]); // Dodajemy nową klatkę } }; // Method to stop animation self.stop = function () { self.playing = false; }; // Method to start/resume animation self.play = function () { self.playing = true; }; // Method to set a specific frame self.gotoFrame = function (frameIndex) { if (frameIndex >= 0 && frameIndex < self.frames.length) { self.removeChildren(); self.currentFrame = frameIndex; self.addChild(self.frames[self.currentFrame]); } }; 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("00:00", { // Zmieniono tekst początkowy size: 60, fill: 0xFFFFFF // Biały kolor }); self.timerText.x = 2048 / 2; // Wyśrodkuj w poziomie self.timerText.y = 50; // Przysuń do góry self.timerText.anchor.set(0.5, 0); // Pozycja Y od górnej krawędzi self.timerText.alpha = 0; // Domyślnie ukryty self.addChild(self.timerText); // --- DODANY NOWY ELEMENT TEKSTOWY DLA REKORDU --- self.highScoreText = new Text2("Best: 00:00", { // Tekst początkowy size: 50, // Mniejszy rozmiar niż aktualny czas fill: 0xFFFF00 // Żółty kolor dla odróżnienia }); self.highScoreText.x = 2048 - 50; // Przysuń do prawej krawędzi (z marginesem 50) self.highScoreText.y = 50; // Przysuń do góry (ta sama wysokość co timer) self.highScoreText.anchor.set(1, 0); // Pozycja Y (poniżej timerText) self.highScoreText.alpha = 0; // Domyślnie ukryty self.addChild(self.highScoreText); // Dodaj do kontenera UI // --- KONIEC DODAWANIA NOWEGO ELEMENTU --- // --- 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) // 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 }); self.bossHealthBarContainer.addChild(self.bossHealthBar); // --- METODY AKTUALIZACJI UI --- // Aktualizuje wizualizację serc na UI self.updateHearts = function (current, max) { current = Math.max(0, current || 0); max = Math.max(1, max || 1); // Max musi być co najmniej 1 if (self.hearts.length !== max) { while (self.hearts.length > 0) { var oldHeart = self.hearts.pop(); if (oldHeart && oldHeart.destroy && !oldHeart.destroyed) { oldHeart.destroy(); } } self.heartContainer.removeChildren(); // Usuń wizualne obiekty serc for (var i = 0; i < max; i++) { var heart = LK.getAsset('heart', { anchorX: 0.5, anchorY: 0.5, x: i * 50, y: 0, tint: i < current ? 0xFF0000 : 0x555555 }); self.hearts.push(heart); self.heartContainer.addChild(heart); } self.heartContainer.x = (2048 - max * 50) / 2 + 25; self.heartContainer.y = 100; // Zmieniono pozycję serc, aby zrobić miejsce } else { for (var j = 0; j < self.hearts.length; j++) { if (self.hearts[j]) { self.hearts[j].tint = j < current ? 0xFF0000 : 0x555555; } } } }; // Aktualizuje wizualizację paska zdrowia bossa self.updateBossHealth = function (current, max) { current = Math.max(0, current || 0); max = Math.max(1, max || 1); var barWidth = 800; var currentWidth = current / max * barWidth; if (self.bossHealthBar) { self.bossHealthBar.width = currentWidth; var shouldBeVisible = (gameState.currentState === "game" || gameState.currentState === "gameOver") && (boss && !boss.dead && current > 0 || gameState.currentState === "gameOver" && !gameOverReasonIsDeath && isNewBossPlusMode); self.bossHealthBarContainer.alpha = shouldBeVisible ? 1 : 0; } if (self.bossHealthBarBg) { // Jeśli używasz tła paska HP 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; if (self.messageTimeout) { LK.clearTimeout(self.messageTimeout); self.messageTimeout = null; } if (duration && duration > 0) { self.messageTimeout = LK.setTimeout(function () { tween(self.messageText, { alpha: 0 }, { duration: 500, onFinish: function onFinish() { self.messageTimeout = null; } }); }, duration); } else { self.messageText.alpha = 1; } }; // Wyświetla tekst tutorialu self.showTutorial = function (text) { self.tutorialText.setText(text); self.tutorialText.alpha = 1; }; // Ukrywa tekst tutorialu (zanikając) self.hideTutorial = function () { tween(self.tutorialText, { alpha: 0 }, { duration: 500 }); }; // Aktualizuje licznik śmierci self.updateDeathsCounter = function () { var deaths = storage.totalDeaths || 0; self.deathsText.setText("Deaths: " + deaths); self.deathsText.visible = gameState.currentState === "game"; }; // Aktualizuje wyświetlanie czasu timera self.updateTimerDisplay = function (seconds) { seconds = Math.max(0, seconds || 0); var minutes = Math.floor(seconds / 60); var remainingSeconds = seconds % 60; var formattedSeconds = remainingSeconds < 10 ? '0' + remainingSeconds : remainingSeconds; var formattedTime = String(minutes).padStart(2, '0') + ':' + formattedSeconds; if (self.timerText) { // W Roll Master wyświetlamy "Time:", w innych trybach tylko czas var prefix = gameState.currentState === "rollMaster" ? "Time: " : ""; self.timerText.setText(prefix + formattedTime); } }; // --- DODANA NOWA FUNKCJA DO AKTUALIZACJI TEKSTU REKORDU --- self.updateHighScoreDisplay = function (seconds) { seconds = Math.max(0, seconds || 0); var minutes = Math.floor(seconds / 60); var remainingSeconds = seconds % 60; var formattedSeconds = remainingSeconds < 10 ? '0' + remainingSeconds : remainingSeconds; var formattedTime = String(minutes).padStart(2, '0') + ':' + formattedSeconds; if (self.highScoreText) { // Sprawdź, czy obiekt istnieje self.highScoreText.setText("Best: " + formattedTime); } }; // --- KONIEC DODAWANIA NOWEJ FUNKCJI --- // Pozycjonuje elementy UI w zależności od stanu gry self.positionElements = function (state) { // Pozycje stałe dla niektórych elementów self.deathsText.x = 2048 - 150; self.deathsText.y = 50; self.heartContainer.y = 80; // Pozycja pionowa serc (może być nadpisywana) self.bossHealthBarContainer.x = 2048 / 2; self.bossHealthBarContainer.y = 160; // Pasek HP bossa poniżej timera/serc // Resetuj widoczność przed ustawieniem dla danego stanu self.titleText.alpha = 0; self.messageText.alpha = 0; self.tutorialText.alpha = 0; self.timerText.alpha = 0; self.heartContainer.alpha = 0; self.bossHealthBarContainer.alpha = 0; self.deathsText.alpha = 0; // Domyślnie ukryty self.highScoreText.alpha = 0; // Domyślnie ukryty // Przywróć domyślną pozycję i styl timerText if (self.timerText) { self.timerText.x = 350; self.timerText.y = 100; self.timerText.anchor.set(0, 0); // Upewnij się, że anchor jest resetowany self.timerText.style = { size: 60, fill: 0xFFFFFF }; } switch (state) { case "title": self.titleText.x = 2048 / 2; self.titleText.y = 800; self.titleText.alpha = 1; self.messageText.x = 2048 / 2; self.messageText.y = 1000; // self.messageText.alpha = 1; // Ustawiane przez showMessage self.tutorialText.x = 2048 / 2; self.tutorialText.y = 1200; // self.tutorialText.alpha = 1; // Ustawiane przez showTutorial break; case "game": self.messageText.x = 2048 / 2; self.messageText.y = 1500; // self.messageText.alpha = 0; // Ustawiane przez showMessage self.tutorialText.x = 2048 / 2; self.tutorialText.y = 200; // self.tutorialText.alpha = 1; // Ustawiane przez showTutorial/hideTutorial // Pokaż elementy walki if (self.heartContainer) { self.heartContainer.alpha = 1; } if (self.timerText) { self.timerText.alpha = 1; } if (self.deathsText) { self.deathsText.alpha = 1; } // Pokaż licznik śmierci // Pasek zdrowia bossa jest zarządzany przez updateBossHealth break; case "grillMenu": self.messageText.x = 2048 / 2; self.messageText.y = 500; // self.messageText.alpha = 0; // Ustawiane przez showMessage break; case "gameOver": self.titleText.x = 2048 / 2; self.titleText.y = 800; // self.titleText.alpha = 1; // Ustawiane w gameState.gameOver self.messageText.x = 2048 / 2; self.messageText.y = 1000; // self.messageText.alpha = 0; // Ustawiane w gameState.gameOver przez showMessage self.tutorialText.x = 2048 / 2; self.tutorialText.y = 1200; // Widoczność paska HP bossa zarządzana przez updateBossHealth if (self.deathsText) { self.deathsText.alpha = 1; } // Pokaż licznik śmierci też na Game Over break; case "intro": case "fakeTutorial": case "realTutorial": // Większość elementów ukryta (domyślnie) break; // --- DODANA OBSŁUGA WIDOCZNOŚCI W ROLL MASTER --- case "rollMaster": // Dostosuj pozycję timera specjalnie dla tego trybu if (self.timerText) { // --- POCZĄTEK ZMIAN --- self.timerText.x = 2048 / 2; // Ustaw X na środek ekranu self.timerText.y = 50; // Ustaw Y blisko góry self.timerText.anchor.set(0.5, 0); // Lewy górny róg self.timerText.style = { size: 60, fill: 0xFFFFFF }; // Ustaw styl, jeśli trzeba self.timerText.alpha = 1; // Pokaż timer } // Pokaż rekord if (self.highScoreText) { self.highScoreText.alpha = 1; } // Ukryj inne niepotrzebne elementy if (self.heartContainer) { self.heartContainer.alpha = 0; } if (self.bossHealthBarContainer) { self.bossHealthBarContainer.alpha = 0; } if (self.deathsText) { self.deathsText.alpha = 0; } if (self.tutorialText) { self.tutorialText.alpha = 0; } if (self.titleText) { self.titleText.alpha = 0; } break; // --- KONIEC DODAWANIA OBSŁUGI --- } }; // Koniec positionElements return self; }); /**** * Initialize Game ****/ // Koniec var UI = Container.expand(...) var game = new LK.Game({ backgroundColor: 0x111111 // Ciemne tło domyślne }); /**** * Game Code ****/ // Globalny kontener na elementy scen intro/tutoriali i Grill Screena (do łatwego czyszczenia) // Komentarze o assetach pominięte dla zwięzłości var currentSceneElements = new Container(); // Zmienne gry var player; var boss; var ui; var walls = []; // Ściany areny bossa // Zmienna do przechowywania aktywnego tła sceny var currentBackground = null; // Flaga do śledzenia, czy jesteśmy w trybie New Boss+ var isNewBossPlusMode = false; // Flaga do śledzenia przyczyny Game Over (true = gracz zginął, false = czas minął) var gameOverReasonIsDeath = false; // Funkcja do czyszczenia elementów z kontenera currentSceneElements (nie usuwa UI ani tła) function clearScene() { while (currentSceneElements.children.length > 0) { var child = currentSceneElements.children[0]; if (child && child.destroy) { child.destroy(); // Użyj destroy jeśli dostępne } else if (child && child.parent) { child.parent.removeChild(child); // Fallback } else { // Jeśli obiekt nie ma ani destroy ani parent, usuń go z tablicy (choć to nie powinno się zdarzyć) currentSceneElements.children.shift(); } } // Usuń też dzieci bezpośrednio z 'game', które mogły zostać dodane poza 'currentSceneElements' w scenach, // ale UWAŻAJ, aby nie usunąć stałych elementów jak UI, walls, currentSceneElements sam w sobie. // Lepsze podejście: zawsze dodawaj elementy specyficzne dla sceny do currentSceneElements. } // Obiekt zarządzający stanami gry var gameState = { currentState: "title", gameDuration: 120, remainingTime: 0, gameTimerInterval: null, fakeTutorialTimerId: null, bossSpeedIncreased: false, currentIntroMusicInstance: null, currentRollSoulsInstance: null, currentRollMasterMusicInstance: null, // Nowa właściwość rollMasterTime: 0, rollMasterDifficulty: 1, rollMasterTimerInterval: null, rollMasterAttacks: [], attackSpawnTimer: 0, attackSpawnInterval: 120, explosionSpawnTimer: 0, explosionSpawnInterval: 240, rollMasterHighScore: 0, isInputActive: false, currentInputPos: { x: 0, y: 0 }, touchStart: { x: 0, y: 0 }, touchEnd: { x: 0, y: 0 }, grillMenuEffects: [], swipeStartTime: 0, init: function init() { storage.totalDeaths = 0; storage.maxHearts = 5; isNewBossPlusMode = false; gameOverReasonIsDeath = false; this.rollMasterHighScore = storage.rollMasterHighScore || 0; this.currentIntroMusicInstance = null; this.currentRollSoulsInstance = null; this.currentRollMasterMusicInstance = null; game.setBackgroundColor(0x111111); ui = game.addChild(new UI()); ui.updateDeathsCounter(); ui.updateHearts(storage.maxHearts, storage.maxHearts); ui.positionElements("title"); game.addChild(currentSceneElements); // Muzyka intro jest teraz uruchamiana i zarządzana w showTitleScreen this.showTitleScreen(); }, // --- gameState.showTitleScreen --- showTitleScreen: function showTitleScreen() { isNewBossPlusMode = false; this.rollMasterTime = 0; console.log("[showTitleScreen] State: Title Screen"); var needsIntroMusic = true; if (this.currentRollSoulsInstance && this.currentRollSoulsInstance.stop) { console.log("[showTitleScreen] Zatrzymywanie currentRollSoulsInstance."); if (typeof this.currentRollSoulsInstance.volume === 'number') { this.currentRollSoulsInstance.volume = 0; } this.currentRollSoulsInstance.stop(); this.currentRollSoulsInstance = null; } else { var musicViaLK_RS = LK.music; if (musicViaLK_RS && musicViaLK_RS.assetId === 'RollSouls' && musicViaLK_RS.playing && musicViaLK_RS.stop) { console.log("[showTitleScreen] Fallback: Zatrzymywanie RollSouls przez LK.music."); if (typeof musicViaLK_RS.volume === 'number') { musicViaLK_RS.volume = 0; } musicViaLK_RS.stop(); } } if (this.currentRollMasterMusicInstance && this.currentRollMasterMusicInstance.stop) { console.log("[showTitleScreen] Zatrzymywanie currentRollMasterMusicInstance."); if (typeof this.currentRollMasterMusicInstance.volume === 'number') { this.currentRollMasterMusicInstance.volume = 0; } this.currentRollMasterMusicInstance.stop(); this.currentRollMasterMusicInstance = null; } else { var musicViaLK_RM = LK.music; if (musicViaLK_RM && musicViaLK_RM.assetId === 'rollmaster' && musicViaLK_RM.playing && musicViaLK_RM.stop) { console.log("[showTitleScreen] Fallback: Zatrzymywanie 'rollmaster' przez LK.music."); if (typeof musicViaLK_RM.volume === 'number') { musicViaLK_RM.volume = 0; } musicViaLK_RM.stop(); } } if (this.currentIntroMusicInstance && this.currentIntroMusicInstance.playing) { console.log("[showTitleScreen] currentIntroMusicInstance już gra."); needsIntroMusic = false; } else { var lkMusicCheck = LK.music; if (lkMusicCheck && lkMusicCheck.assetId === 'introMusic' && lkMusicCheck.playing) { console.log("[showTitleScreen] introMusic już gra (wg LK.music). Zapisuję instancję."); this.currentIntroMusicInstance = lkMusicCheck; needsIntroMusic = false; } } if (needsIntroMusic) { console.log("[showTitleScreen] Uruchamianie introMusic..."); this.currentIntroMusicInstance = LK.playMusic('introMusic', { loop: true, volume: 0.7, fade: { start: 0, end: 0.7, duration: 1000 } }); if (!this.currentIntroMusicInstance) { console.error("[showTitleScreen] LK.playMusic('introMusic') nie zwróciło instancji!"); } } else { console.log("[showTitleScreen] introMusic nie jest ponownie uruchamiana."); } if (this.fakeTutorialTimerId) { LK.clearTimeout(this.fakeTutorialTimerId); this.fakeTutorialTimerId = null; } if (this.gameTimerInterval) { LK.clearInterval(this.gameTimerInterval); this.gameTimerInterval = null; } if (this.rollMasterTimerInterval) { LK.clearInterval(this.rollMasterTimerInterval); this.rollMasterTimerInterval = null; } clearScene(); if (currentBackground) { tween.stop(currentBackground); currentBackground.destroy(); currentBackground = null; } this.currentState = "title"; game.setBackgroundColor(0x1a1a1a); currentBackground = LK.getAsset('titleBg', { anchorX: 0, anchorY: 0, x: 0, y: 0 }); game.addChildAt(currentBackground, 0); if (typeof particleIntervalId !== 'undefined' && particleIntervalId) { LK.clearInterval(particleIntervalId); particleIntervalId = null; } if (typeof startScreenParticles !== 'undefined') { startScreenParticles.forEach(function (p) { if (p && p.destroy) { p.destroy(); } }); startScreenParticles = []; } var localStartScreenParticles = []; function localSpawnBackgroundParticle() {/* ... logika cząsteczek ... */} var localParticleIntervalId = LK.setInterval(localSpawnBackgroundParticle, 400); // Aby uniknąć konfliktów z globalnymi zmiennymi, jeśli są, można przekazać je do gameState lub zarządzać nimi lokalnie. // Dla uproszczenia, zakładam, że particleIntervalId i startScreenParticles są zarządzane w sposób, który nie koliduje. if (player && player.destroy) { player.destroy(); } player = null; if (boss && boss.destroy) { boss.destroy(); } boss = null; walls.forEach(function (wall) { if (wall) { wall.alpha = 1; } }); ui.positionElements("title"); ui.titleText.setText("Welcome Unchosen"); ui.showMessage("Tap to Start", 0); ui.showTutorial("Swipe to Roll - Death is Progress"); ui.updateHearts(storage.maxHearts, storage.maxHearts); ui.updateDeathsCounter(); this.touchStart = { x: 0, y: 0 }; this.touchEnd = { x: 0, y: 0 }; game.off('down'); game.on('down', function () { if (gameState.currentState === 'title') { if (typeof localParticleIntervalId !== 'undefined' && localParticleIntervalId) { LK.clearInterval(localParticleIntervalId); } if (localStartScreenParticles) { localStartScreenParticles.forEach(function (p) { if (p && p.destroy) { p.destroy(); } }); } gameState.showIntro(); } }); }, showIntro: function showIntro() { var skipElement = null; var skipTimerId = null; var self = this; // self tutaj to gameState if (this.fakeTutorialTimerId) { LK.clearTimeout(this.fakeTutorialTimerId); this.fakeTutorialTimerId = null; } if (typeof particleIntervalId !== 'undefined') { LK.clearInterval(particleIntervalId); particleIntervalId = null; } if (typeof startScreenParticles !== 'undefined') { startScreenParticles.forEach(function (p) { if (p && p.destroy) { p.destroy(); } }); startScreenParticles = []; } clearScene(); if (currentBackground) { tween.stop(currentBackground); currentBackground.destroy(); currentBackground = null; } this.currentState = "intro"; game.setBackgroundColor(0x111111); currentBackground = LK.getAsset('introBg', { anchorX: 0.5, anchorY: 0.4, x: 1000, y: 1000, scaleX: 1, scaleY: 1 }); game.addChildAt(currentBackground, 0); tween(currentBackground, { scaleX: 1.5, scaleY: 1.5 }, { duration: 38000, easing: tween.linear, repeat: Infinity, yoyo: true }); if (player && player.destroy) { player.destroy(); } player = null; if (boss && boss.destroy) { boss.destroy(); } boss = null; walls.forEach(function (wall) { if (wall) { wall.alpha = 0; } }); ui.positionElements("intro"); skipTimerId = LK.setTimeout(function () { if (self.currentState !== "intro") { skipTimerId = null; return; } skipElement = new Text2("Skip intro", { size: 70, fill: 0xFFFFFF }); skipElement.x = 2048 - 50; skipElement.y = 2732 - 50; skipElement.anchor.set(1, 1); skipElement.interactive = true; skipElement.cursor = "pointer"; currentSceneElements.addChild(skipElement); skipElement.down = function () { if (self.currentState !== "intro") { return; } console.log("[Skip Intro] Pominięto intro przez tekst!"); if (skipTimerId) { LK.clearTimeout(skipTimerId); skipTimerId = null; } if (currentBackground) { tween.stop(currentBackground); } clearScene(); if (self.currentIntroMusicInstance && self.currentIntroMusicInstance.stop) { console.log("[Skip Intro] Zatrzymywanie currentIntroMusicInstance."); if (typeof self.currentIntroMusicInstance.volume === 'number') { self.currentIntroMusicInstance.volume = 0; } self.currentIntroMusicInstance.stop(); self.currentIntroMusicInstance = null; } else { console.log("[Skip Intro] Brak currentIntroMusicInstance lub metody stop. Fallback na LK.music."); var musicViaLK_I = LK.music; if (musicViaLK_I && musicViaLK_I.assetId === 'introMusic' && musicViaLK_I.stop) { if (typeof musicViaLK_I.volume === 'number') { musicViaLK_I.volume = 0; } musicViaLK_I.stop(); } } if (self.currentRollMasterMusicInstance && self.currentRollMasterMusicInstance.stop) { console.log("[Skip Intro] Zatrzymywanie currentRollMasterMusicInstance."); if (typeof self.currentRollMasterMusicInstance.volume === 'number') { self.currentRollMasterMusicInstance.volume = 0; } self.currentRollMasterMusicInstance.stop(); self.currentRollMasterMusicInstance = null; } console.log("[Skip Intro] Odtwarzanie RollSouls..."); self.currentRollSoulsInstance = LK.playMusic('RollSouls', { loop: true, volume: 0.7, fade: { start: 0, end: 0.7, duration: 1000 } }); if (!self.currentRollSoulsInstance) { console.error("[Skip Intro] LK.playMusic('RollSouls') nie zwróciło instancji!"); } console.log("[Skip Intro] Przechodzenie do showRealTutorial..."); self.showRealTutorial(); }; skipTimerId = null; }, 3000); function showIntroText(text, delay, onComplete) { var introText = new Text2(text, { size: 90, fill: 0xFFFFFF, align: 'center', wordWrap: true, wordWrapWidth: 1800 }); introText.anchor.set(0.5, 0.5); introText.x = 2048 / 2; introText.y = 2232 / 2 - 400; introText.alpha = 0; currentSceneElements.addChild(introText); LK.setTimeout(function () { if (self.currentState !== "intro") { return; } tween(introText, { alpha: 1 }, { duration: 2000, easing: tween.easeInOut }); LK.setTimeout(function () { if (self.currentState !== "intro") { return; } tween(introText, { alpha: 0 }, { duration: 2000, easing: tween.easeInOut }); LK.setTimeout(function () { if (onComplete) { onComplete(); } }, 3000); }, 3000); }, delay); } showIntroText('From the authors of Dark Souls...', 2000, function () { if (self.currentState !== "intro") { return; } showIntroText('I have no information. I don’t know them.', 0, function () { if (self.currentState !== "intro") { return; } showIntroText('Silas GameStudio...', 0, function () { if (self.currentState !== "intro") { return; } showIntroText('Still looking for funding.', 0, function () { if (self.currentState !== "intro") { return; } showIntroText('Oh, and it doesn’t even exist.', 0, function () { if (self.currentState !== "intro") { return; } LK.setTimeout(function () { if (self.currentState !== "intro") { return; } if (skipTimerId) { LK.clearTimeout(skipTimerId); skipTimerId = null; } if (skipElement && skipElement.parent) { currentSceneElements.removeChild(skipElement); if (skipElement.destroy) { skipElement.destroy(); } skipElement = null; } if (typeof gameState.showFakeTutorial === 'function') { gameState.showFakeTutorial(); } else { console.error("Error: gameState.showFakeTutorial is not defined"); gameState.showTitleScreen(); } }, 1000); }); }); }); }); }); }, // Koniec funkcji showIntro // --- KONIEC SEKWENCJI INTRO --- // <--- Koniec funkcji showIntro showFakeTutorial: function showFakeTutorial() { clearScene(); if (this.fakeTutorialTimerId) { LK.clearTimeout(this.fakeTutorialTimerId); this.fakeTutorialTimerId = null; } if (this.currentIntroMusicInstance) { console.log("[FakeTutorial] Próba wyciszenia i zatrzymania currentIntroMusicInstance."); var instanceToFadeAndStop = this.currentIntroMusicInstance; LK.tween(instanceToFadeAndStop, { volume: 0 }, { duration: 3000, onFinish: function onFinish() { if (instanceToFadeAndStop.stop) { instanceToFadeAndStop.stop(); console.log("[FakeTutorial] currentIntroMusicInstance zatrzymane po tweenie."); if (gameState.currentIntroMusicInstance === instanceToFadeAndStop) { gameState.currentIntroMusicInstance = null; } } } }); } else { console.log("[FakeTutorial] Nie znaleziono currentIntroMusicInstance. Sprawdzanie LK.music..."); var musicViaLK_I = LK.music; if (musicViaLK_I && musicViaLK_I.assetId === 'introMusic') { LK.tween(musicViaLK_I, { volume: 0 }, { duration: 3000, onFinish: function onFinish() { if (musicViaLK_I.stop) { musicViaLK_I.stop(); } } }); } } if (this.currentRollMasterMusicInstance && this.currentRollMasterMusicInstance.stop) { console.log("[FakeTutorial] Zatrzymywanie currentRollMasterMusicInstance."); if (typeof this.currentRollMasterMusicInstance.volume === 'number') { this.currentRollMasterMusicInstance.volume = 0; } this.currentRollMasterMusicInstance.stop(); this.currentRollMasterMusicInstance = null; } console.log("[FakeTutorial] Odtwarzanie RollSouls..."); this.currentRollSoulsInstance = LK.playMusic('RollSouls', { loop: true, volume: 0.7, fade: { start: 0, end: 0.7, duration: 3000 } }); if (!this.currentRollSoulsInstance) { console.error("[FakeTutorial] LK.playMusic('RollSouls') nie zwróciło instancji!"); } this.currentState = "fakeTutorial"; ui.positionElements("fakeTutorial"); var instructionText = new Text2('Press LPM to block.. or don’t press.', { size: 100, fill: 0xFFFFFF, align: 'center', wordWrap: true, wordWrapWidth: 1800, dropShadow: true, dropShadowColor: 0x000000, dropShadowDistance: 3, dropShadowBlur: 4, dropShadowAngle: Math.PI / 4 }); instructionText.anchor.set(0.5, 0.5); instructionText.x = 2048 / 2; instructionText.y = 2732 / 2; currentSceneElements.addChild(instructionText); this.fakeTutorialTimerId = LK.setTimeout(function () { try { if (instructionText && instructionText.destroy) { instructionText.destroy(); } } catch (e) {} gameState.fakeTutorialTimerId = null; if (gameState && typeof gameState.showRealTutorial === 'function') { gameState.showRealTutorial(); } else { console.error("Error: gameState.showRealTutorial is not a function in fakeTutorial timeout!"); gameState.showTitleScreen(); } }, 6000); }, // *** NOWA FUNKCJA: Prawdziwy Tutorial (oddzielona od startGame) *** showRealTutorial: function showRealTutorial() { if (this.fakeTutorialTimerId) { LK.clearTimeout(this.fakeTutorialTimerId); } this.fakeTutorialTimerId = null; // Wyczyść timer fake tutorialu clearScene(); // Wyczyść elementy fake tutorialu/fake game over // Zatrzymaj animację tła intro i usuń je if (currentBackground) { tween.stop(currentBackground); currentBackground.destroy(); currentBackground = null; } this.currentState = "realTutorial"; // Ustaw tło tutoriala if (currentBackground && currentBackground.destroy) { currentBackground.destroy(); } currentBackground = LK.getAsset('realtutorialbg', {}); game.addChildAt(currentBackground, 0); // Pokaż ściany areny walls.forEach(function (wall) { if (wall) { wall.alpha = 1; } }); ui.positionElements("realTutorial"); // Ustaw UI (głównie ukrywa elementy gry) // --- NIE MA tutorialTitle ani tutorialDesc (tekst jest na grafice) --- // Przycisk "Let's Roll!" do rozpoczęcia gry var startButton = new Container(); startButton.interactive = true; startButton.x = 2048 / 2; startButton.y = 1250; // Zmniejszona pozycja Y, był 1500 currentSceneElements.addChild(startButton); // Powiększone tło przycisku var startButtonBg = LK.getAsset('button_bg', { anchorX: 0.5, anchorY: 0.5 }); startButtonBg.scale.set(1.3); // Skala tła przycisku zwiększona startButton.addChild(startButtonBg); // Powiększony i wyraźniejszy tekst przycisku var startButtonText = new Text2("Let's Roll!", { size: 80, fill: 0xFFFFFF, stroke: 0x000000, strokeThickness: 6 }); startButtonText.anchor.set(0.5, 0.5); startButton.addChild(startButtonText); // Funkcja kliknięcia przycisku startButton.down = function () { gameState.startGame(); // Rozpocznij grę }; }, // Obsługa inputu w stanie fakeTutorial (fałszywa śmierć) handleFakeTutorialInput: function handleFakeTutorialInput() { // Wyczyść timer, który miał prowadzić do prawdziwego tutorialu if (this.fakeTutorialTimerId) { LK.clearTimeout(this.fakeTutorialTimerId); this.fakeTutorialTimerId = null; // Zresetuj ID timera } clearScene(); // Wyczyść ekran fałszywego tutorialu // Ustaw stan tymczasowo (można by dodać dedykowany stan 'fakeGameOver') this.currentState = "fakeGameOver"; // Zmieniono z "gameOver" // Zatrzymaj animację tła intro, ale nie usuwaj go jeszcze if (currentBackground) { tween.stop(currentBackground); } ui.positionElements("gameOver"); // Użyj pozycjonowania gameOver dla tekstów "YOU DIED" // 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 = 600; currentSceneElements.addChild(diedText); // Wyświetl wyjaśnienie var explanationText = new Text2("Did you check the title of the game?", { size: 70, fill: 0xFFFFFF, align: 'center', wordWrap: true, wordWrapWidth: 1800, dropShadow: true, dropShadowColor: 0x000000, // cień czarny dropShadowDistance: 3, // odległość cienia dropShadowBlur: 4, // lekkie rozmycie cienia dropShadowAngle: Math.PI / 4 // kąt padania cienia (45 stopni) }); explanationText.anchor.set(0.5, 0.5); explanationText.x = 2048 / 2; explanationText.y = 800; 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', { size: 50, fill: 0xFFFFFF }); // Zmieniono tekst i rozmiar buttonText.anchor.set(0.5, 0.5); howToPlayButtonContainer.addChild(buttonText); // Akcja przycisku: Przejdź do prawdziwego tutorialu howToPlayButtonContainer.down = function () { // *** POPRAWKA: Sprawdzenie przed wywołaniem *** if (gameState && typeof gameState.showRealTutorial === 'function') { gameState.showRealTutorial(); // Przejdź do prawdziwego tutorialu } else { console.error("Error: gameState.showRealTutorial is not a function in fake input handler!"); gameState.showTitleScreen(); // Awaryjny powrót do tytułu } }; }, // Przejście do stanu gry (walka z bossem) - wywoływane z Prawdziwego Tutorialu startGame: function startGame() { var _this = this; // _this dla callbacków w timerach poniżej console.log("[StartGame] Rozpoczynanie standardowej gry lub NewBoss+."); // --- POCZĄTEK SEKCJI ZARZĄDZANIA MUZYKĄ --- if (this.currentIntroMusicInstance && this.currentIntroMusicInstance.stop) { console.log("[StartGame] Zatrzymywanie currentIntroMusicInstance."); if (typeof this.currentIntroMusicInstance.volume === 'number') { this.currentIntroMusicInstance.volume = 0; } this.currentIntroMusicInstance.stop(); this.currentIntroMusicInstance = null; } if (this.currentRollMasterMusicInstance && this.currentRollMasterMusicInstance.stop) { console.log("[StartGame] Zatrzymywanie currentRollMasterMusicInstance."); if (typeof this.currentRollMasterMusicInstance.volume === 'number') { this.currentRollMasterMusicInstance.volume = 0; } this.currentRollMasterMusicInstance.stop(); this.currentRollMasterMusicInstance = null; } // Zatrzymaj poprzednią instancję RollSouls (jeśli istnieje) przed uruchomieniem nowej. if (this.currentRollSoulsInstance && this.currentRollSoulsInstance.stop) { console.log("[StartGame] Zatrzymywanie poprzedniej currentRollSoulsInstance."); if (typeof this.currentRollSoulsInstance.volume === 'number') { this.currentRollSoulsInstance.volume = 0; } this.currentRollSoulsInstance.stop(); // this.currentRollSoulsInstance = null; // Zerujemy, bo zaraz przypiszemy nową } console.log("[StartGame] Uruchamianie RollSouls."); this.currentRollSoulsInstance = LK.playMusic('RollSouls', { loop: true, volume: 0.7 }); if (!this.currentRollSoulsInstance) { console.error("[StartGame] LK.playMusic('RollSouls') nie zwróciło instancji!"); } // --- KONIEC SEKCJI ZARZĄDZANIA MUZYKĄ --- if (this.fakeTutorialTimerId) { LK.clearTimeout(this.fakeTutorialTimerId); this.fakeTutorialTimerId = null; } if (this.gameTimerInterval) { LK.clearInterval(this.gameTimerInterval); this.gameTimerInterval = null; } clearScene(); if (currentBackground) { tween.stop(currentBackground); currentBackground.destroy(); currentBackground = null; } this.currentState = "game"; if (gameState.grillMenuEffects && gameState.grillMenuEffects.length > 0) { gameState.grillMenuEffects.forEach(function (effect) { if (effect && effect.parent) { tween.stop(effect); effect.parent.removeChild(effect); if (effect.destroy && !effect.destroyed) { effect.destroy(); } } else if (effect && effect.destroy && !effect.destroyed) { console.warn("Sparkle/Star object found without parent but has destroy method:", effect); effect.destroy(); } else { console.warn("Sparkle/Star object found without parent or destroy method:", effect); } }); gameState.grillMenuEffects = []; } game.setBackgroundColor(0x111111); var arenaBg = LK.getAsset('arena', { anchorX: 0.5, anchorY: 0.5, x: 2048 / 2, y: 2732 / 2 }); game.addChildAt(arenaBg, 0); if (player && player.destroy) { player.destroy(); } player = game.addChild(new Player()); player.health = storage.maxHearts || 5; player.rolling = false; player.invulnerable = false; player.invulnerabilityFrames = 0; player.rollCooldown = 0; if (player && typeof player.clearRollTimeouts === 'function') { player.clearRollTimeouts(); } player.hasRolledThroughBossThisRoll = false; player.x = 2048 / 2; player.y = 2732 / 2 + 400; player.alpha = 1; player.dead = false; if (boss && boss.destroy) { boss.destroy(); } boss = game.addChild(new Boss()); boss.x = 2048 / 2; boss.y = 2732 / 2 - 400; boss.alpha = 1; boss.attackCooldown = 90; boss.attacks = []; boss.attackSpeedMultiplier = 1; boss.repositioning = false; boss.dead = false; boss.phase = 1; boss.tint = 0xFFFFFF; boss.scale.set(1, 1); if (isNewBossPlusMode) { boss.maxHealth = 700; boss.health = boss.maxHealth; this.gameDuration = 600; boss.attackSpeedMultiplier = 0.6; boss.speed = 7; console.log("Starting NEW BOSS+ mode. HP:", boss.maxHealth, "Time:", this.gameDuration); } else { boss.maxHealth = 160; boss.health = boss.maxHealth; this.gameDuration = 120; boss.speed = 5; boss.attackSpeedMultiplier = 1; console.log("Starting STANDARD mode. HP:", boss.maxHealth, "Time:", this.gameDuration); } walls.forEach(function (wall) { if (wall) { wall.alpha = 1; } }); ui.positionElements("game"); ui.updateHearts(player.health, storage.maxHearts); ui.showTutorial("Swipe to Roll!"); ui.updateBossHealth(boss.health, boss.maxHealth); this.remainingTime = this.gameDuration; ui.updateTimerDisplay(this.remainingTime); if (this.gameTimerInterval) { LK.clearInterval(this.gameTimerInterval); } this.bossSpeedIncreased = false; this.gameTimerInterval = LK.setInterval(function () { if (_this.currentState === "game") { _this.remainingTime--; ui.updateTimerDisplay(_this.remainingTime); if (!isNewBossPlusMode) { var accelerationThreshold = _this.gameDuration - 60; if (_this.remainingTime <= accelerationThreshold && !_this.bossSpeedIncreased) { _this.bossSpeedIncreased = true; if (boss && !boss.dead) { boss.attackSpeedMultiplier *= 0.7; boss.speed += 1; ui.showMessage("Boss attacks faster!", 2000); } } } if (_this.remainingTime <= 0) { LK.clearInterval(_this.gameTimerInterval); _this.gameTimerInterval = null; if (isNewBossPlusMode) { gameOverReasonIsDeath = false; gameState.gameOver(false); } else { gameState.showGrillScreen(); } } } else { if (_this.gameTimerInterval) { LK.clearInterval(_this.gameTimerInterval); } _this.gameTimerInterval = null; } }, 1000); LK.setTimeout(function () { if (_this.currentState === "game") { ui.hideTutorial(); } }, 3000); }, // victory() - nieużywane, logika w timerze i boss.die() showGrillScreen: function showGrillScreen() { this.rollMasterTime = 0; // <-- DODAJ TĘ LINIĘ isNewBossPlusMode = false; console.log("State: Grill Menu (isNewBossPlusMode reset to false)"); // Wyczyść timery (pozostaw ten fragment) if (this.gameTimerInterval) { LK.clearInterval(this.gameTimerInterval); } this.gameTimerInterval = null; if (this.fakeTutorialTimerId) { LK.clearTimeout(this.fakeTutorialTimerId); } this.fakeTutorialTimerId = null; this.bossSpeedIncreased = false; // Reset flagi // --- DODANY KOD: Tablica do przechowywania efektów z menu grilla --- gameState.grillMenuEffects = []; // Tworzymy nową tablicę przy wejściu do menu // --- KONIEC DODANEGO KODU --- // --- Rozpoczęcie agresywnego czyszczenia sceny --- // Stwórz listę obiektów (kontenerów, UI, ścian), które powinny pozostać w 'game'. // Upewnij się, że 'ui', 'currentSceneElements' i wszystkie obiekty w tablicy 'walls' są tutaj. var childrenToKeep = [ui, currentSceneElements].concat(walls); // Przejdź przez wszystkie dzieci głównego obiektu 'game' od końca do początku for (var i = game.children.length - 1; i >= 0; i--) { var child = game.children[i]; // Sprawdź, czy obecne dziecko NIE jest na liście obiektów do zachowania if (childrenToKeep.indexOf(child) === -1) { // console.log("Usuwam obiekt:", child); // Możesz tymczasowo odkomentować do debugowania // Usuń i zniszcz obiekt if (child && child.destroy) { child.destroy(); // Użyj destroy jeśli dostępne (zalecane w LK) } else if (child && child.parent) { child.parent.removeChild(child); // Fallback - usuń z rodzica } } } // Po agresywnym czyszczeniu, upewnij się, że globalne zmienne gracza, bossa i tła są null // Te obiekty powinny zostać usunięte przez powyższą pętlę, ale warto zresetować zmienne. currentBackground = null; player = null; boss = null; // --- Koniec agresywnego czyszczenia sceny --- this.currentState = "grillMenu"; game.setBackgroundColor(0x333333); // Tło grilla // Teraz dodaj nowe tło dla ekranu Grilla (ten fragment zostaje taki, jak go poprawiliśmy ostatnio - JEDNO dodanie grillMenu) currentBackground = LK.getAsset('grillMenu', { anchorX: 0.5, anchorY: 0.5, x: 2048 / 2, y: 2732 / 2 }); // Dodaj nowe tło na spód if (currentBackground) { // Sprawdź czy asset został poprawnie załadowany game.addChildAt(currentBackground, 0); } // ✨ DODAJEMY 5 SPARKLINGÓW ✨ // Sparkle 1 var sparkle1 = game.addChild(LK.getAsset('sparkle', { anchorX: 0.5, anchorY: 0.5, x: 2048 / 2 - 460, y: 2732 / 2 + 690 })); gameState.grillMenuEffects.push(sparkle1); // <--- DODANA LINIJA // Sparkle 2 var sparkle2 = game.addChild(LK.getAsset('sparkle', { anchorX: 0.5, anchorY: 0.5, x: 2048 / 2 - 520, y: 2732 / 2 + 580 })); gameState.grillMenuEffects.push(sparkle2); // <--- DODANA LINIJA // Sparkle 3 var sparkle3 = game.addChild(LK.getAsset('sparkle', { anchorX: 0.5, anchorY: 0.5, x: 2048 / 2 - 660, y: 2732 / 2 + 550 })); gameState.grillMenuEffects.push(sparkle3); // <--- DODANA LINIJA // Sparkle 4 var sparkle4 = game.addChild(LK.getAsset('sparkle', { anchorX: 0.5, anchorY: 0.5, x: 2048 / 2 - 500, y: 2732 / 2 + 680 })); gameState.grillMenuEffects.push(sparkle4); // <--- DODANA LINIJA // Sparkle 5 var sparkle5 = game.addChild(LK.getAsset('sparkle', { anchorX: 0.5, anchorY: 0.5, x: 2048 / 2 - 620, y: 2732 / 2 + 720 })); gameState.grillMenuEffects.push(sparkle5); // <--- DODANA LINIJA // Funkcja animacji sparkle function animateSparkle(s) { // Sprawdzenie stanu (pozostaje bez zmian) if (gameState.currentState !== "grillMenu" || !s || s.destroyed) { return; } // ZAPISZ pozycję Y na początku TEGO cyklu animacji var initialYThisCycle = s.y; s.alpha = 0; // Rozpocznij od przezroczystości 0 // Pozycję Y ustawimy za chwilę, nie ma potrzeby resetowania jej tutaj od razu // Animacja pojawienia się i ruchu w górę tween(s, { alpha: 1, y: initialYThisCycle - 40 // Przesuń w górę WZGLĘDEM ZAPISANEJ pozycji }, { duration: 800, easing: tween.easeOut, onFinish: function onFinish() { // Po zakończeniu ruchu w górę if (gameState.currentState !== "grillMenu" || !s || s.destroyed) { return; } // Animacja zanikania (na tej wyższej pozycji) tween(s, { alpha: 0 }, { duration: 300, easing: tween.easeIn, onFinish: function onFinish() { // Po zakończeniu zanikania if (gameState.currentState !== "grillMenu" || !s || s.destroyed) { return; } // *** KLUCZOWA ZMIANA: Resetuj pozycję Y PRZED następnym cyklem *** s.y = initialYThisCycle; // <-- WRÓĆ do pozycji Y z początku tego cyklu // Zaplanuj kolejny cykl animacji LK.setTimeout(function () { animateSparkle(s); // Wywołaj kolejny cykl (teraz zacznie z poprawnej pozycji Y) }, Math.random() * 500 + 300); } }); } }); } // Start animacji sparklingów // Upewnij się, że animacje startują z pewnym opóźnieniem po dodaniu do tablicy LK.setTimeout(function () { if (gameState.currentState === "grillMenu") { animateSparkle(sparkle1); } }, Math.random() * 500); LK.setTimeout(function () { if (gameState.currentState === "grillMenu") { animateSparkle(sparkle2); } }, Math.random() * 500 + 500); LK.setTimeout(function () { if (gameState.currentState === "grillMenu") { animateSparkle(sparkle3); } }, Math.random() * 500 + 1000); LK.setTimeout(function () { if (gameState.currentState === "grillMenu") { animateSparkle(sparkle4); } }, Math.random() * 500 + 1500); LK.setTimeout(function () { if (gameState.currentState === "grillMenu") { animateSparkle(sparkle5); } }, Math.random() * 500 + 2000); // 🌟 DODAJEMY 3 GWIAZDKI NA NIEBIE 🌟 // Star 1 var star1 = game.addChild(LK.getAsset('star', { anchorX: 0.5, anchorY: 0.5, x: 2048 / 2 - 800, y: 2732 / 2 - 1000 })); gameState.grillMenuEffects.push(star1); // <--- DODANA LINIJA // Star 2 var star2 = game.addChild(LK.getAsset('star', { anchorX: 0.5, anchorY: 0.5, x: 2048 / 2 - 300, y: 2732 / 2 - 1150 })); gameState.grillMenuEffects.push(star2); // <--- DODANA LINIJA // Star 3 var star3 = game.addChild(LK.getAsset('star', { anchorX: 0.5, anchorY: 0.5, x: 2048 / 2 - 200, y: 2732 / 2 - 900 })); gameState.grillMenuEffects.push(star3); // <--- DODANA LINIJA // Funkcja animacji gwiazdek function animateStar(star) { // --- DODANE SPRAWDZENIE STANU --- if (gameState.currentState !== "grillMenu" || !star || star.destroyed) { return; } // --- KONIEC DODANEGO SPRAWDZENIA --- star.alpha = 0; tween(star, { alpha: 1 }, { duration: 2000, easing: tween.easeIn, onFinish: function onFinish() { // --- DODANE SPRAWDZENIE STANU W CALLBACKU --- if (gameState.currentState !== "grillMenu" || !star || star.destroyed) { return; } // --- KONIEC DODANEGO SPRAWDZENIA --- tween(star, { alpha: 0 }, { duration: 2000, easing: tween.easeOut, onFinish: function onFinish() { // --- DODANE SPRAWDZENIE STANU W WEWNĘTRZNYM CALLBACKU --- if (gameState.currentState !== "grillMenu" || !star || star.destroyed) { return; } // --- KONIEC DODANEGO SPRAWDZENIA --- // Po całym cyklu czekamy losowo 2-5 sekund przed kolejnym pojawieniem się LK.setTimeout(function () { animateStar(star); }, Math.random() * 3000 + 2000); } }); } }); } // Start animacji gwiazdek // Upewnij się, że animacje startują z pewnym opóźnieniem po dodaniu do tablicy LK.setTimeout(function () { if (gameState.currentState === "grillMenu") { animateStar(star1); } }, Math.random() * 1000); LK.setTimeout(function () { if (gameState.currentState === "grillMenu") { animateStar(star2); } }, Math.random() * 1000 + 500); LK.setTimeout(function () { if (gameState.currentState === "grillMenu") { animateStar(star3); } }, Math.random() * 1000 + 1000); // --- KONIEC gwiazdek --- // --- KONIEC sparklingów --- // Ukryj ściany (jeśli są widoczne w menu) walls.forEach(function (wall) { if (wall) { wall.alpha = 0; } }); // Ustaw UI dla Grill Menu ui.positionElements("grillMenu"); ui.updateBossHealth(0, 1); // Ukryj pasek HP bossa ui.updateHearts(0, storage.maxHearts); // Ukryj serca gracza // --- Przyciski na ekranie Grilla --- var buttonYStart = 1000; var buttonYOffset = 150; // Przycisk "Rest" var restButton = new Container(); restButton.interactive = true; restButton.cursor = "pointer"; restButton.x = 600; // bardziej w lewo nad grilla restButton.y = 650; // wysokość currentSceneElements.addChild(restButton); // Dodaj do kontenera sceny var restButtonBg = LK.getAsset('buttonRest', { anchorX: 0.5, anchorY: 0.5 }); restButton.addChild(restButtonBg); restButton.down = function () { ui.showMessage("Rest in peace...", 2000); LK.setTimeout(function () { ui.showMessage("Thank you for playing!", 3000); }, 2500); LK.setTimeout(function () { gameState.showTitleScreen(); }, 6000); }; // Przycisk "Upgrade Roll" var upgradeButton = new Container(); upgradeButton.interactive = true; upgradeButton.x = 600; // to samo X co reszta upgradeButton.y = 850; // +200 niżej currentSceneElements.addChild(upgradeButton); // Dodaj do kontenera sceny var upgradeButtonBg = LK.getAsset('buttonUpgrade', { anchorX: 0.5, anchorY: 0.5 }); upgradeButton.addChild(upgradeButtonBg); // Akcja po kliknięciu w Upgrade upgradeButton.down = function () { // ... (istniejący kod animacji tła przy przejściu do Upgrade) ... if (currentBackground) { // Zanikajace stare tlo tween(currentBackground, { alpha: 0 }, { duration: 600, easing: tween.easeIn, onFinish: function onFinish() { currentBackground.destroy(); // Usuwamy stare tlo currentBackground = LK.getAsset('upgradebg', { anchorX: 0.5, anchorY: 0.5, x: 2048 / 2, y: 2732 / 2, alpha: 0 // zaczynamy nowe tło od niewidoczności }); game.addChildAt(currentBackground, 0); // Pokazujemy nowe tlo z fade in tween(currentBackground, { alpha: 1 }, { duration: 1200, easing: tween.easeOut }); } }); } }; // Przycisk "New Boss+" var newBossButton = new Container(); newBossButton.interactive = true; newBossButton.cursor = "pointer"; newBossButton.x = 600; newBossButton.y = 1050; // jeszcze niżej currentSceneElements.addChild(newBossButton); // Dodaj do kontenera sceny var newBossButtonBg = LK.getAsset('buttonBoss', { anchorX: 0.5, anchorY: 0.5 }); newBossButton.addChild(newBossButtonBg); newBossButton.down = function () { isNewBossPlusMode = true; // Poprzedni kod usuwający sparkle tutaj jest już niepotrzebny, // ponieważ obsługa usuwania jest teraz w startGame z wykorzystaniem gameState.grillMenuEffects gameState.startGame(); }; // Przycisk "Roll Master" var rollMasterButton = new Container(); // Tworzymy kontener na przycisk rollMasterButton.interactive = true; // Ustawiamy interaktywność rollMasterButton.cursor = "pointer"; // Zmieniamy kursor po najechaniu rollMasterButton.x = 600; // Ta sama pozycja X co inne przyciski rollMasterButton.y = 2500; // Pozycja Y pod przyciskiem "New Boss+" currentSceneElements.addChild(rollMasterButton); // Dodajemy kontener do sceny // --- NOWY KOD TŁA --- // Pobieramy asset obrazka dla przycisku Roll Master var rollMasterButtonImage = LK.getAsset('buttonRollMaster', { anchorX: 0.5, // Ustawiamy punkt zaczepienia na środek obrazka anchorY: 0.5 }); rollMasterButton.addChild(rollMasterButtonImage); // Dodajemy obrazek do kontenera przycisku // --- KONIEC NOWEGO KODU TŁA --- // Usunęliśmy kod tworzący i dodający obiekt Text2 // Akcja przycisku (kliknięcie) pozostaje bez zmian rollMasterButton.down = function () { gameState.startRollMasterMode(); // Uruchom tryb Roll Master }; var creditsButton = new Container(); creditsButton.interactive = true; creditsButton.cursor = "pointer"; // Ustaw pozycję dla prawego dolnego rogu // Te wartości (X, Y) są odległościami od lewego górnego rogu ekranu (0,0) // Musisz je dostosować do rozmiaru swojego przycisku i ekranu. // Załóżmy, że Twój przycisk 'buttoncredits' ma np. 200px szerokości i 80px wysokości. // Aby umieścić go w prawym dolnym rogu z marginesem 50px: var creditsButtonAsset = LK.getAsset('buttoncredits', {}); // Pobierz asset, aby sprawdzić wymiary jeśli trzeba var creditsButtonWidth = creditsButtonAsset.width || 200; // Domyślna szerokość, jeśli nie ma w assecie var creditsButtonHeight = creditsButtonAsset.height || 80; // Domyślna wysokość creditsButton.x = 2048 - creditsButtonWidth / 2 - 50; // Od prawej krawędzi (2048) odejmij połowę szerokości przycisku i margines creditsButton.y = 2732 - creditsButtonHeight / 2 - 50; // Od dolnej krawędzi (2732) odejmij połowę wysokości przycisku i margines currentSceneElements.addChild(creditsButton); // Użyj swojego nowego assetu 'buttoncredits' jako tła/grafiki przycisku var creditsButtonBg = LK.getAsset('buttoncredits', { anchorX: 0.5, // Anchor na środek, bo pozycjonujemy kontener anchorY: 0.5 // Nie potrzebujesz tutaj scale, chyba że Twój asset jest za duży/mały }); creditsButton.addChild(creditsButtonBg); // Tekst na przycisku (jeśli Twój asset 'buttoncredits' nie ma już napisu) // Jeśli 'buttoncredits' to już gotowy obrazek z napisem "Credits", ten obiekt Text2 nie jest potrzebny. var creditsButtonText = new Text2("Credits", { size: 30, // Dostosuj rozmiar, aby pasował do Twojego przycisku fill: 0xFFFFFF // Kolor tekstu // Możesz dodać stroke, jeśli tekst jest na skomplikowanym tle // stroke: 0x000000, // strokeThickness: 2 }); creditsButtonText.anchor.set(0.5, 0.5); // Wyśrodkuj tekst na przycisku creditsButton.addChild(creditsButtonText); // Dodaj tekst DO kontenera przycisku creditsButton.down = function () { if (gameState.currentState === "grillMenu") { console.log("Przycisk Credits kliknięty!"); // Dodaj log dla testu gameState.showCredits(); } }; }, showCredits: function showCredits() { var self = this; var previousState = this.currentState; var wasInputActive = self.isInputActive; self.isInputActive = false; // Tymczasowo zablokuj input gry // self.creditsPlaying = true; // Opcjonalna flaga, jeśli potrzebujesz console.log("[Credits] Pokazywanie Creditsów z własnym tłem"); // 1. Stwórz tło 'creditsbg' var creditsBackground; // Zmienna na nasze tło try { creditsBackground = LK.getAsset('creditsbg', { anchorX: 0.5, // Zakładamy, że chcesz je wyśrodkować anchorY: 0.5, x: 2048 / 2, y: 2732 / 2, alpha: 0 // Zacznij od przezroczystości }); game.addChild(creditsBackground); // Dodaj do sceny } catch (e) { console.error("[Credits] Błąd podczas ładowania assetu 'creditsbg':", e); // Awaryjnie: stwórz czarne tło, jeśli asset się nie załadował creditsBackground = new Shape({ width: 2048, height: 2732, color: 0x111010 }); // Użyjmy Twojego działającego koloru creditsBackground.x = 2048 / 2; creditsBackground.y = 2732 / 2; creditsBackground.alpha = 0; game.addChild(creditsBackground); } var creditsText = null; // Zmienna na tekst creditsów var creditsTextTween = null; // Zmienna na animację tekstu // Handler do pomijania creditsów var _skipCreditsHandler = function skipCreditsHandler() { console.log("[Credits] Pominięto creditsy"); // Zatrzymaj animacje tween.stop(creditsBackground); if (creditsTextTween) { tween.stop(creditsTextTween); } // Usuń obiekty if (creditsText && !creditsText.destroyed) { creditsText.destroy(); } if (creditsBackground && !creditsBackground.destroyed) { creditsBackground.destroy(); } // Przywróć stan self.isInputActive = wasInputActive; // self.creditsPlaying = false; game.off('down', _skipCreditsHandler); // Usuń listener // Jeśli currentState było zmieniane na coś w stylu "creditsScreenActive", przywróć je teraz // self.currentState = previousState; // W tej wersji nie zmieniamy currentState, więc nie ma potrzeby przywracania }; // Animacja pojawienia się tła tween(creditsBackground, { alpha: 1 }, { duration: 500, // Krótki fade-in onFinish: function onFinish() { // Stwórz tekst "Silas" creditsText = new Text2("Silas", { size: 150, fill: 0xFFFFFF, align: 'center' }); creditsText.anchor.set(0.5, 0.5); creditsText.x = 2048 / 2; creditsText.y = 2732 + creditsText.height; game.addChild(creditsText); var textAnimationDuration = 3000; var textEndY = -creditsText.height; // Animacja tekstu w górę creditsTextTween = tween(creditsText, { y: textEndY }, { duration: textAnimationDuration, easing: tween.linear, onFinish: function onFinish() { if (creditsText && !creditsText.destroyed) { creditsText.destroy(); } // Animacja zanikania tła 'creditsbg' tween(creditsBackground, { alpha: 0 }, { duration: 500, onFinish: function onFinish() { if (creditsBackground && !creditsBackground.destroyed) { creditsBackground.destroy(); } console.log("[Credits] Koniec Creditsów, powrót do", previousState); self.isInputActive = wasInputActive; // self.creditsPlaying = false; game.off('down', _skipCreditsHandler); // Usuń listener, jeśli nie został wcześniej wywołany } }); } }); // Dodaj listener do pomijania dopiero po pojawieniu się tła i tekstu (lub od razu, jeśli wolisz) game.once('down', _skipCreditsHandler); } }); }, // Koniec funkcji showGrillScreen (przecinek oddziela ją od następnej metody gameState) // --- NOWA FUNKCJA: Start przejścia do trybu Roll Master --- startRollMasterMode: function startRollMasterMode() { console.log("Rozpoczynanie trybu Roll Master..."); this.currentState = "rollMaster"; // Ustawienie nowego stanu gry // Wyczyszczenie elementów z poprzedniej sceny (np. Grill Menu) // Upewnij się, że 'currentSceneElements' to poprawny kontener dla elementów Grill Menu // lub użyj innej metody czyszczenia specyficznej dla Twojej konfiguracji. // Poniżej przykład czyszczenia globalnego kontenera, jeśli go używasz: if (typeof clearScene === 'function') { // Sprawdź, czy funkcja clearScene istnieje clearScene(); // Użyj globalnej funkcji clearScene, jeśli jest dostępna } else if (currentSceneElements && typeof currentSceneElements.removeChildren === 'function') { // Alternatywnie, jeśli currentSceneElements zawiera tylko elementy menu: currentSceneElements.removeChildren(); } else { console.warn("Nie można automatycznie wyczyścić sceny przed Roll Master!"); } // Dodatkowo, usuń specyficzne efekty z Grill Menu, jeśli istnieją if (this.grillMenuEffects && this.grillMenuEffects.length > 0) { this.grillMenuEffects.forEach(function (effect) { if (effect && effect.destroy) { effect.destroy(); } }); this.grillMenuEffects = []; } // Usuń tło Grill Menu if (currentBackground && currentBackground.destroy) { tween.stop(currentBackground); // Zatrzymaj animacje, jeśli były currentBackground.destroy(); currentBackground = null; } // Wywołanie funkcji konfigurującej scenę tego trybu this.setupRollMasterScene(); }, // <-- WAŻNE: Przecinek tutaj, bo następuje kolejna funkcja gameState // --- NOWA FUNKCJA: Konfiguracja sceny Roll Master --- setupRollMasterScene: function setupRollMasterScene() { var _this2 = this; // _this2 dla callbacków w timerach poniżej console.log("[SetupRollMaster] Konfiguracja sceny Roll Master..."); // --- POCZĄTEK SEKCJI ZARZĄDZANIA MUZYKĄ --- if (this.currentIntroMusicInstance && this.currentIntroMusicInstance.stop) { console.log("[SetupRollMaster] Zatrzymywanie currentIntroMusicInstance."); if (typeof this.currentIntroMusicInstance.volume === 'number') { this.currentIntroMusicInstance.volume = 0; } this.currentIntroMusicInstance.stop(); this.currentIntroMusicInstance = null; } if (this.currentRollSoulsInstance && this.currentRollSoulsInstance.stop) { console.log("[SetupRollMaster] Zatrzymywanie currentRollSoulsInstance."); if (typeof this.currentRollSoulsInstance.volume === 'number') { this.currentRollSoulsInstance.volume = 0; } this.currentRollSoulsInstance.stop(); this.currentRollSoulsInstance = null; } // Zatrzymaj poprzednią instancję rollmaster music, jeśli jakimś cudem istnieje if (this.currentRollMasterMusicInstance && this.currentRollMasterMusicInstance.stop) { console.log("[SetupRollMaster] Zatrzymywanie poprzedniej currentRollMasterMusicInstance."); if (typeof this.currentRollMasterMusicInstance.volume === 'number') { this.currentRollMasterMusicInstance.volume = 0; } this.currentRollMasterMusicInstance.stop(); // this.currentRollMasterMusicInstance = null; // Zerujemy, bo zaraz przypiszemy nową } console.log("[SetupRollMaster] Odtwarzanie muzyki 'rollmaster'."); this.currentRollMasterMusicInstance = LK.playMusic('rollmaster', { loop: true, volume: 0.7, fade: { start: 0, end: 0.7, duration: 1000 } }); if (!this.currentRollMasterMusicInstance) { console.error("[SetupRollMaster] LK.playMusic('rollmaster') nie zwróciło instancji!"); } // --- KONIEC SEKCJI ZARZĄDZANIA MUZYKĄ --- try { currentBackground = LK.getAsset('rollMasterBg', { anchorX: 0.5, anchorY: 0.5, x: 2048 / 2, y: 2732 / 2 }); game.addChildAt(currentBackground, 0); } catch (e) { console.warn("Nie znaleziono assetu 'rollMasterBg', używam koloru tła."); game.setBackgroundColor(0x222233); } if (typeof Player !== 'undefined') { player = game.addChild(new Player()); player.x = 2048 / 2; player.y = 2732 / 2 + 400; player.health = 1; player.rolling = false; player.invulnerable = false; player.invulnerabilityFrames = 0; player.rollCooldown = 0; if (player && typeof player.clearRollTimeouts === 'function') { player.clearRollTimeouts(); } player.hasRolledThroughBossThisRoll = false; player.alpha = 1; player.dead = false; } else { console.error("Klasa Player nie jest zdefiniowana!"); this.showGrillScreen(); return; } this.rollMasterTime = 0; this.rollMasterDifficulty = 1; this.rollMasterAttacks = []; this.attackUnlockOrder = ['rmattack1', 'rmattack2', 'rmattack3', 'rmattack4']; this.unlockedAttacks = ['rmattack1']; this.nextUnlockTime = 15; console.log("Ataki startowe:", this.unlockedAttacks, "Następne odblokowanie w:", this.nextUnlockTime + "s"); this.attackSpawnTimer = 0; this.attackSpawnInterval = 120; this.explosionSpawnTimer = 0; this.explosionSpawnInterval = 240; this.laserSpawnTimer = 0; this.laserSpawnInterval = 300; this.laserWarningTime = 1500; this.spreaderSpawnTimer = 0; this.spreaderSpawnInterval = 600; this.spreaderSplitTime = 180; if (this.randomRmattack1Timer) { LK.clearTimeout(this.randomRmattack1Timer); this.randomRmattack1Timer = null; } this.rollMasterHighScore = storage.rollMasterHighScore || 0; if (ui && ui.updateHighScoreDisplay) { ui.updateHighScoreDisplay(this.rollMasterHighScore); } else { console.error("Nie można zaktualizować wyświetlania rekordu - brak ui.updateHighScoreDisplay"); } if (ui && ui.timerText) { ui.updateTimerDisplay(this.rollMasterTime); } else { console.error("Nie można znaleźć ui.timerText do skonfigurowania!"); } if (ui && ui.updateHearts) { ui.updateHearts(1, 1); } if (ui && ui.updateBossHealth) { ui.updateBossHealth(0, 1); } if (ui && ui.positionElements) { ui.positionElements("rollMaster"); } else { console.error("Nie można ustawić elementów UI dla Roll Master - brak ui.positionElements"); } if (this.rollMasterTimerInterval) { LK.clearInterval(this.rollMasterTimerInterval); } this.rollMasterTimerInterval = LK.setInterval(function () { if (_this2.currentState !== "rollMaster") { LK.clearInterval(_this2.rollMasterTimerInterval); _this2.rollMasterTimerInterval = null; return; } _this2.rollMasterTime++; if (ui && ui.updateTimerDisplay) { ui.updateTimerDisplay(_this2.rollMasterTime); } if (_this2.rollMasterTime > 0 && _this2.rollMasterTime % 20 === 0) { var newAttackJustUnlocked = false; var newAttackFriendlyName = ""; if (_this2.unlockedAttacks.length < _this2.attackUnlockOrder.length) { var nextAttackIndex = _this2.unlockedAttacks.length; var attackToUnlockTechnicalName = _this2.attackUnlockOrder[nextAttackIndex]; _this2.unlockedAttacks.push(attackToUnlockTechnicalName); newAttackJustUnlocked = true; newAttackFriendlyName = attackToUnlockTechnicalName.replace('rmattack1', 'Fart of the Forgotten Pyromancer').replace('rmattack2', 'Blood Ball Z').replace('rmattack3', 'Holy Wifi Beam').replace('rmattack4', 'Flame Skull.exe'); console.log("Odblokowano nowy atak:", attackToUnlockTechnicalName, "w", _this2.rollMasterTime + "s"); } _this2.increaseRollMasterDifficulty(); if (ui && ui.showMessage) { var messageToShow = ""; var currentDifficulty = _this2.rollMasterDifficulty; if (newAttackJustUnlocked) { messageToShow = "New Attack: " + newAttackFriendlyName + "! (Difficulty: " + currentDifficulty + ")"; } else { switch (currentDifficulty) { case 2: case 3: case 4: messageToShow = "Even the pause button is afraid."; break; case 5: messageToShow = "Alright, that’s probably enough difficulty... (Poziom: " + currentDifficulty + ")"; break; case 6: messageToShow = "Wait, are you still playing? I ran out of messages. (Poziom: " + currentDifficulty + ")"; break; case 7: messageToShow = "Okay seriously, stop. I didn’t test this far. (Poziom: " + currentDifficulty + ")"; break; default: messageToShow = "I stopped writing messages after level 7. You're on your own. Level: " + currentDifficulty; break; } } ui.showMessage(messageToShow, 2500); } } }, 1000); if (ui && ui.showMessage && this.unlockedAttacks.length > 0) { var firstAttackName = this.unlockedAttacks[0]; var friendlyFirstName = firstAttackName.replace('rmattack1', 'Fart of the Forgotten Pyromancer').replace('rmattack2', 'Blood Ball Z').replace('rmattack3', 'Holy Wifi Beam').replace('rmattack4', 'Flame Skull.exe'); LK.setTimeout(function () { if (gameState.currentState === "rollMaster") { ui.showMessage("First Attack: " + friendlyFirstName, 3000); } }, 1000); } console.log("Scena Roll Master skonfigurowana i muzyka 'rollmaster' powinna grać."); }, // <-- WAŻNE: Przecinek tutaj // --- NOWA FUNKCJA: Zwiększanie trudności w Roll Master --- increaseRollMasterDifficulty: function increaseRollMasterDifficulty() { this.rollMasterDifficulty++; console.log("Trudność Roll Master zwiększona do:", this.rollMasterDifficulty); var decreaseAmountProjectile = 10; var minIntervalProjectile = 30; this.attackSpawnInterval = Math.max(minIntervalProjectile, this.attackSpawnInterval - decreaseAmountProjectile); console.log("Nowy interwał projectile:", this.attackSpawnInterval); var decreaseAmountExplosion = 15; var minIntervalExplosion = 60; this.explosionSpawnInterval = Math.max(minIntervalExplosion, this.explosionSpawnInterval - decreaseAmountExplosion); console.log("Nowy interwał explosion:", this.explosionSpawnInterval); // --- NOWA LOGIKA DLA LASERA --- var decreaseAmountLaser = 20; // Jak szybko ma skracać się interwał lasera var minIntervalLaser = 120; // Minimalny interwał lasera (np. 2 sekundy) this.laserSpawnInterval = Math.max(minIntervalLaser, this.laserSpawnInterval - decreaseAmountLaser); console.log("Nowy interwał laser:", this.laserSpawnInterval); // --- NOWA LOGIKA DLA SPREADERA --- var decreaseAmountSpreader = 30; // Jak szybko ma skracać się interwał spreadera var minIntervalSpreader = 180; // Minimalny interwał spreadera (np. 3 sekundy) this.spreaderSpawnInterval = Math.max(minIntervalSpreader, this.spreaderSpawnInterval - decreaseAmountSpreader); console.log("Nowy interwał spreader:", this.spreaderSpawnInterval); }, // Przejście do stanu Game Over // isDeath: true (gracz zginął), false (czas minął w Boss+) gameOver: function gameOver(isDeath) { this.currentState = "gameOver"; // Ustaw stan gameOverReasonIsDeath = isDeath; // Zapisz przyczynę // Zatrzymaj timery gry (gameTimerInterval i rollMasterTimerInterval) if (this.gameTimerInterval) { LK.clearInterval(this.gameTimerInterval); this.gameTimerInterval = null; } if (this.rollMasterTimerInterval) { // Zatrzymanie timera Roll Master LK.clearInterval(this.rollMasterTimerInterval); this.rollMasterTimerInterval = null; } this.bossSpeedIncreased = false; // Zatrzymaj animację tła if (currentBackground) { tween.stop(currentBackground); } game.setBackgroundColor(0x000000); // Czarne tło // Ukryj gracza i bossa if (player && player.alpha !== 0) { player.alpha = 0; } if (boss && boss.alpha !== 0) { boss.alpha = 0; } // Ukryj ściany walls.forEach(function (wall) { if (wall) { wall.alpha = 0; } }); // Ustaw UI dla ekranu game over ui.positionElements("gameOver"); // Logika komunikatu Game Over (z łączeniem dla Roll Master) var gameOverMessage = "YOU DIED"; // Domyślny komunikat // Sprawdź, czy to śmierć w Roll Master, aby przygotować wiadomość if (isDeath && this.rollMasterTime > 0) { // Sprawdzamy TUTAJ, aby ew. przygotować wiadomość var finalTime = this.rollMasterTime; // Czas jest > 0, więc to był Roll Master var minutes = Math.floor(finalTime / 60); var seconds = finalTime % 60; var formattedTime = String(minutes).padStart(2, '0') + ':' + String(seconds).padStart(2, '0'); gameOverMessage = "YOU DIED\nYour Time: " + formattedTime; // NIE resetujemy tutaj rollMasterTime, zrobimy to przy przejściu do menu/startu gry } else if (!isDeath) { // Jeśli czas minął w Boss+ gameOverMessage = "You just lost 10 minutes of your life. For what?"; } // W przeciwnym razie (standardowa śmierć) zostaje "YOU DIED" // Wyświetl komunikat ui.titleText.setText(gameOverMessage); ui.titleText.alpha = 1; ui.showMessage("", 0); ui.showTutorial(""); // Zaplanuj przejście po opóźnieniu LK.setTimeout(function () { // Wyczyść scenę przed przejściem clearScene(); // Zniszcz gracza/bossa if (player && player.destroy && !player.destroyed) { player.destroy(); } player = null; if (boss && boss.destroy && !boss.destroyed) { boss.destroy(); } boss = null; // Usuń tło game over if (currentBackground) { currentBackground.destroy(); } currentBackground = null; // Resetuj UI... ui.titleText.setText(""); ui.titleText.alpha = 0; ui.messageText.setText(""); ui.messageText.alpha = 0; ui.tutorialText.setText(""); ui.tutorialText.alpha = 0; ui.updateDeathsCounter(); ui.updateBossHealth(0, 1); ui.updateHearts(0, storage.maxHearts); // --- POCZĄTEK NOWEJ LOGIKI WARUNKOWEJ w Timeout --- // Sprawdzamy warunki TUTAJ, w momencie wykonywania callbacku timeout // Czy śmierć nastąpiła w Roll Master? (czas > 0) if (isDeath && gameState.rollMasterTime > 0) { console.log("gameOver timeout: Roll Master death -> Going to Grill Menu..."); // Przejdź do menu grilla (tutaj rollMasterTime zostanie zresetowane przez showGrillScreen) isNewBossPlusMode = false; // Wyzeruj flagę na wszelki wypadek if (typeof gameState.showGrillScreen === 'function') { gameState.showGrillScreen(); } else { console.error("Nie można wywołać showGrillScreen!"); } } // Czy śmierć nastąpiła w trybie standardowym? (!isNewBossPlusMode i czas RM jest <= 0) else if (isDeath && !isNewBossPlusMode) { console.log("gameOver timeout: Standard death -> Restarting standard game..."); // Restartuj standardową grę (tutaj rollMasterTime zostanie zresetowane przez startGame) isNewBossPlusMode = false; // Wyzeruj flagę gameState.startGame(); } // Pozostałe przypadki (śmierć w Boss+, koniec czasu w Boss+) else { console.log("gameOver timeout: Other case -> Going to Grill Menu..."); // Przejdź do menu grilla (tutaj rollMasterTime zostanie zresetowane przez showGrillScreen) isNewBossPlusMode = false; // Wyzeruj flagę if (typeof gameState.showGrillScreen === 'function') { gameState.showGrillScreen(); } else { console.error("Nie można wywołać showGrillScreen!"); } } // --- KONIEC NOWEJ LOGIKI WARUNKOWEJ w Timeout --- }, 3000); // Odczekaj 3 sekundy }, // --- NOWA FUNKCJA: Zakończenie trybu Roll Master --- endRollMasterMode: function endRollMasterMode(finalTime) { var _this3 = this; // Zachowaj kontekst 'this' (gameState) console.log("Zakończono tryb Roll Master. Czas:", finalTime); this.currentState = "rollMasterGameOver"; // Stan wskazujący na ekran podsumowania // Zatrzymaj główny timer trybu if (this.rollMasterTimerInterval) { LK.clearInterval(this.rollMasterTimerInterval); this.rollMasterTimerInterval = null; } // Zatrzymaj losowy timer dla rmattack1 if (this.randomRmattack1Timer) { LK.clearTimeout(this.randomRmattack1Timer); this.randomRmattack1Timer = null; } // Wyczyść wszystkie aktywne ataki ze sceny i z listy if (this.rollMasterAttacks && this.rollMasterAttacks.length > 0) { this.rollMasterAttacks.forEach(function (attack) { if (attack.visual && attack.visual.destroy && !attack.visual.destroyed) { attack.visual.destroy(); // Zniszcz wizualizację } }); this.rollMasterAttacks = []; // Wyczyść listę } // Zatrzymaj ewentualne akcje gracza (np. timeouty uniku) if (player && player.clearRollTimeouts) { player.clearRollTimeouts(); } // Ukryj gracza (powinien już być w trakcie animacji śmierci, ale dla pewności) if (player) { // Nie niszczymy go od razu, pozwalamy animacji śmierci w Player.die() działać // player.alpha = 0; // Można ukryć, jeśli animacja nie działa poprawnie } // Sprawdź i zapisz nowy rekord var oldHighScore = this.rollMasterHighScore || 0; // Upewnij się, że oldHighScore ma wartość if (finalTime > oldHighScore) { this.rollMasterHighScore = finalTime; storage.rollMasterHighScore = this.rollMasterHighScore; // Zapisz w storage console.log("Nowy rekord Roll Master!", this.rollMasterHighScore); // Opcjonalnie: Wyświetl specjalny komunikat o nowym rekordzie // if (ui && ui.showMessage) { // // Można dodać dodatkowy komunikat, np. po chwili // } } /* // --- POCZĄTEK SEKCJI ZAKOMENTOWANEJ --- // Przygotuj komunikaty końcowe (obliczenia czasu zostawiamy, mogą być potrzebne gdzie indziej) // var minutes = Math.floor(finalTime / 60); // var seconds = finalTime % 60; // var formattedTime = String(minutes).padStart(2, '0') + ':' + String(seconds).padStart(2, '0'); // var scoreMessage = "Your Time: " + formattedTime; // --- ZMIANA: Usunięcie wyświetlania rekordu przez showTutorial --- // Rekord jest już widoczny na stałe dzięki zmianom w UI // var highScoreMessage = "Best: " + String(Math.floor(oldHighScore / 60)).padStart(2, '0') + ":" + String(oldHighScore % 60).padStart(2, '0'); // --- KONIEC ZMIANY --- // Wyświetl tylko wynik końcowy <-- CAŁY TEN BLOK if(ui){...} JEST KOMENTOWANY // if (ui) { // ui.showMessage(scoreMessage, 0); // Pokaż czas gracza (nie zniknie sam) // <--- TA LINIA JEST JUŻ NIEPOTRZEBNA // // ui.showTutorial(highScoreMessage); // <-- USUNIĘTE // // Ukryj timer i rekord na ekranie game over (opcjonalne) // if (ui.timerText) { // ui.timerText.alpha = 0; // } // if (ui.highScoreText) { // ui.highScoreText.alpha = 0; // } // } */ // --- KONIEC SEKCJI ZAKOMENTOWANEJ --- // Zaplanuj powrót do menu po chwili LK.setTimeout(function () { // Wyczyść komunikaty przed przejściem (jeśli jakieś inne by były) if (ui) { ui.showMessage("", 0); // Wyczyść komunikat (jeśli był) ui.titleText.setText(""); // Wyczyść też główny tytuł (na wszelki wypadek) ui.titleText.alpha = 0; // ui.showTutorial(""); // Wyczyść tutorial (jeśli był używany) } // Zniszcz gracza, jeśli jeszcze istnieje if (player && player.destroy && !player.destroyed) { player.destroy(); } player = null; // Wyczyść referencję // Wróć do ekranu Grilla // Upewnij się, że _this3 (czyli gameState) nadal istnieje if (_this3 && typeof _this3.showGrillScreen === 'function') { _this3.showGrillScreen(); } else { console.error("Nie można wywołać showGrillScreen z endRollMasterMode timeout!"); // Awaryjnie, spróbujmy globalnie, jeśli istnieje if (typeof gameState !== 'undefined' && typeof gameState.showGrillScreen === 'function') { gameState.showGrillScreen(); } } }, 5000); // 5 sekund na podziwianie wyniku (teraz bez tekstu, można skrócić?) }, // Koniec funkcji endRo // Obsługa gestów dotykowych/myszy processTouchGesture: function processTouchGesture() { // ... (kod obsługi fake tutorial) ... var dx = this.touchEnd.x - this.touchStart.x; var dy = this.touchEnd.y - this.touchStart.y; var distance = Math.sqrt(dx * dx + dy * dy); var swipeThreshold = 50; // Minimalny dystans, by uznać za swipe if (distance < swipeThreshold) { // Logika Tapnięcia (bez zmian) // ... return; } // --- Obsługa Swipe (Turlania) --- if ((this.currentState === "game" || this.currentState === "rollMaster") && player && !player.dead) { // Normalizuj kierunek (bez zmian) var direction = { x: 0, y: 0 }; if (distance > 0) { direction.x = dx / distance; direction.y = dy / distance; } // <-- NOWA LOGIKA: Obliczanie czasu trwania na podstawie dystansu swipe --> var minSwipeDist = swipeThreshold; // Minimalny dystans swipe'a (np. 50) var maxSwipeDist = 400; // <-- ZWIĘKSZONA dla większej skali var minRollDuration = 50; // <-- LEKKO ZWIĘKSZONA dla płynności minimum var maxRollDuration = 400; // Maksymalny czas uniku w ms (np. 0.45s - dostosuj!) // Ogranicz dystans do zakresu min/max var clampedDistance = Math.max(minSwipeDist, Math.min(distance, maxSwipeDist)); // Znormalizuj dystans do zakresu 0-1 var normalizedDistance = (clampedDistance - minSwipeDist) / (maxSwipeDist - minSwipeDist); // Oblicz czas trwania uniku na podstawie znormalizowanego dystansu var currentRollDuration = minRollDuration + normalizedDistance * (maxRollDuration - minRollDuration); // <-- KONIEC NOWEJ LOGIKI --> // Wywołaj turlanie, przekazując kierunek ORAZ obliczony czas trwania player.roll(direction, currentRollDuration); // Przekazujemy nowy parametr! } } }; // --- Obsługa inputu --- game.down = function (x, y, obj) { // Rejestruj początek dotyku/kliknięcia // <-- ZMIANA: Natychmiastowa obsługa kliknięcia w fakeTutorial --> if (gameState.currentState === "fakeTutorial") { // Sprawdźmy dodatkowo, czy timer jeszcze istnieje, na wszelki wypadek if (gameState.fakeTutorialTimerId) { gameState.handleFakeTutorialInput(); // Wywołaj logikę "fałszywej śmierci" return; // Zakończ, nie rób nic więcej w game.down } } // Stany, w których śledzimy gesty lub kliknięcia przycisków: var trackStates = ["title", "game", /* Usunięto "fakeTutorial" */"fakeGameOver", "realTutorial", "grillMenu", "gameOver", "rollMaster"]; // Można usunąć "fakeTutorial" z listy if (trackStates.indexOf(gameState.currentState) !== -1) { gameState.touchStart.x = x; gameState.touchStart.y = y; gameState.touchEnd.x = x; gameState.touchEnd.y = y; gameState.isInputActive = true; gameState.currentInputPos.x = x; gameState.currentInputPos.y = y; gameState.swipeStartTime = Date.now(); } // Obsługa kliknięć przycisków jest robiona przez ich własne handlery .down // Note: If you have complex button logic that consumes clicks, // you might need to check if 'obj' is null here before setting isInputActive. // For simple cases, setting it always on 'down' might be fine, // but make sure your player movement check in update respects the game state. }; game.up = function (x, y, obj) { // Rejestruj koniec dotyku/kliknięcia i przetwarzaj gest var trackStates = ["title", "game", "fakeTutorial", "fakeGameOver", "realTutorial", "grillMenu", "gameOver", "rollMaster"]; if (trackStates.indexOf(gameState.currentState) !== -1) { gameState.touchEnd.x = x; gameState.touchEnd.y = y; // Zawsze ustawiaj isInputActive na false przy puszczeniu, aby zatrzymać chodzenie gameState.isInputActive = false; // --- TUTAJ JEST KLUCZOWA ZMIANA --- // Oblicz czas trwania gestu var swipeDuration = Date.now() - gameState.swipeStartTime; // Odczytujemy startTime var holdThreshold = 1000; // Próg w ms - PAMIĘTAJ O DOSTROJENIU! // Wywołaj logikę rolla tylko jeśli gest był krótszy niż próg przytrzymania if (swipeDuration < holdThreshold) { // Gest był szybki (potencjalny swipe/tap) console.log("game.up - GEST KRÓTKI (" + swipeDuration + "ms), przetwarzam..."); // Opcjonalny log gameState.processTouchGesture(); // Wywołujemy tylko tutaj! } else { console.log("game.up - GEST DŁUGI (" + swipeDuration + "ms), IGNORUJĘ rolla"); // Opcjonalny log } // Jeśli gest był długi, nic więcej nie robimy. // --- KONIEC KLUCZOWEJ ZMIANY --- } // Obsługa puszczenia przycisków (.up) może być dodana tutaj, jeśli potrzebna }; game.move = function (x, y, obj) { var trackStates = ["title", "game", "fakeTutorial", "fakeGameOver", "realTutorial", "grillMenu", "gameOver", "rollMaster"]; if (trackStates.indexOf(gameState.currentState) !== -1) { gameState.touchEnd.x = x; gameState.touchEnd.y = y; if (gameState.isInputActive) { gameState.currentInputPos.x = x; gameState.currentInputPos.y = y; } } }; function launchRmattack1() { console.log("Launch rmattack1 (projectile)"); var projectileRadius = 45; var startX, startY; var edge = Math.floor(Math.random() * 4); var gameWidth = 2048; // Szerokość pola gry var gameHeight = 2732; // Wysokość pola gry // Pozycjonowanie startowe (bez zmian) if (edge === 0) { // top startX = Math.random() * gameWidth; startY = -projectileRadius; } else if (edge === 1) { // right startX = gameWidth + projectileRadius; startY = Math.random() * gameHeight; } else if (edge === 2) { // bottom startX = Math.random() * gameWidth; startY = gameHeight + projectileRadius; } else { // left startX = -projectileRadius; startY = Math.random() * gameHeight; } // Celowanie w środek (bez zmian) var targetX = gameWidth / 2; var targetY = gameHeight / 2; var dx = targetX - startX; var dy = targetY - startY; var angle = Math.atan2(dy, dx); // --- ZMIANA: Obliczanie prędkości (vx, vy) zamiast endX, endY --- var speed = 4 + gameState.rollMasterDifficulty * 0.65; // Prędkość pocisku (dostosuj wg potrzeb, może być zależna od trudności) var vx = Math.cos(angle) * speed; var vy = Math.sin(angle) * speed; // --- KONIEC ZMIANY --- var rotation = angle; var scaleX = 1; // Tworzenie animacji (bez zmian) var frames = [LK.getAsset('rmattack1_0', { anchorX: 0.5, anchorY: 0.5 }), LK.getAsset('rmattack1_1', { anchorX: 0.5, anchorY: 0.5 }), LK.getAsset('rmattack1_2', { anchorX: 0.5, anchorY: 0.5 }), LK.getAsset('rmattack1_3', { anchorX: 0.5, anchorY: 0.5 }), LK.getAsset('rmattack1_4', { anchorX: 0.5, anchorY: 0.5 }), LK.getAsset('rmattack1_5', { anchorX: 0.5, anchorY: 0.5 }), LK.getAsset('rmattack1_6', { anchorX: 0.5, anchorY: 0.5 })]; var sprite = new SpriteAnimation({ frames: frames, frameDuration: 140, loop: true, x: startX, y: startY, anchorX: 0.5, anchorY: 0.5 }); sprite.scaleX = scaleX; sprite.rotation = rotation; game.addChild(sprite); // --- ZMIANA: Dodanie do listy ataków zamiast tween --- gameState.rollMasterAttacks.push({ type: 'projectile', visual: sprite, vx: vx, vy: vy, radius: projectileRadius // Promień do sprawdzania kolizji }); // Usunięto tween - ruch będzie obsługiwany w game.update // --- KONIEC ZMIANY --- } // Funkcja launchRmattack2 ZE ZMIANĄ POZYCJONOWANIA function launchRmattack2() { var topSafeMargin = 700; console.log("Launch rmattack2 (explosion)"); // Obliczanie bezpiecznej strefy (bez zmian) var bombWidth = 120; var bombHeight = 120; // [cite: 87] var halfBombWidth = bombWidth / 2; var halfBombHeight = bombHeight / 2; // [cite: 88] var minX_b = halfBombWidth; var maxX_b = 2048 - halfBombWidth; // [cite: 88, 89] var minY_b = halfBombHeight; var maxY_b = 2732 - halfBombHeight; // [cite: 89] var x = minX_b + Math.random() * (maxX_b - minX_b); // [cite: 90] var spawnHeight = maxY_b - topSafeMargin; // Oblicz dostępną wysokość var y = spawnHeight > 0 ? topSafeMargin + Math.random() * spawnHeight : topSafeMargin; // Losuj Y w bezpiecznej strefie // Tworzenie bomby (bez zmian) var idleFrame = LK.getAsset('rmattack2_explode_0', { anchorX: 0.5, anchorY: 0.5 }); // [cite: 91] var bomb = new SpriteAnimation({ // [cite: 92] frames: [idleFrame], frameDuration: 500, loop: true, x: x, y: y, anchorX: 0.5, anchorY: 0.5 // [cite: 92] }); game.addChild(bomb); // [cite: 92] bomb.idleTween = tween(bomb, { y: y - 10 }, { duration: 500, yoyo: true, repeat: Infinity, easing: tween.inOutQuad }); // [cite: 93] // Timer eksplozji LK.setTimeout(function () { // [cite: 94] if (bomb.destroyed) { return; } // [cite: 94] try { if (bomb.idleTween && typeof bomb.idleTween.stop === "function") { bomb.idleTween.stop(); } } catch (e) { console.warn("idleTween stop error:", e); } // [cite: 94] // Tworzenie klatek eksplozji (bez zmian) var explosionFrames = []; // [cite: 94] var finalScale = 0; for (var i = 0; i < 11; i++) { // [cite: 94] var scale = 1 + i * 0.4; // [cite: 94] finalScale = scale; // Zapamiętaj ostatnią skalę var frame = LK.getAsset("rmattack2_explode_" + i, { anchorX: 0.5, anchorY: 0.5, scaleX: scale, scaleY: scale }); // [cite: 94] explosionFrames.push(frame); // [cite: 94] } // Tworzenie animacji eksplozji (bez zmian) var explosion = new SpriteAnimation({ // [cite: 94] frames: explosionFrames, frameDuration: 80, loop: false, x: bomb.x, y: bomb.y, anchorX: 0.5, anchorY: 0.5 // [cite: 94] }); bomb.destroy(); // [cite: 94] game.addChild(explosion); // [cite: 94] // --- ZMIANA: Dodanie eksplozji do listy śledzonych ataków --- var explosionDurationFrames = explosionFrames.length * (80 / (1000 / 60)); // Czas trwania w klatkach gry var explosionMaxRadius = bombWidth * finalScale / 2 * 0.8; // Przybliżony promień końcowy (0.8 dla bezpieczeństwa) var explosionData = { type: 'explosion', visual: explosion, // Referencja do animacji radius: 0, // Promień kolizji (zaczyna od 0) maxRadius: explosionMaxRadius, // Maksymalny promień currentFrame: 0, // Aktualna klatka (do obliczenia promienia) totalFrames: explosionFrames.length, // Całkowita liczba klatek frameDurationTicks: 80 / (1000 / 60), // Czas klatki w tickach timera timer: 0, // Wewnętrzny timer klatki damageDealt: false // Czy obrażenia zostały już zadane }; gameState.rollMasterAttacks.push(explosionData); console.log("Dodano eksplozję do śledzenia, maxRadius:", explosionMaxRadius); // --- KONIEC ZMIANY --- // Timer usuwania eksplozji (bez zmian, ale teraz też jest śledzona) LK.setTimeout(function () { // [cite: 94] // Znajdź i usuń z listy, jeśli nadal tam jest (na wypadek gdyby kolizja jej nie usunęła) var index = gameState.rollMasterAttacks.indexOf(explosionData); if (index > -1) { gameState.rollMasterAttacks.splice(index, 1); console.log("Usunięto eksplozję z listy po czasie."); } if (!explosion.destroyed) { explosion.destroy(); } // [cite: 94] }, explosionFrames.length * 80 + 100); // [cite: 94] }, 2000); // Czas przed eksplozją [cite: 94] } // Funkcja launchRmattack3 (bez zmian, już miała bezpieczne pozycjonowanie i pulsowanie) function launchRmattack3() { var topSafeMargin = 600; console.log("Launch rmattack3 (laser)"); // Bezpieczne pozycjonowanie (bez zmian) var margin = 100; var halfLaserWidth = 128 / 2; var halfLaserHeight = 1012 / 2; // [cite: 95, 96] var minX = margin + halfLaserWidth; var maxX = 2048 - margin - halfLaserWidth; // [cite: 96, 97] var calculatedMinY = margin + halfLaserHeight; // Oryginalne obliczenie minY var minY = Math.max(calculatedMinY, topSafeMargin); // Użyj większej wartości: marginesu lub safe zone var maxY = 2732 - margin - halfLaserHeight; // Dolna granica // var laserY = minY + Math.random() * (maxY - minY); // <-- ZAKOMENTUJ LUB USUŃ STARĄ LINIĘ var spawnHeight = maxY - minY; // Oblicz dostępną wysokość var laserY = spawnHeight > 0 ? minY + Math.random() * spawnHeight : minY; // Losuj Y w bezpiecznej strefie // --- KONIEC ZMIAN --- var laserX = minX + Math.random() * (maxX - minX); // Animacja ostrzeżenia (bez zmian) var warningFrames = []; // [cite: 99] for (var i = 0; i < 5; i++) { warningFrames.push(LK.getAsset('rmattack3_warning_' + i, { anchorX: 0.5, anchorY: 0.5, width: 128, height: 1012 })); } // [cite: 100] var warningAnim = new SpriteAnimation({ frames: warningFrames, frameDuration: 100, loop: true, x: laserX, y: laserY, anchorX: 0.5, anchorY: 0.5, alpha: 1.0 }); // [cite: 101] warningAnim.stopPulsing = false; var currentPulseTween = null; // [cite: 101] game.addChild(warningAnim); // [cite: 102] var pulseDuration = 250; // [cite: 102] function pulseUp() { if (!warningAnim || warningAnim.destroyed || warningAnim.stopPulsing) { currentPulseTween = null; return; } currentPulseTween = tween(warningAnim, { alpha: 1.0 }, { duration: pulseDuration, easing: tween.inOutQuad, onFinish: function onFinish() { if (warningAnim && !warningAnim.destroyed && !warningAnim.stopPulsing) { pulseDown(); } else { currentPulseTween = null; } } }); } // [cite: 102, 103, 104] function pulseDown() { if (!warningAnim || warningAnim.destroyed || warningAnim.stopPulsing) { currentPulseTween = null; return; } currentPulseTween = tween(warningAnim, { alpha: 0.1 }, { duration: pulseDuration, easing: tween.inOutQuad, onFinish: function onFinish() { if (warningAnim && !warningAnim.destroyed && !warningAnim.stopPulsing) { pulseUp(); } else { currentPulseTween = null; } } }); } // [cite: 104, 105] pulseDown(); // [cite: 106] // Timer aktywacji lasera LK.setTimeout(function () { // [cite: 106] if (warningAnim && !warningAnim.destroyed) { warningAnim.stopPulsing = true; } // [cite: 106] if (currentPulseTween && typeof currentPulseTween.stop === "function") { try { currentPulseTween.stop(); currentPulseTween = null; } catch (e) { console.warn("Nie udało się zatrzymać tweenu pulsowania:", e); } } // [cite: 106] if (warningAnim && !warningAnim.destroyed) { // Jeśli ostrzeżenie nadal istnieje // Tworzenie klatek ataku (bez zmian) var activeFrames = []; // [cite: 106] var laserStrikeWidth = 228; // Szerokość kolizji var laserStrikeHeight = 1212; // Wysokość kolizji for (var j = 0; j < 5; j++) { activeFrames.push(LK.getAsset('rmattack3_strike_' + j, { anchorX: 0.5, anchorY: 0.5, width: laserStrikeWidth, height: laserStrikeHeight })); } // [cite: 106] // Tworzenie animacji ataku (bez zmian) var strikeAnim = new SpriteAnimation({ // [cite: 106] frames: activeFrames, frameDuration: 40, loop: true, x: laserX, y: laserY, anchorX: 0.5, anchorY: 0.5, alpha: 1 // [cite: 106] }); game.addChild(strikeAnim); // [cite: 106] var laserDuration = 4000; // Czas życia lasera w ms // --- ZMIANA: Dodanie lasera do listy śledzonych ataków --- var laserData = { type: 'laser', visual: strikeAnim, // Referencja do animacji uderzenia // Przekazujemy wymiary potrzebne do kolizji AABB w game.update width: laserStrikeWidth, height: laserStrikeHeight, lifeTime: laserDuration / (1000 / 60) // Czas życia w klatkach gry }; gameState.rollMasterAttacks.push(laserData); console.log("Dodano laser do śledzenia."); // --- KONIEC ZMIANY --- warningAnim.destroy(); // Usuń ostrzeżenie [cite: 106] // Timer usuwania lasera LK.setTimeout(function () { // [cite: 106] // Znajdź i usuń z listy, jeśli nadal tam jest var index = gameState.rollMasterAttacks.indexOf(laserData); if (index > -1) { gameState.rollMasterAttacks.splice(index, 1); console.log("Usunięto laser z listy po czasie."); } if (strikeAnim && !strikeAnim.destroyed) { strikeAnim.destroy(); } // [cite: 106] }, laserDuration); // [cite: 106] } }, 3000); // Czas trwania ostrzeżenia [cite: 107] } // Funkcja launchRmattack4 POPRAWIONA (tylko tworzenie, z bezpiecznym pozycjonowaniem 200px margin) function launchRmattack4() { // Obliczanie bezpiecznej strefy dla spreadera z marginesem 200px var spreaderParentRadius = 60 * 1.2; var margin = 200; var startX = margin + Math.random() * (2048 - 2 * margin); var startY = margin + Math.random() * (2732 - 2 * margin); // Klatki animacji dla Spreader Parent var spreaderParentFrames = [LK.getAsset('rmattack4_parent_0', { anchorX: 0.5, anchorY: 0.5 }), LK.getAsset('rmattack4_parent_1', { anchorX: 0.5, anchorY: 0.5 }), LK.getAsset('rmattack4_parent_2', { anchorX: 0.5, anchorY: 0.5 }), LK.getAsset('rmattack4_parent_3', { anchorX: 0.5, anchorY: 0.5 })]; // Stwórz obiekt SpriteAnimation dla rodzica var spreaderParentAnim = new SpriteAnimation({ frames: spreaderParentFrames, frameDuration: 250, loop: true, x: startX, y: startY, anchorX: 0.5, anchorY: 0.5 }); // Dodaj animację rodzica do sceny gry game.addChild(spreaderParentAnim); // Dodaj obiekt logiczny ataku do listy gameState gameState.rollMasterAttacks.push({ type: 'spreader_parent', visual: spreaderParentAnim, radius: spreaderParentRadius, timer: gameState.spreaderSplitTime }); } // --- Główna pętla aktualizacji gry --- game.update = function () { if (ui) { // Aktualizacja UI (bez zmian) [cite: 113] ui.updateDeathsCounter(); // [cite: 113] if (boss) { // [cite: 114] var maxHp = boss.maxHealth > 0 ? boss.maxHealth : 1; // [cite: 114] ui.updateBossHealth(boss.health, maxHp); // [cite: 114] } else { // [cite: 115] if (!(gameState.currentState === "gameOver" && !gameOverReasonIsDeath && isNewBossPlusMode)) { // [cite: 115] ui.updateBossHealth(0, 1); // [cite: 115] } } if (player) { // [cite: 116] if (gameState.currentState === "rollMaster") { ui.updateHearts(player.health, 1); } // [cite: 116] else { ui.updateHearts(player.health, storage.maxHearts); } // [cite: 117] } else if (gameState.currentState !== "game" && gameState.currentState !== "gameOver") { // [cite: 118] ui.updateHearts(0, storage.maxHearts); // [cite: 118] } } // Logika dla stanu "game" (bez zmian) [cite: 119] if (gameState.currentState === "game") { if (player) { player.update(); } // [cite: 119] if (boss) { boss.update(); } // [cite: 119] } // Logika dla stanu "rollMaster" [cite: 120] else if (gameState.currentState === "rollMaster") { if (player) { player.update(); } // [cite: 120] // --- ZMIANA: Progresywne odblokowywanie ataków --- // --- KONIEC ZMIANY --- // --- ZMIANA: Spawnowanie ataków z uwzględnieniem odblokowania --- // --- Spawner Pocisków (rmattack1) --- // Ta logika używa losowego timera, ale upewnijmy się, że działa tylko gdy atak jest odblokowany if (gameState.unlockedAttacks.includes('rmattack1')) { if (!gameState.randomRmattack1Timer) { // [cite: 121] var _spawnRandomRmattack = function spawnRandomRmattack1() { // [cite: 121] // Sprawdź stan i odblokowanie przed każdym odpaleniem if (gameState.currentState !== "rollMaster" || !gameState.unlockedAttacks.includes('rmattack1')) { // [cite: 121] gameState.randomRmattack1Timer = null; // Zatrzymaj, jeśli warunki niespełnione return; // [cite: 121] } launchRmattack1(); // [cite: 122] var nextDelay = 800 + Math.random() * 1500; // Zwiększono nieco opóźnienie dla balansu [cite: 122] // Użyj self odniesienia do gameState, jeśli _spawnRandomRmattack jest wewnątrz metody gameState // Jeśli jest globalna, użyj gameState bezpośrednio. Zakładam, że jest globalna. gameState.randomRmattack1Timer = LK.setTimeout(_spawnRandomRmattack, nextDelay); // [cite: 122] }; _spawnRandomRmattack(); // [cite: 122] } } else if (gameState.randomRmattack1Timer) { // Jeśli atak został zablokowany (teoretycznie niemożliwe w tym flow), zatrzymaj timer LK.clearTimeout(gameState.randomRmattack1Timer); gameState.randomRmattack1Timer = null; } // --- Spawner Eksplozji (rmattack2) --- if (gameState.unlockedAttacks.includes('rmattack2') && gameState.explosionSpawnInterval > 0) { // [cite: 123] gameState.explosionSpawnTimer++; // [cite: 123] if (gameState.explosionSpawnTimer >= gameState.explosionSpawnInterval) { // [cite: 124] gameState.explosionSpawnTimer = 0; // [cite: 124] launchRmattack2(); // [cite: 124] } } // --- Spawner Lasera (rmattack3) --- if (gameState.unlockedAttacks.includes('rmattack3') && gameState.laserSpawnInterval > 0) { // [cite: 124] gameState.laserSpawnTimer++; // [cite: 124] if (gameState.laserSpawnTimer >= gameState.laserSpawnInterval) { // [cite: 125] gameState.laserSpawnTimer = 0; // [cite: 125] launchRmattack3(); // [cite: 125] } } // --- Spawner Spreader Parent (rmattack4) --- if (gameState.unlockedAttacks.includes('rmattack4') && gameState.spreaderSpawnInterval > 0) { // [cite: 125] gameState.spreaderSpawnTimer++; // [cite: 125] if (gameState.spreaderSpawnTimer >= gameState.spreaderSpawnInterval) { // [cite: 126] gameState.spreaderSpawnTimer = 0; // [cite: 126] console.log("Spawnuję Spreader Parent (rmattack4)!"); // [cite: 126] launchRmattack4(); // [cite: 126] } } // --- KONIEC ZMIANY SPAWNOWNIA --- // --- ZMIANA: Pętla aktualizacji i kolizji dla wszystkich aktywnych ataków --- for (var i = gameState.rollMasterAttacks.length - 1; i >= 0; i--) { var atk = gameState.rollMasterAttacks[i]; // Sprawdzenie, czy atak istnieje i ma wizualizację if (!atk || !atk.visual || atk.visual.destroyed) { // Jeśli brak wizualizacji lub zniszczona, usuń z listy gameState.rollMasterAttacks.splice(i, 1); continue; } var shouldRemove = false; // Flaga do usunięcia ataku na końcu iteracji // Aktualizacja animacji (jeśli istnieje metoda update) if (atk.visual.update && typeof atk.visual.update === 'function') { // [cite: 134] atk.visual.update(); // [cite: 134] } // Logika specyficzna dla typu ataku (ruch, kolizje) if (atk.type === 'projectile') { // [cite: 128] // Ruch pocisku atk.visual.x += atk.vx; // [cite: 128] atk.visual.y += atk.vy; // [cite: 128] // Usuwanie pocisku poza ekranem if (atk.visual.x < -200 || atk.visual.x > 2248 || atk.visual.y < -200 || atk.visual.y > 2932) { // [cite: 129] shouldRemove = true; // [cite: 130] } else if (player && !player.dead && !player.rolling) { // Sprawdzanie kolizji [cite: 130] var dx_p = player.x - atk.visual.x; // [cite: 130] var dy_p = player.y - atk.visual.y; // [cite: 131] var dist_p = Math.sqrt(dx_p * dx_p + dy_p * dy_p); // [cite: 131] var playerRadius = player.width / 2 * 0.8; // [cite: 132] (Upewnij się, że 0.8 jest celowe) var projectileRadius = atk.radius || 45; // [cite: 132] if (dist_p < playerRadius + projectileRadius) { // [cite: 133] console.log("Kolizja: Pocisk!"); // DEBUG if (!player.invulnerable) { // [cite: 133] player.takeDamage(1); // [cite: 133] } shouldRemove = true; // Usuń po trafieniu [cite: 133] } } } else if (atk.type === 'explosion') { // Aktualizuj wewnętrzny timer klatki eksplozji atk.timer++; if (atk.timer >= atk.frameDurationTicks) { atk.timer = 0; atk.currentFrame++; } // Oblicz aktualny promień na podstawie postępu animacji (liniowo) if (atk.currentFrame < atk.totalFrames) { atk.radius = atk.maxRadius * (atk.currentFrame / atk.totalFrames); } else { atk.radius = atk.maxRadius; // Ostatnia klatka ma pełny promień } // Kolizja eksplozji (tylko raz) if (!atk.damageDealt && player && !player.dead && !player.rolling) { var dx_e = player.x - atk.visual.x; var dy_e = player.y - atk.visual.y; var dist_e = Math.sqrt(dx_e * dx_e + dy_e * dy_e); var playerRadius_e = player.width / 2 * 0.8; if (dist_e < playerRadius_e + atk.radius) { console.log("Kolizja: Eksplozja!"); // DEBUG if (!player.invulnerable) { player.takeDamage(1); } atk.damageDealt = true; // Zadano obrażenia // Nie usuwamy od razu, pozwalamy animacji dokończyć } } // Sprawdzenie końca animacji (usunięcie będzie w timeout w launchRmattack2) // ale możemy usunąć z listy śledzenia wcześniej, jeśli już zadała obrażenia if (atk.damageDealt && atk.currentFrame >= atk.totalFrames) { //shouldRemove = true; // Opcjonalnie usuń szybciej po zadaniu obrażeń i skończeniu animacji } } else if (atk.type === 'laser') { // [cite: 136] // Kolizja lasera (AABB) if (player && !player.dead && !player.rolling) { // [cite: 136] // Użyj wymiarów zapisanych w atk, bo visual.width/height może się zmieniać z klatkami animacji var laserHalfWidth = (atk.width || 128) / 2; // [cite: 136] var laserHalfHeight = (atk.height || 1012) / 2; // [cite: 137] var playerHalfWidth = player.width / 2 * 0.8; // [cite: 137] var playerHalfHeight = player.height / 2 * 0.8; // [cite: 138] var laserLeft = atk.visual.x - laserHalfWidth; // [cite: 138] var laserRight = atk.visual.x + laserHalfWidth; // [cite: 138] var laserTop = atk.visual.y - laserHalfHeight; // [cite: 139] var laserBottom = atk.visual.y + laserHalfHeight; // [cite: 139] var playerLeft = player.x - playerHalfWidth; // [cite: 139] var playerRight = player.x + playerHalfWidth; // [cite: 140] var playerTop = player.y - playerHalfHeight; // [cite: 140] var playerBottom = player.y + playerHalfHeight; // [cite: 140] if (laserLeft < playerRight && laserRight > playerLeft && laserTop < playerBottom && laserBottom > playerTop) { // [cite: 141] console.log("Kolizja: Laser!"); // DEBUG if (!player.invulnerable) { // [cite: 141] player.takeDamage(1); // [cite: 141] // Laser zadaje obrażenia ciągle, dopóki gracz w nim jest } } } // Czas życia lasera (usuwanie jest w timeout w launchRmattack3) // ale możemy go oznaczyć do usunięcia z listy śledzenia atk.lifeTime--; if (atk.lifeTime <= 0) { // shouldRemove = true; // Opcjonalnie usuń szybciej z listy } } else if (atk.type === 'spreader_parent') { // [cite: 144] atk.timer--; // [cite: 144] if (atk.timer <= 0) { // Czas na podział [cite: 144] console.log("Spreader Parent dzieli się!"); var originX = atk.visual.x; // [cite: 144] var originY = atk.visual.y; // [cite: 145] var projectileSpeed = 5 + gameState.rollMasterDifficulty * 0.5; // Dostosuj prędkość dzieci [cite: 145] var projectileRadius = 45; // [cite: 145] var numProjectiles = 12; // [cite: 145] for (var j = 0; j < numProjectiles; j++) { // [cite: 146] var angle = Math.PI * 2 / numProjectiles * j; // [cite: 146] var vx = Math.cos(angle) * projectileSpeed; // [cite: 147] var vy = Math.sin(angle) * projectileSpeed; // [cite: 147] var projectileFrames = [ // [cite: 148] LK.getAsset('rmattack1_0', { anchorX: 0.5, anchorY: 0.5 }), LK.getAsset('rmattack1_1', { anchorX: 0.5, anchorY: 0.5 }), LK.getAsset('rmattack1_2', { anchorX: 0.5, anchorY: 0.5 }), LK.getAsset('rmattack1_3', { anchorX: 0.5, anchorY: 0.5 }), LK.getAsset('rmattack1_4', { anchorX: 0.5, anchorY: 0.5 }), LK.getAsset('rmattack1_5', { anchorX: 0.5, anchorY: 0.5 }), LK.getAsset('rmattack1_6', { anchorX: 0.5, anchorY: 0.5 })]; // [cite: 148] var projectileVisual = new SpriteAnimation({ frames: projectileFrames, frameDuration: 100, loop: true, x: originX, y: originY, anchorX: 0.5, anchorY: 0.5, scaleX: 1.2, scaleY: 1.2 }); // Zmniejszono skalę dzieci [cite: 149] projectileVisual.rotation = Math.atan2(vy, vx); // [cite: 150] var projectileObject = game.addChild(projectileVisual); // [cite: 150] // Dodaj dziecko do listy ataków gameState.rollMasterAttacks.push({ type: 'projectile', visual: projectileObject, vx: vx, vy: vy, radius: projectileRadius }); // [cite: 151] } shouldRemove = true; // Usuń rodzica po podziale [cite: 151] } // Kolizja rodzica z graczem (dopóki się nie podzieli) else if (player && !player.dead && !player.rolling) { // [cite: 151] var dx_sp = player.x - atk.visual.x; // [cite: 151] var dy_sp = player.y - atk.visual.y; // [cite: 152] var dist_sp = Math.sqrt(dx_sp * dx_sp + dy_sp * dy_sp); // [cite: 152] var playerRadius_sp = player.width / 2 * 0.8; // [cite: 153] var spreaderRadius = atk.radius || 60; // [cite: 153] if (dist_sp < playerRadius_sp + spreaderRadius) { // [cite: 154] console.log("Kolizja: Spreader Parent!"); // DEBUG if (!player.invulnerable) { player.takeDamage(1); } // [cite: 154] // Nie usuwamy rodzica po kolizji, tylko po podziale } } } // Usuń atak, jeśli flaga `shouldRemove` jest ustawiona if (shouldRemove) { // Upewnij się, że niszczysz wizualizację przed usunięciem z listy if (atk.visual && atk.visual.destroy) { atk.visual.destroy(); } gameState.rollMasterAttacks.splice(i, 1); continue; // Przejdź do następnego ataku w pętli } } // Koniec pętli for przez ataki // --- KONIEC ZMIANY PĘTLI AKTUALIZACJI --- // Sprawdzenie śmierci gracza (bez zmian) if (player && player.dead) { // [cite: 155] if (typeof gameState !== 'undefined' && typeof gameState.endRollMasterMode === 'function') { // [cite: 155] gameState.endRollMasterMode(gameState.rollMasterTime); // [cite: 155] } else { // [cite: 156] console.error("Nie można zakończyć trybu Roll Master - brak gameState lub metody endRollMasterMode"); // [cite: 156] if (typeof gameState !== 'undefined' && typeof gameState.showGrillScreen === 'function') { // [cite: 157] gameState.showGrillScreen(); // [cite: 157] } } } } // Koniec else if (gameState.currentState === "rollMaster") [cite: 157] }; // Koniec game.update // --- Rozpoczęcie gry --- // Zakładamy, że obiekt gameState jest zdefiniowany gdzieś wcześniej w kodzie if (typeof gameState !== 'undefined' && typeof gameState.init === 'function') { gameState.init(); } else { console.error("Obiekt gameState lub gameState.init nie jest zdefiniowany przed wywołaniem init!"); }
===================================================================
--- original.js
+++ change.js
@@ -2739,78 +2739,109 @@
showCredits: function showCredits() {
var self = this;
var previousState = this.currentState;
var wasInputActive = self.isInputActive;
- self.isInputActive = false;
- // self.creditsPlaying = true; // Opcjonalna flaga
- console.log("[Credits] Pokazywanie Creditsów");
- var blackBg = new Shape({
- width: 2048,
- height: 2732,
- color: 0x111010
- });
- blackBg.x = 2048 / 2;
- blackBg.y = 2732 / 2;
- blackBg.alpha = 0;
- game.addChild(blackBg);
- tween(blackBg, {
+ self.isInputActive = false; // Tymczasowo zablokuj input gry
+ // self.creditsPlaying = true; // Opcjonalna flaga, jeśli potrzebujesz
+ console.log("[Credits] Pokazywanie Creditsów z własnym tłem");
+ // 1. Stwórz tło 'creditsbg'
+ var creditsBackground; // Zmienna na nasze tło
+ try {
+ creditsBackground = LK.getAsset('creditsbg', {
+ anchorX: 0.5,
+ // Zakładamy, że chcesz je wyśrodkować
+ anchorY: 0.5,
+ x: 2048 / 2,
+ y: 2732 / 2,
+ alpha: 0 // Zacznij od przezroczystości
+ });
+ game.addChild(creditsBackground); // Dodaj do sceny
+ } catch (e) {
+ console.error("[Credits] Błąd podczas ładowania assetu 'creditsbg':", e);
+ // Awaryjnie: stwórz czarne tło, jeśli asset się nie załadował
+ creditsBackground = new Shape({
+ width: 2048,
+ height: 2732,
+ color: 0x111010
+ }); // Użyjmy Twojego działającego koloru
+ creditsBackground.x = 2048 / 2;
+ creditsBackground.y = 2732 / 2;
+ creditsBackground.alpha = 0;
+ game.addChild(creditsBackground);
+ }
+ var creditsText = null; // Zmienna na tekst creditsów
+ var creditsTextTween = null; // Zmienna na animację tekstu
+ // Handler do pomijania creditsów
+ var _skipCreditsHandler = function skipCreditsHandler() {
+ console.log("[Credits] Pominięto creditsy");
+ // Zatrzymaj animacje
+ tween.stop(creditsBackground);
+ if (creditsTextTween) {
+ tween.stop(creditsTextTween);
+ }
+ // Usuń obiekty
+ if (creditsText && !creditsText.destroyed) {
+ creditsText.destroy();
+ }
+ if (creditsBackground && !creditsBackground.destroyed) {
+ creditsBackground.destroy();
+ }
+ // Przywróć stan
+ self.isInputActive = wasInputActive;
+ // self.creditsPlaying = false;
+ game.off('down', _skipCreditsHandler); // Usuń listener
+ // Jeśli currentState było zmieniane na coś w stylu "creditsScreenActive", przywróć je teraz
+ // self.currentState = previousState;
+ // W tej wersji nie zmieniamy currentState, więc nie ma potrzeby przywracania
+ };
+ // Animacja pojawienia się tła
+ tween(creditsBackground, {
alpha: 1
}, {
duration: 500,
+ // Krótki fade-in
onFinish: function onFinish() {
- var creditsText = new Text2("Silas", {
+ // Stwórz tekst "Silas"
+ creditsText = new Text2("Silas", {
size: 150,
fill: 0xFFFFFF,
align: 'center'
});
creditsText.anchor.set(0.5, 0.5);
creditsText.x = 2048 / 2;
creditsText.y = 2732 + creditsText.height;
game.addChild(creditsText);
- var textAnimationDuration = 6000;
+ var textAnimationDuration = 3000;
var textEndY = -creditsText.height;
- var creditsTextTween = tween(creditsText, {
+ // Animacja tekstu w górę
+ creditsTextTween = tween(creditsText, {
y: textEndY
}, {
- // Zapisz tween do zmiennej
duration: textAnimationDuration,
easing: tween.linear,
onFinish: function onFinish() {
if (creditsText && !creditsText.destroyed) {
creditsText.destroy();
}
- tween(blackBg, {
+ // Animacja zanikania tła 'creditsbg'
+ tween(creditsBackground, {
alpha: 0
}, {
duration: 500,
onFinish: function onFinish() {
- if (blackBg && !blackBg.destroyed) {
- blackBg.destroy();
+ if (creditsBackground && !creditsBackground.destroyed) {
+ creditsBackground.destroy();
}
console.log("[Credits] Koniec Creditsów, powrót do", previousState);
self.isInputActive = wasInputActive;
// self.creditsPlaying = false;
- game.off('down', _skipCreditsHandler); // Usuń listener, jeśli był dodany przez game.on
+ game.off('down', _skipCreditsHandler); // Usuń listener, jeśli nie został wcześniej wywołany
}
});
}
});
- // Obsługa pominięcia
- var _skipCreditsHandler = function skipCreditsHandler() {
- console.log("[Credits] Pominięto creditsy");
- tween.stop(blackBg);
- tween.stop(creditsTextTween); // Zatrzymaj konkretny tween tekstu
- if (creditsText && !creditsText.destroyed) {
- creditsText.destroy();
- }
- if (blackBg && !blackBg.destroyed) {
- blackBg.destroy();
- }
- self.isInputActive = wasInputActive;
- // self.creditsPlaying = false;
- game.off('down', _skipCreditsHandler); // Usuń listener
- };
- game.once('down', _skipCreditsHandler); // Użyj game.once
+ // Dodaj listener do pomijania dopiero po pojawieniu się tła i tekstu (lub od razu, jeśli wolisz)
+ game.once('down', _skipCreditsHandler);
}
});
},
// Koniec funkcji showGrillScreen (przecinek oddziela ją od następnej metody gameState)