Code edit (3 edits merged)
Please save this source code
User prompt
Please fix the bug: 'TypeError: self.clearRollTimeouts is not a function' in or related to this line: 'self.clearRollTimeouts();' Line Number: 627
Code edit (1 edits merged)
Please save this source code
Code edit (6 edits merged)
Please save this source code
User prompt
add new asset confirmRestButton
Code edit (1 edits merged)
Please save this source code
User prompt
add new asset coffinDanceMeme
Code edit (2 edits merged)
Please save this source code
User prompt
add new assets deathMeme1 to deathMeme5
Code edit (1 edits merged)
Please save this source code
Code edit (3 edits merged)
Please save this source code
User prompt
add new asset creditsbg
Code edit (14 edits merged)
Please save this source code
User prompt
add new asset buttoncredits
Code edit (1 edits merged)
Please save this source code
Code edit (1 edits merged)
Please save this source code
User prompt
add new assets fireballnew1 fireballnew2 fireballnew3 fireballnew4
Code edit (1 edits merged)
Please save this source code
Code edit (1 edits merged)
Please save this source code
Code edit (1 edits merged)
Please save this source code
Code edit (6 edits merged)
Please save this source code
User prompt
add new assets rmattack2_explode_7 to rmattack2_explode_10
Code edit (7 edits merged)
Please save this source code
User prompt
add new asset rmattack4_parent_3
Code edit (1 edits merged)
Please save this source code
/****
* 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" || gameState.currentState === "rollMaster") && player && !player.dead) {
var targetX = gameState.currentInputPos.x;
var targetY = gameState.currentInputPos.y;
var dx_m = targetX - self.x;
var dy_m = targetY - self.y;
var distance_m = Math.sqrt(dx_m * dx_m + dy_m * dy_m);
if (distance_m > 10) {
var normalizedX = dx_m / distance_m;
var normalizedY = dy_m / distance_m;
var moveSpeed = 3; // Prędkość chodzenia - można by użyć self.speed?
self.x += normalizedX * moveSpeed;
self.y += normalizedY * moveSpeed;
if (normalizedX > 0.1) {
targetScaleX = 1;
} else if (normalizedX < -0.1) {
targetScaleX = -1;
}
}
}
if (self.idleAnimationSprite && !self.idleAnimationSprite.destroyed) {
self.idleAnimationSprite.scaleX = targetScaleX;
}
}
// Aktualizacja animacji idle
if (self.idleAnimationSprite && self.idleAnimationSprite.visible && !self.idleAnimationSprite.destroyed && self.idleAnimationSprite.update) {
self.idleAnimationSprite.update();
}
}; // Koniec funkcji self.update
return self;
});
// <--- To zamyka Container.expand dla gracza
var Shape = Container.expand(function (options) {
var self = Container.call(this);
// <--- Tutaj zaczyna się Shape
// ...
options = options || {};
var width = options.width || 100;
var height = options.height || 100;
var color = options.color || 0xFFFFFF;
var shape = options.shape || 'box';
// Create the shape as an asset
var asset = self.attachAsset(shape, {
anchorX: 0.5,
anchorY: 0.5,
width: width,
height: height,
tint: color
});
// Set width and height for easier access
self.width = width;
self.height = height;
// Add anchor property to Shape for positioning
self.anchor = {
set: function set(x, y) {
// This mimics the behavior of the anchor.set method
self.anchorX = x;
self.anchorY = y;
}
};
return self;
});
var SpriteAnimation = Container.expand(function (options) {
var self = Container.call(this);
// Initialize with default values
options = options || {};
self.frames = options.frames || [];
self.frameDuration = options.frameDuration || 120; // Default 100ms per frame
self.loop = options.loop !== undefined ? options.loop : true;
self.currentFrame = 0;
self.frameTimer = 0;
self.playing = true;
// Set initial position and anchor if provided
if (options.x !== undefined) {
self.x = options.x;
}
if (options.y !== undefined) {
self.y = options.y;
}
// Handle anchor options
if (options.anchorX !== undefined || options.anchorY !== undefined) {
var anchorX = options.anchorX !== undefined ? options.anchorX : 0;
var anchorY = options.anchorY !== undefined ? options.anchorY : 0;
// Apply anchor to all frames
self.frames.forEach(function (frame) {
if (frame && frame.anchor) {
frame.anchor.set(anchorX, anchorY);
}
});
}
// Add the first frame to display initially
if (self.frames.length > 0) {
self.removeChildren();
var firstFrame = self.frames[self.currentFrame];
if (firstFrame && firstFrame.anchor) {
firstFrame.anchor.set(options.anchorX || 0, options.anchorY || 0);
}
self.addChild(firstFrame);
}
// Animation update method
self.update = function () {
if (!self.playing || self.frames.length === 0) {
return;
}
self.frameTimer++;
if (self.frameTimer >= self.frameDuration / (1000 / 60)) {
self.frameTimer = 0;
if (self.children.length > 0) {
self.removeChild(self.children[0]); // Usuwamy tylko 1 aktualną klatkę
}
self.currentFrame++;
if (self.currentFrame >= self.frames.length) {
if (self.loop) {
self.currentFrame = 0;
} else {
self.currentFrame = self.frames.length - 1;
self.playing = false;
}
}
self.addChild(self.frames[self.currentFrame]); // Dodajemy nową klatkę
}
};
// Method to stop animation
self.stop = function () {
self.playing = false;
};
// Method to start/resume animation
self.play = function () {
self.playing = true;
};
// Method to set a specific frame
self.gotoFrame = function (frameIndex) {
if (frameIndex >= 0 && frameIndex < self.frames.length) {
self.removeChildren();
self.currentFrame = frameIndex;
self.addChild(self.frames[self.currentFrame]);
}
};
return self;
});
var UI = Container.expand(function () {
var self = Container.call(this);
// Create heart containers (wizualizacja zdrowia)
self.hearts = [];
self.heartContainer = new Container(); // Kontener na ikony serc
self.addChild(self.heartContainer);
// Title and messages (teksty na ekranach)
self.titleText = new Text2("ROLL SOULS", {
size: 150,
fill: 0xFFFFFF
});
self.titleText.anchor.set(0.5, 0.5);
self.addChild(self.titleText);
self.messageText = new Text2("", {
size: 60,
fill: 0xFFFFFF
});
self.messageText.anchor.set(0.5, 0.5);
self.addChild(self.messageText);
// Deaths counter (licznik śmierci)
self.deathsText = new Text2("Deaths: 0", {
size: 40,
fill: 0xFFFFFF
});
self.deathsText.anchor.set(1, 0); // Wyrównanie do prawego górnego rogu
self.deathsText.x = 2048 - 50; // Pozycja X od prawej krawędzi
self.deathsText.y = 50; // Pozycja Y od góry
self.addChild(self.deathsText);
// Tutorial text (teksty tutoriali)
self.tutorialText = new Text2("", {
size: 50,
fill: 0xFFFFFF
});
self.tutorialText.anchor.set(0.5, 0); // Wyrównanie do środka na górze
self.tutorialText.x = 2048 / 2; // Pozycja X na środku
self.tutorialText.y = 200; // Pozycja Y (może być różna w zależności od stanu)
self.addChild(self.tutorialText);
// --- Timer Text ---
self.timerText = new Text2("00:00", {
// Zmieniono tekst początkowy
size: 60,
fill: 0xFFFFFF // Biały kolor
});
self.timerText.x = 2048 / 2; // Wyśrodkuj w poziomie
self.timerText.y = 50; // Przysuń do góry
self.timerText.anchor.set(1, 0); // Pozycja Y od górnej krawędzi
self.timerText.alpha = 0; // Domyślnie ukryty
self.addChild(self.timerText);
// --- DODANY NOWY ELEMENT TEKSTOWY DLA REKORDU ---
self.highScoreText = new Text2("Best: 00:00", {
// Tekst początkowy
size: 50,
// Mniejszy rozmiar niż aktualny czas
fill: 0xFFFF00 // Żółty kolor dla odróżnienia
});
self.highScoreText.x = 2048 - 50; // Przysuń do prawej krawędzi (z marginesem 50)
self.highScoreText.y = 50; // Przysuń do góry (ta sama wysokość co timer)
self.highScoreText.anchor.set(1, 0); // Pozycja Y (poniżej timerText)
self.highScoreText.alpha = 0; // Domyślnie ukryty
self.addChild(self.highScoreText); // Dodaj do kontenera UI
// --- KONIEC DODAWANIA NOWEGO ELEMENTU ---
// --- Boss Health Bar (wizualizacja zdrowia bossa) ---
self.bossHealthBarContainer = new Container(); // Kontener na pasek zdrowia bossa
self.bossHealthBarContainer.x = 2048 / 2; // Wyśrodkuj poziomo
self.bossHealthBarContainer.y = 150; // Pozycja Y (poniżej timera)
self.bossHealthBarContainer.alpha = 0; // Domyślnie ukryty
self.addChild(self.bossHealthBarContainer);
var barWidth = 800; // Szerokość paska zdrowia (musi być taka sama jak szerokość assetu bossHpbar)
var barHeight = 30; // Wysokość paska zdrowia (musi być taka sama jak wysokość assetu bossHpbar)
// Właściwy pasek zdrowia bossa (czerwony) - UŻYWAMY TERAZ NOWEGO ASSETU bossHpbar
self.bossHealthBar = self.attachAsset('bossHpbar', {
// Użyj assetu 'bossHpbar'
anchorX: 0,
// Ustaw punkt odniesienia na lewą krawędź (0), środek pionowo (0.5)
anchorY: 0.5,
x: -barWidth / 2 // Przesuń w lewo o połowę szerokości tła
});
self.bossHealthBarContainer.addChild(self.bossHealthBar);
// --- METODY AKTUALIZACJI UI ---
// Aktualizuje wizualizację serc na UI
self.updateHearts = function (current, max) {
current = Math.max(0, current || 0);
max = Math.max(1, max || 1); // Max musi być co najmniej 1
if (self.hearts.length !== max) {
while (self.hearts.length > 0) {
var oldHeart = self.hearts.pop();
if (oldHeart && oldHeart.destroy && !oldHeart.destroyed) {
oldHeart.destroy();
}
}
self.heartContainer.removeChildren(); // Usuń wizualne obiekty serc
for (var i = 0; i < max; i++) {
var heart = LK.getAsset('heart', {
anchorX: 0.5,
anchorY: 0.5,
x: i * 50,
y: 0,
tint: i < current ? 0xFF0000 : 0x555555
});
self.hearts.push(heart);
self.heartContainer.addChild(heart);
}
self.heartContainer.x = (2048 - max * 50) / 2 + 25;
self.heartContainer.y = 100; // Zmieniono pozycję serc, aby zrobić miejsce
} else {
for (var j = 0; j < self.hearts.length; j++) {
if (self.hearts[j]) {
self.hearts[j].tint = j < current ? 0xFF0000 : 0x555555;
}
}
}
};
// Aktualizuje wizualizację paska zdrowia bossa
self.updateBossHealth = function (current, max) {
current = Math.max(0, current || 0);
max = Math.max(1, max || 1);
var barWidth = 800;
var currentWidth = current / max * barWidth;
if (self.bossHealthBar) {
self.bossHealthBar.width = currentWidth;
var shouldBeVisible = (gameState.currentState === "game" || gameState.currentState === "gameOver") && (boss && !boss.dead && current > 0 || gameState.currentState === "gameOver" && !gameOverReasonIsDeath && isNewBossPlusMode);
self.bossHealthBarContainer.alpha = shouldBeVisible ? 1 : 0;
}
if (self.bossHealthBarBg) {
// Jeśli używasz tła paska HP
self.bossHealthBarBg.alpha = self.bossHealthBarContainer.alpha;
}
};
// Wyświetla komunikat na środku ekranu
self.showMessage = function (message, duration) {
self.messageText.setText(message);
self.messageText.alpha = 1;
if (self.messageTimeout) {
LK.clearTimeout(self.messageTimeout);
self.messageTimeout = null;
}
if (duration && duration > 0) {
self.messageTimeout = LK.setTimeout(function () {
tween(self.messageText, {
alpha: 0
}, {
duration: 500,
onFinish: function onFinish() {
self.messageTimeout = null;
}
});
}, duration);
} else {
self.messageText.alpha = 1;
}
};
// Wyświetla tekst tutorialu
self.showTutorial = function (text) {
self.tutorialText.setText(text);
self.tutorialText.alpha = 1;
};
// Ukrywa tekst tutorialu (zanikając)
self.hideTutorial = function () {
tween(self.tutorialText, {
alpha: 0
}, {
duration: 500
});
};
// Aktualizuje licznik śmierci
self.updateDeathsCounter = function () {
var deaths = storage.totalDeaths || 0;
self.deathsText.setText("Deaths: " + deaths);
self.deathsText.visible = gameState.currentState === "game";
};
// Aktualizuje wyświetlanie czasu timera
self.updateTimerDisplay = function (seconds) {
seconds = Math.max(0, seconds || 0);
var minutes = Math.floor(seconds / 60);
var remainingSeconds = seconds % 60;
var formattedSeconds = remainingSeconds < 10 ? '0' + remainingSeconds : remainingSeconds;
var formattedTime = String(minutes).padStart(2, '0') + ':' + formattedSeconds;
if (self.timerText) {
// W Roll Master wyświetlamy "Time:", w innych trybach tylko czas
var prefix = gameState.currentState === "rollMaster" ? "Time: " : "";
self.timerText.setText(prefix + formattedTime);
}
};
// --- DODANA NOWA FUNKCJA DO AKTUALIZACJI TEKSTU REKORDU ---
self.updateHighScoreDisplay = function (seconds) {
seconds = Math.max(0, seconds || 0);
var minutes = Math.floor(seconds / 60);
var remainingSeconds = seconds % 60;
var formattedSeconds = remainingSeconds < 10 ? '0' + remainingSeconds : remainingSeconds;
var formattedTime = String(minutes).padStart(2, '0') + ':' + formattedSeconds;
if (self.highScoreText) {
// Sprawdź, czy obiekt istnieje
self.highScoreText.setText("Best: " + formattedTime);
}
};
// --- KONIEC DODAWANIA NOWEJ FUNKCJI ---
// Pozycjonuje elementy UI w zależności od stanu gry
self.positionElements = function (state) {
// Pozycje stałe dla niektórych elementów
self.deathsText.x = 2048 - 150;
self.deathsText.y = 50;
self.heartContainer.y = 80; // Pozycja pionowa serc (może być nadpisywana)
self.bossHealthBarContainer.x = 2048 / 2;
self.bossHealthBarContainer.y = 160; // Pasek HP bossa poniżej timera/serc
// Resetuj widoczność przed ustawieniem dla danego stanu
self.titleText.alpha = 0;
self.messageText.alpha = 0;
self.tutorialText.alpha = 0;
self.timerText.alpha = 0;
self.heartContainer.alpha = 0;
self.bossHealthBarContainer.alpha = 0;
self.deathsText.alpha = 0; // Domyślnie ukryty
self.highScoreText.alpha = 0; // Domyślnie ukryty
// Przywróć domyślną pozycję i styl timerText
if (self.timerText) {
self.timerText.x = 350;
self.timerText.y = 100;
self.timerText.anchor.set(0, 0); // Upewnij się, że anchor jest resetowany
self.timerText.style = {
size: 60,
fill: 0xFFFFFF
};
}
switch (state) {
case "title":
self.titleText.x = 2048 / 2;
self.titleText.y = 800;
self.titleText.alpha = 1;
self.messageText.x = 2048 / 2;
self.messageText.y = 1000;
// self.messageText.alpha = 1; // Ustawiane przez showMessage
self.tutorialText.x = 2048 / 2;
self.tutorialText.y = 1200;
// self.tutorialText.alpha = 1; // Ustawiane przez showTutorial
break;
case "game":
self.messageText.x = 2048 / 2;
self.messageText.y = 1500;
// self.messageText.alpha = 0; // Ustawiane przez showMessage
self.tutorialText.x = 2048 / 2;
self.tutorialText.y = 200;
// self.tutorialText.alpha = 1; // Ustawiane przez showTutorial/hideTutorial
// Pokaż elementy walki
if (self.heartContainer) {
self.heartContainer.alpha = 1;
}
if (self.timerText) {
self.timerText.alpha = 1;
}
if (self.deathsText) {
self.deathsText.alpha = 1;
} // Pokaż licznik śmierci
// Pasek zdrowia bossa jest zarządzany przez updateBossHealth
break;
case "grillMenu":
self.messageText.x = 2048 / 2;
self.messageText.y = 500;
// self.messageText.alpha = 0; // Ustawiane przez showMessage
break;
case "gameOver":
self.titleText.x = 2048 / 2;
self.titleText.y = 800;
// self.titleText.alpha = 1; // Ustawiane w gameState.gameOver
self.messageText.x = 2048 / 2;
self.messageText.y = 1000;
// self.messageText.alpha = 0; // Ustawiane w gameState.gameOver przez showMessage
self.tutorialText.x = 2048 / 2;
self.tutorialText.y = 1200;
// Widoczność paska HP bossa zarządzana przez updateBossHealth
if (self.deathsText) {
self.deathsText.alpha = 1;
} // Pokaż licznik śmierci też na Game Over
break;
case "intro":
case "fakeTutorial":
case "realTutorial":
// Większość elementów ukryta (domyślnie)
break;
// --- DODANA OBSŁUGA WIDOCZNOŚCI W ROLL MASTER ---
case "rollMaster":
// Dostosuj pozycję timera specjalnie dla tego trybu
if (self.timerText) {
self.timerText.x = 50;
self.timerText.y = 50;
self.timerText.anchor.set(0, 0); // Lewy górny róg
self.timerText.style = {
size: 60,
fill: 0xFFFFFF
}; // Ustaw styl, jeśli trzeba
self.timerText.alpha = 1; // Pokaż timer
}
// Pokaż rekord
if (self.highScoreText) {
self.highScoreText.alpha = 1;
}
// Ukryj inne niepotrzebne elementy
if (self.heartContainer) {
self.heartContainer.alpha = 0;
}
if (self.bossHealthBarContainer) {
self.bossHealthBarContainer.alpha = 0;
}
if (self.deathsText) {
self.deathsText.alpha = 0;
}
if (self.tutorialText) {
self.tutorialText.alpha = 0;
}
if (self.titleText) {
self.titleText.alpha = 0;
}
break;
// --- KONIEC DODAWANIA OBSŁUGI ---
}
}; // Koniec positionElements
return self;
});
/****
* Initialize Game
****/
// Koniec var UI = Container.expand(...)
var game = new LK.Game({
backgroundColor: 0x111111 // Ciemne tło domyślne
});
/****
* Game Code
****/
// Globalny kontener na elementy scen intro/tutoriali i Grill Screena (do łatwego czyszczenia)
// Komentarze o assetach pominięte dla zwięzłości
var currentSceneElements = new Container();
// Zmienne gry
var player;
var boss;
var ui;
var walls = []; // Ściany areny bossa
// Zmienna do przechowywania aktywnego tła sceny
var currentBackground = null;
// Flaga do śledzenia, czy jesteśmy w trybie New Boss+
var isNewBossPlusMode = false;
// Flaga do śledzenia przyczyny Game Over (true = gracz zginął, false = czas minął)
var gameOverReasonIsDeath = false;
// Funkcja do czyszczenia elementów z kontenera currentSceneElements (nie usuwa UI ani tła)
function clearScene() {
while (currentSceneElements.children.length > 0) {
var child = currentSceneElements.children[0];
if (child && child.destroy) {
child.destroy(); // Użyj destroy jeśli dostępne
} else if (child && child.parent) {
child.parent.removeChild(child); // Fallback
} else {
// Jeśli obiekt nie ma ani destroy ani parent, usuń go z tablicy (choć to nie powinno się zdarzyć)
currentSceneElements.children.shift();
}
}
// Usuń też dzieci bezpośrednio z 'game', które mogły zostać dodane poza 'currentSceneElements' w scenach,
// ale UWAŻAJ, aby nie usunąć stałych elementów jak UI, walls, currentSceneElements sam w sobie.
// Lepsze podejście: zawsze dodawaj elementy specyficzne dla sceny do currentSceneElements.
}
// Obiekt zarządzający stanami gry
var gameState = {
currentState: "title",
gameDuration: 120,
remainingTime: 0,
gameTimerInterval: null,
fakeTutorialTimerId: null,
bossSpeedIncreased: false,
rollMasterTime: 0,
rollMasterDifficulty: 1,
rollMasterTimerInterval: null,
rollMasterAttacks: [],
attackSpawnTimer: 0,
attackSpawnInterval: 120,
explosionSpawnTimer: 0,
// <<< NOWE: Timer dla eksplozji
explosionSpawnInterval: 240,
// <<< NOWE: Interwał dla eksplozji (np. 4s)
rollMasterHighScore: 0,
isInputActive: false,
currentInputPos: {
x: 0,
y: 0
},
touchStart: {
x: 0,
y: 0
},
touchEnd: {
x: 0,
y: 0
},
grillMenuEffects: [],
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; // Zachowaj kontekst 'this' (gameState)
console.log("Konfiguracja sceny Roll Master...");
// Ustaw tło
try {
currentBackground = LK.getAsset('rollMasterBg', {
anchorX: 0.5,
anchorY: 0.5,
x: 2048 / 2,
y: 2732 / 2
});
game.addChildAt(currentBackground, 0);
} catch (e) {
console.warn("Nie znaleziono assetu 'rollMasterBg', używam koloru tła.");
game.setBackgroundColor(0x222233); // Ciemnoniebieskie tło awaryjne
}
// Stwórz gracza
if (typeof Player !== 'undefined') {
player = game.addChild(new Player());
player.x = 2048 / 2;
player.y = 2732 / 2 + 400; // Pozycja startowa
player.health = 1; // Tylko 1 HP w tym trybie
// Upewnij się, że gracz jest gotowy (reset stanu)
player.rolling = false;
player.invulnerable = false;
player.invulnerabilityFrames = 0;
player.rollCooldown = 0;
if (player && typeof player.clearRollTimeouts === 'function') {
player.clearRollTimeouts();
}
player.hasRolledThroughBossThisRoll = false; // Chociaż nie ma tu bossa
player.alpha = 1;
player.dead = false;
} else {
console.error("Klasa Player nie jest zdefiniowana!");
// Rozważ powrót do menu lub inny handling błędu
this.showGrillScreen();
return;
}
// Inicjalizacja zmiennych trybu Roll Master
this.rollMasterTime = 0;
this.rollMasterDifficulty = 1;
this.rollMasterAttacks = []; // Tablica na aktywne ataki
this.attackUnlockOrder = ['rmattack1', 'rmattack2', 'rmattack3', 'rmattack4']; // Kolejność odblokowywania
this.unlockedAttacks = ['rmattack1']; // Zaczynamy tylko z pierwszym atakiem
this.nextUnlockTime = 15; // Czas (w sekundach) odblokowania następnego ataku
console.log("Ataki startowe:", this.unlockedAttacks, "Następne odblokowanie w:", this.nextUnlockTime + "s");
// Resetuj timery spawnrate'ów
this.attackSpawnTimer = 0; // Timer dla rmattack1 (jeśli używany inaczej niż losowo)
this.attackSpawnInterval = 120; // Interwał dla rmattack1 (2 sekundy)
this.explosionSpawnTimer = 0; // Timer dla rmattack2
this.explosionSpawnInterval = 240; // Interwał dla rmattack2 (4 sekundy)
this.laserSpawnTimer = 0; // Timer dla rmattack3
this.laserSpawnInterval = 300; // Interwał dla rmattack3 (5 sekund)
this.laserWarningTime = 1500; // Czas ostrzeżenia lasera w ms (było 90 klatek)
this.spreaderSpawnTimer = 0; // Timer dla rmattack4
this.spreaderSpawnInterval = 600; // Interwał dla rmattack4 (10 sekund)
this.spreaderSplitTime = 180; // Czas do podziału spreadera w klatkach (3 sekundy)
// Wyczyść ewentualny stary timer dla rmattack1
if (this.randomRmattack1Timer) {
LK.clearTimeout(this.randomRmattack1Timer);
this.randomRmattack1Timer = null;
}
// --- ZMIANA TUTAJ: Wczytanie rekordu i aktualizacja UI ---
this.rollMasterHighScore = storage.rollMasterHighScore || 0;
if (ui && ui.updateHighScoreDisplay) {
// Sprawdź, czy UI i funkcja istnieją
ui.updateHighScoreDisplay(this.rollMasterHighScore); // Wywołaj funkcję aktualizacji rekordu
} else {
console.error("Nie można zaktualizować wyświetlania rekordu - brak ui.updateHighScoreDisplay");
}
// --- KONIEC ZMIANY ---
// Konfiguracja UI dla Roll Master
if (ui && ui.timerText) {
// Ustawienia timera zostały przeniesione do ui.positionElements
ui.updateTimerDisplay(this.rollMasterTime); // Ustaw początkowy czas na 00:00
} else {
console.error("Nie można znaleźć ui.timerText do skonfigurowania!");
}
if (ui && ui.updateHearts) {
ui.updateHearts(1, 1); // Pokaż 1 serce (szare, bo 1/1)
}
if (ui && ui.updateBossHealth) {
ui.updateBossHealth(0, 1); // Ukryj pasek HP bossa
}
// WAŻNE: Wywołaj pozycjonowanie elementów UI PO konfiguracji
if (ui && ui.positionElements) {
ui.positionElements("rollMaster"); // To ustawi widoczność i pozycje
} else {
console.error("Nie można ustawić elementów UI dla Roll Master - brak ui.positionElements");
}
// Główny timer trybu (zwiększanie czasu i trudności)
if (this.rollMasterTimerInterval) {
LK.clearInterval(this.rollMasterTimerInterval); // Wyczyść stary, jeśli istnieje
}
this.rollMasterTimerInterval = LK.setInterval(function () {
if (_this2.currentState !== "rollMaster") {
// Używaj _this2 dla odniesienia do gameState
LK.clearInterval(_this2.rollMasterTimerInterval);
_this2.rollMasterTimerInterval = null;
return;
}
_this2.rollMasterTime++; // Zwiększ czas
if (ui && ui.updateTimerDisplay) {
ui.updateTimerDisplay(_this2.rollMasterTime); // Aktualizuj wyświetlanie czasu
}
// Zwiększanie trudności co 30 sekund (zmieniono z 60 dla szybszej progresji)
if (_this2.rollMasterTime > 0 && _this2.rollMasterTime % 30 === 0) {
_this2.increaseRollMasterDifficulty();
}
// Logika odblokowywania ataków przeniesiona do game.update
}, 1000); // Interwał co 1 sekundę
console.log("Scena Roll Master skonfigurowana.");
},
// <-- WAŻNE: Przecinek tutaj
// --- NOWA FUNKCJA: Zwiększanie trudności w Roll Master ---
increaseRollMasterDifficulty: function increaseRollMasterDifficulty() {
this.rollMasterDifficulty++;
console.log("Trudność Roll Master zwiększona do:", this.rollMasterDifficulty);
var decreaseAmountProjectile = 10;
var minIntervalProjectile = 30;
this.attackSpawnInterval = Math.max(minIntervalProjectile, this.attackSpawnInterval - decreaseAmountProjectile);
console.log("Nowy interwał projectile:", this.attackSpawnInterval);
var decreaseAmountExplosion = 15;
var minIntervalExplosion = 60;
this.explosionSpawnInterval = Math.max(minIntervalExplosion, this.explosionSpawnInterval - decreaseAmountExplosion);
console.log("Nowy interwał explosion:", this.explosionSpawnInterval);
// --- NOWA LOGIKA DLA LASERA ---
var decreaseAmountLaser = 20; // Jak szybko ma skracać się interwał lasera
var minIntervalLaser = 120; // Minimalny interwał lasera (np. 2 sekundy)
this.laserSpawnInterval = Math.max(minIntervalLaser, this.laserSpawnInterval - decreaseAmountLaser);
console.log("Nowy interwał laser:", this.laserSpawnInterval);
// --- NOWA LOGIKA DLA SPREADERA ---
var decreaseAmountSpreader = 30; // Jak szybko ma skracać się interwał spreadera
var minIntervalSpreader = 180; // Minimalny interwał spreadera (np. 3 sekundy)
this.spreaderSpawnInterval = Math.max(minIntervalSpreader, this.spreaderSpawnInterval - decreaseAmountSpreader);
console.log("Nowy interwał spreader:", this.spreaderSpawnInterval);
if (ui && ui.showMessage) {
ui.showMessage("Difficulty Increased! (" + this.rollMasterDifficulty + ")", 1500);
}
},
// Przejście do stanu Game Over
// isDeath: true (gracz zginął), false (czas minął w Boss+)
gameOver: function gameOver(isDeath) {
this.currentState = "gameOver"; // Ustaw stan
gameOverReasonIsDeath = isDeath; // Zapisz przyczynę
// Zatrzymaj timer gry
if (this.gameTimerInterval) {
LK.clearInterval(this.gameTimerInterval);
}
this.gameTimerInterval = null;
this.bossSpeedIncreased = false; // Reset flagi przyspieszenia
// Zatrzymaj animację tła (jeśli istnieje, ale nie powinno w tym stanie)
if (currentBackground) {
tween.stop(currentBackground);
}
game.setBackgroundColor(0x000000); // Czarne tło
// Ukryj/zniszcz gracza i bossa (powinny być już zniszczone przez swoje metody die)
if (player && player.alpha !== 0) {
player.alpha = 0;
} // Ukryj, jeśli nie zniknął
if (boss && boss.alpha !== 0) {
boss.alpha = 0;
}
// Ukryj ściany
walls.forEach(function (wall) {
if (wall) {
wall.alpha = 0;
}
});
// Ustaw UI dla ekranu game over
ui.positionElements("gameOver");
// Logika komunikatu Game Over
var gameOverMessage = "YOU DIED"; // Domyślny komunikat
if (!isDeath) {
// Jeśli czas minął (tylko w Boss+)
gameOverMessage = "You just lost 10 minutes of your life. For what?"; // Zmieniono komunikat zwycięstwa Boss+
}
// Wyświetl komunikat i tytuł
ui.titleText.setText(gameOverMessage);
ui.titleText.alpha = 1; // Pokaż tytuł
ui.showMessage("", 0); // Wyczyść ewentualny poprzedni komunikat
ui.showTutorial(""); // Ukryj tutorial
// Pasek zdrowia bossa jest zarządzany przez updateBossHealth w pętli gry
// W tym stanie updateBossHealth pokaże pasek tylko jeśli !isDeath && isNewBossPlusMode
// Zaplanuj restart gry po opóźnieniu
LK.setTimeout(function () {
// Wyczyść scenę przed restartem
clearScene();
// Zniszcz gracza/bossa jeśli jakimś cudem przetrwali
if (player && player.destroy) {
player.destroy();
}
player = null;
if (boss && boss.destroy) {
boss.destroy();
}
boss = null;
// Wyczyść ataki bossa (na wszelki wypadek) - teraz robione w Boss.die / Boss.update
// if (boss && boss.attacks) boss.attacks = [];
// Usuń tło game over (czarne)
if (currentBackground) {
currentBackground.destroy();
}
currentBackground = null;
// Resetuj UI do stanu "przed grą" (np. jak w tytule, ale bez tekstów)
// Pozycjonowanie jest ok, ale trzeba wyczyścić teksty
ui.titleText.setText("");
ui.titleText.alpha = 0;
ui.messageText.setText("");
ui.messageText.alpha = 0;
ui.tutorialText.setText("");
ui.tutorialText.alpha = 0;
ui.updateDeathsCounter(); // Zaktualizuj licznik śmierci
ui.updateBossHealth(0, 1); // Ukryj pasek HP
ui.updateHearts(0, storage.maxHearts); // Ukryj serca
// Flaga isNewBossPlusMode pozostaje niezmieniona (jeśli zginąłeś w Boss+, restartujesz w Boss+)
// gameOverReasonIsDeath zostanie zresetowana przy następnym wywołaniu gameOver
// Restartuj walkę (startGame użyje flagi isNewBossPlusMode)
gameState.startGame();
}, 4000); // Zwiększono opóźnienie do 4 sekund przed restartem
},
// --- NOWA FUNKCJA: Zakończenie trybu Roll Master ---
endRollMasterMode: function endRollMasterMode(finalTime) {
var _this3 = this; // Zachowaj kontekst 'this' (gameState)
console.log("Zakończono tryb Roll Master. Czas:", finalTime);
this.currentState = "rollMasterGameOver"; // Stan wskazujący na ekran podsumowania
// Zatrzymaj główny timer trybu
if (this.rollMasterTimerInterval) {
LK.clearInterval(this.rollMasterTimerInterval);
this.rollMasterTimerInterval = null;
}
// Zatrzymaj losowy timer dla rmattack1
if (this.randomRmattack1Timer) {
LK.clearTimeout(this.randomRmattack1Timer);
this.randomRmattack1Timer = null;
}
// Wyczyść wszystkie aktywne ataki ze sceny i z listy
if (this.rollMasterAttacks && this.rollMasterAttacks.length > 0) {
this.rollMasterAttacks.forEach(function (attack) {
if (attack.visual && attack.visual.destroy && !attack.visual.destroyed) {
attack.visual.destroy(); // Zniszcz wizualizację
}
});
this.rollMasterAttacks = []; // Wyczyść listę
}
// Zatrzymaj ewentualne akcje gracza (np. timeouty uniku)
if (player && player.clearRollTimeouts) {
player.clearRollTimeouts();
}
// Ukryj gracza (powinien już być w trakcie animacji śmierci, ale dla pewności)
if (player) {
// Nie niszczymy go od razu, pozwalamy animacji śmierci w Player.die() działać
// player.alpha = 0; // Można ukryć, jeśli animacja nie działa poprawnie
}
// Sprawdź i zapisz nowy rekord
var oldHighScore = this.rollMasterHighScore || 0; // Upewnij się, że oldHighScore ma wartość
if (finalTime > oldHighScore) {
this.rollMasterHighScore = finalTime;
storage.rollMasterHighScore = this.rollMasterHighScore; // Zapisz w storage
console.log("Nowy rekord Roll Master!", this.rollMasterHighScore);
// Opcjonalnie: Wyświetl specjalny komunikat o nowym rekordzie
if (ui && ui.showMessage) {
// Można dodać dodatkowy komunikat, np. po chwili
}
}
// Przygotuj komunikaty końcowe
var minutes = Math.floor(finalTime / 60);
var seconds = finalTime % 60;
var formattedTime = String(minutes).padStart(2, '0') + ':' + String(seconds).padStart(2, '0');
var scoreMessage = "Your Time: " + formattedTime;
// --- ZMIANA: Usunięcie wyświetlania rekordu przez showTutorial ---
// Rekord jest już widoczny na stałe dzięki zmianom w UI
// var highScoreMessage = "Best: " + String(Math.floor(oldHighScore / 60)).padStart(2, '0') + ":" + String(oldHighScore % 60).padStart(2, '0');
// --- KONIEC ZMIANY ---
// Wyświetl tylko wynik końcowy
if (ui) {
ui.showMessage(scoreMessage, 0); // Pokaż czas gracza (nie zniknie sam)
// ui.showTutorial(highScoreMessage); // <-- USUNIĘTE
// Ukryj timer i rekord na ekranie game over (opcjonalne)
if (ui.timerText) {
ui.timerText.alpha = 0;
}
if (ui.highScoreText) {
ui.highScoreText.alpha = 0;
}
}
// Zaplanuj powrót do menu po chwili
LK.setTimeout(function () {
// Wyczyść komunikaty przed przejściem
if (ui) {
ui.showMessage("", 0); // Wyczyść komunikat wyniku
// ui.showTutorial(""); // Wyczyść tutorial (jeśli był używany)
}
// Zniszcz gracza, jeśli jeszcze istnieje
if (player && player.destroy && !player.destroyed) {
player.destroy();
}
player = null; // Wyczyść referencję
// Wróć do ekranu Grilla
_this3.showGrillScreen(); // Użyj _this3 dla odniesienia do gameState
}, 5000); // 5 sekund na podziwianie wyniku
},
// Koniec funkcji endRo
// Obsługa gestów dotykowych/myszy
processTouchGesture: function processTouchGesture() {
// ... (kod obsługi fake tutorial) ...
var dx = this.touchEnd.x - this.touchStart.x;
var dy = this.touchEnd.y - this.touchStart.y;
var distance = Math.sqrt(dx * dx + dy * dy);
var swipeThreshold = 50; // Minimalny dystans, by uznać za swipe
if (distance < swipeThreshold) {
// Logika Tapnięcia (bez zmian)
// ...
return;
}
// --- Obsługa Swipe (Turlania) ---
if ((this.currentState === "game" || this.currentState === "rollMaster") && player && !player.dead) {
// Normalizuj kierunek (bez zmian)
var direction = {
x: 0,
y: 0
};
if (distance > 0) {
direction.x = dx / distance;
direction.y = dy / distance;
}
// <-- NOWA LOGIKA: Obliczanie czasu trwania na podstawie dystansu swipe -->
var minSwipeDist = swipeThreshold; // Minimalny dystans swipe'a (np. 50)
var maxSwipeDist = 500; // <-- ZWIĘKSZONA dla większej skali
var minRollDuration = 90; // <-- LEKKO ZWIĘKSZONA dla płynności minimum
var maxRollDuration = 500; // Maksymalny czas uniku w ms (np. 0.45s - dostosuj!)
// 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)");
var projectileRadius = 45;
var startX, startY;
var edge = Math.floor(Math.random() * 4);
var gameWidth = 2048; // Szerokość pola gry
var gameHeight = 2732; // Wysokość pola gry
// Pozycjonowanie startowe (bez zmian)
if (edge === 0) {
// top
startX = Math.random() * gameWidth;
startY = -projectileRadius;
} else if (edge === 1) {
// right
startX = gameWidth + projectileRadius;
startY = Math.random() * gameHeight;
} else if (edge === 2) {
// bottom
startX = Math.random() * gameWidth;
startY = gameHeight + projectileRadius;
} else {
// left
startX = -projectileRadius;
startY = Math.random() * gameHeight;
}
// Celowanie w środek (bez zmian)
var targetX = gameWidth / 2;
var targetY = gameHeight / 2;
var dx = targetX - startX;
var dy = targetY - startY;
var angle = Math.atan2(dy, dx);
// --- ZMIANA: Obliczanie prędkości (vx, vy) zamiast endX, endY ---
var speed = 4 + gameState.rollMasterDifficulty * 0.5; // Prędkość pocisku (dostosuj wg potrzeb, może być zależna od trudności)
var vx = Math.cos(angle) * speed;
var vy = Math.sin(angle) * speed;
// --- KONIEC ZMIANY ---
var rotation = angle;
var scaleX = 1;
// Tworzenie animacji (bez zmian)
var frames = [LK.getAsset('rmattack1_0', {
anchorX: 0.5,
anchorY: 0.5
}), LK.getAsset('rmattack1_1', {
anchorX: 0.5,
anchorY: 0.5
}), LK.getAsset('rmattack1_2', {
anchorX: 0.5,
anchorY: 0.5
}), LK.getAsset('rmattack1_3', {
anchorX: 0.5,
anchorY: 0.5
}), LK.getAsset('rmattack1_4', {
anchorX: 0.5,
anchorY: 0.5
}), LK.getAsset('rmattack1_5', {
anchorX: 0.5,
anchorY: 0.5
}), LK.getAsset('rmattack1_6', {
anchorX: 0.5,
anchorY: 0.5
})];
var sprite = new SpriteAnimation({
frames: frames,
frameDuration: 140,
loop: true,
x: startX,
y: startY,
anchorX: 0.5,
anchorY: 0.5
});
sprite.scaleX = scaleX;
sprite.rotation = rotation;
game.addChild(sprite);
// --- ZMIANA: Dodanie do listy ataków zamiast tween ---
gameState.rollMasterAttacks.push({
type: 'projectile',
visual: sprite,
vx: vx,
vy: vy,
radius: projectileRadius // Promień do sprawdzania kolizji
});
// Usunięto tween - ruch będzie obsługiwany w game.update
// --- KONIEC ZMIANY ---
}
// Funkcja launchRmattack2 ZE ZMIANĄ POZYCJONOWANIA
function launchRmattack2() {
var topSafeMargin = 700;
console.log("Launch rmattack2 (explosion)");
// Obliczanie bezpiecznej strefy (bez zmian)
var bombWidth = 120;
var bombHeight = 120; // [cite: 87]
var halfBombWidth = bombWidth / 2;
var halfBombHeight = bombHeight / 2; // [cite: 88]
var minX_b = halfBombWidth;
var maxX_b = 2048 - halfBombWidth; // [cite: 88, 89]
var minY_b = halfBombHeight;
var maxY_b = 2732 - halfBombHeight; // [cite: 89]
var x = minX_b + Math.random() * (maxX_b - minX_b); // [cite: 90]
var spawnHeight = maxY_b - topSafeMargin; // Oblicz dostępną wysokość
var y = spawnHeight > 0 ? topSafeMargin + Math.random() * spawnHeight : topSafeMargin; // Losuj Y w bezpiecznej strefie
// Tworzenie bomby (bez zmian)
var idleFrame = LK.getAsset('rmattack2_explode_0', {
anchorX: 0.5,
anchorY: 0.5
}); // [cite: 91]
var bomb = new SpriteAnimation({
// [cite: 92]
frames: [idleFrame],
frameDuration: 500,
loop: true,
x: x,
y: y,
anchorX: 0.5,
anchorY: 0.5 // [cite: 92]
});
game.addChild(bomb); // [cite: 92]
bomb.idleTween = tween(bomb, {
y: y - 10
}, {
duration: 500,
yoyo: true,
repeat: Infinity,
easing: tween.inOutQuad
}); // [cite: 93]
// Timer eksplozji
LK.setTimeout(function () {
// [cite: 94]
if (bomb.destroyed) {
return;
} // [cite: 94]
try {
if (bomb.idleTween && typeof bomb.idleTween.stop === "function") {
bomb.idleTween.stop();
}
} catch (e) {
console.warn("idleTween stop error:", e);
} // [cite: 94]
// Tworzenie klatek eksplozji (bez zmian)
var explosionFrames = []; // [cite: 94]
var finalScale = 0;
for (var i = 0; i < 11; i++) {
// [cite: 94]
var scale = 1 + i * 0.4; // [cite: 94]
finalScale = scale; // Zapamiętaj ostatnią skalę
var frame = LK.getAsset("rmattack2_explode_" + i, {
anchorX: 0.5,
anchorY: 0.5,
scaleX: scale,
scaleY: scale
}); // [cite: 94]
explosionFrames.push(frame); // [cite: 94]
}
// Tworzenie animacji eksplozji (bez zmian)
var explosion = new SpriteAnimation({
// [cite: 94]
frames: explosionFrames,
frameDuration: 80,
loop: false,
x: bomb.x,
y: bomb.y,
anchorX: 0.5,
anchorY: 0.5 // [cite: 94]
});
bomb.destroy(); // [cite: 94]
game.addChild(explosion); // [cite: 94]
// --- ZMIANA: Dodanie eksplozji do listy śledzonych ataków ---
var explosionDurationFrames = explosionFrames.length * (80 / (1000 / 60)); // Czas trwania w klatkach gry
var explosionMaxRadius = bombWidth * finalScale / 2 * 0.8; // Przybliżony promień końcowy (0.8 dla bezpieczeństwa)
var explosionData = {
type: 'explosion',
visual: explosion,
// Referencja do animacji
radius: 0,
// Promień kolizji (zaczyna od 0)
maxRadius: explosionMaxRadius,
// Maksymalny promień
currentFrame: 0,
// Aktualna klatka (do obliczenia promienia)
totalFrames: explosionFrames.length,
// Całkowita liczba klatek
frameDurationTicks: 80 / (1000 / 60),
// Czas klatki w tickach timera
timer: 0,
// Wewnętrzny timer klatki
damageDealt: false // Czy obrażenia zostały już zadane
};
gameState.rollMasterAttacks.push(explosionData);
console.log("Dodano eksplozję do śledzenia, maxRadius:", explosionMaxRadius);
// --- KONIEC ZMIANY ---
// Timer usuwania eksplozji (bez zmian, ale teraz też jest śledzona)
LK.setTimeout(function () {
// [cite: 94]
// Znajdź i usuń z listy, jeśli nadal tam jest (na wypadek gdyby kolizja jej nie usunęła)
var index = gameState.rollMasterAttacks.indexOf(explosionData);
if (index > -1) {
gameState.rollMasterAttacks.splice(index, 1);
console.log("Usunięto eksplozję z listy po czasie.");
}
if (!explosion.destroyed) {
explosion.destroy();
} // [cite: 94]
}, explosionFrames.length * 80 + 100); // [cite: 94]
}, 2000); // Czas przed eksplozją [cite: 94]
}
// Funkcja launchRmattack3 (bez zmian, już miała bezpieczne pozycjonowanie i pulsowanie)
function launchRmattack3() {
var topSafeMargin = 600;
console.log("Launch rmattack3 (laser)");
// Bezpieczne pozycjonowanie (bez zmian)
var margin = 100;
var halfLaserWidth = 128 / 2;
var halfLaserHeight = 1012 / 2; // [cite: 95, 96]
var minX = margin + halfLaserWidth;
var maxX = 2048 - margin - halfLaserWidth; // [cite: 96, 97]
var calculatedMinY = margin + halfLaserHeight; // Oryginalne obliczenie minY
var minY = Math.max(calculatedMinY, topSafeMargin); // Użyj większej wartości: marginesu lub safe zone
var maxY = 2732 - margin - halfLaserHeight; // Dolna granica
// var laserY = minY + Math.random() * (maxY - minY); // <-- ZAKOMENTUJ LUB USUŃ STARĄ LINIĘ
var spawnHeight = maxY - minY; // Oblicz dostępną wysokość
var laserY = spawnHeight > 0 ? minY + Math.random() * spawnHeight : minY; // Losuj Y w bezpiecznej strefie
// --- KONIEC ZMIAN ---
var laserX = minX + Math.random() * (maxX - minX);
// Animacja ostrzeżenia (bez zmian)
var warningFrames = []; // [cite: 99]
for (var i = 0; i < 5; i++) {
warningFrames.push(LK.getAsset('rmattack3_warning_' + i, {
anchorX: 0.5,
anchorY: 0.5,
width: 128,
height: 1012
}));
} // [cite: 100]
var warningAnim = new SpriteAnimation({
frames: warningFrames,
frameDuration: 100,
loop: true,
x: laserX,
y: laserY,
anchorX: 0.5,
anchorY: 0.5,
alpha: 1.0
}); // [cite: 101]
warningAnim.stopPulsing = false;
var currentPulseTween = null; // [cite: 101]
game.addChild(warningAnim); // [cite: 102]
var pulseDuration = 250; // [cite: 102]
function pulseUp() {
if (!warningAnim || warningAnim.destroyed || warningAnim.stopPulsing) {
currentPulseTween = null;
return;
}
currentPulseTween = tween(warningAnim, {
alpha: 1.0
}, {
duration: pulseDuration,
easing: tween.inOutQuad,
onFinish: function onFinish() {
if (warningAnim && !warningAnim.destroyed && !warningAnim.stopPulsing) {
pulseDown();
} else {
currentPulseTween = null;
}
}
});
} // [cite: 102, 103, 104]
function pulseDown() {
if (!warningAnim || warningAnim.destroyed || warningAnim.stopPulsing) {
currentPulseTween = null;
return;
}
currentPulseTween = tween(warningAnim, {
alpha: 0.1
}, {
duration: pulseDuration,
easing: tween.inOutQuad,
onFinish: function onFinish() {
if (warningAnim && !warningAnim.destroyed && !warningAnim.stopPulsing) {
pulseUp();
} else {
currentPulseTween = null;
}
}
});
} // [cite: 104, 105]
pulseDown(); // [cite: 106]
// Timer aktywacji lasera
LK.setTimeout(function () {
// [cite: 106]
if (warningAnim && !warningAnim.destroyed) {
warningAnim.stopPulsing = true;
} // [cite: 106]
if (currentPulseTween && typeof currentPulseTween.stop === "function") {
try {
currentPulseTween.stop();
currentPulseTween = null;
} catch (e) {
console.warn("Nie udało się zatrzymać tweenu pulsowania:", e);
}
} // [cite: 106]
if (warningAnim && !warningAnim.destroyed) {
// Jeśli ostrzeżenie nadal istnieje
// Tworzenie klatek ataku (bez zmian)
var activeFrames = []; // [cite: 106]
var laserStrikeWidth = 228; // Szerokość kolizji
var laserStrikeHeight = 1212; // Wysokość kolizji
for (var j = 0; j < 5; j++) {
activeFrames.push(LK.getAsset('rmattack3_strike_' + j, {
anchorX: 0.5,
anchorY: 0.5,
width: laserStrikeWidth,
height: laserStrikeHeight
}));
} // [cite: 106]
// Tworzenie animacji ataku (bez zmian)
var strikeAnim = new SpriteAnimation({
// [cite: 106]
frames: activeFrames,
frameDuration: 40,
loop: true,
x: laserX,
y: laserY,
anchorX: 0.5,
anchorY: 0.5,
alpha: 1 // [cite: 106]
});
game.addChild(strikeAnim); // [cite: 106]
var laserDuration = 4000; // Czas życia lasera w ms
// --- ZMIANA: Dodanie lasera do listy śledzonych ataków ---
var laserData = {
type: 'laser',
visual: strikeAnim,
// Referencja do animacji uderzenia
// Przekazujemy wymiary potrzebne do kolizji AABB w game.update
width: laserStrikeWidth,
height: laserStrikeHeight,
lifeTime: laserDuration / (1000 / 60) // Czas życia w klatkach gry
};
gameState.rollMasterAttacks.push(laserData);
console.log("Dodano laser do śledzenia.");
// --- KONIEC ZMIANY ---
warningAnim.destroy(); // Usuń ostrzeżenie [cite: 106]
// Timer usuwania lasera
LK.setTimeout(function () {
// [cite: 106]
// Znajdź i usuń z listy, jeśli nadal tam jest
var index = gameState.rollMasterAttacks.indexOf(laserData);
if (index > -1) {
gameState.rollMasterAttacks.splice(index, 1);
console.log("Usunięto laser z listy po czasie.");
}
if (strikeAnim && !strikeAnim.destroyed) {
strikeAnim.destroy();
} // [cite: 106]
}, laserDuration); // [cite: 106]
}
}, 3000); // Czas trwania ostrzeżenia [cite: 107]
}
// Funkcja launchRmattack4 POPRAWIONA (tylko tworzenie, z bezpiecznym pozycjonowaniem 200px margin)
function launchRmattack4() {
// Obliczanie bezpiecznej strefy dla spreadera z marginesem 200px
var spreaderParentRadius = 60 * 1.2;
var margin = 200;
var startX = margin + Math.random() * (2048 - 2 * margin);
var startY = margin + Math.random() * (2732 - 2 * margin);
// Klatki animacji dla Spreader Parent
var spreaderParentFrames = [LK.getAsset('rmattack4_parent_0', {
anchorX: 0.5,
anchorY: 0.5
}), LK.getAsset('rmattack4_parent_1', {
anchorX: 0.5,
anchorY: 0.5
}), LK.getAsset('rmattack4_parent_2', {
anchorX: 0.5,
anchorY: 0.5
}), LK.getAsset('rmattack4_parent_3', {
anchorX: 0.5,
anchorY: 0.5
})];
// Stwórz obiekt SpriteAnimation dla rodzica
var spreaderParentAnim = new SpriteAnimation({
frames: spreaderParentFrames,
frameDuration: 250,
loop: true,
x: startX,
y: startY,
anchorX: 0.5,
anchorY: 0.5
});
// Dodaj animację rodzica do sceny gry
game.addChild(spreaderParentAnim);
// Dodaj obiekt logiczny ataku do listy gameState
gameState.rollMasterAttacks.push({
type: 'spreader_parent',
visual: spreaderParentAnim,
radius: spreaderParentRadius,
timer: gameState.spreaderSplitTime
});
}
// --- Główna pętla aktualizacji gry ---
game.update = function () {
if (ui) {
// Aktualizacja UI (bez zmian) [cite: 113]
ui.updateDeathsCounter(); // [cite: 113]
if (boss) {
// [cite: 114]
var maxHp = boss.maxHealth > 0 ? boss.maxHealth : 1; // [cite: 114]
ui.updateBossHealth(boss.health, maxHp); // [cite: 114]
} else {
// [cite: 115]
if (!(gameState.currentState === "gameOver" && !gameOverReasonIsDeath && isNewBossPlusMode)) {
// [cite: 115]
ui.updateBossHealth(0, 1); // [cite: 115]
}
}
if (player) {
// [cite: 116]
if (gameState.currentState === "rollMaster") {
ui.updateHearts(player.health, 1);
} // [cite: 116]
else {
ui.updateHearts(player.health, storage.maxHearts);
} // [cite: 117]
} else if (gameState.currentState !== "game" && gameState.currentState !== "gameOver") {
// [cite: 118]
ui.updateHearts(0, storage.maxHearts); // [cite: 118]
}
}
// Logika dla stanu "game" (bez zmian) [cite: 119]
if (gameState.currentState === "game") {
if (player) {
player.update();
} // [cite: 119]
if (boss) {
boss.update();
} // [cite: 119]
}
// Logika dla stanu "rollMaster" [cite: 120]
else if (gameState.currentState === "rollMaster") {
if (player) {
player.update();
} // [cite: 120]
// --- ZMIANA: Progresywne odblokowywanie ataków ---
if (gameState.unlockedAttacks.length < gameState.attackUnlockOrder.length && gameState.rollMasterTime >= gameState.nextUnlockTime) {
var nextAttackIndex = gameState.unlockedAttacks.length;
var attackToUnlock = gameState.attackUnlockOrder[nextAttackIndex];
gameState.unlockedAttacks.push(attackToUnlock);
gameState.nextUnlockTime += 15; // Następne odblokowanie za 15 sekund
console.log("Odblokowano atak:", attackToUnlock, "Następne odblokowanie w:", gameState.nextUnlockTime, "s.");
// Wyświetl komunikat graczowi
if (ui && ui.showMessage) {
// Zamień nazwę techniczną na bardziej przyjazną
var friendlyName = attackToUnlock.replace('rmattack1', 'Projectiles').replace('rmattack2', 'Explosions').replace('rmattack3', 'Lasers').replace('rmattack4', 'Spreaders');
ui.showMessage("New Attack Unlocked: " + friendlyName, 2000);
}
}
// --- KONIEC ZMIANY ---
// --- ZMIANA: Spawnowanie ataków z uwzględnieniem odblokowania ---
// --- Spawner Pocisków (rmattack1) ---
// Ta logika używa losowego timera, ale upewnijmy się, że działa tylko gdy atak jest odblokowany
if (gameState.unlockedAttacks.includes('rmattack1')) {
if (!gameState.randomRmattack1Timer) {
// [cite: 121]
var _spawnRandomRmattack = function spawnRandomRmattack1() {
// [cite: 121]
// Sprawdź stan i odblokowanie przed każdym odpaleniem
if (gameState.currentState !== "rollMaster" || !gameState.unlockedAttacks.includes('rmattack1')) {
// [cite: 121]
gameState.randomRmattack1Timer = null; // Zatrzymaj, jeśli warunki niespełnione
return; // [cite: 121]
}
launchRmattack1(); // [cite: 122]
var nextDelay = 800 + Math.random() * 1500; // Zwiększono nieco opóźnienie dla balansu [cite: 122]
// Użyj self odniesienia do gameState, jeśli _spawnRandomRmattack jest wewnątrz metody gameState
// Jeśli jest globalna, użyj gameState bezpośrednio. Zakładam, że jest globalna.
gameState.randomRmattack1Timer = LK.setTimeout(_spawnRandomRmattack, nextDelay); // [cite: 122]
};
_spawnRandomRmattack(); // [cite: 122]
}
} else if (gameState.randomRmattack1Timer) {
// Jeśli atak został zablokowany (teoretycznie niemożliwe w tym flow), zatrzymaj timer
LK.clearTimeout(gameState.randomRmattack1Timer);
gameState.randomRmattack1Timer = null;
}
// --- Spawner Eksplozji (rmattack2) ---
if (gameState.unlockedAttacks.includes('rmattack2') && gameState.explosionSpawnInterval > 0) {
// [cite: 123]
gameState.explosionSpawnTimer++; // [cite: 123]
if (gameState.explosionSpawnTimer >= gameState.explosionSpawnInterval) {
// [cite: 124]
gameState.explosionSpawnTimer = 0; // [cite: 124]
launchRmattack2(); // [cite: 124]
}
}
// --- Spawner Lasera (rmattack3) ---
if (gameState.unlockedAttacks.includes('rmattack3') && gameState.laserSpawnInterval > 0) {
// [cite: 124]
gameState.laserSpawnTimer++; // [cite: 124]
if (gameState.laserSpawnTimer >= gameState.laserSpawnInterval) {
// [cite: 125]
gameState.laserSpawnTimer = 0; // [cite: 125]
launchRmattack3(); // [cite: 125]
}
}
// --- Spawner Spreader Parent (rmattack4) ---
if (gameState.unlockedAttacks.includes('rmattack4') && gameState.spreaderSpawnInterval > 0) {
// [cite: 125]
gameState.spreaderSpawnTimer++; // [cite: 125]
if (gameState.spreaderSpawnTimer >= gameState.spreaderSpawnInterval) {
// [cite: 126]
gameState.spreaderSpawnTimer = 0; // [cite: 126]
console.log("Spawnuję Spreader Parent (rmattack4)!"); // [cite: 126]
launchRmattack4(); // [cite: 126]
}
}
// --- KONIEC ZMIANY SPAWNOWNIA ---
// --- ZMIANA: Pętla aktualizacji i kolizji dla wszystkich aktywnych ataków ---
for (var i = gameState.rollMasterAttacks.length - 1; i >= 0; i--) {
var atk = gameState.rollMasterAttacks[i];
// Sprawdzenie, czy atak istnieje i ma wizualizację
if (!atk || !atk.visual || atk.visual.destroyed) {
// Jeśli brak wizualizacji lub zniszczona, usuń z listy
gameState.rollMasterAttacks.splice(i, 1);
continue;
}
var shouldRemove = false; // Flaga do usunięcia ataku na końcu iteracji
// Aktualizacja animacji (jeśli istnieje metoda update)
if (atk.visual.update && typeof atk.visual.update === 'function') {
// [cite: 134]
atk.visual.update(); // [cite: 134]
}
// Logika specyficzna dla typu ataku (ruch, kolizje)
if (atk.type === 'projectile') {
// [cite: 128]
// Ruch pocisku
atk.visual.x += atk.vx; // [cite: 128]
atk.visual.y += atk.vy; // [cite: 128]
// Usuwanie pocisku poza ekranem
if (atk.visual.x < -200 || atk.visual.x > 2248 || atk.visual.y < -200 || atk.visual.y > 2932) {
// [cite: 129]
shouldRemove = true; // [cite: 130]
} else if (player && !player.dead && !player.rolling) {
// Sprawdzanie kolizji [cite: 130]
var dx_p = player.x - atk.visual.x; // [cite: 130]
var dy_p = player.y - atk.visual.y; // [cite: 131]
var dist_p = Math.sqrt(dx_p * dx_p + dy_p * dy_p); // [cite: 131]
var playerRadius = player.width / 2 * 0.8; // [cite: 132] (Upewnij się, że 0.8 jest celowe)
var projectileRadius = atk.radius || 45; // [cite: 132]
if (dist_p < playerRadius + projectileRadius) {
// [cite: 133]
console.log("Kolizja: Pocisk!"); // DEBUG
if (!player.invulnerable) {
// [cite: 133]
player.takeDamage(1); // [cite: 133]
}
shouldRemove = true; // Usuń po trafieniu [cite: 133]
}
}
} else if (atk.type === 'explosion') {
// Aktualizuj wewnętrzny timer klatki eksplozji
atk.timer++;
if (atk.timer >= atk.frameDurationTicks) {
atk.timer = 0;
atk.currentFrame++;
}
// Oblicz aktualny promień na podstawie postępu animacji (liniowo)
if (atk.currentFrame < atk.totalFrames) {
atk.radius = atk.maxRadius * (atk.currentFrame / atk.totalFrames);
} else {
atk.radius = atk.maxRadius; // Ostatnia klatka ma pełny promień
}
// Kolizja eksplozji (tylko raz)
if (!atk.damageDealt && player && !player.dead && !player.rolling) {
var dx_e = player.x - atk.visual.x;
var dy_e = player.y - atk.visual.y;
var dist_e = Math.sqrt(dx_e * dx_e + dy_e * dy_e);
var playerRadius_e = player.width / 2 * 0.8;
if (dist_e < playerRadius_e + atk.radius) {
console.log("Kolizja: Eksplozja!"); // DEBUG
if (!player.invulnerable) {
player.takeDamage(1);
}
atk.damageDealt = true; // Zadano obrażenia
// Nie usuwamy od razu, pozwalamy animacji dokończyć
}
}
// Sprawdzenie końca animacji (usunięcie będzie w timeout w launchRmattack2)
// ale możemy usunąć z listy śledzenia wcześniej, jeśli już zadała obrażenia
if (atk.damageDealt && atk.currentFrame >= atk.totalFrames) {
//shouldRemove = true; // Opcjonalnie usuń szybciej po zadaniu obrażeń i skończeniu animacji
}
} else if (atk.type === 'laser') {
// [cite: 136]
// Kolizja lasera (AABB)
if (player && !player.dead && !player.rolling) {
// [cite: 136]
// Użyj wymiarów zapisanych w atk, bo visual.width/height może się zmieniać z klatkami animacji
var laserHalfWidth = (atk.width || 128) / 2; // [cite: 136]
var laserHalfHeight = (atk.height || 1012) / 2; // [cite: 137]
var playerHalfWidth = player.width / 2 * 0.8; // [cite: 137]
var playerHalfHeight = player.height / 2 * 0.8; // [cite: 138]
var laserLeft = atk.visual.x - laserHalfWidth; // [cite: 138]
var laserRight = atk.visual.x + laserHalfWidth; // [cite: 138]
var laserTop = atk.visual.y - laserHalfHeight; // [cite: 139]
var laserBottom = atk.visual.y + laserHalfHeight; // [cite: 139]
var playerLeft = player.x - playerHalfWidth; // [cite: 139]
var playerRight = player.x + playerHalfWidth; // [cite: 140]
var playerTop = player.y - playerHalfHeight; // [cite: 140]
var playerBottom = player.y + playerHalfHeight; // [cite: 140]
if (laserLeft < playerRight && laserRight > playerLeft && laserTop < playerBottom && laserBottom > playerTop) {
// [cite: 141]
console.log("Kolizja: Laser!"); // DEBUG
if (!player.invulnerable) {
// [cite: 141]
player.takeDamage(1); // [cite: 141]
// Laser zadaje obrażenia ciągle, dopóki gracz w nim jest
}
}
}
// Czas życia lasera (usuwanie jest w timeout w launchRmattack3)
// ale możemy go oznaczyć do usunięcia z listy śledzenia
atk.lifeTime--;
if (atk.lifeTime <= 0) {
// shouldRemove = true; // Opcjonalnie usuń szybciej z listy
}
} else if (atk.type === 'spreader_parent') {
// [cite: 144]
atk.timer--; // [cite: 144]
if (atk.timer <= 0) {
// Czas na podział [cite: 144]
console.log("Spreader Parent dzieli się!");
var originX = atk.visual.x; // [cite: 144]
var originY = atk.visual.y; // [cite: 145]
var projectileSpeed = 5 + gameState.rollMasterDifficulty * 0.5; // Dostosuj prędkość dzieci [cite: 145]
var projectileRadius = 45; // [cite: 145]
var numProjectiles = 12; // [cite: 145]
for (var j = 0; j < numProjectiles; j++) {
// [cite: 146]
var angle = Math.PI * 2 / numProjectiles * j; // [cite: 146]
var vx = Math.cos(angle) * projectileSpeed; // [cite: 147]
var vy = Math.sin(angle) * projectileSpeed; // [cite: 147]
var projectileFrames = [
// [cite: 148]
LK.getAsset('rmattack1_0', {
anchorX: 0.5,
anchorY: 0.5
}), LK.getAsset('rmattack1_1', {
anchorX: 0.5,
anchorY: 0.5
}), LK.getAsset('rmattack1_2', {
anchorX: 0.5,
anchorY: 0.5
}), LK.getAsset('rmattack1_3', {
anchorX: 0.5,
anchorY: 0.5
}), LK.getAsset('rmattack1_4', {
anchorX: 0.5,
anchorY: 0.5
}), LK.getAsset('rmattack1_5', {
anchorX: 0.5,
anchorY: 0.5
}), LK.getAsset('rmattack1_6', {
anchorX: 0.5,
anchorY: 0.5
})]; // [cite: 148]
var projectileVisual = new SpriteAnimation({
frames: projectileFrames,
frameDuration: 100,
loop: true,
x: originX,
y: originY,
anchorX: 0.5,
anchorY: 0.5,
scaleX: 1.2,
scaleY: 1.2
}); // Zmniejszono skalę dzieci [cite: 149]
projectileVisual.rotation = Math.atan2(vy, vx); // [cite: 150]
var projectileObject = game.addChild(projectileVisual); // [cite: 150]
// Dodaj dziecko do listy ataków
gameState.rollMasterAttacks.push({
type: 'projectile',
visual: projectileObject,
vx: vx,
vy: vy,
radius: projectileRadius
}); // [cite: 151]
}
shouldRemove = true; // Usuń rodzica po podziale [cite: 151]
}
// Kolizja rodzica z graczem (dopóki się nie podzieli)
else if (player && !player.dead && !player.rolling) {
// [cite: 151]
var dx_sp = player.x - atk.visual.x; // [cite: 151]
var dy_sp = player.y - atk.visual.y; // [cite: 152]
var dist_sp = Math.sqrt(dx_sp * dx_sp + dy_sp * dy_sp); // [cite: 152]
var playerRadius_sp = player.width / 2 * 0.8; // [cite: 153]
var spreaderRadius = atk.radius || 60; // [cite: 153]
if (dist_sp < playerRadius_sp + spreaderRadius) {
// [cite: 154]
console.log("Kolizja: Spreader Parent!"); // DEBUG
if (!player.invulnerable) {
player.takeDamage(1);
} // [cite: 154]
// Nie usuwamy rodzica po kolizji, tylko po podziale
}
}
}
// Usuń atak, jeśli flaga `shouldRemove` jest ustawiona
if (shouldRemove) {
// Upewnij się, że niszczysz wizualizację przed usunięciem z listy
if (atk.visual && atk.visual.destroy) {
atk.visual.destroy();
}
gameState.rollMasterAttacks.splice(i, 1);
continue; // Przejdź do następnego ataku w pętli
}
} // Koniec pętli for przez ataki
// --- KONIEC ZMIANY PĘTLI AKTUALIZACJI ---
// Sprawdzenie śmierci gracza (bez zmian)
if (player && player.dead) {
// [cite: 155]
if (typeof gameState !== 'undefined' && typeof gameState.endRollMasterMode === 'function') {
// [cite: 155]
gameState.endRollMasterMode(gameState.rollMasterTime); // [cite: 155]
} else {
// [cite: 156]
console.error("Nie można zakończyć trybu Roll Master - brak gameState lub metody endRollMasterMode"); // [cite: 156]
if (typeof gameState !== 'undefined' && typeof gameState.showGrillScreen === 'function') {
// [cite: 157]
gameState.showGrillScreen(); // [cite: 157]
}
}
}
} // Koniec else if (gameState.currentState === "rollMaster") [cite: 157]
}; // Koniec game.update
// --- Rozpoczęcie gry ---
// Zakładamy, że obiekt gameState jest zdefiniowany gdzieś wcześniej w kodzie
if (typeof gameState !== 'undefined' && typeof gameState.init === 'function') {
gameState.init();
} else {
console.error("Obiekt gameState lub gameState.init nie jest zdefiniowany przed wywołaniem init!");
} ===================================================================
--- original.js
+++ change.js
@@ -958,10 +958,9 @@
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"
+ if (gameState.isInputActive && (gameState.currentState === "game" || gameState.currentState === "rollMaster") && player && !player.dead) {
var targetX = gameState.currentInputPos.x;
var targetY = gameState.currentInputPos.y;
var dx_m = targetX - self.x;
var dy_m = targetY - self.y;
@@ -1136,170 +1135,292 @@
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)
+ self.timerText = new Text2("00:00", {
+ // Zmieniono tekst początkowy
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.x = 2048 / 2; // Wyśrodkuj w poziomie
+ self.timerText.y = 50; // Przysuń do góry
+ self.timerText.anchor.set(1, 0); // Pozycja Y od górnej krawędzi
self.timerText.alpha = 0; // Domyślnie ukryty
self.addChild(self.timerText);
+ // --- DODANY NOWY ELEMENT TEKSTOWY DLA REKORDU ---
+ self.highScoreText = new Text2("Best: 00:00", {
+ // Tekst początkowy
+ size: 50,
+ // Mniejszy rozmiar niż aktualny czas
+ fill: 0xFFFF00 // Żółty kolor dla odróżnienia
+ });
+ self.highScoreText.x = 2048 - 50; // Przysuń do prawej krawędzi (z marginesem 50)
+ self.highScoreText.y = 50; // Przysuń do góry (ta sama wysokość co timer)
+ self.highScoreText.anchor.set(1, 0); // Pozycja Y (poniżej timerText)
+ self.highScoreText.alpha = 0; // Domyślnie ukryty
+ self.addChild(self.highScoreText); // Dodaj do kontenera UI
+ // --- KONIEC DODAWANIA NOWEGO ELEMENTU ---
// --- Boss Health Bar (wizualizacja zdrowia bossa) ---
self.bossHealthBarContainer = new Container(); // Kontener na pasek zdrowia bossa
self.bossHealthBarContainer.x = 2048 / 2; // Wyśrodkuj poziomo
self.bossHealthBarContainer.y = 150; // Pozycja Y (poniżej timera)
self.bossHealthBarContainer.alpha = 0; // Domyślnie ukryty
self.addChild(self.bossHealthBarContainer);
var barWidth = 800; // Szerokość paska zdrowia (musi być taka sama jak szerokość assetu bossHpbar)
var barHeight = 30; // Wysokość paska zdrowia (musi być taka sama jak wysokość assetu bossHpbar)
+ // Właściwy pasek zdrowia bossa (czerwony) - UŻYWAMY TERAZ NOWEGO ASSETU bossHpbar
self.bossHealthBar = self.attachAsset('bossHpbar', {
// Użyj assetu 'bossHpbar'
anchorX: 0,
// Ustaw punkt odniesienia na lewą krawędź (0), środek pionowo (0.5)
anchorY: 0.5,
x: -barWidth / 2 // Przesuń w lewo o połowę szerokości tła
});
self.bossHealthBarContainer.addChild(self.bossHealthBar);
- // --- 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 ---
+ // --- METODY AKTUALIZACJI UI ---
// Aktualizuje wizualizację serc na UI
- self.updateHearts = function (current, max) {/* ... bez zmian ... */};
+ self.updateHearts = function (current, max) {
+ current = Math.max(0, current || 0);
+ max = Math.max(1, max || 1); // Max musi być co najmniej 1
+ if (self.hearts.length !== max) {
+ while (self.hearts.length > 0) {
+ var oldHeart = self.hearts.pop();
+ if (oldHeart && oldHeart.destroy && !oldHeart.destroyed) {
+ oldHeart.destroy();
+ }
+ }
+ self.heartContainer.removeChildren(); // Usuń wizualne obiekty serc
+ for (var i = 0; i < max; i++) {
+ var heart = LK.getAsset('heart', {
+ anchorX: 0.5,
+ anchorY: 0.5,
+ x: i * 50,
+ y: 0,
+ tint: i < current ? 0xFF0000 : 0x555555
+ });
+ self.hearts.push(heart);
+ self.heartContainer.addChild(heart);
+ }
+ self.heartContainer.x = (2048 - max * 50) / 2 + 25;
+ self.heartContainer.y = 100; // Zmieniono pozycję serc, aby zrobić miejsce
+ } else {
+ for (var j = 0; j < self.hearts.length; j++) {
+ if (self.hearts[j]) {
+ self.hearts[j].tint = j < current ? 0xFF0000 : 0x555555;
+ }
+ }
+ }
+ };
// Aktualizuje wizualizację paska zdrowia bossa
- self.updateBossHealth = function (current, max) {/* ... bez zmian ... */};
+ self.updateBossHealth = function (current, max) {
+ current = Math.max(0, current || 0);
+ max = Math.max(1, max || 1);
+ var barWidth = 800;
+ var currentWidth = current / max * barWidth;
+ if (self.bossHealthBar) {
+ self.bossHealthBar.width = currentWidth;
+ var shouldBeVisible = (gameState.currentState === "game" || gameState.currentState === "gameOver") && (boss && !boss.dead && current > 0 || gameState.currentState === "gameOver" && !gameOverReasonIsDeath && isNewBossPlusMode);
+ self.bossHealthBarContainer.alpha = shouldBeVisible ? 1 : 0;
+ }
+ if (self.bossHealthBarBg) {
+ // Jeśli używasz tła paska HP
+ self.bossHealthBarBg.alpha = self.bossHealthBarContainer.alpha;
+ }
+ };
// Wyświetla komunikat na środku ekranu
- self.showMessage = function (message, duration) {/* ... bez zmian ... */};
+ self.showMessage = function (message, duration) {
+ self.messageText.setText(message);
+ self.messageText.alpha = 1;
+ if (self.messageTimeout) {
+ LK.clearTimeout(self.messageTimeout);
+ self.messageTimeout = null;
+ }
+ if (duration && duration > 0) {
+ self.messageTimeout = LK.setTimeout(function () {
+ tween(self.messageText, {
+ alpha: 0
+ }, {
+ duration: 500,
+ onFinish: function onFinish() {
+ self.messageTimeout = null;
+ }
+ });
+ }, duration);
+ } else {
+ self.messageText.alpha = 1;
+ }
+ };
// Wyświetla tekst tutorialu
- self.showTutorial = function (text) {/* ... bez zmian ... */};
+ self.showTutorial = function (text) {
+ self.tutorialText.setText(text);
+ self.tutorialText.alpha = 1;
+ };
// Ukrywa tekst tutorialu (zanikając)
- self.hideTutorial = function () {/* ... bez zmian ... */};
+ self.hideTutorial = function () {
+ tween(self.tutorialText, {
+ alpha: 0
+ }, {
+ duration: 500
+ });
+ };
// Aktualizuje licznik śmierci
- self.updateDeathsCounter = function () {/* ... bez zmian ... */};
+ self.updateDeathsCounter = function () {
+ var deaths = storage.totalDeaths || 0;
+ self.deathsText.setText("Deaths: " + deaths);
+ self.deathsText.visible = gameState.currentState === "game";
+ };
// Aktualizuje wyświetlanie czasu timera
- self.updateTimerDisplay = function (seconds) {/* ... bez zmian ... */};
+ self.updateTimerDisplay = function (seconds) {
+ seconds = Math.max(0, seconds || 0);
+ var minutes = Math.floor(seconds / 60);
+ var remainingSeconds = seconds % 60;
+ var formattedSeconds = remainingSeconds < 10 ? '0' + remainingSeconds : remainingSeconds;
+ var formattedTime = String(minutes).padStart(2, '0') + ':' + formattedSeconds;
+ if (self.timerText) {
+ // W Roll Master wyświetlamy "Time:", w innych trybach tylko czas
+ var prefix = gameState.currentState === "rollMaster" ? "Time: " : "";
+ self.timerText.setText(prefix + formattedTime);
+ }
+ };
+ // --- DODANA NOWA FUNKCJA DO AKTUALIZACJI TEKSTU REKORDU ---
+ self.updateHighScoreDisplay = function (seconds) {
+ seconds = Math.max(0, seconds || 0);
+ var minutes = Math.floor(seconds / 60);
+ var remainingSeconds = seconds % 60;
+ var formattedSeconds = remainingSeconds < 10 ? '0' + remainingSeconds : remainingSeconds;
+ var formattedTime = String(minutes).padStart(2, '0') + ':' + formattedSeconds;
+ if (self.highScoreText) {
+ // Sprawdź, czy obiekt istnieje
+ self.highScoreText.setText("Best: " + formattedTime);
+ }
+ };
+ // --- KONIEC DODAWANIA NOWEJ FUNKCJI ---
// Pozycjonuje elementy UI w zależności od stanu gry
- // 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
+ // Pozycje stałe dla niektórych elementów
self.deathsText.x = 2048 - 150;
- self.deathsText.y = 100;
- self.heartContainer.y = 80;
+ self.deathsText.y = 50;
+ self.heartContainer.y = 80; // Pozycja pionowa serc (może być nadpisywana)
self.bossHealthBarContainer.x = 2048 / 2;
- self.bossHealthBarContainer.y = 160;
- // Resetuj widoczność
+ self.bossHealthBarContainer.y = 160; // Pasek HP bossa poniżej timera/serc
+ // Resetuj widoczność przed ustawieniem dla danego stanu
self.titleText.alpha = 0;
self.messageText.alpha = 0;
self.tutorialText.alpha = 0;
self.timerText.alpha = 0;
self.heartContainer.alpha = 0;
- self.bossHealthBarContainer.alpha = 0; // Resetuj też pasek bossa
- self.deathsText.alpha = 1;
- self.highScoreText.alpha = 0;
+ self.bossHealthBarContainer.alpha = 0;
+ self.deathsText.alpha = 0; // Domyślnie ukryty
+ self.highScoreText.alpha = 0; // Domyślnie ukryty
+ // Przywróć domyślną pozycję i styl timerText
+ if (self.timerText) {
+ self.timerText.x = 350;
+ self.timerText.y = 100;
+ self.timerText.anchor.set(0, 0); // Upewnij się, że anchor jest resetowany
+ self.timerText.style = {
+ size: 60,
+ fill: 0xFFFFFF
+ };
+ }
switch (state) {
case "title":
self.titleText.x = 2048 / 2;
self.titleText.y = 800;
self.titleText.alpha = 1;
self.messageText.x = 2048 / 2;
self.messageText.y = 1000;
+ // self.messageText.alpha = 1; // Ustawiane przez showMessage
self.tutorialText.x = 2048 / 2;
self.tutorialText.y = 1200;
- // --- FIX: Ustaw alpha dla tekstów tytułowych ---
- self.messageText.alpha = 1;
- self.tutorialText.alpha = 1;
- // --- KONIEC FIX ---
+ // self.tutorialText.alpha = 1; // Ustawiane przez showTutorial
break;
case "game":
self.messageText.x = 2048 / 2;
self.messageText.y = 1500;
+ // self.messageText.alpha = 0; // Ustawiane przez showMessage
self.tutorialText.x = 2048 / 2;
self.tutorialText.y = 200;
- // --- 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
+ // self.tutorialText.alpha = 1; // Ustawiane przez showTutorial/hideTutorial
+ // Pokaż elementy walki
+ if (self.heartContainer) {
+ self.heartContainer.alpha = 1;
+ }
+ if (self.timerText) {
+ self.timerText.alpha = 1;
+ }
+ if (self.deathsText) {
+ self.deathsText.alpha = 1;
+ } // Pokaż licznik śmierci
+ // Pasek zdrowia bossa jest zarządzany przez updateBossHealth
break;
case "grillMenu":
self.messageText.x = 2048 / 2;
self.messageText.y = 500;
+ // self.messageText.alpha = 0; // Ustawiane przez showMessage
break;
case "gameOver":
self.titleText.x = 2048 / 2;
self.titleText.y = 800;
- ui.titleText.alpha = 1; // Pokaż tytuł game over
+ // self.titleText.alpha = 1; // Ustawiane w gameState.gameOver
self.messageText.x = 2048 / 2;
self.messageText.y = 1000;
+ // self.messageText.alpha = 0; // Ustawiane w gameState.gameOver przez showMessage
self.tutorialText.x = 2048 / 2;
self.tutorialText.y = 1200;
- // Pasek HP bossa w gameOver jest zarządzany przez updateBossHealth
+ // Widoczność paska HP bossa zarządzana przez updateBossHealth
+ if (self.deathsText) {
+ self.deathsText.alpha = 1;
+ } // Pokaż licznik śmierci też na Game Over
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
+ // Większość elementów ukryta (domyślnie)
break;
+ // --- DODANA OBSŁUGA WIDOCZNOŚCI W ROLL MASTER ---
+ case "rollMaster":
+ // Dostosuj pozycję timera specjalnie dla tego trybu
+ if (self.timerText) {
+ self.timerText.x = 50;
+ self.timerText.y = 50;
+ self.timerText.anchor.set(0, 0); // Lewy górny róg
+ self.timerText.style = {
+ size: 60,
+ fill: 0xFFFFFF
+ }; // Ustaw styl, jeśli trzeba
+ self.timerText.alpha = 1; // Pokaż timer
+ }
+ // Pokaż rekord
+ if (self.highScoreText) {
+ self.highScoreText.alpha = 1;
+ }
+ // Ukryj inne niepotrzebne elementy
+ if (self.heartContainer) {
+ self.heartContainer.alpha = 0;
+ }
+ if (self.bossHealthBarContainer) {
+ self.bossHealthBarContainer.alpha = 0;
+ }
+ if (self.deathsText) {
+ self.deathsText.alpha = 0;
+ }
+ if (self.tutorialText) {
+ self.tutorialText.alpha = 0;
+ }
+ if (self.titleText) {
+ self.titleText.alpha = 0;
+ }
+ break;
+ // --- KONIEC DODAWANIA OBSŁUGI ---
}
}; // Koniec positionElements
return self;
});
/****
* Initialize Game
****/
+// Koniec var UI = Container.expand(...)
var game = new LK.Game({
backgroundColor: 0x111111 // Ciemne tło domyślne
});
@@ -2526,94 +2647,146 @@
},
// <-- WAŻNE: Przecinek tutaj, bo następuje kolejna funkcja gameState
// --- NOWA FUNKCJA: Konfiguracja sceny Roll Master ---
setupRollMasterScene: function setupRollMasterScene() {
- var _this2 = this;
+ var _this2 = this; // Zachowaj kontekst 'this' (gameState)
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 */}
+ // Ustaw tło
+ try {
+ currentBackground = LK.getAsset('rollMasterBg', {
+ anchorX: 0.5,
+ anchorY: 0.5,
+ x: 2048 / 2,
+ y: 2732 / 2
+ });
+ game.addChildAt(currentBackground, 0);
+ } catch (e) {
+ console.warn("Nie znaleziono assetu 'rollMasterBg', używam koloru tła.");
+ game.setBackgroundColor(0x222233); // Ciemnoniebieskie tło awaryjne
+ }
+ // Stwórz gracza
+ if (typeof Player !== 'undefined') {
+ player = game.addChild(new Player());
+ player.x = 2048 / 2;
+ player.y = 2732 / 2 + 400; // Pozycja startowa
+ player.health = 1; // Tylko 1 HP w tym trybie
+ // Upewnij się, że gracz jest gotowy (reset stanu)
+ player.rolling = false;
+ player.invulnerable = false;
+ player.invulnerabilityFrames = 0;
+ player.rollCooldown = 0;
+ if (player && typeof player.clearRollTimeouts === 'function') {
+ player.clearRollTimeouts();
+ }
+ player.hasRolledThroughBossThisRoll = false; // Chociaż nie ma tu bossa
+ player.alpha = 1;
+ player.dead = false;
+ } else {
+ console.error("Klasa Player nie jest zdefiniowana!");
+ // Rozważ powrót do menu lub inny handling błędu
+ this.showGrillScreen();
+ return;
+ }
+ // Inicjalizacja zmiennych trybu Roll Master
+ this.rollMasterTime = 0;
+ this.rollMasterDifficulty = 1;
+ this.rollMasterAttacks = []; // Tablica na aktywne ataki
+ this.attackUnlockOrder = ['rmattack1', 'rmattack2', 'rmattack3', 'rmattack4']; // Kolejność odblokowywania
+ this.unlockedAttacks = ['rmattack1']; // Zaczynamy tylko z pierwszym atakiem
+ this.nextUnlockTime = 15; // Czas (w sekundach) odblokowania następnego ataku
+ console.log("Ataki startowe:", this.unlockedAttacks, "Następne odblokowanie w:", this.nextUnlockTime + "s");
+ // Resetuj timery spawnrate'ów
+ this.attackSpawnTimer = 0; // Timer dla rmattack1 (jeśli używany inaczej niż losowo)
+ this.attackSpawnInterval = 120; // Interwał dla rmattack1 (2 sekundy)
+ this.explosionSpawnTimer = 0; // Timer dla rmattack2
+ this.explosionSpawnInterval = 240; // Interwał dla rmattack2 (4 sekundy)
+ this.laserSpawnTimer = 0; // Timer dla rmattack3
+ this.laserSpawnInterval = 300; // Interwał dla rmattack3 (5 sekund)
+ this.laserWarningTime = 1500; // Czas ostrzeżenia lasera w ms (było 90 klatek)
+ this.spreaderSpawnTimer = 0; // Timer dla rmattack4
+ this.spreaderSpawnInterval = 600; // Interwał dla rmattack4 (10 sekund)
+ this.spreaderSplitTime = 180; // Czas do podziału spreadera w klatkach (3 sekundy)
+ // Wyczyść ewentualny stary timer dla rmattack1
+ if (this.randomRmattack1Timer) {
+ LK.clearTimeout(this.randomRmattack1Timer);
+ this.randomRmattack1Timer = null;
+ }
+ // --- ZMIANA TUTAJ: Wczytanie rekordu i aktualizacja UI ---
+ this.rollMasterHighScore = storage.rollMasterHighScore || 0;
+ if (ui && ui.updateHighScoreDisplay) {
+ // Sprawdź, czy UI i funkcja istnieją
+ ui.updateHighScoreDisplay(this.rollMasterHighScore); // Wywołaj funkcję aktualizacji rekordu
+ } else {
+ console.error("Nie można zaktualizować wyświetlania rekordu - brak ui.updateHighScoreDisplay");
+ }
+ // --- KONIEC ZMIANY ---
+ // Konfiguracja UI dla Roll Master
+ if (ui && ui.timerText) {
+ // Ustawienia timera zostały przeniesione do ui.positionElements
+ ui.updateTimerDisplay(this.rollMasterTime); // Ustaw początkowy czas na 00:00
+ } else {
+ console.error("Nie można znaleźć ui.timerText do skonfigurowania!");
+ }
if (ui && ui.updateHearts) {
- ui.updateHearts(1, 1);
- } // Ustaw jedno serce
+ ui.updateHearts(1, 1); // Pokaż 1 serce (szare, bo 1/1)
+ }
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!");
+ ui.updateBossHealth(0, 1); // Ukryj pasek HP bossa
}
- // --- KONIEC NOWOŚCI ---
- // --- WAŻNE: Wywołaj pozycjonowanie elementów UI dla tego stanu ---
+ // WAŻNE: Wywołaj pozycjonowanie elementów UI PO konfiguracji
if (ui && ui.positionElements) {
- ui.positionElements("rollMaster"); // Ustawia pozycje i widoczność timera, high score, serc itp.
+ ui.positionElements("rollMaster"); // To ustawi widoczność i pozycje
+ } else {
+ console.error("Nie można ustawić elementów UI dla Roll Master - brak ui.positionElements");
}
- // --- KONIEC WAŻNEGO ---
- // Główny timer trybu Roll Master (istniejący kod)
+ // Główny timer trybu (zwiększanie czasu i trudności)
if (this.rollMasterTimerInterval) {
- LK.clearInterval(this.rollMasterTimerInterval);
+ LK.clearInterval(this.rollMasterTimerInterval); // Wyczyść stary, jeśli istnieje
}
- var self = this;
- this.rollMasterTimerInterval = LK.setInterval(function () {/* ... */}, 1000);
+ this.rollMasterTimerInterval = LK.setInterval(function () {
+ if (_this2.currentState !== "rollMaster") {
+ // Używaj _this2 dla odniesienia do gameState
+ LK.clearInterval(_this2.rollMasterTimerInterval);
+ _this2.rollMasterTimerInterval = null;
+ return;
+ }
+ _this2.rollMasterTime++; // Zwiększ czas
+ if (ui && ui.updateTimerDisplay) {
+ ui.updateTimerDisplay(_this2.rollMasterTime); // Aktualizuj wyświetlanie czasu
+ }
+ // Zwiększanie trudności co 30 sekund (zmieniono z 60 dla szybszej progresji)
+ if (_this2.rollMasterTime > 0 && _this2.rollMasterTime % 30 === 0) {
+ _this2.increaseRollMasterDifficulty();
+ }
+ // Logika odblokowywania ataków przeniesiona do game.update
+ }, 1000); // Interwał co 1 sekundę
console.log("Scena Roll Master skonfigurowana.");
},
// <-- WAŻNE: Przecinek tutaj
// --- NOWA FUNKCJA: Zwiększanie trudności w Roll Master ---
increaseRollMasterDifficulty: function increaseRollMasterDifficulty() {
this.rollMasterDifficulty++;
- 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
+ console.log("Trudność Roll Master zwiększona do:", this.rollMasterDifficulty);
+ var decreaseAmountProjectile = 10;
+ var minIntervalProjectile = 30;
+ this.attackSpawnInterval = Math.max(minIntervalProjectile, this.attackSpawnInterval - decreaseAmountProjectile);
+ console.log("Nowy interwał projectile:", this.attackSpawnInterval);
+ var decreaseAmountExplosion = 15;
var minIntervalExplosion = 60;
this.explosionSpawnInterval = Math.max(minIntervalExplosion, this.explosionSpawnInterval - decreaseAmountExplosion);
console.log("Nowy interwał explosion:", this.explosionSpawnInterval);
- var decreaseAmountLaser = 20; // rmattack3
- var minIntervalLaser = 120;
+ // --- NOWA LOGIKA DLA LASERA ---
+ var decreaseAmountLaser = 20; // Jak szybko ma skracać się interwał lasera
+ var minIntervalLaser = 120; // Minimalny interwał lasera (np. 2 sekundy)
this.laserSpawnInterval = Math.max(minIntervalLaser, this.laserSpawnInterval - decreaseAmountLaser);
console.log("Nowy interwał laser:", this.laserSpawnInterval);
- var decreaseAmountSpreader = 30; // rmattack4
- var minIntervalSpreader = 180;
+ // --- NOWA LOGIKA DLA SPREADERA ---
+ var decreaseAmountSpreader = 30; // Jak szybko ma skracać się interwał spreadera
+ var minIntervalSpreader = 180; // Minimalny interwał spreadera (np. 3 sekundy)
this.spreaderSpawnInterval = Math.max(minIntervalSpreader, this.spreaderSpawnInterval - decreaseAmountSpreader);
console.log("Nowy interwał spreader:", this.spreaderSpawnInterval);
- // (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);
+ ui.showMessage("Difficulty Increased! (" + this.rollMasterDifficulty + ")", 1500);
}
},
// Przejście do stanu Game Over
// isDeath: true (gracz zginął), false (czas minął w Boss+)
@@ -2697,59 +2870,86 @@
}, 4000); // Zwiększono opóźnienie do 4 sekund przed restartem
},
// --- NOWA FUNKCJA: Zakończenie trybu Roll Master ---
endRollMasterMode: function endRollMasterMode(finalTime) {
- var _this3 = this;
+ var _this3 = this; // Zachowaj kontekst 'this' (gameState)
console.log("Zakończono tryb Roll Master. Czas:", finalTime);
- this.currentState = "rollMasterGameOver";
+ this.currentState = "rollMasterGameOver"; // Stan wskazujący na ekran podsumowania
+ // Zatrzymaj główny timer trybu
if (this.rollMasterTimerInterval) {
LK.clearInterval(this.rollMasterTimerInterval);
this.rollMasterTimerInterval = null;
}
+ // Zatrzymaj losowy timer dla rmattack1
if (this.randomRmattack1Timer) {
LK.clearTimeout(this.randomRmattack1Timer);
this.randomRmattack1Timer = null;
}
+ // Wyczyść wszystkie aktywne ataki ze sceny i z listy
if (this.rollMasterAttacks && this.rollMasterAttacks.length > 0) {
this.rollMasterAttacks.forEach(function (attack) {
- if (attack.visual && attack.visual.destroy) {
- attack.visual.destroy();
+ if (attack.visual && attack.visual.destroy && !attack.visual.destroyed) {
+ attack.visual.destroy(); // Zniszcz wizualizację
}
});
- this.rollMasterAttacks = [];
+ this.rollMasterAttacks = []; // Wyczyść listę
}
+ // Zatrzymaj ewentualne akcje gracza (np. timeouty uniku)
if (player && player.clearRollTimeouts) {
player.clearRollTimeouts();
}
+ // Ukryj gracza (powinien już być w trakcie animacji śmierci, ale dla pewności)
if (player) {
- player.alpha = 0;
+ // Nie niszczymy go od razu, pozwalamy animacji śmierci w Player.die() działać
+ // player.alpha = 0; // Można ukryć, jeśli animacja nie działa poprawnie
}
- var oldHighScore = this.rollMasterHighScore;
+ // Sprawdź i zapisz nowy rekord
+ var oldHighScore = this.rollMasterHighScore || 0; // Upewnij się, że oldHighScore ma wartość
if (finalTime > oldHighScore) {
this.rollMasterHighScore = finalTime;
- storage.rollMasterHighScore = this.rollMasterHighScore;
+ storage.rollMasterHighScore = this.rollMasterHighScore; // Zapisz w storage
console.log("Nowy rekord Roll Master!", this.rollMasterHighScore);
+ // Opcjonalnie: Wyświetl specjalny komunikat o nowym rekordzie
+ if (ui && ui.showMessage) {
+ // Można dodać dodatkowy komunikat, np. po chwili
+ }
}
+ // Przygotuj komunikaty końcowe
var minutes = Math.floor(finalTime / 60);
var seconds = finalTime % 60;
- var formattedTime = String(minutes).padStart(2, '0') + ":" + String(seconds).padStart(2, '0');
+ var 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');
+ // --- ZMIANA: Usunięcie wyświetlania rekordu przez showTutorial ---
+ // Rekord jest już widoczny na stałe dzięki zmianom w UI
+ // var highScoreMessage = "Best: " + String(Math.floor(oldHighScore / 60)).padStart(2, '0') + ":" + String(oldHighScore % 60).padStart(2, '0');
+ // --- KONIEC ZMIANY ---
+ // Wyświetl tylko wynik końcowy
if (ui) {
- ui.showMessage(scoreMessage, 0);
- ui.showTutorial(highScoreMessage);
+ ui.showMessage(scoreMessage, 0); // Pokaż czas gracza (nie zniknie sam)
+ // ui.showTutorial(highScoreMessage); // <-- USUNIĘTE
+ // Ukryj timer i rekord na ekranie game over (opcjonalne)
+ if (ui.timerText) {
+ ui.timerText.alpha = 0;
+ }
+ if (ui.highScoreText) {
+ ui.highScoreText.alpha = 0;
+ }
}
+ // Zaplanuj powrót do menu po chwili
LK.setTimeout(function () {
+ // Wyczyść komunikaty przed przejściem
if (ui) {
- ui.showMessage("", 0);
- ui.showTutorial("");
+ ui.showMessage("", 0); // Wyczyść komunikat wyniku
+ // ui.showTutorial(""); // Wyczyść tutorial (jeśli był używany)
}
- if (player && player.destroy) {
+ // Zniszcz gracza, jeśli jeszcze istnieje
+ if (player && player.destroy && !player.destroyed) {
player.destroy();
}
- player = null;
- _this3.showGrillScreen();
- }, 5000);
+ player = null; // Wyczyść referencję
+ // Wróć do ekranu Grilla
+ _this3.showGrillScreen(); // Użyj _this3 dla odniesienia do gameState
+ }, 5000); // 5 sekund na podziwianie wyniku
},
// Koniec funkcji endRo
// Obsługa gestów dotykowych/myszy
processTouchGesture: function processTouchGesture() {
@@ -2775,11 +2975,11 @@
direction.y = dy / distance;
}
// <-- NOWA LOGIKA: Obliczanie czasu trwania na podstawie dystansu swipe -->
var minSwipeDist = swipeThreshold; // Minimalny dystans swipe'a (np. 50)
- var maxSwipeDist = 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!)
+ var maxSwipeDist = 500; // <-- ZWIĘKSZONA dla większej skali
+ var minRollDuration = 90; // <-- LEKKO ZWIĘKSZONA dla płynności minimum
+ var maxRollDuration = 500; // Maksymalny czas uniku w ms (np. 0.45s - dostosuj!)
// 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);
@@ -2842,45 +3042,46 @@
}
}
};
function launchRmattack1() {
- //console.log("Launch rmattack1 (projectile)"); // Można wyciszyć, jeśli jest za dużo logów
+ console.log("Launch rmattack1 (projectile)");
var projectileRadius = 45;
var startX, startY;
var edge = Math.floor(Math.random() * 4);
- var gameWidth = 2048;
- var gameHeight = 2732;
- // Pozycjonowanie startowe
+ var gameWidth = 2048; // Szerokość pola gry
+ var gameHeight = 2732; // Wysokość pola gry
+ // Pozycjonowanie startowe (bez zmian)
if (edge === 0) {
+ // top
startX = Math.random() * gameWidth;
startY = -projectileRadius;
} else if (edge === 1) {
+ // right
startX = gameWidth + projectileRadius;
startY = Math.random() * gameHeight;
} else if (edge === 2) {
+ // bottom
startX = Math.random() * gameWidth;
startY = gameHeight + projectileRadius;
} else {
+ // left
startX = -projectileRadius;
startY = Math.random() * gameHeight;
}
- // Celowanie w środek
+ // Celowanie w środek (bez zmian)
var targetX = gameWidth / 2;
var targetY = gameHeight / 2;
var dx = targetX - startX;
var dy = targetY - startY;
var angle = Math.atan2(dy, dx);
- // --- ZMIANA: 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 ---
+ // --- ZMIANA: Obliczanie prędkości (vx, vy) zamiast endX, endY ---
+ var speed = 4 + gameState.rollMasterDifficulty * 0.5; // Prędkość pocisku (dostosuj wg potrzeb, może być zależna od trudności)
var vx = Math.cos(angle) * speed;
var vy = Math.sin(angle) * speed;
+ // --- KONIEC ZMIANY ---
var rotation = angle;
var scaleX = 1;
- // Tworzenie animacji
+ // Tworzenie animacji (bez zmian)
var frames = [LK.getAsset('rmattack1_0', {
anchorX: 0.5,
anchorY: 0.5
}), LK.getAsset('rmattack1_1', {
@@ -2913,137 +3114,167 @@
});
sprite.scaleX = scaleX;
sprite.rotation = rotation;
game.addChild(sprite);
- // Dodanie do listy ataków
+ // --- ZMIANA: Dodanie do listy ataków zamiast tween ---
gameState.rollMasterAttacks.push({
type: 'projectile',
visual: sprite,
vx: vx,
vy: vy,
- radius: projectileRadius
+ radius: projectileRadius // Promień do sprawdzania kolizji
});
+ // Usunięto tween - ruch będzie obsługiwany w game.update
+ // --- KONIEC ZMIANY ---
}
// Funkcja launchRmattack2 ZE ZMIANĄ POZYCJONOWANIA
function launchRmattack2() {
- //console.log("Launch rmattack2 (explosion)");
+ var topSafeMargin = 700;
+ console.log("Launch rmattack2 (explosion)");
+ // Obliczanie bezpiecznej strefy (bez zmian)
var bombWidth = 120;
- var bombHeight = 120;
+ var bombHeight = 120; // [cite: 87]
var halfBombWidth = bombWidth / 2;
- var halfBombHeight = bombHeight / 2;
+ var halfBombHeight = bombHeight / 2; // [cite: 88]
var minX_b = halfBombWidth;
- var maxX_b = 2048 - halfBombWidth;
+ var maxX_b = 2048 - halfBombWidth; // [cite: 88, 89]
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 maxY_b = 2732 - halfBombHeight; // [cite: 89]
+ var x = minX_b + Math.random() * (maxX_b - minX_b); // [cite: 90]
+ var spawnHeight = maxY_b - topSafeMargin; // Oblicz dostępną wysokość
+ var y = spawnHeight > 0 ? topSafeMargin + Math.random() * spawnHeight : topSafeMargin; // Losuj Y w bezpiecznej strefie
+ // Tworzenie bomby (bez zmian)
var idleFrame = LK.getAsset('rmattack2_explode_0', {
anchorX: 0.5,
anchorY: 0.5
- });
+ }); // [cite: 91]
var bomb = new SpriteAnimation({
+ // [cite: 92]
frames: [idleFrame],
frameDuration: 500,
loop: true,
x: x,
y: y,
anchorX: 0.5,
- anchorY: 0.5
+ anchorY: 0.5 // [cite: 92]
});
- game.addChild(bomb);
+ game.addChild(bomb); // [cite: 92]
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 ---
+ }); // [cite: 93]
+ // Timer eksplozji
LK.setTimeout(function () {
+ // [cite: 94]
if (bomb.destroyed) {
return;
- }
+ } // [cite: 94]
try {
if (bomb.idleTween && typeof bomb.idleTween.stop === "function") {
bomb.idleTween.stop();
}
} catch (e) {
console.warn("idleTween stop error:", e);
- }
- var explosionFrames = [];
+ } // [cite: 94]
+ // Tworzenie klatek eksplozji (bez zmian)
+ var explosionFrames = []; // [cite: 94]
var finalScale = 0;
for (var i = 0; i < 11; i++) {
- var scale = 1 + i * 0.4;
- finalScale = scale;
+ // [cite: 94]
+ var scale = 1 + i * 0.4; // [cite: 94]
+ finalScale = scale; // Zapamiętaj ostatnią skalę
var frame = LK.getAsset("rmattack2_explode_" + i, {
anchorX: 0.5,
anchorY: 0.5,
scaleX: scale,
scaleY: scale
- });
- explosionFrames.push(frame);
+ }); // [cite: 94]
+ explosionFrames.push(frame); // [cite: 94]
}
+ // Tworzenie animacji eksplozji (bez zmian)
var explosion = new SpriteAnimation({
+ // [cite: 94]
frames: explosionFrames,
frameDuration: 80,
loop: false,
x: bomb.x,
y: bomb.y,
anchorX: 0.5,
- anchorY: 0.5
+ anchorY: 0.5 // [cite: 94]
});
- bomb.destroy();
- game.addChild(explosion);
- var explosionDurationFrames = explosionFrames.length * (80 / (1000 / 60));
- var explosionMaxRadius = bombWidth * finalScale / 2 * 0.8;
+ bomb.destroy(); // [cite: 94]
+ game.addChild(explosion); // [cite: 94]
+ // --- ZMIANA: Dodanie eksplozji do listy śledzonych ataków ---
+ var explosionDurationFrames = explosionFrames.length * (80 / (1000 / 60)); // Czas trwania w klatkach gry
+ var explosionMaxRadius = bombWidth * finalScale / 2 * 0.8; // Przybliżony promień końcowy (0.8 dla bezpieczeństwa)
var explosionData = {
type: 'explosion',
visual: explosion,
+ // Referencja do animacji
radius: 0,
+ // Promień kolizji (zaczyna od 0)
maxRadius: explosionMaxRadius,
+ // Maksymalny promień
currentFrame: 0,
+ // Aktualna klatka (do obliczenia promienia)
totalFrames: explosionFrames.length,
+ // Całkowita liczba klatek
frameDurationTicks: 80 / (1000 / 60),
+ // Czas klatki w tickach timera
timer: 0,
- damageDealt: false
+ // Wewnętrzny timer klatki
+ damageDealt: false // Czy obrażenia zostały już zadane
};
gameState.rollMasterAttacks.push(explosionData);
- //console.log("Dodano eksplozję do śledzenia, maxRadius:", explosionMaxRadius);
+ console.log("Dodano eksplozję do śledzenia, maxRadius:", explosionMaxRadius);
+ // --- KONIEC ZMIANY ---
+ // Timer usuwania eksplozji (bez zmian, ale teraz też jest śledzona)
LK.setTimeout(function () {
+ // [cite: 94]
+ // Znajdź i usuń z listy, jeśli nadal tam jest (na wypadek gdyby kolizja jej nie usunęła)
var index = gameState.rollMasterAttacks.indexOf(explosionData);
if (index > -1) {
- gameState.rollMasterAttacks.splice(index, 1); /*console.log("Usunięto eksplozję z listy po czasie.");*/
+ 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
+ } // [cite: 94]
+ }, explosionFrames.length * 80 + 100); // [cite: 94]
+ }, 2000); // Czas przed eksplozją [cite: 94]
}
// Funkcja launchRmattack3 (bez zmian, już miała bezpieczne pozycjonowanie i pulsowanie)
function launchRmattack3() {
- //console.log("Launch rmattack3 (laser)");
+ var topSafeMargin = 600;
+ console.log("Launch rmattack3 (laser)");
+ // Bezpieczne pozycjonowanie (bez zmian)
var margin = 100;
var halfLaserWidth = 128 / 2;
- var halfLaserHeight = 1012 / 2;
+ var halfLaserHeight = 1012 / 2; // [cite: 95, 96]
var minX = margin + halfLaserWidth;
- var maxX = 2048 - margin - halfLaserWidth;
- var minY = margin + halfLaserHeight;
- var maxY = 2732 - margin - halfLaserHeight;
+ var maxX = 2048 - margin - halfLaserWidth; // [cite: 96, 97]
+ var calculatedMinY = margin + halfLaserHeight; // Oryginalne obliczenie minY
+ var minY = Math.max(calculatedMinY, topSafeMargin); // Użyj większej wartości: marginesu lub safe zone
+ var maxY = 2732 - margin - halfLaserHeight; // Dolna granica
+ // var laserY = minY + Math.random() * (maxY - minY); // <-- ZAKOMENTUJ LUB USUŃ STARĄ LINIĘ
+ var spawnHeight = maxY - minY; // Oblicz dostępną wysokość
+ var laserY = spawnHeight > 0 ? minY + Math.random() * spawnHeight : minY; // Losuj Y w bezpiecznej strefie
+ // --- KONIEC ZMIAN ---
var laserX = minX + Math.random() * (maxX - minX);
- var laserY = minY + Math.random() * (maxY - minY);
- var warningFrames = [];
+ // Animacja ostrzeżenia (bez zmian)
+ var warningFrames = []; // [cite: 99]
for (var i = 0; i < 5; i++) {
warningFrames.push(LK.getAsset('rmattack3_warning_' + i, {
anchorX: 0.5,
anchorY: 0.5,
width: 128,
height: 1012
}));
- }
+ } // [cite: 100]
var warningAnim = new SpriteAnimation({
frames: warningFrames,
frameDuration: 100,
loop: true,
@@ -3051,76 +3282,123 @@
y: laserY,
anchorX: 0.5,
anchorY: 0.5,
alpha: 1.0
- });
+ }); // [cite: 101]
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 ---
+ var currentPulseTween = null; // [cite: 101]
+ game.addChild(warningAnim); // [cite: 102]
+ var pulseDuration = 250; // [cite: 102]
+ function pulseUp() {
+ if (!warningAnim || warningAnim.destroyed || warningAnim.stopPulsing) {
+ currentPulseTween = null;
+ return;
+ }
+ currentPulseTween = tween(warningAnim, {
+ alpha: 1.0
+ }, {
+ duration: pulseDuration,
+ easing: tween.inOutQuad,
+ onFinish: function onFinish() {
+ if (warningAnim && !warningAnim.destroyed && !warningAnim.stopPulsing) {
+ pulseDown();
+ } else {
+ currentPulseTween = null;
+ }
+ }
+ });
+ } // [cite: 102, 103, 104]
+ function pulseDown() {
+ if (!warningAnim || warningAnim.destroyed || warningAnim.stopPulsing) {
+ currentPulseTween = null;
+ return;
+ }
+ currentPulseTween = tween(warningAnim, {
+ alpha: 0.1
+ }, {
+ duration: pulseDuration,
+ easing: tween.inOutQuad,
+ onFinish: function onFinish() {
+ if (warningAnim && !warningAnim.destroyed && !warningAnim.stopPulsing) {
+ pulseUp();
+ } else {
+ currentPulseTween = null;
+ }
+ }
+ });
+ } // [cite: 104, 105]
+ pulseDown(); // [cite: 106]
+ // Timer aktywacji lasera
LK.setTimeout(function () {
+ // [cite: 106]
if (warningAnim && !warningAnim.destroyed) {
warningAnim.stopPulsing = true;
- }
+ } // [cite: 106]
if (currentPulseTween && typeof currentPulseTween.stop === "function") {
try {
currentPulseTween.stop();
currentPulseTween = null;
} catch (e) {
console.warn("Nie udało się zatrzymać tweenu pulsowania:", e);
}
- }
+ } // [cite: 106]
if (warningAnim && !warningAnim.destroyed) {
- var activeFrames = [];
- var laserStrikeWidth = 228;
- var laserStrikeHeight = 1212;
+ // Jeśli ostrzeżenie nadal istnieje
+ // Tworzenie klatek ataku (bez zmian)
+ var activeFrames = []; // [cite: 106]
+ var laserStrikeWidth = 228; // Szerokość kolizji
+ var laserStrikeHeight = 1212; // Wysokość kolizji
for (var j = 0; j < 5; j++) {
activeFrames.push(LK.getAsset('rmattack3_strike_' + j, {
anchorX: 0.5,
anchorY: 0.5,
width: laserStrikeWidth,
height: laserStrikeHeight
}));
- }
+ } // [cite: 106]
+ // Tworzenie animacji ataku (bez zmian)
var strikeAnim = new SpriteAnimation({
+ // [cite: 106]
frames: activeFrames,
frameDuration: 40,
loop: true,
x: laserX,
y: laserY,
anchorX: 0.5,
anchorY: 0.5,
- alpha: 1
+ alpha: 1 // [cite: 106]
});
- game.addChild(strikeAnim);
- var laserDuration = 4000; // Czas życia lasera - na razie stały
+ game.addChild(strikeAnim); // [cite: 106]
+ var laserDuration = 4000; // Czas życia lasera w ms
+ // --- ZMIANA: Dodanie lasera do listy śledzonych ataków ---
var laserData = {
type: 'laser',
visual: strikeAnim,
+ // Referencja do animacji uderzenia
+ // Przekazujemy wymiary potrzebne do kolizji AABB w game.update
width: laserStrikeWidth,
height: laserStrikeHeight,
- lifeTime: laserDuration / (1000 / 60)
+ lifeTime: laserDuration / (1000 / 60) // Czas życia w klatkach gry
};
gameState.rollMasterAttacks.push(laserData);
- //console.log("Dodano laser do śledzenia.");
- warningAnim.destroy();
+ console.log("Dodano laser do śledzenia.");
+ // --- KONIEC ZMIANY ---
+ warningAnim.destroy(); // Usuń ostrzeżenie [cite: 106]
+ // Timer usuwania lasera
LK.setTimeout(function () {
+ // [cite: 106]
+ // Znajdź i usuń z listy, jeśli nadal tam jest
var index = gameState.rollMasterAttacks.indexOf(laserData);
if (index > -1) {
- gameState.rollMasterAttacks.splice(index, 1); /*console.log("Usunięto laser z listy po czasie.");*/
+ gameState.rollMasterAttacks.splice(index, 1);
+ console.log("Usunięto laser z listy po czasie.");
}
if (strikeAnim && !strikeAnim.destroyed) {
strikeAnim.destroy();
- }
- }, laserDuration);
+ } // [cite: 106]
+ }, laserDuration); // [cite: 106]
}
- }, warningTime); // Użyj zmiennej warningTime
+ }, 3000); // Czas trwania ostrzeżenia [cite: 107]
}
// Funkcja launchRmattack4 POPRAWIONA (tylko tworzenie, z bezpiecznym pozycjonowaniem 200px margin)
function launchRmattack4() {
// Obliczanie bezpiecznej strefy dla spreadera z marginesem 200px
@@ -3205,22 +3483,19 @@
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) {
+ if (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);
+ gameState.unlockedAttacks.push(attackToUnlock);
+ gameState.nextUnlockTime += 15; // Następne odblokowanie za 15 sekund
+ console.log("Odblokowano atak:", attackToUnlock, "Następne odblokowanie w:", gameState.nextUnlockTime, "s.");
+ // Wyświetl komunikat graczowi
+ if (ui && ui.showMessage) {
+ // Zamień nazwę techniczną na bardziej przyjazną
+ var friendlyName = attackToUnlock.replace('rmattack1', 'Projectiles').replace('rmattack2', 'Explosions').replace('rmattack3', 'Lasers').replace('rmattack4', 'Spreaders');
+ ui.showMessage("New Attack Unlocked: " + friendlyName, 2000);
}
}
// --- KONIEC ZMIANY ---
// --- ZMIANA: Spawnowanie ataków z uwzględnieniem odblokowania ---
@@ -3391,33 +3666,25 @@
if (atk.lifeTime <= 0) {
// shouldRemove = true; // Opcjonalnie usuń szybciej z listy
}
} else if (atk.type === 'spreader_parent') {
- // [cite: 1]
- atk.timer--; // [cite: 1]
+ // [cite: 144]
+ atk.timer--; // [cite: 144]
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]
+ // Czas na podział [cite: 144]
+ console.log("Spreader Parent dzieli się!");
+ var originX = atk.visual.x; // [cite: 144]
+ var originY = atk.visual.y; // [cite: 145]
+ var projectileSpeed = 5 + gameState.rollMasterDifficulty * 0.5; // Dostosuj prędkość dzieci [cite: 145]
+ var projectileRadius = 45; // [cite: 145]
+ var numProjectiles = 12; // [cite: 145]
for (var j = 0; j < numProjectiles; j++) {
- // 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]
+ // [cite: 146]
+ var angle = Math.PI * 2 / numProjectiles * j; // [cite: 146]
+ var vx = Math.cos(angle) * projectileSpeed; // [cite: 147]
+ var vy = Math.sin(angle) * projectileSpeed; // [cite: 147]
var projectileFrames = [
- // Definicja klatek (bez zmian) [cite: 6]
+ // [cite: 148]
LK.getAsset('rmattack1_0', {
anchorX: 0.5,
anchorY: 0.5
}), LK.getAsset('rmattack1_1', {
@@ -3437,50 +3704,76 @@
anchorY: 0.5
}), LK.getAsset('rmattack1_6', {
anchorX: 0.5,
anchorY: 0.5
- })]; // [cite: 7]
+ })]; // [cite: 148]
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]
+ scaleY: 1.2
+ }); // Zmniejszono skalę dzieci [cite: 149]
+ projectileVisual.rotation = Math.atan2(vy, vx); // [cite: 150]
+ var projectileObject = game.addChild(projectileVisual); // [cite: 150]
+ // Dodaj dziecko do listy ataków
gameState.rollMasterAttacks.push({
type: 'projectile',
visual: projectileObject,
vx: vx,
vy: vy,
radius: projectileRadius
- }); // [cite: 9]
- } // Koniec pętli for tworzącej dzieci
- shouldRemove = true; // Usuń rodzica po podziale [cite: 9]
+ }); // [cite: 151]
+ }
+ shouldRemove = true; // Usuń rodzica po podziale [cite: 151]
}
- // Kolizja rodzica z graczem (dopóki się nie podzieli) - bez zmian [cite: 10]
+ // Kolizja rodzica z graczem (dopóki się nie podzieli)
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]
+ // [cite: 151]
+ var dx_sp = player.x - atk.visual.x; // [cite: 151]
+ var dy_sp = player.y - atk.visual.y; // [cite: 152]
+ var dist_sp = Math.sqrt(dx_sp * dx_sp + dy_sp * dy_sp); // [cite: 152]
+ var playerRadius_sp = player.width / 2 * 0.8; // [cite: 153]
+ var spreaderRadius = atk.radius || 60; // [cite: 153]
if (dist_sp < playerRadius_sp + spreaderRadius) {
- // [cite: 13]
- console.log("Kolizja: Spreader Parent!"); // DEBUG [cite: 13]
+ // [cite: 154]
+ console.log("Kolizja: Spreader Parent!"); // DEBUG
if (!player.invulnerable) {
player.takeDamage(1);
- } // [cite: 14]
+ } // [cite: 154]
+ // Nie usuwamy rodzica po kolizji, tylko po podziale
}
}
}
+ // Usuń atak, jeśli flaga `shouldRemove` jest ustawiona
+ if (shouldRemove) {
+ // Upewnij się, że niszczysz wizualizację przed usunięciem z listy
+ if (atk.visual && atk.visual.destroy) {
+ atk.visual.destroy();
+ }
+ gameState.rollMasterAttacks.splice(i, 1);
+ continue; // Przejdź do następnego ataku w pętli
+ }
+ } // Koniec pętli for przez ataki
+ // --- KONIEC ZMIANY PĘTLI AKTUALIZACJI ---
+ // Sprawdzenie śmierci gracza (bez zmian)
+ if (player && player.dead) {
+ // [cite: 155]
+ if (typeof gameState !== 'undefined' && typeof gameState.endRollMasterMode === 'function') {
+ // [cite: 155]
+ gameState.endRollMasterMode(gameState.rollMasterTime); // [cite: 155]
+ } else {
+ // [cite: 156]
+ console.error("Nie można zakończyć trybu Roll Master - brak gameState lub metody endRollMasterMode"); // [cite: 156]
+ if (typeof gameState !== 'undefined' && typeof gameState.showGrillScreen === 'function') {
+ // [cite: 157]
+ gameState.showGrillScreen(); // [cite: 157]
+ }
+ }
}
} // Koniec else if (gameState.currentState === "rollMaster") [cite: 157]
}; // Koniec game.update
// --- Rozpoczęcie gry ---