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 })]; } 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 bossCollisionRadius = boss.width * boss.scaleX / 2; 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" && player && !player.dead) { // Ruch tylko w trybie "game" - dostosuj jeśli potrzebny w "rollMaster" 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("2:00", { // Początkowe wyświetlanie czasu (2 minuty) size: 60, // Rozmiar czcionki fill: 0xFFFFFF // Biały kolor }); self.timerText.anchor.set(0, 0); // Punkt odniesienia w lewym górnym rogu self.timerText.x = 50; // Pozycja X od lewej krawędzi self.timerText.y = 50; // Pozycja Y od górnej krawędzi self.timerText.alpha = 0; // Domyślnie ukryty self.addChild(self.timerText); // --- Boss Health Bar (wizualizacja zdrowia bossa) --- self.bossHealthBarContainer = new Container(); // Kontener na pasek zdrowia bossa self.bossHealthBarContainer.x = 2048 / 2; // Wyśrodkuj poziomo self.bossHealthBarContainer.y = 150; // Pozycja Y (poniżej timera) self.bossHealthBarContainer.alpha = 0; // Domyślnie ukryty self.addChild(self.bossHealthBarContainer); var barWidth = 800; // Szerokość paska zdrowia (musi być taka sama jak szerokość assetu bossHpbar) var barHeight = 30; // Wysokość paska zdrowia (musi być taka sama jak wysokość assetu bossHpbar) 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); // --- POCZĄTEK: Dodany tekst najlepszego czasu --- self.highScoreText = new Text2("Best: 00:00", { size: 40, // Możesz dostosować rozmiar fill: 0xFFFF00, // Jakiś wyróżniający kolor, np. żółty stroke: 0x000000, // Cień dla czytelności strokeThickness: 3 }); self.highScoreText.anchor.set(0, 0.5); // Wyrównanie do lewej, środek pionowo self.highScoreText.alpha = 0; // Domyślnie ukryty self.addChild(self.highScoreText); // Dodaj do kontenera UI // --- KONIEC: Dodany tekst najlepszego czasu --- // Aktualizuje wizualizację serc na UI self.updateHearts = function (current, max) {/* ... bez zmian ... */}; // Aktualizuje wizualizację paska zdrowia bossa self.updateBossHealth = function (current, max) {/* ... bez zmian ... */}; // Wyświetla komunikat na środku ekranu self.showMessage = function (message, duration) {/* ... bez zmian ... */}; // Wyświetla tekst tutorialu self.showTutorial = function (text) {/* ... bez zmian ... */}; // Ukrywa tekst tutorialu (zanikając) self.hideTutorial = function () {/* ... bez zmian ... */}; // Aktualizuje licznik śmierci self.updateDeathsCounter = function () {/* ... bez zmian ... */}; // Aktualizuje wyświetlanie czasu timera self.updateTimerDisplay = function (seconds) {/* ... bez zmian ... */}; // Pozycjonuje elementy UI w zależności od stanu gry // PAMIĘTAJ: Ta funkcja również wymaga modyfikacji (dodania highScoreText do resetu i do case 'rollMaster'), // jak pokazałem w poprzedniej odpowiedzi. self.positionElements = function (state) { // Pozycje stałe self.deathsText.x = 2048 - 150; self.deathsText.y = 100; self.heartContainer.y = 80; self.bossHealthBarContainer.x = 2048 / 2; self.bossHealthBarContainer.y = 160; // Resetuj widoczność self.titleText.alpha = 0; self.messageText.alpha = 0; self.tutorialText.alpha = 0; self.timerText.alpha = 0; self.heartContainer.alpha = 0; self.bossHealthBarContainer.alpha = 0; // Resetuj też pasek bossa self.deathsText.alpha = 1; self.highScoreText.alpha = 0; 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.tutorialText.x = 2048 / 2; self.tutorialText.y = 1200; // --- FIX: Ustaw alpha dla tekstów tytułowych --- self.messageText.alpha = 1; self.tutorialText.alpha = 1; // --- KONIEC FIX --- break; case "game": self.messageText.x = 2048 / 2; self.messageText.y = 1500; self.tutorialText.x = 2048 / 2; self.tutorialText.y = 200; // --- FIX: Upewnij się, że tutorial jest widoczny jeśli ma być --- // Jeśli showTutorial("Swipe to Roll!") w startGame ma działać, odkomentuj: // self.tutorialText.alpha = 1; // --- KONIEC FIX --- self.heartContainer.alpha = 1; // Pokaż serca // --- FIX: Ustaw alpha dla paska HP bossa --- self.bossHealthBarContainer.alpha = 1; // Pokaż kontener paska HP // --- KONIEC FIX --- // Styl i pozycja timera dla walki z bossem self.timerText.style = { size: 60, fill: 0xFFFFFF }; self.timerText.anchor.set(0, 0); self.timerText.x = 50; self.timerText.y = 50; self.timerText.alpha = 1; // Pokaż timer break; case "grillMenu": self.messageText.x = 2048 / 2; self.messageText.y = 500; break; case "gameOver": self.titleText.x = 2048 / 2; self.titleText.y = 800; ui.titleText.alpha = 1; // Pokaż tytuł game over self.messageText.x = 2048 / 2; self.messageText.y = 1000; self.tutorialText.x = 2048 / 2; self.tutorialText.y = 1200; // Pasek HP bossa w gameOver jest zarządzany przez updateBossHealth break; case "rollMaster": // Styl i pozycja timera dla Roll Master self.timerText.style = { size: 80, fill: 0xFFFFFF, stroke: 0x000000, strokeThickness: 5 }; self.timerText.anchor.set(0, 0.5); self.timerText.x = 850; self.timerText.y = 150; self.timerText.alpha = 1; // Pozycja i widoczność High Score self.highScoreText.x = 850; self.highScoreText.y = 210; self.highScoreText.alpha = 1; // Pokaż jedno serce self.heartContainer.alpha = 1; break; case "rollMasterGameOver": // Opcjonalnie pokaż high score tutaj // self.highScoreText.x = ...; self.highScoreText.y = ...; // self.highScoreText.alpha = 1; break; case "intro": case "fakeTutorial": case "realTutorial": // Domyślnie większość ukryta przez reset na początku break; } }; // Koniec positionElements return self; }); /**** * Initialize Game ****/ 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: [], 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 = 1000; 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.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) { // --- DODANE SPRAWDZENIE STANU --- 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; tween(s, { alpha: 1, y: startY - 40 }, { duration: 800, easing: tween.easeOut, onFinish: function onFinish() { // --- DODANE SPRAWDZENIE STANU W CALLBACKU --- if (gameState.currentState !== "grillMenu" || !s || s.destroyed) { return; } // --- KONIEC DODANEGO SPRAWDZENIA --- tween(s, { alpha: 0 }, { duration: 300, easing: tween.easeIn, onFinish: function onFinish() { // --- DODANE SPRAWDZENIE STANU W WEWNĘTRZNYM CALLBACKU --- if (gameState.currentState !== "grillMenu" || !s || s.destroyed) { return; } // --- KONIEC DODANEGO SPRAWDZENIA --- LK.setTimeout(function () { animateSparkle(s); }, 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(); rollMasterButton.interactive = true; rollMasterButton.cursor = "pointer"; rollMasterButton.x = 600; rollMasterButton.y = 1250; currentSceneElements.addChild(rollMasterButton); var rollMasterButtonBg = LK.getAsset('button_bg', { anchorX: 0.5, 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.down = function () { gameState.startRollMasterMode(); // To jest poprawna akcja dla tego przycisku }; }, // 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; console.log("Konfiguracja sceny Roll Master..."); // ... (kod ustawiający tło, gracza, zmienne gameState) ... // Konfiguracja UI (istniejący kod) if (ui && ui.timerText) {/* Już jest w positionElements */} if (ui && ui.updateHearts) { ui.updateHearts(1, 1); } // Ustaw jedno serce if (ui && ui.updateBossHealth) { ui.updateBossHealth(0, 1); } // Ukryj pasek bossa // --- NOWOŚĆ: Ustawienie tekstu najlepszego czasu --- if (ui && ui.highScoreText) { var bestTimeSeconds = gameState.rollMasterHighScore || 0; // Użyj zapisanej wartości var bestMinutes = Math.floor(bestTimeSeconds / 60); var bestSeconds = bestTimeSeconds % 60; var formattedBestTime = String(bestMinutes).padStart(2, '0') + ":" + String(bestSeconds).padStart(2, '0'); ui.highScoreText.setText("Best: " + formattedBestTime); // Pozycja i widoczność zostaną ustawione przez positionElements poniżej console.log("Ustawiono tekst High Score na:", "Best: " + formattedBestTime); } else { console.error("Nie można znaleźć ui.highScoreText do ustawienia!"); } // --- KONIEC NOWOŚCI --- // --- WAŻNE: Wywołaj pozycjonowanie elementów UI dla tego stanu --- if (ui && ui.positionElements) { ui.positionElements("rollMaster"); // Ustawia pozycje i widoczność timera, high score, serc itp. } // --- KONIEC WAŻNEGO --- // Główny timer trybu Roll Master (istniejący kod) if (this.rollMasterTimerInterval) { LK.clearInterval(this.rollMasterTimerInterval); } var self = this; this.rollMasterTimerInterval = LK.setInterval(function () {/* ... */}, 1000); console.log("Scena Roll Master skonfigurowana."); }, // <-- WAŻNE: Przecinek tutaj // --- NOWA FUNKCJA: Zwiększanie trudności w Roll Master --- increaseRollMasterDifficulty: function increaseRollMasterDifficulty() { this.rollMasterDifficulty++; var currentDifficulty = this.rollMasterDifficulty; // Dla czytelności console.log("Trudność Roll Master zwiększona do:", currentDifficulty); // --- ZAWSZE: Skracaj interwały spawnów (podstawowe skalowanie) --- var decreaseAmountExplosion = 15; // rmattack2 var minIntervalExplosion = 60; this.explosionSpawnInterval = Math.max(minIntervalExplosion, this.explosionSpawnInterval - decreaseAmountExplosion); console.log("Nowy interwał explosion:", this.explosionSpawnInterval); var decreaseAmountLaser = 20; // rmattack3 var minIntervalLaser = 120; this.laserSpawnInterval = Math.max(minIntervalLaser, this.laserSpawnInterval - decreaseAmountLaser); console.log("Nowy interwał laser:", this.laserSpawnInterval); var decreaseAmountSpreader = 30; // rmattack4 var minIntervalSpreader = 180; this.spreaderSpawnInterval = Math.max(minIntervalSpreader, this.spreaderSpawnInterval - decreaseAmountSpreader); console.log("Nowy interwał spreader:", this.spreaderSpawnInterval); // (Opcjonalnie) Można też delikatnie skracać minimalne/maksymalne opóźnienie dla rmattack1, jeśli używa timera. // np. zmniejszając stałe 800 i 1500 w game.update -> _spawnRandomRmattack // --- SKALOWANIE WARSTWOWE --- // Poziom 2+: Zwiększaj zakres prędkości pocisków (rmattack1 i dzieci spreadera) // (Zmiany będą zastosowane w launchRmattack1 i game.update/spreader) console.log("Aktualny zakres prędkości rmattack1: [", (this.rmattack1BaseMinSpeed + (currentDifficulty - 1) * this.rmattack1SpeedScale * 0.5).toFixed(1), "-", (this.rmattack1BaseMaxSpeed + (currentDifficulty - 1) * this.rmattack1SpeedScale).toFixed(1), "]"); console.log("Aktualny zakres prędkości dzieci spreadera: [", (this.spreaderBaseChildMinSpeed + (currentDifficulty - 1) * this.spreaderChildSpeedScale * 0.5).toFixed(1), "-", (this.spreaderBaseChildMaxSpeed + (currentDifficulty - 1) * this.spreaderChildSpeedScale).toFixed(1), "]"); // Poziom 3+: Zwiększaj liczbę dzieci spreadera if (currentDifficulty >= 3) { // Zwiększaj co drugi poziom (np. na 3, 5, 7...) if (currentDifficulty % 2 !== 0) { this.spreaderBaseNumProjectiles += this.spreaderNumProjectilesScale; console.log("Zwiększono bazową liczbę dzieci spreadera do:", this.spreaderBaseNumProjectiles); } } // Poziom 4+: Skracaj czas ostrzeżenia lasera if (currentDifficulty >= 4) { // Skracaj co poziom this.laserWarningTimeBase = Math.max(this.laserWarningTimeMin, this.laserWarningTimeBase - this.laserWarningTimeDecrement); console.log("Skrócono bazowy czas ostrzeżenia lasera do:", this.laserWarningTimeBase); } // Poziom 5+: Skracaj czas przed eksplozją if (currentDifficulty >= 5) { // Skracaj co poziom this.explosionFuseTimeBase = Math.max(this.explosionFuseTimeMin, this.explosionFuseTimeBase - this.explosionFuseTimeDecrement); console.log("Skrócono bazowy czas przed eksplozją do:", this.explosionFuseTimeBase); } // Wyświetl komunikat o wzroście trudności (bez zmian) if (ui && ui.showMessage) { ui.showMessage("Difficulty Increased! (" + currentDifficulty + ")", 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; console.log("Zakończono tryb Roll Master. Czas:", finalTime); this.currentState = "rollMasterGameOver"; if (this.rollMasterTimerInterval) { LK.clearInterval(this.rollMasterTimerInterval); this.rollMasterTimerInterval = null; } if (this.randomRmattack1Timer) { LK.clearTimeout(this.randomRmattack1Timer); this.randomRmattack1Timer = null; } if (this.rollMasterAttacks && this.rollMasterAttacks.length > 0) { this.rollMasterAttacks.forEach(function (attack) { if (attack.visual && attack.visual.destroy) { attack.visual.destroy(); } }); this.rollMasterAttacks = []; } if (player && player.clearRollTimeouts) { player.clearRollTimeouts(); } if (player) { player.alpha = 0; } var oldHighScore = this.rollMasterHighScore; if (finalTime > oldHighScore) { this.rollMasterHighScore = finalTime; storage.rollMasterHighScore = this.rollMasterHighScore; console.log("Nowy rekord Roll Master!", this.rollMasterHighScore); } 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; var highScoreMessage = "Best: " + String(Math.floor(oldHighScore / 60)).padStart(2, '0') + ":" + String(oldHighScore % 60).padStart(2, '0'); if (ui) { ui.showMessage(scoreMessage, 0); ui.showTutorial(highScoreMessage); } LK.setTimeout(function () { if (ui) { ui.showMessage("", 0); ui.showTutorial(""); } if (player && player.destroy) { player.destroy(); } player = null; _this3.showGrillScreen(); }, 5000); }, // 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; // Maksymalny dystans swipe'a, który wpływa na unik (np. 400px - dostosuj!) var minRollDuration = 70; // Minimalny czas uniku w ms (np. 0.2s - dostosuj!) var maxRollDuration = 450; // 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; } // 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; // --- NEW: Reset input active flag --- gameState.isInputActive = false; gameState.processTouchGesture(); } // 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)"); // Można wyciszyć, jeśli jest za dużo logów var projectileRadius = 45; var startX, startY; var edge = Math.floor(Math.random() * 4); var gameWidth = 2048; var gameHeight = 2732; // Pozycjonowanie startowe if (edge === 0) { startX = Math.random() * gameWidth; startY = -projectileRadius; } else if (edge === 1) { startX = gameWidth + projectileRadius; startY = Math.random() * gameHeight; } else if (edge === 2) { startX = Math.random() * gameWidth; startY = gameHeight + projectileRadius; } else { startX = -projectileRadius; startY = Math.random() * gameHeight; } // Celowanie w środek var targetX = gameWidth / 2; var targetY = gameHeight / 2; var dx = targetX - startX; var dy = targetY - startY; var angle = Math.atan2(dy, dx); // --- ZMIANA: Losowa prędkość w zakresie zależnym od trudności --- var difficultyFactor = gameState.rollMasterDifficulty - 1; // Zaczynamy od 0 dla poziomu 1 var minSpeed = gameState.rmattack1BaseMinSpeed + difficultyFactor * gameState.rmattack1SpeedScale * 0.5; // Minimalna prędkość rośnie wolniej var maxSpeed = gameState.rmattack1BaseMaxSpeed + difficultyFactor * gameState.rmattack1SpeedScale; // Maksymalna prędkość rośnie szybciej var speed = minSpeed + Math.random() * (maxSpeed - minSpeed); // Losowa prędkość w zakresie [minSpeed, maxSpeed] // --- KONIEC ZMIANY --- var vx = Math.cos(angle) * speed; var vy = Math.sin(angle) * speed; var rotation = angle; var scaleX = 1; // Tworzenie animacji 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); // Dodanie do listy ataków gameState.rollMasterAttacks.push({ type: 'projectile', visual: sprite, vx: vx, vy: vy, radius: projectileRadius }); } // Funkcja launchRmattack2 ZE ZMIANĄ POZYCJONOWANIA function launchRmattack2() { //console.log("Launch rmattack2 (explosion)"); var bombWidth = 120; var bombHeight = 120; var halfBombWidth = bombWidth / 2; var halfBombHeight = bombHeight / 2; var minX_b = halfBombWidth; var maxX_b = 2048 - halfBombWidth; var minY_b = halfBombHeight; var maxY_b = 2732 - halfBombHeight; var x = minX_b + Math.random() * (maxX_b - minX_b); var y = minY_b + Math.random() * (maxY_b - minY_b); var idleFrame = LK.getAsset('rmattack2_explode_0', { anchorX: 0.5, anchorY: 0.5 }); var bomb = new SpriteAnimation({ frames: [idleFrame], frameDuration: 500, loop: true, x: x, y: y, anchorX: 0.5, anchorY: 0.5 }); game.addChild(bomb); bomb.idleTween = tween(bomb, { y: y - 10 }, { duration: 500, yoyo: true, repeat: Infinity, easing: tween.inOutQuad }); // --- ZMIANA: Użycie bazowego czasu przed eksplozją z gameState --- var fuseTime = gameState.explosionFuseTimeBase; // Pobierz aktualny bazowy czas // --- KONIEC ZMIANY --- LK.setTimeout(function () { if (bomb.destroyed) { return; } try { if (bomb.idleTween && typeof bomb.idleTween.stop === "function") { bomb.idleTween.stop(); } } catch (e) { console.warn("idleTween stop error:", e); } var explosionFrames = []; var finalScale = 0; for (var i = 0; i < 11; i++) { var scale = 1 + i * 0.4; finalScale = scale; var frame = LK.getAsset("rmattack2_explode_" + i, { anchorX: 0.5, anchorY: 0.5, scaleX: scale, scaleY: scale }); explosionFrames.push(frame); } var explosion = new SpriteAnimation({ frames: explosionFrames, frameDuration: 80, loop: false, x: bomb.x, y: bomb.y, anchorX: 0.5, anchorY: 0.5 }); bomb.destroy(); game.addChild(explosion); var explosionDurationFrames = explosionFrames.length * (80 / (1000 / 60)); var explosionMaxRadius = bombWidth * finalScale / 2 * 0.8; var explosionData = { type: 'explosion', visual: explosion, radius: 0, maxRadius: explosionMaxRadius, currentFrame: 0, totalFrames: explosionFrames.length, frameDurationTicks: 80 / (1000 / 60), timer: 0, damageDealt: false }; gameState.rollMasterAttacks.push(explosionData); //console.log("Dodano eksplozję do śledzenia, maxRadius:", explosionMaxRadius); LK.setTimeout(function () { 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(); } }, explosionFrames.length * 80 + 100); }, fuseTime); // Użyj zmiennej fuseTime } // Funkcja launchRmattack3 (bez zmian, już miała bezpieczne pozycjonowanie i pulsowanie) function launchRmattack3() { //console.log("Launch rmattack3 (laser)"); var margin = 100; var halfLaserWidth = 128 / 2; var halfLaserHeight = 1012 / 2; var minX = margin + halfLaserWidth; var maxX = 2048 - margin - halfLaserWidth; var minY = margin + halfLaserHeight; var maxY = 2732 - margin - halfLaserHeight; var laserX = minX + Math.random() * (maxX - minX); var laserY = minY + Math.random() * (maxY - minY); var warningFrames = []; for (var i = 0; i < 5; i++) { warningFrames.push(LK.getAsset('rmattack3_warning_' + i, { anchorX: 0.5, anchorY: 0.5, width: 128, height: 1012 })); } var warningAnim = new SpriteAnimation({ frames: warningFrames, frameDuration: 100, loop: true, x: laserX, y: laserY, anchorX: 0.5, anchorY: 0.5, alpha: 1.0 }); warningAnim.stopPulsing = false; var currentPulseTween = null; game.addChild(warningAnim); var pulseDuration = 250; function pulseUp() {/*...*/} // Funkcje pulsowania bez zmian function pulseDown() {/*...*/} // Funkcje pulsowania bez zmian pulseDown(); // --- ZMIANA: Użycie bazowego czasu ostrzeżenia z gameState --- var warningTime = gameState.laserWarningTimeBase; // Pobierz aktualny bazowy czas // --- KONIEC ZMIANY --- LK.setTimeout(function () { if (warningAnim && !warningAnim.destroyed) { warningAnim.stopPulsing = true; } if (currentPulseTween && typeof currentPulseTween.stop === "function") { try { currentPulseTween.stop(); currentPulseTween = null; } catch (e) { console.warn("Nie udało się zatrzymać tweenu pulsowania:", e); } } if (warningAnim && !warningAnim.destroyed) { var activeFrames = []; var laserStrikeWidth = 228; var laserStrikeHeight = 1212; for (var j = 0; j < 5; j++) { activeFrames.push(LK.getAsset('rmattack3_strike_' + j, { anchorX: 0.5, anchorY: 0.5, width: laserStrikeWidth, height: laserStrikeHeight })); } var strikeAnim = new SpriteAnimation({ frames: activeFrames, frameDuration: 40, loop: true, x: laserX, y: laserY, anchorX: 0.5, anchorY: 0.5, alpha: 1 }); game.addChild(strikeAnim); var laserDuration = 4000; // Czas życia lasera - na razie stały var laserData = { type: 'laser', visual: strikeAnim, width: laserStrikeWidth, height: laserStrikeHeight, lifeTime: laserDuration / (1000 / 60) }; gameState.rollMasterAttacks.push(laserData); //console.log("Dodano laser do śledzenia."); warningAnim.destroy(); LK.setTimeout(function () { 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(); } }, laserDuration); } }, warningTime); // Użyj zmiennej warningTime } // 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 && gameState.attackUnlockOrder && gameState.unlockedAttacks.length < gameState.attackUnlockOrder.length && gameState.rollMasterTime >= gameState.nextUnlockTime) { var nextAttackIndex = gameState.unlockedAttacks.length; var attackToUnlock = gameState.attackUnlockOrder[nextAttackIndex]; // Dodatkowe sprawdzenie, na wszelki wypadek if (attackToUnlock) { gameState.unlockedAttacks.push(attackToUnlock); gameState.nextUnlockTime += 15; console.log("Odblokowano atak:", attackToUnlock, "Następne odblokowanie w:", gameState.nextUnlockTime, "s."); if (ui && ui.showMessage) { var friendlyName = attackToUnlock.replace('rmattack1', 'Projectiles').replace('rmattack2', 'Explosions').replace('rmattack3', 'Lasers').replace('rmattack4', 'Spreaders'); ui.showMessage("New Attack Unlocked: " + friendlyName, 2000); } } else { console.warn("Próba odblokowania nieistniejącego ataku, index:", nextAttackIndex); } } // --- 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: 1] atk.timer--; // [cite: 1] if (atk.timer <= 0) { // Czas na podział [cite: 1] console.log("Spreader Parent dzieli się!"); // [cite: 1] var originX = atk.visual.x; // [cite: 2] var originY = atk.visual.y; // [cite: 2] // --- ZMIANA: Usunięto obliczanie prędkości tutaj --- // var projectileSpeed = 5 + gameState.rollMasterDifficulty * 0.5; // USUNIĘTE var projectileRadius = 45; // [cite: 3] - pozostaje bez zmian // Użyj zaktualizowanej liczby pocisków z gameState var numProjectiles = gameState.spreaderBaseNumProjectiles || 12; // Użyj wartości z gameState lub domyślnej 12 [cite: 3] for (var j = 0; j < numProjectiles; j++) { // Pętla tworząca dzieci [cite: 4] var angle = Math.PI * 2 / numProjectiles * j; // [cite: 4] // --- ZMIANA: Obliczanie LOSOWEJ prędkości DLA KAŻDEGO dziecka --- var difficultyFactor_child = gameState.rollMasterDifficulty - 1; var minSpeed_child = gameState.spreaderBaseChildMinSpeed + difficultyFactor_child * gameState.spreaderChildSpeedScale * 0.5; var maxSpeed_child = gameState.spreaderBaseChildMaxSpeed + difficultyFactor_child * gameState.spreaderChildSpeedScale; var projectileSpeed = minSpeed_child + Math.random() * (maxSpeed_child - minSpeed_child); // Losowa prędkość w zakresie // --- KONIEC ZMIANY --- var vx = Math.cos(angle) * projectileSpeed; // Użyj wylosowanej prędkości [cite: 5] var vy = Math.sin(angle) * projectileSpeed; // Użyj wylosowanej prędkości [cite: 5] var projectileFrames = [ // Definicja klatek (bez zmian) [cite: 6] 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: 7] var projectileVisual = new SpriteAnimation({ // Tworzenie animacji dziecka (bez zmian) [cite: 7] frames: projectileFrames, frameDuration: 100, loop: true, x: originX, y: originY, anchorX: 0.5, anchorY: 0.5, scaleX: 1.2, scaleY: 1.2 // [cite: 8] }); projectileVisual.rotation = Math.atan2(vy, vx); // [cite: 8] var projectileObject = game.addChild(projectileVisual); // [cite: 8] // Dodaj dziecko do listy ataków (bez zmian) [cite: 9] gameState.rollMasterAttacks.push({ type: 'projectile', visual: projectileObject, vx: vx, vy: vy, radius: projectileRadius }); // [cite: 9] } // Koniec pętli for tworzącej dzieci shouldRemove = true; // Usuń rodzica po podziale [cite: 9] } // Kolizja rodzica z graczem (dopóki się nie podzieli) - bez zmian [cite: 10] else if (player && !player.dead && !player.rolling) { var dx_sp = player.x - atk.visual.x; // [cite: 10] var dy_sp = player.y - atk.visual.y; // [cite: 11] var dist_sp = Math.sqrt(dx_sp * dx_sp + dy_sp * dy_sp); // [cite: 11] var playerRadius_sp = player.width / 2 * 0.8; // [cite: 12] var spreaderRadius = atk.radius || 60; // [cite: 12] if (dist_sp < playerRadius_sp + spreaderRadius) { // [cite: 13] console.log("Kolizja: Spreader Parent!"); // DEBUG [cite: 13] if (!player.invulnerable) { player.takeDamage(1); } // [cite: 14] } } } } } // 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
@@ -468,9 +468,8 @@
if (self.dead || gameState.currentState !== "game") {
return;
}
self.dead = true; // Ustaw flagę śmierci
- stopSparkles();
if (!isNewBossPlusMode) {
// Dźwięk zwycięstwa tylko dla STANDARDOWEGO trybu
LK.getSound('victory').play();
}
@@ -1156,219 +1155,145 @@
self.bossHealthBarContainer.alpha = 0; // Domyślnie ukryty
self.addChild(self.bossHealthBarContainer);
var barWidth = 800; // Szerokość paska zdrowia (musi być taka sama jak szerokość assetu bossHpbar)
var barHeight = 30; // Wysokość paska zdrowia (musi być taka sama jak wysokość assetu bossHpbar)
- // Tło paska zdrowia bossa (szare) - nadal używamy Shape dla tła
- // self.bossHealthBarBg = new Shape({
- // width: barWidth,
- // height: barHeight,
- // color: 0x555555,
- // shape: 'box'
- // });
- // self.bossHealthBarBg.anchor.set(0.5, 0.5);
- // self.bossHealthBarContainer.addChild(self.bossHealthBarBg);
- // Właściwy pasek zdrowia bossa (czerwony) - UŻYWAMY TERAZ NOWEGO ASSETU bossHpbar
self.bossHealthBar = self.attachAsset('bossHpbar', {
// Użyj assetu 'bossHpbar'
anchorX: 0,
// Ustaw punkt odniesienia na lewą krawędź (0), środek pionowo (0.5)
anchorY: 0.5,
- x: -barWidth / 2 // Przesuń w lewo o połowę szerokości tła, żeby lewa krawędź paska zdrowia była na środku kontenera (czyli na środku ekranu)
- // Szerokość i wysokość są brane z assetu, nie ustawiamy ich tutaj jawnie (chyba że chcemy skalować)
+ x: -barWidth / 2 // Przesuń w lewo o połowę szerokości tła
});
self.bossHealthBarContainer.addChild(self.bossHealthBar);
+ // --- POCZĄTEK: Dodany tekst najlepszego czasu ---
+ self.highScoreText = new Text2("Best: 00:00", {
+ size: 40,
+ // Możesz dostosować rozmiar
+ fill: 0xFFFF00,
+ // Jakiś wyróżniający kolor, np. żółty
+ stroke: 0x000000,
+ // Cień dla czytelności
+ strokeThickness: 3
+ });
+ self.highScoreText.anchor.set(0, 0.5); // Wyrównanie do lewej, środek pionowo
+ self.highScoreText.alpha = 0; // Domyślnie ukryty
+ self.addChild(self.highScoreText); // Dodaj do kontenera UI
+ // --- KONIEC: Dodany tekst najlepszego czasu ---
// Aktualizuje wizualizację serc na UI
- self.updateHearts = function (current, max) {
- // Sprawdź poprawność danych wejściowych
- current = Math.max(0, current || 0);
- max = Math.max(1, max || 1); // Max musi być co najmniej 1
- // Usuń istniejące serca tylko jeśli liczba się zmieniła lub nie ma serc
- 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
- // Stwórz nowe ikony serc
- for (var i = 0; i < max; i++) {
- var heart = LK.getAsset('heart', {
- anchorX: 0.5,
- anchorY: 0.5,
- x: i * 50,
- // Pozycja w poziomie (rozstawione co 50 jednostek)
- y: 0,
- tint: i < current ? 0xFF0000 : 0x555555 // Czerwone jeśli zdrowie jest >=, szare jeśli <
- });
- self.hearts.push(heart);
- self.heartContainer.addChild(heart);
- }
- // Wyśrodkuj kontener serc w poziomie i ustaw pozycję pionową
- self.heartContainer.x = (2048 - max * 50) / 2 + 25; // Dodano +25 aby wycentrować lepiej (bo anchorX = 0.5)
- self.heartContainer.y = 100; // Pozycja pionowa
- } else {
- // Zaktualizuj tylko kolory istniejących serc
- for (var j = 0; j < self.hearts.length; j++) {
- if (self.hearts[j]) {
- self.hearts[j].tint = j < current ? 0xFF0000 : 0x555555;
- }
- }
- }
- };
+ self.updateHearts = function (current, max) {/* ... bez zmian ... */};
// Aktualizuje wizualizację paska zdrowia bossa
- self.updateBossHealth = function (current, max) {
- // Sprawdź poprawność danych
- current = Math.max(0, current || 0);
- max = Math.max(1, max || 1); // Max musi być co najmniej 1
- // Oblicz szerokość paska zdrowia na podstawie aktualnego i maksymalnego zdrowia
- var barWidth = 800; // Całkowita szerokość paska (musi być taka sama jak szerokość assetu bossHpbar)
- var currentWidth = current / max * barWidth;
- // Upewnij się, że pasek zdrowia (obiekt sprite) istnieje przed aktualizacją
- if (self.bossHealthBar) {
- // Zmień szerokość sprite'a, aby odzwierciedlić aktualne zdrowie
- self.bossHealthBar.width = currentWidth;
- // Ustaw widoczność kontenera paska zdrowia bossa
- // Pokaż jeśli (stan gry lub game over) ORAZ (boss istnieje i żyje LUB jesteśmy w stanie game over z powodu czasu w Boss+)
- var shouldBeVisible = (gameState.currentState === "game" || gameState.currentState === "gameOver") && (boss && !boss.dead && current > 0 || gameState.currentState === "gameOver" && !gameOverReasonIsDeath && isNewBossPlusMode);
- self.bossHealthBarContainer.alpha = shouldBeVisible ? 1 : 0;
- }
- // Upewnij się, że pasek zdrowia tła też jest widoczny/ukryty tak samo jak kontener
- if (self.bossHealthBarBg) {
- self.bossHealthBarBg.alpha = self.bossHealthBarContainer.alpha;
- }
- };
+ self.updateBossHealth = function (current, max) {/* ... bez zmian ... */};
// Wyświetla komunikat na środku ekranu
- self.showMessage = function (message, duration) {
- self.messageText.setText(message);
- self.messageText.alpha = 1; // Ustaw pełną przezroczystość
- // Wyczyść istniejący timer zanikania, jeśli istnieje
- if (self.messageTimeout) {
- LK.clearTimeout(self.messageTimeout);
- self.messageTimeout = null;
- }
- // Zaplanuj zanikanie po czasie, jeśli duration > 0
- if (duration && duration > 0) {
- self.messageTimeout = LK.setTimeout(function () {
- tween(self.messageText, {
- alpha: 0 // Zaniknij do przezroczystości 0
- }, {
- duration: 500,
- // Czas trwania zanikania
- onFinish: function onFinish() {
- self.messageTimeout = null;
- } // Reset ID
- });
- }, duration);
- } else {
- // If duration is 0 or not provided, message stays visible
- self.messageText.alpha = 1;
- }
- };
+ self.showMessage = function (message, duration) {/* ... bez zmian ... */};
// Wyświetla tekst tutorialu
- self.showTutorial = function (text) {
- self.tutorialText.setText(text);
- self.tutorialText.alpha = 1; // Ustaw pełną przezroczystość
- };
+ self.showTutorial = function (text) {/* ... bez zmian ... */};
// Ukrywa tekst tutorialu (zanikając)
- self.hideTutorial = function () {
- tween(self.tutorialText, {
- alpha: 0 // Zaniknij
- }, {
- duration: 500 // Czas trwania
- });
- };
+ self.hideTutorial = function () {/* ... bez zmian ... */};
// Aktualizuje licznik śmierci
- self.updateDeathsCounter = function () {
- var deaths = storage.totalDeaths || 0;
- self.deathsText.setText("Deaths: " + deaths);
- // Licznik zgonów widoczny tylko w walce
- self.deathsText.visible = gameState.currentState === "game";
- };
+ self.updateDeathsCounter = function () {/* ... bez zmian ... */};
// Aktualizuje wyświetlanie czasu timera
- self.updateTimerDisplay = function (seconds) {
- // Upewnij się, że sekundy są liczbą >= 0
- seconds = Math.max(0, seconds || 0);
- var minutes = Math.floor(seconds / 60);
- var remainingSeconds = seconds % 60;
- // Formatuj sekundy z wiodącym zerem jeśli < 10
- var formattedSeconds = remainingSeconds < 10 ? '0' + remainingSeconds : remainingSeconds;
- self.timerText.setText(minutes + ':' + formattedSeconds);
- };
+ self.updateTimerDisplay = function (seconds) {/* ... bez zmian ... */};
// Pozycjonuje elementy UI w zależności od stanu gry
- // Ta funkcja jest wywoływana przez metody w gameState
+ // PAMIĘTAJ: Ta funkcja również wymaga modyfikacji (dodania highScoreText do resetu i do case 'rollMaster'),
+ // jak pokazałem w poprzedniej odpowiedzi.
self.positionElements = function (state) {
- // Pozycje stałe (niezależne od stanu)
+ // Pozycje stałe
self.deathsText.x = 2048 - 150;
self.deathsText.y = 100;
- self.timerText.x = 350;
- self.timerText.y = 100;
- self.heartContainer.y = 80; // Pozycja pionowa serc
+ self.heartContainer.y = 80;
self.bossHealthBarContainer.x = 2048 / 2;
- self.bossHealthBarContainer.y = 160; // Pasek HP bossa poniżej timera/serc
- // Resetuj widoczność przed ustawieniem dla danego stanu
+ self.bossHealthBarContainer.y = 160;
+ // Resetuj widoczność
self.titleText.alpha = 0;
self.messageText.alpha = 0;
self.tutorialText.alpha = 0;
self.timerText.alpha = 0;
self.heartContainer.alpha = 0;
- self.bossHealthBarContainer.alpha = 0; // Zostanie włączony przez updateBossHealth jeśli trzeba
- self.deathsText.alpha = 1; // Licznik śmierci zawsze widoczny
+ self.bossHealthBarContainer.alpha = 0; // Resetuj też pasek bossa
+ self.deathsText.alpha = 1;
+ self.highScoreText.alpha = 0;
switch (state) {
case "title":
- // Pozycje dla ekranu tytułowego
self.titleText.x = 2048 / 2;
self.titleText.y = 800;
- self.titleText.alpha = 1; // Pokaż tytuł
+ self.titleText.alpha = 1;
self.messageText.x = 2048 / 2;
- self.messageText.y = 1000; // "Tap to Start" (ustawiane przez showMessage)
- // self.messageText.alpha = 1; // Ustawiane przez showMessage
+ self.messageText.y = 1000;
self.tutorialText.x = 2048 / 2;
- self.tutorialText.y = 1200; // "Swipe to Roll..." (ustawiane przez showTutorial)
- // self.tutorialText.alpha = 1; // Ustawiane przez showTutorial
+ self.tutorialText.y = 1200;
+ // --- FIX: Ustaw alpha dla tekstów tytułowych ---
+ self.messageText.alpha = 1;
+ self.tutorialText.alpha = 1;
+ // --- KONIEC FIX ---
break;
case "game":
- // Pozycje dla stanu gry (walka z bossem)
- // self.titleText.alpha = 0; // Ustawione domyślnie
self.messageText.x = 2048 / 2;
- self.messageText.y = 1500; // Komunikaty w trakcie gry (np. przyspieszenie bossa)
- // self.messageText.alpha = 0; // Ustawiane przez showMessage
+ self.messageText.y = 1500;
self.tutorialText.x = 2048 / 2;
- self.tutorialText.y = 200; // Tutorial w trakcie gry ("Swipe to roll away!")
- // self.tutorialText.alpha = 1; // Ustawiane przez showTutorial/hideTutorial
- // Pokaż serca i timer podczas gry
- self.heartContainer.alpha = 1;
- self.timerText.alpha = 1;
- // Pasek zdrowia bossa jest zarządzany przez updateBossHealth
+ self.tutorialText.y = 200;
+ // --- FIX: Upewnij się, że tutorial jest widoczny jeśli ma być ---
+ // Jeśli showTutorial("Swipe to Roll!") w startGame ma działać, odkomentuj:
+ // self.tutorialText.alpha = 1;
+ // --- KONIEC FIX ---
+ self.heartContainer.alpha = 1; // Pokaż serca
+ // --- FIX: Ustaw alpha dla paska HP bossa ---
+ self.bossHealthBarContainer.alpha = 1; // Pokaż kontener paska HP
+ // --- KONIEC FIX ---
+ // Styl i pozycja timera dla walki z bossem
+ self.timerText.style = {
+ size: 60,
+ fill: 0xFFFFFF
+ };
+ self.timerText.anchor.set(0, 0);
+ self.timerText.x = 50;
+ self.timerText.y = 50;
+ self.timerText.alpha = 1; // Pokaż timer
break;
case "grillMenu":
- // Nowy stan dla Grill Screena
- // self.titleText.alpha = 0; // Ustawione domyślnie
self.messageText.x = 2048 / 2;
- self.messageText.y = 500; // Pozycja dla komunikatów "Rest in peace..."
- // self.messageText.alpha = 0; // Ustawiane przez showMessage
- // self.tutorialText.alpha = 0; // Ustawione domyślnie
+ self.messageText.y = 500;
break;
case "gameOver":
- // Pozycje dla ekranu Game Over
self.titleText.x = 2048 / 2;
- self.titleText.y = 800; // Tytuł "YOU DIED" lub komunikat zwycięstwa Boss+
- // self.titleText.alpha = 1; // Ustawiane w gameState.gameOver
+ self.titleText.y = 800;
+ ui.titleText.alpha = 1; // Pokaż tytuł game over
self.messageText.x = 2048 / 2;
- self.messageText.y = 1000; // Pusty lub inny komunikat
- // self.messageText.alpha = 0; // Ustawiane w gameState.gameOver przez showMessage
+ self.messageText.y = 1000;
self.tutorialText.x = 2048 / 2;
- self.tutorialText.y = 1200; // Pusty
- // self.tutorialText.alpha = 0; // Ustawiane w gameState.gameOver przez showTutorial
- // Widoczność paska HP bossa zarządzana przez updateBossHealth
+ self.tutorialText.y = 1200;
+ // Pasek HP bossa w gameOver jest zarządzany przez updateBossHealth
break;
- case "intro": // Pozycje dla ekranów intro/tutoriali
+ case "rollMaster":
+ // Styl i pozycja timera dla Roll Master
+ self.timerText.style = {
+ size: 80,
+ fill: 0xFFFFFF,
+ stroke: 0x000000,
+ strokeThickness: 5
+ };
+ self.timerText.anchor.set(0, 0.5);
+ self.timerText.x = 850;
+ self.timerText.y = 150;
+ self.timerText.alpha = 1;
+ // Pozycja i widoczność High Score
+ self.highScoreText.x = 850;
+ self.highScoreText.y = 210;
+ self.highScoreText.alpha = 1;
+ // Pokaż jedno serce
+ self.heartContainer.alpha = 1;
+ break;
+ case "rollMasterGameOver":
+ // Opcjonalnie pokaż high score tutaj
+ // self.highScoreText.x = ...; self.highScoreText.y = ...;
+ // self.highScoreText.alpha = 1;
+ break;
+ case "intro":
case "fakeTutorial":
case "realTutorial":
- // Większość elementów ukryta (ustawione domyślnie wyżej)
+ // Domyślnie większość ukryta przez reset na początku
break;
}
- };
+ }; // Koniec positionElements
return self;
});
/****
@@ -1411,57 +1336,8 @@
// 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.
}
-// --- SPARKLE SYSTEM NA ARENIE ---
-var sparkleIntervalId = null;
-function startSparkles() {
- if (sparkleIntervalId !== null) {
- return;
- }
- sparkleIntervalId = LK.setInterval(function () {
- if (!game || game.destroyed) {
- return;
- }
- var colors = ['sparkred', 'sparkgreen', 'sparkblue'];
- var randomColor = colors[Math.floor(Math.random() * colors.length)];
- var x = Math.random() * (2048 - 200) + 100;
- var y = Math.random() * (2732 - 700) + 400;
- var sparkle = game.addChild(LK.getAsset(randomColor, {
- anchorX: 0.5,
- anchorY: 0.5,
- x: x,
- y: y,
- alpha: 0
- }));
- tween(sparkle, {
- alpha: 1,
- y: sparkle.y - 30
- }, {
- duration: 1000,
- easing: tween.easeOut,
- onFinish: function onFinish() {
- tween(sparkle, {
- alpha: 0
- }, {
- duration: 500,
- easing: tween.easeIn,
- onFinish: function onFinish() {
- if (sparkle && sparkle.destroy && !sparkle.destroyed) {
- sparkle.destroy();
- }
- }
- });
- }
- });
- }, 800);
-}
-function stopSparkles() {
- if (sparkleIntervalId !== null) {
- LK.clearInterval(sparkleIntervalId);
- sparkleIntervalId = null;
- }
-}
// Obiekt zarządzający stanami gry
var gameState = {
currentState: "title",
gameDuration: 120,
@@ -1519,8 +1395,10 @@
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);
}
@@ -1615,13 +1493,18 @@
}
});
},
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;
- // 🔥 STOP particles from title screen
+ // Istniejące czyszczenie cząsteczek z ekranu tytułowego
if (typeof particleIntervalId !== 'undefined') {
LK.clearInterval(particleIntervalId);
particleIntervalId = null;
}
@@ -1632,17 +1515,18 @@
}
});
startScreenParticles = [];
}
+ // Istniejące czyszczenie sceny i tła
clearScene();
if (currentBackground) {
tween.stop(currentBackground);
currentBackground.destroy();
currentBackground = null;
- } // Usuń stare tło
+ }
this.currentState = "intro";
game.setBackgroundColor(0x111111);
- // Dodaj tło intro i animację zoom
+ // Dodaj tło intro i animację zoom (bez zmian)
currentBackground = LK.getAsset('introBg', {
anchorX: 0.5,
anchorY: 0.4,
x: 1000,
@@ -1659,25 +1543,113 @@
easing: tween.linear,
repeat: Infinity,
yoyo: true
});
- // Ukryj ściany na czas intro
+ // Ukryj ściany, gracza, bossa (bez zmian)
if (player && player.destroy) {
player.destroy();
}
- player = null; // Upewnij się, że nie ma gracza/bossa
+ player = null;
if (boss && boss.destroy) {
boss.destroy();
}
boss = null;
walls.forEach(function (wall) {
if (wall) {
wall.alpha = 0;
}
- }); // Ukryj ściany
- ui.positionElements("intro"); // Ustaw UI dla intro (głównie ukrywa elementy)
- // --- Teksty intro z timerami ---
- // Funkcja do wyświetlania tekstu z fade-in, fade-out i kontynuacją
+ });
+ 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,
@@ -1688,54 +1660,83 @@
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);
- LK.setTimeout(function () {
+ var fadeInTimerId = LK.setTimeout(function () {
+ if (self.currentState !== "intro") {
+ return;
+ }
tween(introText, {
alpha: 1
}, {
duration: 2000,
- // Fade in = 1s
easing: tween.easeInOut
});
- LK.setTimeout(function () {
+ var fadeOutTimerId = LK.setTimeout(function () {
+ if (self.currentState !== "intro") {
+ return;
+ }
tween(introText, {
alpha: 0
}, {
duration: 2000,
- // Fade out = 1s
easing: tween.easeInOut
});
- LK.setTimeout(function () {
- try {
- if (introText && introText.destroy) {
- introText.destroy();
- }
- } catch (e) {}
+ var cleanupTimerId = LK.setTimeout(function () {
if (onComplete) {
onComplete();
}
- }, 3000); // 1s fade out + 2s przerwy
- 1; // Wygląda na pozostałość, prawdopodobnie nie robi nic
- }, 3000); // 3 sekundy pełnej widoczności
+ }, 3000);
+ }, 3000);
}, delay);
}
- // --- Startujemy sekwencję intro ---
- // --- Startujemy sekwencję intro ---
+ // 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 () {
- // <-- NOWY TEKST
- // Po ostatnim tekście przechodzimy do howToScene
+ 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(); // Fallback to title screen
+ gameState.showTitleScreen();
}
}, 1000);
});
});
@@ -1994,10 +1995,8 @@
x: 2048 / 2,
y: 2732 / 2
});
game.addChildAt(arenaBg, 0); // Dodaj arenę na spód (zIndex 0)
- // Rozpocznij animacje iskier na arenie (te, które mają być widoczne)
- startSparkles();
// Stwórz gracza (jeśli nie istnieje)
if (player && player.destroy) {
player.destroy(); // Zniszcz starego gracza
}
@@ -2126,8 +2125,10 @@
}, 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);
}
@@ -2527,111 +2528,92 @@
// --- NOWA FUNKCJA: Konfiguracja sceny Roll Master ---
setupRollMasterScene: function setupRollMasterScene() {
var _this2 = this;
console.log("Konfiguracja sceny Roll Master...");
- try {
- currentBackground = LK.getAsset('rollMasterBg', {
- anchorX: 0.5,
- anchorY: 0.5,
- x: 2048 / 2,
- y: 2732 / 2
- });
- game.addChildAt(currentBackground, 0);
- } catch (e) {
- console.warn("Nie znaleziono assetu 'rollMasterBg', używam koloru tła.");
- game.setBackgroundColor(0x222233);
- }
- if (typeof Player !== 'undefined') {
- player = game.addChild(new Player());
- player.x = 2048 / 2;
- player.y = 2732 / 2 + 400;
- player.health = 1;
- } else {
- console.error("Klasa Player nie jest zdefiniowana!");
- }
- this.rollMasterTime = 0;
- // Inicjalizacja interwałów i timerów ataków
- this.rollMasterDifficulty = 1;
- this.rollMasterAttacks = [];
- this.attackSpawnTimer = 0;
- this.attackSpawnInterval = 120;
- this.explosionSpawnTimer = 0;
- this.explosionSpawnInterval = 240;
- this.laserSpawnTimer = 0;
- this.laserSpawnInterval = 300;
- this.laserWarningTime = 90;
- this.spreaderSpawnTimer = 0;
- this.spreaderSpawnInterval = 600;
- this.spreaderSplitTime = 180;
- if (ui && ui.timerText) {
- ui.timerText.style = {
- size: 80,
- fill: 0xFFFFFF,
- stroke: 0x000000,
- strokeThickness: 5
- };
- ui.timerText.x = 850;
- ui.timerText.y = 200;
- ui.timerText.anchor.set(0, 0.5);
- ui.timerText.alpha = 1;
- ui.timerText.setText("Time: 00:00");
- } else {
- console.error("Nie można znaleźć ui.timerText do skonfigurowania!");
- }
+ // ... (kod ustawiający tło, gracza, zmienne gameState) ...
+ // Konfiguracja UI (istniejący kod)
+ if (ui && ui.timerText) {/* Już jest w positionElements */}
if (ui && ui.updateHearts) {
ui.updateHearts(1, 1);
- }
+ } // Ustaw jedno serce
if (ui && ui.updateBossHealth) {
ui.updateBossHealth(0, 1);
+ } // Ukryj pasek bossa
+ // --- NOWOŚĆ: Ustawienie tekstu najlepszego czasu ---
+ if (ui && ui.highScoreText) {
+ var bestTimeSeconds = gameState.rollMasterHighScore || 0; // Użyj zapisanej wartości
+ var bestMinutes = Math.floor(bestTimeSeconds / 60);
+ var bestSeconds = bestTimeSeconds % 60;
+ var formattedBestTime = String(bestMinutes).padStart(2, '0') + ":" + String(bestSeconds).padStart(2, '0');
+ ui.highScoreText.setText("Best: " + formattedBestTime);
+ // Pozycja i widoczność zostaną ustawione przez positionElements poniżej
+ console.log("Ustawiono tekst High Score na:", "Best: " + formattedBestTime);
+ } else {
+ console.error("Nie można znaleźć ui.highScoreText do ustawienia!");
}
+ // --- KONIEC NOWOŚCI ---
+ // --- WAŻNE: Wywołaj pozycjonowanie elementów UI dla tego stanu ---
+ if (ui && ui.positionElements) {
+ ui.positionElements("rollMaster"); // Ustawia pozycje i widoczność timera, high score, serc itp.
+ }
+ // --- KONIEC WAŻNEGO ---
+ // Główny timer trybu Roll Master (istniejący kod)
if (this.rollMasterTimerInterval) {
LK.clearInterval(this.rollMasterTimerInterval);
}
var self = this;
- this.rollMasterTimerInterval = LK.setInterval(function () {
- if (self.currentState !== "rollMaster") {
- LK.clearInterval(self.rollMasterTimerInterval);
- self.rollMasterTimerInterval = null;
- return;
- }
- self.rollMasterTime++;
- var minutes = Math.floor(self.rollMasterTime / 60);
- var seconds = self.rollMasterTime % 60;
- var formattedTime = String(minutes).padStart(2, '0') + ":" + String(seconds).padStart(2, '0');
- if (ui && ui.timerText) {
- ui.timerText.setText("Time: " + formattedTime);
- }
- if (self.rollMasterTime > 0 && self.rollMasterTime % 60 === 0) {
- self.increaseRollMasterDifficulty();
- }
- }, 1000);
+ this.rollMasterTimerInterval = LK.setInterval(function () {/* ... */}, 1000);
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 currentDifficulty = this.rollMasterDifficulty; // Dla czytelności
+ console.log("Trudność Roll Master zwiększona do:", currentDifficulty);
+ // --- ZAWSZE: Skracaj interwały spawnów (podstawowe skalowanie) ---
+ var decreaseAmountExplosion = 15; // rmattack2
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)
+ var decreaseAmountLaser = 20; // rmattack3
+ var minIntervalLaser = 120;
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)
+ var decreaseAmountSpreader = 30; // rmattack4
+ var minIntervalSpreader = 180;
this.spreaderSpawnInterval = Math.max(minIntervalSpreader, this.spreaderSpawnInterval - decreaseAmountSpreader);
console.log("Nowy interwał spreader:", this.spreaderSpawnInterval);
+ // (Opcjonalnie) Można też delikatnie skracać minimalne/maksymalne opóźnienie dla rmattack1, jeśli używa timera.
+ // np. zmniejszając stałe 800 i 1500 w game.update -> _spawnRandomRmattack
+ // --- SKALOWANIE WARSTWOWE ---
+ // Poziom 2+: Zwiększaj zakres prędkości pocisków (rmattack1 i dzieci spreadera)
+ // (Zmiany będą zastosowane w launchRmattack1 i game.update/spreader)
+ console.log("Aktualny zakres prędkości rmattack1: [", (this.rmattack1BaseMinSpeed + (currentDifficulty - 1) * this.rmattack1SpeedScale * 0.5).toFixed(1), "-", (this.rmattack1BaseMaxSpeed + (currentDifficulty - 1) * this.rmattack1SpeedScale).toFixed(1), "]");
+ console.log("Aktualny zakres prędkości dzieci spreadera: [", (this.spreaderBaseChildMinSpeed + (currentDifficulty - 1) * this.spreaderChildSpeedScale * 0.5).toFixed(1), "-", (this.spreaderBaseChildMaxSpeed + (currentDifficulty - 1) * this.spreaderChildSpeedScale).toFixed(1), "]");
+ // Poziom 3+: Zwiększaj liczbę dzieci spreadera
+ if (currentDifficulty >= 3) {
+ // Zwiększaj co drugi poziom (np. na 3, 5, 7...)
+ if (currentDifficulty % 2 !== 0) {
+ this.spreaderBaseNumProjectiles += this.spreaderNumProjectilesScale;
+ console.log("Zwiększono bazową liczbę dzieci spreadera do:", this.spreaderBaseNumProjectiles);
+ }
+ }
+ // Poziom 4+: Skracaj czas ostrzeżenia lasera
+ if (currentDifficulty >= 4) {
+ // Skracaj co poziom
+ this.laserWarningTimeBase = Math.max(this.laserWarningTimeMin, this.laserWarningTimeBase - this.laserWarningTimeDecrement);
+ console.log("Skrócono bazowy czas ostrzeżenia lasera do:", this.laserWarningTimeBase);
+ }
+ // Poziom 5+: Skracaj czas przed eksplozją
+ if (currentDifficulty >= 5) {
+ // Skracaj co poziom
+ this.explosionFuseTimeBase = Math.max(this.explosionFuseTimeMin, this.explosionFuseTimeBase - this.explosionFuseTimeDecrement);
+ console.log("Skrócono bazowy czas przed eksplozją do:", this.explosionFuseTimeBase);
+ }
+ // Wyświetl komunikat o wzroście trudności (bez zmian)
if (ui && ui.showMessage) {
- ui.showMessage("Difficulty Increased! (" + this.rollMasterDifficulty + ")", 1500);
+ ui.showMessage("Difficulty Increased! (" + currentDifficulty + ")", 1500);
}
},
// Przejście do stanu Game Over
// isDeath: true (gracz zginął), false (czas minął w Boss+)
@@ -2722,8 +2704,12 @@
if (this.rollMasterTimerInterval) {
LK.clearInterval(this.rollMasterTimerInterval);
this.rollMasterTimerInterval = null;
}
+ if (this.randomRmattack1Timer) {
+ LK.clearTimeout(this.randomRmattack1Timer);
+ this.randomRmattack1Timer = null;
+ }
if (this.rollMasterAttacks && this.rollMasterAttacks.length > 0) {
this.rollMasterAttacks.forEach(function (attack) {
if (attack.visual && attack.visual.destroy) {
attack.visual.destroy();
@@ -2816,19 +2802,17 @@
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", "fakeTutorial", "fakeGameOver", "realTutorial", "grillMenu", "gameOver", "rollMaster"];
+ 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; // Reset end point for gesture
+ gameState.touchEnd.x = x;
gameState.touchEnd.y = y;
- // --- NEW: Set input active flag and initial current position ---
gameState.isInputActive = true;
gameState.currentInputPos.x = x;
gameState.currentInputPos.y = y;
- // ------------------------------------------------------------------
}
// 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.
@@ -2858,40 +2842,45 @@
}
}
};
function launchRmattack1() {
+ //console.log("Launch rmattack1 (projectile)"); // Można wyciszyć, jeśli jest za dużo logów
var projectileRadius = 45;
var startX, startY;
var edge = Math.floor(Math.random() * 4);
+ var gameWidth = 2048;
+ var gameHeight = 2732;
+ // Pozycjonowanie startowe
if (edge === 0) {
- // top
- startX = Math.random() * 2048;
+ startX = Math.random() * gameWidth;
startY = -projectileRadius;
} else if (edge === 1) {
- // right
- startX = 2048 + projectileRadius;
- startY = Math.random() * 2732;
+ startX = gameWidth + projectileRadius;
+ startY = Math.random() * gameHeight;
} else if (edge === 2) {
- // bottom
- startX = Math.random() * 2048;
- startY = 2732 + projectileRadius;
+ startX = Math.random() * gameWidth;
+ startY = gameHeight + projectileRadius;
} else {
- // left
startX = -projectileRadius;
- startY = Math.random() * 2732;
+ startY = Math.random() * gameHeight;
}
- var targetX = 2048 / 2;
- var targetY = 2732 / 2;
+ // Celowanie w środek
+ var targetX = gameWidth / 2;
+ var targetY = gameHeight / 2;
var dx = targetX - startX;
var dy = targetY - startY;
var angle = Math.atan2(dy, dx);
- var dist = Math.sqrt(dx * dx + dy * dy);
- var speed = 30; // Stała prędkość - można zmienić na skalowaną
- // var speed = 30 + gameState.rollMasterDifficulty * 2; // Przykład skalowania
- var endX = startX + Math.cos(angle) * (dist + 1000);
- var endY = startY + Math.sin(angle) * (dist + 1000);
- var rotation = angle; // Używamy tej rotacji dla samego obiektu sprite
+ // --- ZMIANA: Losowa prędkość w zakresie zależnym od trudności ---
+ var difficultyFactor = gameState.rollMasterDifficulty - 1; // Zaczynamy od 0 dla poziomu 1
+ var minSpeed = gameState.rmattack1BaseMinSpeed + difficultyFactor * gameState.rmattack1SpeedScale * 0.5; // Minimalna prędkość rośnie wolniej
+ var maxSpeed = gameState.rmattack1BaseMaxSpeed + difficultyFactor * gameState.rmattack1SpeedScale; // Maksymalna prędkość rośnie szybciej
+ var speed = minSpeed + Math.random() * (maxSpeed - minSpeed); // Losowa prędkość w zakresie [minSpeed, maxSpeed]
+ // --- KONIEC ZMIANY ---
+ var vx = Math.cos(angle) * speed;
+ var vy = Math.sin(angle) * speed;
+ var rotation = angle;
var scaleX = 1;
+ // Tworzenie animacji
var frames = [LK.getAsset('rmattack1_0', {
anchorX: 0.5,
anchorY: 0.5
}), LK.getAsset('rmattack1_1', {
@@ -2922,26 +2911,22 @@
anchorX: 0.5,
anchorY: 0.5
});
sprite.scaleX = scaleX;
- sprite.rotation = rotation; // Ustawienie rotacji dla rmattack1
+ sprite.rotation = rotation;
game.addChild(sprite);
- tween(sprite, {
- x: endX,
- y: endY
- }, {
- duration: 4200,
- easing: tween.linear,
- onFinish: function onFinish() {
- if (sprite && !sprite.destroyed) {
- sprite.destroy();
- }
- }
+ // Dodanie do listy ataków
+ gameState.rollMasterAttacks.push({
+ type: 'projectile',
+ visual: sprite,
+ vx: vx,
+ vy: vy,
+ radius: projectileRadius
});
}
// Funkcja launchRmattack2 ZE ZMIANĄ POZYCJONOWANIA
function launchRmattack2() {
- // Obliczanie bezpiecznej strefy dla bomby
+ //console.log("Launch rmattack2 (explosion)");
var bombWidth = 120;
var bombHeight = 120;
var halfBombWidth = bombWidth / 2;
var halfBombHeight = bombHeight / 2;
@@ -2950,9 +2935,8 @@
var minY_b = halfBombHeight;
var maxY_b = 2732 - halfBombHeight;
var x = minX_b + Math.random() * (maxX_b - minX_b);
var y = minY_b + Math.random() * (maxY_b - minY_b);
- // Reszta funkcji bez zmian
var idleFrame = LK.getAsset('rmattack2_explode_0', {
anchorX: 0.5,
anchorY: 0.5
});
@@ -2973,8 +2957,11 @@
yoyo: true,
repeat: Infinity,
easing: tween.inOutQuad
});
+ // --- ZMIANA: Użycie bazowego czasu przed eksplozją z gameState ---
+ var fuseTime = gameState.explosionFuseTimeBase; // Pobierz aktualny bazowy czas
+ // --- KONIEC ZMIANY ---
LK.setTimeout(function () {
if (bomb.destroyed) {
return;
}
@@ -2985,11 +2972,12 @@
} catch (e) {
console.warn("idleTween stop error:", e);
}
var explosionFrames = [];
+ var finalScale = 0;
for (var i = 0; i < 11; i++) {
- // Zmiana: Pętla do 11 klatek
var scale = 1 + i * 0.4;
+ finalScale = scale;
var frame = LK.getAsset("rmattack2_explode_" + i, {
anchorX: 0.5,
anchorY: 0.5,
scaleX: scale,
@@ -3007,17 +2995,37 @@
anchorY: 0.5
});
bomb.destroy();
game.addChild(explosion);
+ var explosionDurationFrames = explosionFrames.length * (80 / (1000 / 60));
+ var explosionMaxRadius = bombWidth * finalScale / 2 * 0.8;
+ var explosionData = {
+ type: 'explosion',
+ visual: explosion,
+ radius: 0,
+ maxRadius: explosionMaxRadius,
+ currentFrame: 0,
+ totalFrames: explosionFrames.length,
+ frameDurationTicks: 80 / (1000 / 60),
+ timer: 0,
+ damageDealt: false
+ };
+ gameState.rollMasterAttacks.push(explosionData);
+ //console.log("Dodano eksplozję do śledzenia, maxRadius:", explosionMaxRadius);
LK.setTimeout(function () {
+ 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();
}
}, explosionFrames.length * 80 + 100);
- }, 2000);
+ }, fuseTime); // Użyj zmiennej fuseTime
}
// Funkcja launchRmattack3 (bez zmian, już miała bezpieczne pozycjonowanie i pulsowanie)
function launchRmattack3() {
+ //console.log("Launch rmattack3 (laser)");
var margin = 100;
var halfLaserWidth = 128 / 2;
var halfLaserHeight = 1012 / 2;
var minX = margin + halfLaserWidth;
@@ -3048,47 +3056,14 @@
warningAnim.stopPulsing = false;
var currentPulseTween = null;
game.addChild(warningAnim);
var pulseDuration = 250;
- 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;
- }
- }
- });
- }
- 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;
- }
- }
- });
- }
+ function pulseUp() {/*...*/} // Funkcje pulsowania bez zmian
+ function pulseDown() {/*...*/} // Funkcje pulsowania bez zmian
pulseDown();
+ // --- ZMIANA: Użycie bazowego czasu ostrzeżenia z gameState ---
+ var warningTime = gameState.laserWarningTimeBase; // Pobierz aktualny bazowy czas
+ // --- KONIEC ZMIANY ---
LK.setTimeout(function () {
if (warningAnim && !warningAnim.destroyed) {
warningAnim.stopPulsing = true;
}
@@ -3101,14 +3076,16 @@
}
}
if (warningAnim && !warningAnim.destroyed) {
var activeFrames = [];
+ var laserStrikeWidth = 228;
+ var laserStrikeHeight = 1212;
for (var j = 0; j < 5; j++) {
activeFrames.push(LK.getAsset('rmattack3_strike_' + j, {
anchorX: 0.5,
anchorY: 0.5,
- width: 228,
- height: 1212
+ width: laserStrikeWidth,
+ height: laserStrikeHeight
}));
}
var strikeAnim = new SpriteAnimation({
frames: activeFrames,
@@ -3120,16 +3097,30 @@
anchorY: 0.5,
alpha: 1
});
game.addChild(strikeAnim);
+ var laserDuration = 4000; // Czas życia lasera - na razie stały
+ var laserData = {
+ type: 'laser',
+ visual: strikeAnim,
+ width: laserStrikeWidth,
+ height: laserStrikeHeight,
+ lifeTime: laserDuration / (1000 / 60)
+ };
+ gameState.rollMasterAttacks.push(laserData);
+ //console.log("Dodano laser do śledzenia.");
warningAnim.destroy();
LK.setTimeout(function () {
+ 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();
}
- }, 4000);
+ }, laserDuration);
}
- }, 3000);
+ }, warningTime); // Użyj zmiennej warningTime
}
// Funkcja launchRmattack4 POPRAWIONA (tylko tworzenie, z bezpiecznym pozycjonowaniem 200px margin)
function launchRmattack4() {
// Obliczanie bezpiecznej strefy dla spreadera z marginesem 200px
@@ -3173,170 +3164,260 @@
}
// --- Główna pętla aktualizacji gry ---
game.update = function () {
if (ui) {
- // Aktualizacja UI (bez zmian)
- ui.updateDeathsCounter();
+ // Aktualizacja UI (bez zmian) [cite: 113]
+ ui.updateDeathsCounter(); // [cite: 113]
if (boss) {
- // Ta część nie dotyczy Roll Master
- var maxHp = boss.maxHealth > 0 ? boss.maxHealth : 1;
- ui.updateBossHealth(boss.health, maxHp);
+ // [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)) {
- ui.updateBossHealth(0, 1); // Ukryj pasek HP bossa
+ // [cite: 115]
+ ui.updateBossHealth(0, 1); // [cite: 115]
}
}
if (player) {
+ // [cite: 116]
if (gameState.currentState === "rollMaster") {
- ui.updateHearts(player.health, 1); // W Roll Master zawsze 1 serce
- } else {
- ui.updateHearts(player.health, storage.maxHearts); // W trybie bossa normalnie
- }
+ ui.updateHearts(player.health, 1);
+ } // [cite: 116]
+ else {
+ ui.updateHearts(player.health, storage.maxHearts);
+ } // [cite: 117]
} else if (gameState.currentState !== "game" && gameState.currentState !== "gameOver") {
- ui.updateHearts(0, storage.maxHearts); // Ukryj serca poza grą
+ // [cite: 118]
+ ui.updateHearts(0, storage.maxHearts); // [cite: 118]
}
}
- // Logika dla stanu "game" (bez zmian)
+ // 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"
+ // 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 && gameState.attackUnlockOrder && gameState.unlockedAttacks.length < gameState.attackUnlockOrder.length && gameState.rollMasterTime >= gameState.nextUnlockTime) {
+ var nextAttackIndex = gameState.unlockedAttacks.length;
+ var attackToUnlock = gameState.attackUnlockOrder[nextAttackIndex];
+ // Dodatkowe sprawdzenie, na wszelki wypadek
+ if (attackToUnlock) {
+ gameState.unlockedAttacks.push(attackToUnlock);
+ gameState.nextUnlockTime += 15;
+ console.log("Odblokowano atak:", attackToUnlock, "Następne odblokowanie w:", gameState.nextUnlockTime, "s.");
+ if (ui && ui.showMessage) {
+ var friendlyName = attackToUnlock.replace('rmattack1', 'Projectiles').replace('rmattack2', 'Explosions').replace('rmattack3', 'Lasers').replace('rmattack4', 'Spreaders');
+ ui.showMessage("New Attack Unlocked: " + friendlyName, 2000);
+ }
+ } else {
+ console.warn("Próba odblokowania nieistniejącego ataku, index:", nextAttackIndex);
+ }
}
- // USUNIĘTO blok odblokowywania ataków
+ // --- KONIEC ZMIANY ---
+ // --- ZMIANA: Spawnowanie ataków z uwzględnieniem odblokowania ---
// --- Spawner Pocisków (rmattack1) ---
- if (!gameState.randomRmattack1Timer) {
- var _spawnRandomRmattack = function spawnRandomRmattack1() {
- if (gameState.currentState !== "rollMaster") {
- return;
- }
- launchRmattack1();
- var nextDelay = 500 + Math.random() * 1100;
- gameState.randomRmattack1Timer = LK.setTimeout(_spawnRandomRmattack, nextDelay);
- };
- _spawnRandomRmattack();
+ // 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) ---
- // USUNIĘTO sprawdzanie flagi - działa od początku
- if (gameState.explosionSpawnInterval > 0) {
- gameState.explosionSpawnTimer++;
+ if (gameState.unlockedAttacks.includes('rmattack2') && gameState.explosionSpawnInterval > 0) {
+ // [cite: 123]
+ gameState.explosionSpawnTimer++; // [cite: 123]
if (gameState.explosionSpawnTimer >= gameState.explosionSpawnInterval) {
- gameState.explosionSpawnTimer = 0;
- launchRmattack2();
+ // [cite: 124]
+ gameState.explosionSpawnTimer = 0; // [cite: 124]
+ launchRmattack2(); // [cite: 124]
}
}
// --- Spawner Lasera (rmattack3) ---
- // USUNIĘTO sprawdzanie flagi - działa od początku
- if (gameState.laserSpawnInterval > 0) {
- gameState.laserSpawnTimer++;
+ if (gameState.unlockedAttacks.includes('rmattack3') && gameState.laserSpawnInterval > 0) {
+ // [cite: 124]
+ gameState.laserSpawnTimer++; // [cite: 124]
if (gameState.laserSpawnTimer >= gameState.laserSpawnInterval) {
- gameState.laserSpawnTimer = 0;
- launchRmattack3();
+ // [cite: 125]
+ gameState.laserSpawnTimer = 0; // [cite: 125]
+ launchRmattack3(); // [cite: 125]
}
}
// --- Spawner Spreader Parent (rmattack4) ---
- // USUNIĘTO sprawdzanie flagi i poprawiono wywołanie
- if (gameState.spreaderSpawnInterval > 0) {
- gameState.spreaderSpawnTimer++;
+ if (gameState.unlockedAttacks.includes('rmattack4') && gameState.spreaderSpawnInterval > 0) {
+ // [cite: 125]
+ gameState.spreaderSpawnTimer++; // [cite: 125]
if (gameState.spreaderSpawnTimer >= gameState.spreaderSpawnInterval) {
- gameState.spreaderSpawnTimer = 0;
- console.log("Spawnuję Spreader Parent (rmattack4)!");
- launchRmattack4(); // Wywołanie poprawionej funkcji
+ // [cite: 126]
+ gameState.spreaderSpawnTimer = 0; // [cite: 126]
+ console.log("Spawnuję Spreader Parent (rmattack4)!"); // [cite: 126]
+ launchRmattack4(); // [cite: 126]
}
}
- // --- Ruch pocisków typu projectile (rmattack1 i dzieci rmattack4) ---
+ // --- 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];
- if (atk.type === 'projectile' && atk.visual && !atk.visual.destroyed) {
- atk.visual.x += atk.vx;
- atk.visual.y += atk.vy;
+ // 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) {
- if (atk.visual.destroy) {
- atk.visual.destroy();
+ // [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]
}
- gameState.rollMasterAttacks.splice(i, 1);
- continue;
}
- if (player && !player.dead && !player.rolling) {
- var dx_p = player.x - atk.visual.x;
- var dy_p = player.y - atk.visual.y;
- var dist_p = Math.sqrt(dx_p * dx_p + dy_p * dy_p);
- var playerRadius = player.width / 2 * 0.8;
- var projectileRadius = atk.radius || 45;
- if (dist_p < playerRadius + projectileRadius) {
+ } 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);
}
- if (atk.visual.destroy) {
- atk.visual.destroy();
- }
- gameState.rollMasterAttacks.splice(i, 1);
- continue;
+ atk.damageDealt = true; // Zadano obrażenia
+ // Nie usuwamy od razu, pozwalamy animacji dokończyć
}
}
- }
- // Aktualizacja animacji
- if (atk && atk.visual && atk.visual.update && typeof atk.visual.update === 'function' && !atk.visual.destroyed) {
- atk.visual.update();
- }
- }
- // --- Aktualizacja laserów (rmattack3) ---
- for (var i = gameState.rollMasterAttacks.length - 1; i >= 0; i--) {
- var atk = gameState.rollMasterAttacks[i];
- if (atk.type === 'laser' && atk.visual && !atk.visual.destroyed) {
- // Kolizja (bez zmian)
+ // 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) {
- var laserHalfWidth = (atk.visual.width || 128) / 2;
- var laserHalfHeight = (atk.visual.height || 1012) / 2;
- var playerHalfWidth = player.width / 2 * 0.8;
- var playerHalfHeight = player.height / 2 * 0.8;
- var laserLeft = atk.visual.x - laserHalfWidth;
- var laserRight = atk.visual.x + laserHalfWidth;
- var laserTop = atk.visual.y - laserHalfHeight;
- var laserBottom = atk.visual.y + laserHalfHeight;
- var playerLeft = player.x - playerHalfWidth;
- var playerRight = player.x + playerHalfWidth;
- var playerTop = player.y - playerHalfHeight;
- var playerBottom = player.y + playerHalfHeight;
+ // [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) {
- player.takeDamage(1);
+ // [cite: 141]
+ player.takeDamage(1); // [cite: 141]
+ // Laser zadaje obrażenia ciągle, dopóki gracz w nim jest
}
}
}
- // Aktualizacja animacji
- if (atk.visual.update && typeof atk.visual.update === 'function') {
- atk.visual.update();
+ // 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
}
- }
- }
- // --- Aktualizacja spreader_parent (rmattack4) ---
- for (var i = gameState.rollMasterAttacks.length - 1; i >= 0; i--) {
- var atk = gameState.rollMasterAttacks[i];
- if (atk.type === 'spreader_parent') {
- atk.timer--;
+ } else if (atk.type === 'spreader_parent') {
+ // [cite: 1]
+ atk.timer--; // [cite: 1]
if (atk.timer <= 0) {
- if (atk.visual && atk.visual.destroy && !atk.visual.destroyed) {
- atk.visual.destroy();
- }
- var originX = atk.visual.x;
- var originY = atk.visual.y;
- var projectileSpeed = 6 + gameState.rollMasterDifficulty;
- var projectileRadius = 45;
- var numProjectiles = 12;
+ // Czas na podział [cite: 1]
+ console.log("Spreader Parent dzieli się!"); // [cite: 1]
+ var originX = atk.visual.x; // [cite: 2]
+ var originY = atk.visual.y; // [cite: 2]
+ // --- ZMIANA: Usunięto obliczanie prędkości tutaj ---
+ // var projectileSpeed = 5 + gameState.rollMasterDifficulty * 0.5; // USUNIĘTE
+ var projectileRadius = 45; // [cite: 3] - pozostaje bez zmian
+ // Użyj zaktualizowanej liczby pocisków z gameState
+ var numProjectiles = gameState.spreaderBaseNumProjectiles || 12; // Użyj wartości z gameState lub domyślnej 12 [cite: 3]
for (var j = 0; j < numProjectiles; j++) {
- var angle = Math.PI * 2 / numProjectiles * j;
- var vx = Math.cos(angle) * projectileSpeed;
- var vy = Math.sin(angle) * projectileSpeed;
+ // Pętla tworząca dzieci [cite: 4]
+ var angle = Math.PI * 2 / numProjectiles * j; // [cite: 4]
+ // --- ZMIANA: Obliczanie LOSOWEJ prędkości DLA KAŻDEGO dziecka ---
+ var difficultyFactor_child = gameState.rollMasterDifficulty - 1;
+ var minSpeed_child = gameState.spreaderBaseChildMinSpeed + difficultyFactor_child * gameState.spreaderChildSpeedScale * 0.5;
+ var maxSpeed_child = gameState.spreaderBaseChildMaxSpeed + difficultyFactor_child * gameState.spreaderChildSpeedScale;
+ var projectileSpeed = minSpeed_child + Math.random() * (maxSpeed_child - minSpeed_child); // Losowa prędkość w zakresie
+ // --- KONIEC ZMIANY ---
+ var vx = Math.cos(angle) * projectileSpeed; // Użyj wylosowanej prędkości [cite: 5]
+ var vy = Math.sin(angle) * projectileSpeed; // Użyj wylosowanej prędkości [cite: 5]
var projectileFrames = [
- // Używamy klatek rmattack1
+ // Definicja klatek (bez zmian) [cite: 6]
LK.getAsset('rmattack1_0', {
anchorX: 0.5,
anchorY: 0.5
}), LK.getAsset('rmattack1_1', {
@@ -3356,66 +3437,52 @@
anchorY: 0.5
}), LK.getAsset('rmattack1_6', {
anchorX: 0.5,
anchorY: 0.5
- })];
+ })]; // [cite: 7]
var projectileVisual = new SpriteAnimation({
+ // Tworzenie animacji dziecka (bez zmian) [cite: 7]
frames: projectileFrames,
frameDuration: 100,
loop: true,
x: originX,
y: originY,
anchorX: 0.5,
anchorY: 0.5,
- scaleX: 1.4,
- scaleY: 1.4
+ scaleX: 1.2,
+ scaleY: 1.2 // [cite: 8]
});
- // <-- ZMIANA: Ustawienie rotacji pocisku -->
- projectileVisual.rotation = Math.atan2(vy, vx);
- // <-- KONIEC ZMIANY -->
- var projectileObject = game.addChild(projectileVisual);
+ projectileVisual.rotation = Math.atan2(vy, vx); // [cite: 8]
+ var projectileObject = game.addChild(projectileVisual); // [cite: 8]
+ // Dodaj dziecko do listy ataków (bez zmian) [cite: 9]
gameState.rollMasterAttacks.push({
type: 'projectile',
visual: projectileObject,
vx: vx,
vy: vy,
radius: projectileRadius
- });
- }
- gameState.rollMasterAttacks.splice(i, 1);
- continue; // Przejdź do następnego ataku
+ }); // [cite: 9]
+ } // Koniec pętli for tworzącej dzieci
+ shouldRemove = true; // Usuń rodzica po podziale [cite: 9]
}
- // Kolizja rodzica z graczem (bez zmian)
- else if (atk.visual && !atk.visual.destroyed && player && !player.dead && !player.rolling) {
- var dx_sp = player.x - atk.visual.x;
- var dy_sp = player.y - atk.visual.y;
- var dist_sp = Math.sqrt(dx_sp * dx_sp + dy_sp * dy_sp);
- var playerRadius_sp = player.width / 2 * 0.8;
- var spreaderRadius = atk.radius || 60;
+ // Kolizja rodzica z graczem (dopóki się nie podzieli) - bez zmian [cite: 10]
+ else if (player && !player.dead && !player.rolling) {
+ var dx_sp = player.x - atk.visual.x; // [cite: 10]
+ var dy_sp = player.y - atk.visual.y; // [cite: 11]
+ var dist_sp = Math.sqrt(dx_sp * dx_sp + dy_sp * dy_sp); // [cite: 11]
+ var playerRadius_sp = player.width / 2 * 0.8; // [cite: 12]
+ var spreaderRadius = atk.radius || 60; // [cite: 12]
if (dist_sp < playerRadius_sp + spreaderRadius) {
+ // [cite: 13]
+ console.log("Kolizja: Spreader Parent!"); // DEBUG [cite: 13]
if (!player.invulnerable) {
player.takeDamage(1);
- }
+ } // [cite: 14]
}
}
}
- // Aktualizacja animacji (przeniesiona do wspólnej pętli wyżej)
- if (atk && atk.visual && atk.visual.update && typeof atk.visual.update === 'function' && !atk.visual.destroyed) {
- atk.visual.update();
- }
}
- // Sprawdzenie śmierci gracza w trybie Roll Master
- if (player && player.dead) {
- if (typeof gameState !== 'undefined' && typeof gameState.endRollMasterMode === 'function') {
- gameState.endRollMasterMode(gameState.rollMasterTime);
- } else {
- console.error("Nie można zakończyć trybu Roll Master - brak gameState lub metody endRollMasterMode");
- if (typeof gameState !== 'undefined' && typeof gameState.showGrillScreen === 'function') {
- gameState.showGrillScreen();
- }
- }
- }
- } // Koniec else if (gameState.currentState === "rollMaster")
+ } // 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') {