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
Code edit (11 edits merged)
Please save this source code
User prompt
add new assets rmattack1_0 rmattack1_1 rmattack1_2 rmattack1_3
Code edit (1 edits merged)
Please save this source code
Code edit (14 edits merged)
Please save this source code
User prompt
add new assets rmattack1 rmattack2 rmattack3 rmattack4
Code edit (1 edits merged)
Please save this source code
/**** * Plugins ****/ var tween = LK.import("@upit/tween.v1"); var storage = LK.import("@upit/storage.v1"); /**** * Classes ****/ // Klasy z stworzylo zloto.txt (bardziej zaawansowana walka z bossem) // 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', { // <-- Dodana anchorX: 0.5, anchorY: 0.5 }), LK.getAsset('fireball10', { // <-- Dodana anchorX: 0.5, anchorY: 0.5 }), LK.getAsset('fireball11', { // <-- Dodana anchorX: 0.5, anchorY: 0.5 }), LK.getAsset('fireball12', { // <-- Dodana anchorX: 0.5, anchorY: 0.5 }), LK.getAsset('fireball13', { // <-- Dodana anchorX: 0.5, anchorY: 0.5 }), LK.getAsset('fireball14', { // <-- Dodana 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 spriteAnim = game.addChild(new SpriteAnimation({ frames: frames, frameDuration: 120, // czas w ms jednej klatki loop: true, 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)), isActive: true }; self.attacks.push(attackData); LK.setTimeout(function () { var index = self.attacks.indexOf(attackData); if (index !== -1) { self.attacks.splice(index, 1); } if (spriteAnim && !spriteAnim.destroyed) { spriteAnim.destroy(); } }, 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, rollMasterTime: 0, rollMasterDifficulty: 1, rollMasterTimerInterval: null, rollMasterAttacks: [], attackSpawnTimer: 0, attackSpawnInterval: 120, explosionSpawnTimer: 0, // <<< NOWE: Timer dla eksplozji explosionSpawnInterval: 240, // <<< NOWE: Interwał dla eksplozji (np. 4s) 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() { // Resetuj stan przy każdym uruchomieniu storage.totalDeaths = 0; storage.maxHearts = 5; // Zaczynaj zawsze z 5 sercami isNewBossPlusMode = false; gameOverReasonIsDeath = false; this.rollMasterHighScore = storage.rollMasterHighScore || 0; // Odczytaj zapisany high score game.setBackgroundColor(0x111111); // Ustaw domyślny kolor tła ui = game.addChild(new UI()); // Stwórz UI // Zainicjalizuj UI dla ekranu tytułowego ui.updateDeathsCounter(); ui.updateHearts(storage.maxHearts, storage.maxHearts); // Pokaż początkowe serca ui.positionElements("title"); // Pozycjonuj elementy UI dla tytułu game.addChild(currentSceneElements); // Dodaj kontener na elementy scen // Rozpocznij muzykę w tle LK.playMusic('introMusic', { fade: { start: 0, end: 0.3, duration: 1000 } }); this.showTitleScreen(); // Rozpocznij od ekranu tytułowego }, // ---------- Metody przejścia między stanami ---------- showTitleScreen: function showTitleScreen() { isNewBossPlusMode = false; console.log("State: Title Screen (isNewBossPlusMode reset to false)"); // Zatrzymaj timery poprzedniego stanu if (this.fakeTutorialTimerId) { LK.clearTimeout(this.fakeTutorialTimerId); } this.fakeTutorialTimerId = null; if (this.gameTimerInterval) { LK.clearInterval(this.gameTimerInterval); } this.gameTimerInterval = null; clearScene(); // Wyczyść elementy poprzedniej sceny (Grill, GameOver, itp.) // Usuń stare tło, jeśli istnieje if (currentBackground) { tween.stop(currentBackground); // Zatrzymaj animacje tła currentBackground.destroy(); currentBackground = null; } this.currentState = "title"; game.setBackgroundColor(0x1a1a1a); // Ciemne tło bazowe dla tytułu // Dodaj tło ekranu tytułowego currentBackground = LK.getAsset('titleBg', { anchorX: 0, anchorY: 0, x: 0, y: 0 }); game.addChildAt(currentBackground, 0); // Dodaj na spód // START SCREEN PARTICLES var startScreenParticles = []; function spawnBackgroundParticle() { var particle = LK.getAsset('sparticlebg', { x: Math.random() * 2048, y: -50, scale: 0.3 + Math.random() * 0.3, anchorX: 0.5, anchorY: 0.5, alpha: 0.7 + Math.random() * 0.3 }); game.addChildAt(particle, 0); // Dodaj na spód startScreenParticles.push(particle); // Animacja cząsteczki tween(particle, { y: 1300, alpha: 0 }, { duration: (2 + Math.random() * 2) * 1000, easing: tween.linear, onFinish: function onFinish() { game.removeChild(particle); var index = startScreenParticles.indexOf(particle); if (index !== -1) { startScreenParticles.splice(index, 1); } } }); } var particleIntervalId = LK.setInterval(spawnBackgroundParticle, 400); // Ukryj gracza/bossa jeśli istnieją if (player && player.destroy) { player.destroy(); } player = null; if (boss && boss.destroy) { boss.destroy(); } boss = null; // Pokaż ściany (jako część tła menu) walls.forEach(function (wall) { if (wall) { wall.alpha = 1; } }); // Ustaw UI dla tytułu ui.positionElements("title"); ui.titleText.setText("Welcome Unchosen"); ui.showMessage("Tap to Start", 0); ui.showTutorial("Swipe to Roll - Death is Progress"); ui.showMessage("Tap to Start", 0); ui.showTutorial("Swipe to Roll - Death is Progress"); // <-- Koniec definicji akcji dla kliknięcia // Resetuj stan gestu this.touchStart = { x: 0, y: 0 }; this.touchEnd = { x: 0, y: 0 }; // --- Tap anywhere on title screen to start the intro --- game.on('down', function () { if (gameState.currentState === 'title') { gameState.showIntro(); } }); }, showIntro: function showIntro() { // Zmienne dla elementu "Pomiń" i jego timera var skipElement = null; // Zmieniona nazwa zmiennej (przechowuje teraz Text2) var skipTimerId = null; // Zmieniona nazwa zmiennej timera var self = this; // Zachowaj kontekst 'this' (gameState) // Istniejące czyszczenie timera fakeTutorial if (this.fakeTutorialTimerId) { LK.clearTimeout(this.fakeTutorialTimerId); } this.fakeTutorialTimerId = null; // Istniejące czyszczenie cząsteczek z ekranu tytułowego if (typeof particleIntervalId !== 'undefined') { LK.clearInterval(particleIntervalId); particleIntervalId = null; } if (typeof startScreenParticles !== 'undefined') { startScreenParticles.forEach(function (p) { if (p && p.destroy) { p.destroy(); } }); startScreenParticles = []; } // Istniejące czyszczenie sceny i tła clearScene(); if (currentBackground) { tween.stop(currentBackground); currentBackground.destroy(); currentBackground = null; } this.currentState = "intro"; game.setBackgroundColor(0x111111); // Dodaj tło intro i animację zoom (bez zmian) 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 }); // Ukryj ściany, gracza, bossa (bez zmian) 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"); // --- ZMIANA: Timer i tworzenie samego tekstu "Pomiń Intro" --- skipTimerId = LK.setTimeout(function () { if (self.currentState !== "intro") { skipTimerId = null; return; } // Stwórz bezpośrednio obiekt Text2 skipElement = new Text2("Skip intro", { size: 70, // Rozmiar czcionki - dostosuj wg potrzeb fill: 0xFFFFFF // Kolor tekstu (biały) // Opcjonalnie dodaj cień dla lepszej czytelności: // dropShadow: true, // dropShadowColor: 0x000000, // dropShadowDistance: 1, // dropShadowAlpha: 0.7 }); // Ustaw pozycję w prawym dolnym rogu skipElement.x = 2048 - 50; // Mniejszy margines od prawej skipElement.y = 2732 - 50; // Mniejszy margines od dołu // Ustaw punkt zaczepienia (anchor) na prawy dolny róg tekstu (1, 1) skipElement.anchor.set(1, 1); // Nadaj interaktywność i kursor skipElement.interactive = true; skipElement.cursor = "pointer"; // Dodaj element tekstowy do currentSceneElements currentSceneElements.addChild(skipElement); // Zdefiniuj akcję po kliknięciu (identyczna jak poprzednio) skipElement.down = function () { if (self.currentState !== "intro") { return; } console.log("Pominięto intro przez tekst!"); // 1. Wyczyść timer (jeśli jeszcze istnieje) if (skipTimerId) { LK.clearTimeout(skipTimerId); skipTimerId = null; } // 2. Zatrzymaj animację tła intro (jeśli trzeba) if (currentBackground) { tween.stop(currentBackground); } // 3. Wyczyść elementy sceny intro (teksty, element Pomiń) clearScene(); // 4. Obsłuż muzykę var introMusic = LK.music; if (introMusic && introMusic.assetId === 'introMusic') { console.log("Wygaszanie introMusic..."); LK.tween(introMusic, { volume: 0 }, { duration: 500, onFinish: function onFinish() { if (introMusic.stop) { introMusic.stop(); } console.log("introMusic zatrzymane."); console.log("Odtwarzanie RollSouls..."); LK.playMusic('RollSouls', { loop: true, volume: 0.7, fade: { start: 0, end: 0.7, duration: 1000 } }); } }); } else { console.log("Muzyka intro nie grała, odtwarzanie RollSouls..."); LK.playMusic('RollSouls', { loop: true, volume: 0.7, fade: { start: 0, end: 0.7, duration: 1000 } }); } // 5. Przejdź do realTutorial console.log("Przechodzenie do showRealTutorial..."); self.showRealTutorial(); }; // Timer wykonał zadanie skipTimerId = null; }, 3000); // Pokaż tekst po 3 sekundach // --- KONIEC ZMIANY --- // Definicja funkcji showIntroText (bez zmian, ale upewnij się, że dodaje do currentSceneElements) 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; // Upewnij się, że tekst jest dodawany do currentSceneElements currentSceneElements.addChild(introText); var fadeInTimerId = LK.setTimeout(function () { if (self.currentState !== "intro") { return; } tween(introText, { alpha: 1 }, { duration: 2000, easing: tween.easeInOut }); var fadeOutTimerId = LK.setTimeout(function () { if (self.currentState !== "intro") { return; } tween(introText, { alpha: 0 }, { duration: 2000, easing: tween.easeInOut }); var cleanupTimerId = LK.setTimeout(function () { if (onComplete) { onComplete(); } }, 3000); }, 3000); }, delay); } // Start sekwencji intro (bez zmian) 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; } // --- ZMIANA: Czyszczenie timera i elementu tekstowego --- if (skipTimerId) { LK.clearTimeout(skipTimerId); skipTimerId = null; console.log("Intro zakończone normalnie, wyczyszczono timer skip."); } // Usuń element tekstowy, jeśli istnieje i jest na scenie if (skipElement && skipElement.parent) { currentSceneElements.removeChild(skipElement); if (skipElement.destroy) { skipElement.destroy(); } skipElement = null; } // --- KONIEC ZMIANY --- 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(); // Wyczyść tekst intro if (this.fakeTutorialTimerId) { LK.clearTimeout(this.fakeTutorialTimerId); } this.fakeTutorialTimerId = null; // Wyczyść stary timer // --- DODAJ TO TUTAJ --- var introMusic = LK.music; if (introMusic) { LK.tween(introMusic, { volume: 0 }, { duration: 3000, onFinish: function onFinish() { introMusic.stop(); } }); } LK.playMusic('RollSouls', { loop: true, fade: { start: 0, end: 0.7, duration: 3000 } }); // --- KONIEC DODANEGO --- this.currentState = "fakeTutorial"; // Zachowaj tło intro? Jeśli tak, usuń game.setBackgroundColor i nie niszcz currentBackground // game.setBackgroundColor(0x000000); // Czarne tło - usunięte, aby zachować tło intro ui.positionElements("fakeTutorial"); // Ustaw UI // Tekst fałszywego tutorialu 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, // czarny cień dropShadowDistance: 3, // odległość cienia dropShadowBlur: 4, // lekkie rozmycie cienia dropShadowAngle: Math.PI / 4 // kąt cienia }); instructionText.anchor.set(0.5, 0.5); instructionText.x = 2048 / 2; instructionText.y = 2732 / 2; currentSceneElements.addChild(instructionText); // Timer przechodzący do prawdziwego tutorialu this.fakeTutorialTimerId = LK.setTimeout(function () { try { if (instructionText && instructionText.destroy) { instructionText.destroy(); } } catch (e) {} gameState.fakeTutorialTimerId = null; // Zresetuj ID // *** 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 fakeTutorial timeout!"); gameState.showTitleScreen(); // Awaryjny powrót do tytułu } } /*.bind(this)*/, 6000); // 6 sekund }, // *** 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; // Wyczyść timery z poprzednich stanów if (this.fakeTutorialTimerId) { LK.clearTimeout(this.fakeTutorialTimerId); } this.fakeTutorialTimerId = null; if (this.gameTimerInterval) { LK.clearInterval(this.gameTimerInterval); } this.gameTimerInterval = null; // Wyczyść elementy poprzedniej sceny (te w currentSceneElements) clearScene(); // Usuń tło tutorialu/intro jeśli istnieje if (currentBackground) { tween.stop(currentBackground); // Zatrzymaj ewentualne tweens na tle currentBackground.destroy(); // Zniszcz obiekt tła currentBackground = null; // Wyczyść referencję } this.currentState = "game"; // Ustaw stan na "game" // --- DODANY KOD: Usuń iskry i gwiazdki z Grill Menu przechowywane w tablicy --- if (gameState.grillMenuEffects && gameState.grillMenuEffects.length > 0) { gameState.grillMenuEffects.forEach(function (effect) { // Upewnij się, że obiekt istnieje i ma rodzica (powinien nim być obiekt game) if (effect && effect.parent) { tween.stop(effect); // Zatrzymaj ewentualne tweens (chociaż powinny już być zatrzymane przez sprawdzenie stanu w animacji) effect.parent.removeChild(effect); // *** Najpierw usuń obiekt z jego rodzica (usuwa ze sceny) *** // Opcjonalnie spróbuj zniszczyć obiekt, jeśli metoda destroy istnieje i nie był już zniszczony if (effect.destroy && !effect.destroyed) { effect.destroy(); // Zniszcz obiekt (zwalnia zasoby) } } else if (effect && effect.destroy && !effect.destroyed) { // Jeśli z jakiegoś powodu obiekt nie ma rodzica (dziwne), ale ma metodę destroy, spróbuj ją wywołać console.warn("Sparkle/Star object found without parent but has destroy method:", effect); // Log pomocniczy effect.destroy(); } else { // Jeśli obiekt istnieje, ale nie ma ani parenta, ani destroy, zaloguj, bo to nietypowa sytuacja console.warn("Sparkle/Star object found without parent or destroy method:", effect); // Log pomocniczy } }); gameState.grillMenuEffects = []; // Wyczyść tablicę po próbie zniszczenia/usunięcia obiektów } // --- KONIEC DODANEGO KODU --- // Najpierw ustaw tło kolorem (żeby nie zamalować areny później) game.setBackgroundColor(0x111111); // Dodaj nowe tło areny podczas walki var arenaBg = LK.getAsset('arena', { anchorX: 0.5, anchorY: 0.5, x: 2048 / 2, y: 2732 / 2 }); game.addChildAt(arenaBg, 0); // Dodaj arenę na spód (zIndex 0) // Stwórz gracza (jeśli nie istnieje) if (player && player.destroy) { player.destroy(); // Zniszcz starego gracza } player = game.addChild(new Player()); // Stwórz nowego gracza player.health = storage.maxHearts || 5; // Ustaw zdrowie gracza // Resetuj stan gracza 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; // Pozycja startowa gracza player.alpha = 1; // Upewnij się, że jest widoczny // Stwórz bossa (jeśli nie istnieje) if (boss && boss.destroy) { boss.destroy(); // Zniszcz starego bossa } boss = game.addChild(new Boss()); // Stwórz nowego bossa boss.x = 2048 / 2; boss.y = 2732 / 2 - 400; // Pozycja startowa bossa boss.alpha = 1; // Upewnij się, że jest widoczny // Resetuj stan bossa boss.attackCooldown = 90; // Daj graczowi chwilę przed pierwszym atakiem (ok 1.5s) boss.attacks = []; boss.attackSpeedMultiplier = 1; boss.repositioning = false; boss.dead = false; // Upewnij się, że nie jest martwy boss.phase = 1; // Zacznij od fazy 1 boss.tint = 0xFFFFFF; // Resetuj kolor (na wypadek przejścia fazy w poprzedniej grze) boss.scale.set(1, 1); // Resetuj skalę (na wypadek animacji śmierci) // Ustaw parametry bossa i gry zależnie od trybu if (isNewBossPlusMode) { boss.maxHealth = 900; boss.health = boss.maxHealth; this.gameDuration = 600; // 10 minut boss.attackSpeedMultiplier = 0.6; // Szybsze ataki od początku boss.speed = 7; // Szybszy ruch console.log("Starting NEW BOSS+ mode. HP:", boss.maxHealth, "Time:", this.gameDuration); } else { boss.maxHealth = 170; // Standardowy boss HP boss.health = boss.maxHealth; this.gameDuration = 120; // 2 minuty boss.speed = 5; // Standardowa prędkość boss.attackSpeedMultiplier = 1; // Standardowa prędkość ataków console.log("Starting STANDARD mode. HP:", boss.maxHealth, "Time:", this.gameDuration); } // Pokaż ściany areny (jeśli są częścią areny) walls.forEach(function (wall) { if (wall) { wall.alpha = 1; } }); // Ustaw UI dla stanu gry ui.positionElements("game"); ui.updateHearts(player.health, storage.maxHearts); ui.showTutorial("Swipe to Roll!"); // Wyświetl krótki tutorial ui.updateBossHealth(boss.health, boss.maxHealth); // Pokaż pasek HP bossa // --- Timer walki z bossem --- this.remainingTime = this.gameDuration; // Ustaw początkowy czas ui.updateTimerDisplay(this.remainingTime); // Wyświetl czas // Rozpocznij interwał timera if (this.gameTimerInterval) { LK.clearInterval(this.gameTimerInterval); } // Wyczyść stary timer this.bossSpeedIncreased = false; // Resetuj flagę przyspieszenia bossa // Użyj funkcji strzałkowej, aby this wewnątrz setInterval odnosiło się do gameState this.gameTimerInterval = LK.setInterval(function () { // Użyto funkcji strzałkowej dla zachowania kontekstu 'this' // Aktualizuj tylko w stanie gry if (_this.currentState === "game") { // Użyj this.currentState _this.remainingTime--; // Użyj this.remainingTime ui.updateTimerDisplay(_this.remainingTime); // Użyj this.remainingTime // --- Przyspieszenie bossa - TYLKO W STANDARDOWYM TRYBIE --- if (!isNewBossPlusMode) { var accelerationThreshold = _this.gameDuration - 60; // Użyj this.gameDuration if (_this.remainingTime <= accelerationThreshold && !_this.bossSpeedIncreased) { // Użyj this.remainingTime, this.bossSpeedIncreased _this.bossSpeedIncreased = true; // Użyj this.bossSpeedIncreased if (boss && !boss.dead) { // Sprawdź czy boss nadal żyje boss.attackSpeedMultiplier *= 0.7; // Przyspiesz ataki boss.speed += 1; // Lekko przyspiesz ruch ui.showMessage("Boss attacks faster!", 2000); // Komunikat } } } // --- Koniec Przyspieszenia bossa --- // Sprawdź koniec gry (czas minął) if (_this.remainingTime <= 0) { // Użyj this.remainingTime LK.clearInterval(_this.gameTimerInterval); // Użyj this.gameTimerInterval _this.gameTimerInterval = null; // Użyj this.gameTimerInterval if (isNewBossPlusMode) { // Czas minął w Boss+ -> Zwycięstwo przez przetrwanie gameOverReasonIsDeath = false; // Przyczyna: czas gameState.gameOver(false); // Przejdź do ekranu końca gry (specjalny komunikat) } else { // Czas minął w standardowym trybie -> Przejdź do Grill Menu // Zakładamy, że przetrwanie 2 minut w standardowym trybie to też "zwycięstwo" gameState.showGrillScreen(); } } } else { // Jeśli stan gry się zmienił, zatrzymaj timer if (_this.gameTimerInterval) { // Użyj this.gameTimerInterval LK.clearInterval(_this.gameTimerInterval); } _this.gameTimerInterval = null; // Użyj this.gameTimerInterval } }, 1000); // Interwał co 1 sekundę // Ukryj tutorial po chwili // Użyj funkcji strzałkowej, aby this wewnątrz setTimeout odnosiło się do gameState LK.setTimeout(function () { // Użyto funkcji strzałkowej dla zachowania kontekstu 'this' if (_this.currentState === "game") { // Użyj this.currentState ui.hideTutorial(); } // Rozpoczęcie ataków bossa jest teraz obsługiwane przez Boss.update i jego cooldown }, 3000); // 3 sekundy opóźnienia }, // victory() - nieużywane, logika w timerze i boss.die() showGrillScreen: function showGrillScreen() { 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 }; }, // 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; // Zachowaj kontekst 'this' (gameState) console.log("Konfiguracja sceny Roll Master..."); // Ustaw tło 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); // Ciemnoniebieskie tło awaryjne } // Stwórz gracza if (typeof Player !== 'undefined') { player = game.addChild(new Player()); player.x = 2048 / 2; player.y = 2732 / 2 + 400; // Pozycja startowa player.health = 1; // Tylko 1 HP w tym trybie // Upewnij się, że gracz jest gotowy (reset stanu) player.rolling = false; player.invulnerable = false; player.invulnerabilityFrames = 0; player.rollCooldown = 0; if (player && typeof player.clearRollTimeouts === 'function') { player.clearRollTimeouts(); } player.hasRolledThroughBossThisRoll = false; // Chociaż nie ma tu bossa player.alpha = 1; player.dead = false; } else { console.error("Klasa Player nie jest zdefiniowana!"); // Rozważ powrót do menu lub inny handling błędu this.showGrillScreen(); return; } // Inicjalizacja zmiennych trybu Roll Master this.rollMasterTime = 0; this.rollMasterDifficulty = 1; this.rollMasterAttacks = []; // Tablica na aktywne ataki this.attackUnlockOrder = ['rmattack1', 'rmattack2', 'rmattack3', 'rmattack4']; // Kolejność odblokowywania this.unlockedAttacks = ['rmattack1']; // Zaczynamy tylko z pierwszym atakiem this.nextUnlockTime = 15; // Czas (w sekundach) odblokowania następnego ataku console.log("Ataki startowe:", this.unlockedAttacks, "Następne odblokowanie w:", this.nextUnlockTime + "s"); // Resetuj timery spawnrate'ów this.attackSpawnTimer = 0; this.attackSpawnInterval = 120; this.explosionSpawnTimer = 0; this.explosionSpawnInterval = 240; this.laserSpawnTimer = 0; this.laserSpawnInterval = 300; this.laserWarningTime = 1500; // Czas ostrzeżenia lasera w ms this.spreaderSpawnTimer = 0; this.spreaderSpawnInterval = 600; this.spreaderSplitTime = 180; // Czas do podziału spreadera w klatkach // Wyczyść ewentualny stary timer dla rmattack1 if (this.randomRmattack1Timer) { LK.clearTimeout(this.randomRmattack1Timer); this.randomRmattack1Timer = null; } // Wczytanie rekordu i aktualizacja wyświetlania rekordu 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"); } // --- POCZĄTEK POPRAWIONEJ KONFIGURACJI UI --- // Ustaw tylko początkowy tekst timera (pozycja i alpha będą z positionElements) if (ui && ui.timerText) { // Usunięto ustawienia stylu, x, y, anchor - będą brane z positionElements // Jeśli chcesz specjalny styl, możesz go tu ustawić: // ui.timerText.style = { size: 60, fill: 0xFFFFFF }; // np. standardowy rozmiar ui.updateTimerDisplay(this.rollMasterTime); // Ustaw czas na "Time: 00:00" } else { console.error("Nie można znaleźć ui.timerText do skonfigurowania!"); } // Konfiguracja reszty UI (serca, pasek HP) if (ui && ui.updateHearts) { ui.updateHearts(1, 1); // Pokaż 1 serce (ustawi też alpha = 0 w positionElements) } if (ui && ui.updateBossHealth) { ui.updateBossHealth(0, 1); // Ukryj pasek HP bossa (ustawi też alpha = 0 w positionElements) } // Wywołaj funkcję pozycjonującą WSZYSTKIE elementy UI dla tego trybu if (ui && ui.positionElements) { ui.positionElements("rollMaster"); // To ustawi poprawne pozycje i widoczność timera i rekordu } else { console.error("Nie można ustawić elementów UI dla Roll Master - brak ui.positionElements"); } // --- KONIEC POPRAWIONEJ KONFIGURACJI UI --- // Główny timer trybu (zwiększanie czasu i trudności) if (this.rollMasterTimerInterval) { LK.clearInterval(this.rollMasterTimerInterval); // Wyczyść stary, jeśli istnieje } this.rollMasterTimerInterval = LK.setInterval(function () { if (_this2.currentState !== "rollMaster") { // Używaj _this2 dla odniesienia do gameState LK.clearInterval(_this2.rollMasterTimerInterval); _this2.rollMasterTimerInterval = null; return; } _this2.rollMasterTime++; // Zwiększ czas if (ui && ui.updateTimerDisplay) { ui.updateTimerDisplay(_this2.rollMasterTime); // Aktualizuj wyświetlanie czasu } // Zwiększanie trudności co 30 sekund if (_this2.rollMasterTime > 0 && _this2.rollMasterTime % 30 === 0) { _this2.increaseRollMasterDifficulty(); } // Logika odblokowywania ataków jest w game.update }, 1000); // Interwał co 1 sekundę console.log("Scena Roll Master skonfigurowana."); }, // <-- 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); if (ui && ui.showMessage) { ui.showMessage("Difficulty Increased! (" + this.rollMasterDifficulty + ")", 1500); } }, // 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 timer gry if (this.gameTimerInterval) { LK.clearInterval(this.gameTimerInterval); } this.gameTimerInterval = null; this.bossSpeedIncreased = false; // Reset flagi przyspieszenia // Zatrzymaj animację tła (jeśli istnieje, ale nie powinno w tym stanie) if (currentBackground) { tween.stop(currentBackground); } game.setBackgroundColor(0x000000); // Czarne tło // Ukryj/zniszcz gracza i bossa (powinny być już zniszczone przez swoje metody die) if (player && player.alpha !== 0) { player.alpha = 0; } // Ukryj, jeśli nie zniknął 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 var gameOverMessage = "YOU DIED"; // Domyślny komunikat if (!isDeath) { // Jeśli czas minął (tylko w Boss+) gameOverMessage = "You just lost 10 minutes of your life. For what?"; // Zmieniono komunikat zwycięstwa Boss+ } // Wyświetl komunikat i tytuł ui.titleText.setText(gameOverMessage); ui.titleText.alpha = 1; // Pokaż tytuł ui.showMessage("", 0); // Wyczyść ewentualny poprzedni komunikat ui.showTutorial(""); // Ukryj tutorial // Pasek zdrowia bossa jest zarządzany przez updateBossHealth w pętli gry // W tym stanie updateBossHealth pokaże pasek tylko jeśli !isDeath && isNewBossPlusMode // Zaplanuj restart gry po opóźnieniu LK.setTimeout(function () { // Wyczyść scenę przed restartem clearScene(); // Zniszcz gracza/bossa jeśli jakimś cudem przetrwali if (player && player.destroy) { player.destroy(); } player = null; if (boss && boss.destroy) { boss.destroy(); } boss = null; // Wyczyść ataki bossa (na wszelki wypadek) - teraz robione w Boss.die / Boss.update // if (boss && boss.attacks) boss.attacks = []; // Usuń tło game over (czarne) if (currentBackground) { currentBackground.destroy(); } currentBackground = null; // Resetuj UI do stanu "przed grą" (np. jak w tytule, ale bez tekstów) // Pozycjonowanie jest ok, ale trzeba wyczyścić teksty ui.titleText.setText(""); ui.titleText.alpha = 0; ui.messageText.setText(""); ui.messageText.alpha = 0; ui.tutorialText.setText(""); ui.tutorialText.alpha = 0; ui.updateDeathsCounter(); // Zaktualizuj licznik śmierci ui.updateBossHealth(0, 1); // Ukryj pasek HP ui.updateHearts(0, storage.maxHearts); // Ukryj serca // Flaga isNewBossPlusMode pozostaje niezmieniona (jeśli zginąłeś w Boss+, restartujesz w Boss+) // gameOverReasonIsDeath zostanie zresetowana przy następnym wywołaniu gameOver // Restartuj walkę (startGame użyje flagi isNewBossPlusMode) gameState.startGame(); }, 4000); // Zwiększono opóźnienie do 4 sekund przed restartem }, // --- 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 } } // Przygotuj komunikaty końcowe 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 if (ui) { ui.showMessage(scoreMessage, 0); // Pokaż czas gracza (nie zniknie sam) // 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; } } // Zaplanuj powrót do menu po chwili LK.setTimeout(function () { // Wyczyść komunikaty przed przejściem if (ui) { ui.showMessage("", 0); // Wyczyść komunikat wyniku // 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 _this3.showGrillScreen(); // Użyj _this3 dla odniesienia do gameState }, 5000); // 5 sekund na podziwianie wyniku }, // 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.5; // 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 --- if (gameState.unlockedAttacks.length < gameState.attackUnlockOrder.length && gameState.rollMasterTime >= gameState.nextUnlockTime) { var nextAttackIndex = gameState.unlockedAttacks.length; var attackToUnlock = gameState.attackUnlockOrder[nextAttackIndex]; gameState.unlockedAttacks.push(attackToUnlock); gameState.nextUnlockTime += 15; // Następne odblokowanie za 15 sekund console.log("Odblokowano atak:", attackToUnlock, "Następne odblokowanie w:", gameState.nextUnlockTime, "s."); // Wyświetl komunikat graczowi if (ui && ui.showMessage) { // Zamień nazwę techniczną na bardziej przyjazną var friendlyName = attackToUnlock.replace('rmattack1', 'Fireball').replace('rmattack2', 'Bloodburst Orb').replace('rmattack3', 'Judgment of Light').replace('rmattack4', 'Infernal Skull'); ui.showMessage("New Attack Unlocked: " + friendlyName, 2000); } } // --- 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
@@ -238,8 +238,32 @@
anchorY: 0.5
}), LK.getAsset('fireball09', {
anchorX: 0.5,
anchorY: 0.5
+ }), LK.getAsset('fireball1', {
+ // <-- Dodana
+ anchorX: 0.5,
+ anchorY: 0.5
+ }), LK.getAsset('fireball10', {
+ // <-- Dodana
+ anchorX: 0.5,
+ anchorY: 0.5
+ }), LK.getAsset('fireball11', {
+ // <-- Dodana
+ anchorX: 0.5,
+ anchorY: 0.5
+ }), LK.getAsset('fireball12', {
+ // <-- Dodana
+ anchorX: 0.5,
+ anchorY: 0.5
+ }), LK.getAsset('fireball13', {
+ // <-- Dodana
+ anchorX: 0.5,
+ anchorY: 0.5
+ }), LK.getAsset('fireball14', {
+ // <-- Dodana
+ anchorX: 0.5,
+ anchorY: 0.5
})];
} else if (type === 'line') {
frames = [LK.getAsset('fireball2', {
anchorX: 0.5,
@@ -939,9 +963,10 @@
var playerCollisionRadius = self.width / 2;
if (currentVisualSprite) {
playerCollisionRadius = Math.max(currentVisualSprite.width, currentVisualSprite.height) / 2 * currentVisualSprite.scaleX;
}
- var bossCollisionRadius = boss.width * boss.scaleX / 2;
+ 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);
@@ -1142,9 +1167,9 @@
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(1, 0); // Pozycja Y od górnej krawędzi
+ 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", {
@@ -1379,11 +1404,12 @@
// --- DODANA OBSŁUGA WIDOCZNOŚCI W ROLL MASTER ---
case "rollMaster":
// Dostosuj pozycję timera specjalnie dla tego trybu
if (self.timerText) {
- self.timerText.x = 50;
- self.timerText.y = 50;
- self.timerText.anchor.set(0, 0); // Lewy górny róg
+ // --- 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
@@ -1490,8 +1516,9 @@
x: 0,
y: 0
},
grillMenuEffects: [],
+ swipeStartTime: 0,
init: function init() {
// Resetuj stan przy każdym uruchomieniu
storage.totalDeaths = 0;
storage.maxHearts = 5; // Zaczynaj zawsze z 5 sercami
@@ -2153,16 +2180,16 @@
boss.tint = 0xFFFFFF; // Resetuj kolor (na wypadek przejścia fazy w poprzedniej grze)
boss.scale.set(1, 1); // Resetuj skalę (na wypadek animacji śmierci)
// Ustaw parametry bossa i gry zależnie od trybu
if (isNewBossPlusMode) {
- boss.maxHealth = 1000;
+ boss.maxHealth = 900;
boss.health = boss.maxHealth;
this.gameDuration = 600; // 10 minut
boss.attackSpeedMultiplier = 0.6; // Szybsze ataki od początku
boss.speed = 7; // Szybszy ruch
console.log("Starting NEW BOSS+ mode. HP:", boss.maxHealth, "Time:", this.gameDuration);
} else {
- boss.maxHealth = 50; // Standardowy boss HP
+ boss.maxHealth = 170; // Standardowy boss HP
boss.health = boss.maxHealth;
this.gameDuration = 120; // 2 minuty
boss.speed = 5; // Standardowa prędkość
boss.attackSpeedMultiplier = 1; // Standardowa prędkość ataków
@@ -2342,41 +2369,44 @@
}));
gameState.grillMenuEffects.push(sparkle5); // <--- DODANA LINIJA
// Funkcja animacji sparkle
function animateSparkle(s) {
- // --- DODANE SPRAWDZENIE STANU ---
+ // Sprawdzenie stanu (pozostaje bez zmian)
if (gameState.currentState !== "grillMenu" || !s || s.destroyed) {
- // Jeśli nie jesteśmy w menu grilla lub obiekt już nie istnieje, zakończ
return;
}
- // --- KONIEC DODANEGO SPRAWDZENIA ---
- s.alpha = 0;
- var startY = s.y;
+ // 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: startY - 40
+ y: initialYThisCycle - 40 // Przesuń w górę WZGLĘDEM ZAPISANEJ pozycji
}, {
duration: 800,
easing: tween.easeOut,
onFinish: function onFinish() {
- // --- DODANE SPRAWDZENIE STANU W CALLBACKU ---
+ // Po zakończeniu ruchu w górę
if (gameState.currentState !== "grillMenu" || !s || s.destroyed) {
return;
}
- // --- KONIEC DODANEGO SPRAWDZENIA ---
+ // Animacja zanikania (na tej wyższej pozycji)
tween(s, {
alpha: 0
}, {
duration: 300,
easing: tween.easeIn,
onFinish: function onFinish() {
- // --- DODANE SPRAWDZENIE STANU W WEWNĘTRZNYM CALLBACKU ---
+ // Po zakończeniu zanikania
if (gameState.currentState !== "grillMenu" || !s || s.destroyed) {
return;
}
- // --- KONIEC DODANEGO SPRAWDZENIA ---
+ // *** 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);
+ animateSparkle(s); // Wywołaj kolejny cykl (teraz zacznie z poprawnej pozycji Y)
}, Math.random() * 500 + 300);
}
});
}
@@ -2586,28 +2616,27 @@
// ponieważ obsługa usuwania jest teraz w startGame z wykorzystaniem gameState.grillMenuEffects
gameState.startGame();
};
// Przycisk "Roll Master"
- var rollMasterButton = new Container();
- rollMasterButton.interactive = true;
- rollMasterButton.cursor = "pointer";
- rollMasterButton.x = 600;
- rollMasterButton.y = 1250;
- currentSceneElements.addChild(rollMasterButton);
- var rollMasterButtonBg = LK.getAsset('button_bg', {
+ 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(rollMasterButtonBg);
- var rollMasterButtonText = new Text2("Roll Master", {
- size: 50,
- fill: 0xFFFFFF
- });
- rollMasterButtonText.anchor.set(0.5, 0.5);
- rollMasterButton.addChild(rollMasterButtonText);
- // --- ZMIANA TUTAJ: Akcja przycisku Roll Master ---
+ 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(); // To jest poprawna akcja dla tego przycisku
+ gameState.startRollMasterMode(); // Uruchom tryb Roll Master
};
},
// Koniec funkcji showGrillScreen (przecinek oddziela ją od następnej metody gameState)
// --- NOWA FUNKCJA: Start przejścia do trybu Roll Master ---
@@ -2694,51 +2723,54 @@
this.unlockedAttacks = ['rmattack1']; // Zaczynamy tylko z pierwszym atakiem
this.nextUnlockTime = 15; // Czas (w sekundach) odblokowania następnego ataku
console.log("Ataki startowe:", this.unlockedAttacks, "Następne odblokowanie w:", this.nextUnlockTime + "s");
// Resetuj timery spawnrate'ów
- this.attackSpawnTimer = 0; // Timer dla rmattack1 (jeśli używany inaczej niż losowo)
- this.attackSpawnInterval = 120; // Interwał dla rmattack1 (2 sekundy)
- this.explosionSpawnTimer = 0; // Timer dla rmattack2
- this.explosionSpawnInterval = 240; // Interwał dla rmattack2 (4 sekundy)
- this.laserSpawnTimer = 0; // Timer dla rmattack3
- this.laserSpawnInterval = 300; // Interwał dla rmattack3 (5 sekund)
- this.laserWarningTime = 1500; // Czas ostrzeżenia lasera w ms (było 90 klatek)
- this.spreaderSpawnTimer = 0; // Timer dla rmattack4
- this.spreaderSpawnInterval = 600; // Interwał dla rmattack4 (10 sekund)
- this.spreaderSplitTime = 180; // Czas do podziału spreadera w klatkach (3 sekundy)
+ this.attackSpawnTimer = 0;
+ this.attackSpawnInterval = 120;
+ this.explosionSpawnTimer = 0;
+ this.explosionSpawnInterval = 240;
+ this.laserSpawnTimer = 0;
+ this.laserSpawnInterval = 300;
+ this.laserWarningTime = 1500; // Czas ostrzeżenia lasera w ms
+ this.spreaderSpawnTimer = 0;
+ this.spreaderSpawnInterval = 600;
+ this.spreaderSplitTime = 180; // Czas do podziału spreadera w klatkach
// Wyczyść ewentualny stary timer dla rmattack1
if (this.randomRmattack1Timer) {
LK.clearTimeout(this.randomRmattack1Timer);
this.randomRmattack1Timer = null;
}
- // --- ZMIANA TUTAJ: Wczytanie rekordu i aktualizacja UI ---
+ // Wczytanie rekordu i aktualizacja wyświetlania rekordu
this.rollMasterHighScore = storage.rollMasterHighScore || 0;
if (ui && ui.updateHighScoreDisplay) {
- // Sprawdź, czy UI i funkcja istnieją
- ui.updateHighScoreDisplay(this.rollMasterHighScore); // Wywołaj funkcję aktualizacji rekordu
+ ui.updateHighScoreDisplay(this.rollMasterHighScore);
} else {
console.error("Nie można zaktualizować wyświetlania rekordu - brak ui.updateHighScoreDisplay");
}
- // --- KONIEC ZMIANY ---
- // Konfiguracja UI dla Roll Master
+ // --- POCZĄTEK POPRAWIONEJ KONFIGURACJI UI ---
+ // Ustaw tylko początkowy tekst timera (pozycja i alpha będą z positionElements)
if (ui && ui.timerText) {
- // Ustawienia timera zostały przeniesione do ui.positionElements
- ui.updateTimerDisplay(this.rollMasterTime); // Ustaw początkowy czas na 00:00
+ // Usunięto ustawienia stylu, x, y, anchor - będą brane z positionElements
+ // Jeśli chcesz specjalny styl, możesz go tu ustawić:
+ // ui.timerText.style = { size: 60, fill: 0xFFFFFF }; // np. standardowy rozmiar
+ ui.updateTimerDisplay(this.rollMasterTime); // Ustaw czas na "Time: 00:00"
} else {
console.error("Nie można znaleźć ui.timerText do skonfigurowania!");
}
+ // Konfiguracja reszty UI (serca, pasek HP)
if (ui && ui.updateHearts) {
- ui.updateHearts(1, 1); // Pokaż 1 serce (szare, bo 1/1)
+ ui.updateHearts(1, 1); // Pokaż 1 serce (ustawi też alpha = 0 w positionElements)
}
if (ui && ui.updateBossHealth) {
- ui.updateBossHealth(0, 1); // Ukryj pasek HP bossa
+ ui.updateBossHealth(0, 1); // Ukryj pasek HP bossa (ustawi też alpha = 0 w positionElements)
}
- // WAŻNE: Wywołaj pozycjonowanie elementów UI PO konfiguracji
+ // Wywołaj funkcję pozycjonującą WSZYSTKIE elementy UI dla tego trybu
if (ui && ui.positionElements) {
- ui.positionElements("rollMaster"); // To ustawi widoczność i pozycje
+ ui.positionElements("rollMaster"); // To ustawi poprawne pozycje i widoczność timera i rekordu
} else {
console.error("Nie można ustawić elementów UI dla Roll Master - brak ui.positionElements");
}
+ // --- KONIEC POPRAWIONEJ KONFIGURACJI UI ---
// Główny timer trybu (zwiększanie czasu i trudności)
if (this.rollMasterTimerInterval) {
LK.clearInterval(this.rollMasterTimerInterval); // Wyczyść stary, jeśli istnieje
}
@@ -2752,13 +2784,13 @@
_this2.rollMasterTime++; // Zwiększ czas
if (ui && ui.updateTimerDisplay) {
ui.updateTimerDisplay(_this2.rollMasterTime); // Aktualizuj wyświetlanie czasu
}
- // Zwiększanie trudności co 30 sekund (zmieniono z 60 dla szybszej progresji)
+ // Zwiększanie trudności co 30 sekund
if (_this2.rollMasterTime > 0 && _this2.rollMasterTime % 30 === 0) {
_this2.increaseRollMasterDifficulty();
}
- // Logika odblokowywania ataków przeniesiona do game.update
+ // Logika odblokowywania ataków jest w game.update
}, 1000); // Interwał co 1 sekundę
console.log("Scena Roll Master skonfigurowana.");
},
// <-- WAŻNE: Przecinek tutaj
@@ -2975,11 +3007,11 @@
direction.y = dy / distance;
}
// <-- NOWA LOGIKA: Obliczanie czasu trwania na podstawie dystansu swipe -->
var minSwipeDist = swipeThreshold; // Minimalny dystans swipe'a (np. 50)
- var maxSwipeDist = 500; // <-- ZWIĘKSZONA dla większej skali
- var minRollDuration = 90; // <-- LEKKO ZWIĘKSZONA dla płynności minimum
- var maxRollDuration = 500; // Maksymalny czas uniku w ms (np. 0.45s - dostosuj!)
+ 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);
@@ -3011,8 +3043,9 @@
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.
@@ -3024,11 +3057,24 @@
var trackStates = ["title", "game", "fakeTutorial", "fakeGameOver", "realTutorial", "grillMenu", "gameOver", "rollMaster"];
if (trackStates.indexOf(gameState.currentState) !== -1) {
gameState.touchEnd.x = x;
gameState.touchEnd.y = y;
- // --- NEW: Reset input active flag ---
+ // Zawsze ustawiaj isInputActive na false przy puszczeniu, aby zatrzymać chodzenie
gameState.isInputActive = false;
- gameState.processTouchGesture();
+ // --- 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) {
@@ -3492,9 +3538,9 @@
console.log("Odblokowano atak:", attackToUnlock, "Następne odblokowanie w:", gameState.nextUnlockTime, "s.");
// Wyświetl komunikat graczowi
if (ui && ui.showMessage) {
// Zamień nazwę techniczną na bardziej przyjazną
- var friendlyName = attackToUnlock.replace('rmattack1', 'Projectiles').replace('rmattack2', 'Explosions').replace('rmattack3', 'Lasers').replace('rmattack4', 'Spreaders');
+ var friendlyName = attackToUnlock.replace('rmattack1', 'Fireball').replace('rmattack2', 'Bloodburst Orb').replace('rmattack3', 'Judgment of Light').replace('rmattack4', 'Infernal Skull');
ui.showMessage("New Attack Unlocked: " + friendlyName, 2000);
}
}
// --- KONIEC ZMIANY ---