Code edit (21 edits merged)
Please save this source code
User prompt
add new assets linearattack1 to linearattack5
Code edit (1 edits merged)
Please save this source code
Code edit (5 edits merged)
Please save this source code
User prompt
add new assets ultimatebossattack_orb_0 to ultimatebossattack_orb_7
Code edit (13 edits merged)
Please save this source code
User prompt
add new assets bossAttack4 bossAttack5 bossAttack6
Code edit (1 edits merged)
Please save this source code
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
/****
* 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 = [];
// Od 0 do 6
for (var i = 0; i <= 6; i++) {
frames.push(LK.getAsset('bossAttack' + i, {}));
}
// Od 5 do 1 (bez 6 i 0, by uniknąć powtórzenia)
for (var i = 5; i >= 1; i--) {
frames.push(LK.getAsset('bossAttack' + i, {}));
}
var bossAttackAnim = new SpriteAnimation({
frames: frames,
frameDuration: 100,
loop: false,
anchorX: 0.5,
anchorY: 0.5,
x: 0,
y: 0
});
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) ***
// UWAGA: Ultimate Attack (type 3) również nie zmienia grafiki bossa
if (attackType !== 2 && attackType !== 3) {
// Dodany warunek dla attackType 3
// 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 obieobu 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 lub attackType === 3, czyli ultimateAttack): playBossAttackAnim nic nie robi z grafiką.
// chargeAttack i ultimateAttack same zadbają o ustawienie grafiki 'idle' JAKO DZIECKO BOSSA.
};
// *** STAROŚĆ WRACA!
// Stara funkcja createAttack (przyjmuje x, y, duration) ***
self.createAttack = function (x, y, duration, type) {
var frames = [];
if (type === 'circle') {
frames = [LK.getAsset('fireball0', {
anchorX: 0.5,
anchorY: 0.5
}), LK.getAsset('fireball00', {
anchorX: 0.5,
anchorY: 0.5
}), LK.getAsset('fireball01', {
anchorX: 0.5,
anchorY: 0.5
}), LK.getAsset('fireball02', {
anchorX: 0.5,
anchorY: 0.5
}), LK.getAsset('fireball03', {
anchorX: 0.5,
anchorY: 0.5
}), LK.getAsset('fireball04', {
anchorX: 0.5,
anchorY: 0.5
}), LK.getAsset('fireball05', {
anchorX: 0.5,
anchorY: 0.5
}), LK.getAsset('fireball06', {
anchorX: 0.5,
anchorY: 0.5
}), LK.getAsset('fireball07', {
anchorX: 0.5,
anchorY: 0.5
}), LK.getAsset('fireball08', {
anchorX: 0.5,
anchorY: 0.5
}), LK.getAsset('fireball09', {
anchorX: 0.5,
anchorY: 0.5
}), LK.getAsset('fireball1', {
anchorX: 0.5,
anchorY: 0.5
}), LK.getAsset('fireball10', {
anchorX: 0.5,
anchorY: 0.5
}), LK.getAsset('fireball11', {
anchorX: 0.5,
anchorY: 0.5
}), LK.getAsset('fireball12', {
anchorX: 0.5,
anchorY: 0.5
}), LK.getAsset('fireball13', {
anchorX: 0.5,
anchorY: 0.5
}), LK.getAsset('fireball14', {
anchorX: 0.5,
anchorY: 0.5
}), LK.getAsset('fireballnew1', {
anchorX: 0.5,
anchorY: 0.5
}), LK.getAsset('fireballnew2', {
anchorX: 0.5,
anchorY: 0.5
}), LK.getAsset('fireballnew3', {
anchorX: 0.5,
anchorY: 0.5
}), LK.getAsset('fireballnew4', {
anchorX: 0.5,
anchorY: 0.5
})];
} else if (type === 'line') {
frames = [LK.getAsset('fireball2', {
anchorX: 0.5,
anchorY: 0.5
}), LK.getAsset('fireball3', {
anchorX: 0.5,
anchorY: 0.5
}), LK.getAsset('fireball4', {
anchorX: 0.5,
anchorY: 0.5
}), LK.getAsset('fireball5', {
anchorX: 0.5,
anchorY: 0.5
}), LK.getAsset('fireball6', {
anchorX: 0.5,
anchorY: 0.5
}), LK.getAsset('fireball7', {
anchorX: 0.5,
anchorY: 0.5
}), LK.getAsset('fireball8', {
anchorX: 0.5,
anchorY: 0.5
}), LK.getAsset('fireball9', {
anchorX: 0.5,
anchorY: 0.5
}), LK.getAsset('fireball15', {
anchorX: 0.5,
anchorY: 0.5
}), LK.getAsset('fireball16', {
anchorX: 0.5,
anchorY: 0.5
})];
}
var currentFrameDuration = 140; // Używamy zmiennej dla czasu klatki
var spriteAnim = game.addChild(new SpriteAnimation({
frames: frames,
frameDuration: currentFrameDuration,
// Użycie zmiennej
// czas w ms jednej klatki // Twój komentarz zostawiony
loop: false,
// Zmienione na false
anchorX: 0.5,
anchorY: 0.5,
x: x,
y: y
}));
spriteAnim.scaleX = 1.6;
spriteAnim.scaleY = 1.6;
var attackData = {
x: x,
y: y,
radius: 60,
visual: spriteAnim,
lifeTime: Math.floor(duration / (1000 / 60)),
// Czas życia logiki ataku
isActive: true
};
self.attacks.push(attackData);
// Oblicz rzeczywisty czas trwania animacji wizualnej
var animationDuration = frames.length * currentFrameDuration;
LK.setTimeout(function () {
// Callback niszczący animację i usuwający dane ataku
var index = self.attacks.indexOf(attackData);
if (index !== -1) {
self.attacks.splice(index, 1);
}
if (spriteAnim && !spriteAnim.destroyed) {
spriteAnim.destroy();
}
}, animationDuration);
// Użyj obliczonego 'animationDuration' ZAMIAST 'duration'
};
// *** STAROŚĆ WRACA! Stara funkcja circleAttack ***
self.circleAttack = function () {
LK.getSound('bossAttack').play();
var center = {
x: self.x,
y: self.y
};
var count = isNewBossPlusMode ? 12 : 8;
var radius = 300;
var attackLifeTime = isNewBossPlusMode ? 2000 : 3000;
for (var i = 0; i < count; i++) {
var angle = i / count * Math.PI * 2;
var x = center.x + Math.cos(angle) * radius;
var y = center.y + Math.sin(angle) * radius;
self.createAttack(x, y, attackLifeTime, 'circle');
// DODANE 'circle'
}
};
// Zmodyfikowana funkcja lineAttack, teraz używa starej sygnatury createAttack
self.lineAttack = function () {
LK.getSound('bossAttack').play();
if (!player) {
return;
}
var targetX = player.x;
var targetY = player.y;
var count = isNewBossPlusMode ? 12 : 12;
var attackLifeTime = isNewBossPlusMode ? 3000 : 3500;
var delayBetweenAttacks = isNewBossPlusMode ? 60 : 60;
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();
if (!player) {
return;
}
if (self.bossAttackAnim) {
self.bossAttackAnim.stop();
if (self.bossAttackAnim.parent === self) {
self.bossAttackAnim.parent.removeChild(self.bossAttackAnim);
} else if (self.bossAttackAnim.parent) {
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) {
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;
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;
}
var startX = self.x;
var startY = self.y;
var chargeDistance = isNewBossPlusMode ? 700 : 500;
var chargeDuration = isNewBossPlusMode ? 600 : 800;
var attacksOnPathCount = isNewBossPlusMode ? 8 : 5; // Ta zmienna jest, ale nie ma pętli jej używającej do tworzenia ataków w obecnej wersji kodu
var attackLifeTime = isNewBossPlusMode ? 1000 : 1500;
tween(self, {
x: self.x + dx * chargeDistance,
y: self.y + dy * chargeDistance
}, {
duration: chargeDuration * self.attackSpeedMultiplier,
easing: tween.easeIn,
onFinish: function onFinish() {
// --- POCZĄTEK POPRAWKI ---
// Usunięto: niszczenie self (bossa) i wywoływanie gameState.gameOver(true)
// Po zakończeniu ruchu szarży, boss powinien po prostu wrócić do swojego cyklu AI.
// Jego attackCooldown będzie się dalej zmniejszał w Boss.update(),
// a gdy dojdzie do zera, startAttackPattern() wybierze nowy atak.
console.log("Boss: Szarża (ruch) zakończona. Pozycja bossa: x=" + self.x + ", y=" + self.y);
// Nie ma potrzeby robić tu nic więcej, chyba że szarża ma mieć specjalny efekt
// na końcu (np. krótki czas odnowienia przed kolejnym atakiem).
//
// Obecna logika AI (attackCooldown i startAttackPattern) powinna sobie poradzić.
// --- KONIEC POPRAWKI ---
}
});
// Uwaga: W obecnej wersji tej funkcji (w pliku naprawa.txt) nie ma logiki,
// która tworzyłaby jakiekolwiek pociski/ataki W TRAKCIE trwania szarży.
// Jeśli takie zachowanie było zamierzone, odpowiedni kod (pętla używająca attacksOnPathCount
// i wywołująca self.createAttack) musiałby zostać tutaj dodany.
// Na ten moment, chargeAttack to tylko ruch bossa.
};
// --- NOWA FUNKCJA: Ultimate Attack (Gravity Well / Singularity Orb) ---
self.ultimateAttack = function () {
if (self.dead || gameState.currentState !== "game" || !isNewBossPlusMode) {
return;
}
console.log("Boss uruchamia ULTIMATE ATTACK (3 kule)");
// --- KOD DLA IKONY ATAKU (Bez zmian, pozostaje, jeśli chcesz go) ---
var iconDuration = 3500;
var iconFadeOutDuration = 500;
var iconOffsetX = 250;
var iconOffsetY = 250;
var attackIcon = game.addChild(LK.getAsset('ultimateBossAttack_icon', {
anchorX: 0.5,
anchorY: 0.5,
x: 2048 - iconOffsetX,
y: iconOffsetY,
alpha: 0,
scaleX: 0.8,
scaleY: 0.8
}));
tween(attackIcon, {
alpha: 1,
scaleX: 1,
scaleY: 1
}, {
duration: 200,
easing: tween.easeOutQuad,
onFinish: function onFinish() {
if (attackIcon.destroyed) {
return;
}
LK.setTimeout(function () {
if (attackIcon.destroyed) {
return;
}
tween(attackIcon, {
alpha: 0
}, {
duration: iconFadeOutDuration,
easing: tween.easeInQuad,
onFinish: function onFinish() {
if (attackIcon && !attackIcon.destroyed) {
attackIcon.destroy();
}
}
});
}, iconDuration - iconFadeOutDuration);
}
});
// --- KONIEC KODU DLA IKONY ATAKU ---
// --- DEFINICJA WSPÓLNYCH PARAMETRÓW DLA KUL ---
var orbFrames = [];
for (var i = 0; i <= 7; i++) {
// Mamy 8 klatek animacji (od 0 do 7)
orbFrames.push(LK.getAsset('ultimatebossattack_orb_' + i, {
anchorX: 0.5,
anchorY: 0.5
}));
}
var originalOrbWidth = 100; // <-- Zmień na rzeczywistą szerokość TWOJEGO assetu 'ultimatebossattack_orb_0'
var originalOrbHeight = 100; // <-- Zmień na rzeczywistą wysokość TWOJEGO assetu 'ultimatebossattack_orb_0'
var baseOrbRadius = Math.max(originalOrbWidth, originalOrbHeight) / 2;
var growAndFadeInDuration = 5000; // Czas wzrostu i pojawienia się kuli (5 sekund)
var finalScale = 8.0; // Końcowa skala 800% (8x większa)
var orbSpeed = 15; // Prędkość kuli (dostosuj)
var travelDurationMs = 6000; // Czas życia kuli w ms
// --- POMOCNICZA FUNKCJA DO TWORZENIA POJEDYNCZEJ KULI ---
function createAndLaunchOrb(spawnX, spawnDelay) {
LK.setTimeout(function () {
// Upewnij się, że gra nadal działa i boss żyje
if (self.dead || gameState.currentState !== "game") {
return;
}
var orbAnim = new SpriteAnimation({
frames: orbFrames,
frameDuration: 100,
// Szybkość animacji klatek w trakcie tweena
loop: true,
x: spawnX,
// Pozycja X podana jako argument
y: 300,
// Stała pozycja Y
anchorX: 0.5,
anchorY: 0.5,
scaleX: 0.1,
// Początkowa skala 10%
scaleY: 0.1,
alpha: 0 // Zaczyna się od przezroczystości 0
});
game.addChild(orbAnim);
tween(orbAnim, {
scaleX: finalScale,
scaleY: finalScale,
alpha: 1
}, {
duration: growAndFadeInDuration,
// 5 sekund wzrostu
easing: tween.easeOutQuad,
onFinish: function onFinish() {
if (orbAnim.destroyed) {
return;
}
// Faza 3: Kula zaczyna lecieć w stronę gracza (dopiero po zakończeniu animacji pojawiania)
var targetX = player.x;
var targetY = player.y;
var actualOrbRadius = baseOrbRadius * finalScale;
var dx = targetX - orbAnim.x;
var dy = targetY - orbAnim.y;
var distance = Math.sqrt(dx * dx + dy * dy);
var vx = dx / distance * orbSpeed;
var vy = dy / distance * orbSpeed;
self.attacks.push({
type: 'ultimate_orb',
visual: orbAnim,
vx: vx,
vy: vy,
radius: actualOrbRadius,
lifeTime: travelDurationMs / (1000 / 60) // Czas życia w klatkach
});
}
});
}, spawnDelay); // Opóźnienie dla każdej kuli
}
// --- SPAWNOWANIE TRZECH KUL Z OPÓŹNIENIEM ---
var spawnDelayBetweenOrbs = 2500; // Opóźnienie między kulami (3 sekundy)
// Kula 1: Środek
createAndLaunchOrb(2048 / 2, 0);
// Kula 2: Lewo
createAndLaunchOrb(2048 / 2 - 600, spawnDelayBetweenOrbs); // Po 3 sekundach
// Kula 3: Prawo
createAndLaunchOrb(2048 / 2 + 600, spawnDelayBetweenOrbs * 2); // Po 6 sekundach (2 * 3s)
};
self.takeDamage = function (amount) {
// Dodano log na początku
console.log("DEBUG: Boss.takeDamage(", amount, ") called. Boss dead:", self.dead, "Current state:", gameState.currentState);
if (self.dead || gameState.currentState !== "game") {
return;
}
self.health -= amount;
self.health = Math.max(0, self.health);
console.log("DEBUG: Boss health now:", self.health, "/", self.maxHealth); // Log po zmianie zdrowia
LK.effects.flashObject(self, 0xFFFFFF, 200);
if (!isNewBossPlusMode) {
if (self.health <= self.maxHealth / 2 && self.phase === 1) {
console.log("DEBUG: Boss entering phase 2.");
// Log zmiany fazy
self.phase = 2;
self.speed += 2;
self.attackSpeedMultiplier *= 0.8;
tween(self, {
tint: 0xFF3300
}, {
duration: 1000,
easing: tween.easeInOut
});
}
}
if (self.health <= 0) {
console.log("DEBUG: Boss health <= 0, calling self.die()."); // Log przed wywołaniem die
self.die();
}
};
self.die = function () {
if (self.dead || gameState.currentState !== "game") {
console.log("DEBUG: Boss.die() exited early. Already dead or wrong state:", self.dead, gameState.currentState);
return;
}
console.log("DEBUG: Boss.die() called.");
self.dead = true;
if (!isNewBossPlusMode) {
LK.getSound('victory').play();
}
console.log("DEBUG: Boss.die - Clearing attacks. Count before:", self.attacks.length);
self.attacks.forEach(function (attack, index) {
if (attack.visual && attack.visual.destroy && !attack.visual.destroyed) {
attack.visual.destroy();
}
});
self.attacks = [];
console.log("DEBUG: Boss.die - Attacks cleared. Count after:", self.attacks.length);
// Dodajemy log przed rozpoczęciem animacji śmierci bossa
console.log("DEBUG: Boss.die - Starting death animation tween.");
tween(self, {
alpha: 0,
scaleX: 2,
scaleY: 2
}, {
duration: 2000,
easing: tween.easeOut,
onFinish: function onFinish() {
console.log("DEBUG: Boss.die tween onFinish reached.");
if (self && self.destroy && !self.destroyed) {
console.log("DEBUG: Boss.die onFinish - Destroying boss object.");
self.destroy();
}
// Używamy parseInt dla bezpieczeństwa
storage.bossesDefeated = (parseInt(storage.bossesDefeated, 10) || 0) + 1;
console.log("DEBUG: Boss.die onFinish - Bosses defeated:", storage.bossesDefeated);
console.log("DEBUG: Boss.die onFinish - Calling gameState.showGrillScreen()...");
if (typeof gameState !== 'undefined' && typeof gameState.showGrillScreen === 'function') {
gameState.showGrillScreen();
} else {
console.error("!!! CRITICAL: Cannot call gameState.showGrillScreen from Boss.die.onFinish!");
}
}
});
}; // Zamykający nawias klamrowy dla self.die
// 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 || !attack.visual || attack.visual.destroyed) {
self.attacks.splice(i, 1);
continue;
}
if (attack.visual.update && typeof attack.visual.update === 'function') {
attack.visual.update();
}
var shouldRemove = false;
// Logika dla Ultimate Orb (kula lecąca)
if (attack.type === 'ultimate_orb') {
attack.visual.x += attack.vx;
attack.visual.y += attack.vy;
attack.lifeTime--; // Zmniejszaj czas życia
// Sprawdzenie kolizji gracza z kulą
if (player && !player.dead && !player.rolling && !player.invulnerable) {
var dx_o = player.x - attack.visual.x;
var dy_o = player.y - attack.visual.y;
var dist_o = Math.sqrt(dx_o * dx_o + dy_o * dy_o);
var playerRadius_o = player.width / 2 * 0.8;
if (dist_o < playerRadius_o + attack.radius) {
console.log("Kolizja: Ultimate Orb!");
player.takeDamage(1);
shouldRemove = true; // Usuń kulę po trafieniu gracza
// LK.getSound('ultimatebossattack_hit_sound').play(); // Dźwięk trafienia
}
}
// Sprawdzenie, czy kula wyleciała poza ekran
var screenMargin = 200;
if (attack.visual.x < -screenMargin || attack.visual.x > 2048 + screenMargin || attack.visual.y < -screenMargin || attack.visual.y > 2732 + screenMargin) {
console.log("Ultimate Orb wyleciała poza ekran.");
shouldRemove = true;
}
// Jeśli czas życia dobiegł końca, usuń kulę (awaryjne usunięcie)
if (attack.lifeTime <= 0) {
console.log("Ultimate Orb osiągnęła limit życia i zniknęła.");
shouldRemove = true;
}
} else 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);
}
// Jeśli flaga shouldRemove została ustawiona dla ultimate_orb, usuń go
if (shouldRemove) {
if (attack.visual && attack.visual.destroy && !attack.visual.destroyed) {
attack.visual.destroy();
}
self.attacks.splice(i, 1);
continue; // Przejdź do następnego ataku w pętli
}
}
// --- 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 (ten jest domyślny dla innych ataków)
self.attackCooldown = isNewBossPlusMode ? 150 : 200;
var attackType;
if (isNewBossPlusMode) {
// W trybie Boss+ dodaj szansę na Ultimate Attack (teraz 4 typy: 0, 1, 2, 3)
attackType = Math.floor(Math.random() * 4);
} else {
attackType = Math.floor(Math.random() * 3); // 0, 1 lub 2 (stare ataki w trybie standardowym)
}
// 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 if (attackType === 2) {
// Atak szarży
self.chargeAttack();
} else if (attackType === 3 && isNewBossPlusMode) {
// Nowy atak (ultimateAttack) tylko w trybie Boss+
self.ultimateAttack();
// --- PRZYWRÓCENIE LOSOWEGO COOLDOWNU DLA ULTIMATE ATTACK ---
// Ustawienie cooldownu na losową wartość między 20 a 30 sekund
// Pamiętaj, że 1 sekunda to 60 klatek (jeśli gra działa w 60 FPS)
var minSeconds = 20;
var maxSeconds = 30;
self.attackCooldown = Math.floor(Math.random() * (maxSeconds - minSeconds + 1) + minSeconds) * 60;
console.log("Ultimate Attack odpalony! Następny za około " + self.attackCooldown / 60 + " sekund.");
}
};
return self; // Zwróć instancję obiektu Boss
});
// Zamykająca klamra dla Container.expand klasy Boss
// --- POCZĄTEK BLOKU DO SKOPIOWANIA ---
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;
self.speed = 8;
self.rolling = false;
self.rollDirection = {
x: 0,
y: 0
};
self.rollSpeed = 20;
self.rollDuration = 300;
self.rollCooldown = 0;
self.invulnerable = false;
self.invulnerabilityFrames = 0;
self.defaultInvulnerabilityFrames = 30;
self.postHitInvulnerabilityTimer = null;
self.dead = false;
self.rollTimeoutId = null;
self.invulnerabilityTimeoutId = null;
self.rollAnimationInterval = null;
self.hasRolledThroughBossThisRoll = false;
// --- Funkcje Pomocnicze ---
// *** TO JEST DEFINICJA FUNKCJI, KTÓREJ BRAKUJE ***
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;
}
};
// *** KONIEC DEFINICJI clearRollTimeouts ***
// --- Mechanika Uniku (Roll) ---
self.roll = function (direction, duration) {
if (!self.rolling && self.rollCooldown <= 0 && !self.dead) {
var targetScaleX = direction.x >= 0 || direction.x === 0 ? 1 : -1;
var currentRollDuration = duration || self.rollDuration;
self.rolling = true;
self.rollDirection = direction;
self.rollCooldown = 45;
var durationRatio = currentRollDuration / self.rollDuration;
self.invulnerabilityFrames = Math.round(self.defaultInvulnerabilityFrames * durationRatio);
self.hasRolledThroughBossThisRoll = false;
if (self.idleAnimationSprite && !self.idleAnimationSprite.destroyed) {
self.idleAnimationSprite.stop();
self.idleAnimationSprite.visible = false;
}
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);
if (self.rollTimeoutId) {
LK.clearTimeout(self.rollTimeoutId);
}
self.rollTimeoutId = LK.setTimeout(function () {
if (!self || self.destroyed) {
return;
}
self.rolling = false;
if (self.rollAnimationInterval) {
LK.clearInterval(self.rollAnimationInterval);
self.rollAnimationInterval = null;
}
if (rollAnimationSprite && rollAnimationSprite.destroy) {
rollAnimationSprite.destroy();
rollAnimationSprite = null;
}
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;
}
if (self.idleAnimationSprite && !self.idleAnimationSprite.destroyed) {
self.idleAnimationSprite.scaleX = targetScaleX;
self.idleAnimationSprite.visible = true;
self.idleAnimationSprite.play();
}
}
}, 100);
self.rollTimeoutId = null;
}, currentRollDuration);
}
};
// --- Otrzymywanie Obrażeń ---
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;
}
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;
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ć (z poprawkami dla storage i onFinish) ---
self.die = function () {
// Log na samym początku
console.log("!!!! DEBUG: Player.die() called! State:", gameState.currentState, "Boss dead:", boss ? boss.dead : 'N/A');
if (self.dead) {
console.log("DEBUG: Player.die() exited - already dead.");
return;
}
self.dead = true;
var currentDeaths = parseInt(storage.totalDeaths, 10) || 0;
storage.totalDeaths = currentDeaths + 1;
console.log("DEBUG: Zaktualizowano totalDeaths na:", storage.totalDeaths);
var currentMaxHearts = parseInt(storage.maxHearts, 10);
if (isNaN(currentMaxHearts) || currentMaxHearts < 5) {
storage.maxHearts = 5;
}
if (storage.maxHearts < 10) {
storage.maxHearts = (parseInt(storage.maxHearts, 10) || 5) + 1;
}
console.log("DEBUG: Zaktualizowano maxHearts na:", storage.maxHearts);
try {
self.clearRollTimeouts();
} catch (e) {
console.error("!!! Błąd podczas wywoływania self.clearRollTimeouts w Player.die:", e);
}
tween(self, {
alpha: 0,
scaleX: 2,
scaleY: 2
}, {
duration: 1000,
easing: tween.easeOut,
onFinish: function onFinish() {
console.log("DEBUG: Player.die tween onFinish reached. State:", gameState.currentState); // Log w onFinish
if (self && self.destroy && !self.destroyed) {
self.destroy();
}
gameOverReasonIsDeath = true;
if (gameState.currentState !== "rollMaster" && gameState.currentState !== "rollMasterGameOver") {
console.log("DEBUG: Player.die onFinish - Calling gameState.gameOver(true) for non-RollMaster."); // Log przed wywołaniem gameOver
if (typeof gameState !== 'undefined' && typeof gameState.gameOver === 'function') {
gameState.gameOver(true);
} else {
console.error("Nie można wywołać gameState.gameOver() z Player.die dla trybu non-RollMaster");
}
} else {
console.log("Player.die (RollMaster): onFinish. Przejście obsługiwane przez endRollMasterMode.");
}
}
});
}; // Koniec self.die
// --- Aktualizacja (Update) ---
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--;
}
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) {
if (self.invulnerabilityFrames > 0) {
self.invulnerabilityFrames--;
if (currentVisualSprite) {
currentVisualSprite.alpha = self.invulnerabilityFrames % 4 > 1 ? 0.3 : 1;
}
} else {
if (currentVisualSprite) {
currentVisualSprite.alpha = 1;
}
}
var rollDx = self.rollDirection.x * self.rollSpeed;
var rollDy = self.rollDirection.y * self.rollSpeed;
var nextX = self.x + rollDx;
var nextY = self.y + rollDy;
var halfWidth = self.width / 2;
var halfHeight = self.height / 2;
if (currentVisualSprite) {
halfWidth = currentVisualSprite.width / 2 * currentVisualSprite.scaleX;
halfHeight = currentVisualSprite.height / 2 * currentVisualSprite.scaleY;
}
var minX = 100 + halfWidth;
var maxX = 2048 - 100 - halfWidth;
var minY = 300 + halfHeight;
var maxY = 2732 - 100 - halfHeight;
self.x = Math.max(minX, Math.min(nextX, maxX));
self.y = Math.max(minY, Math.min(nextY, maxY));
if (boss && !boss.dead && !self.hasRolledThroughBossThisRoll) {
var dx_b = self.x - boss.x;
var dy_b = self.y - boss.y;
var distance_b = Math.sqrt(dx_b * dx_b + dy_b * dy_b);
var playerCollisionRadius = self.width / 2;
if (currentVisualSprite) {
playerCollisionRadius = Math.max(currentVisualSprite.width, currentVisualSprite.height) / 2 * currentVisualSprite.scaleX;
}
var hitboxMultiplier = 1.2;
var bossCollisionRadius = boss.width * boss.scaleX / 2 * hitboxMultiplier;
if (distance_b < playerCollisionRadius + bossCollisionRadius) {
boss.takeDamage(10);
self.hasRolledThroughBossThisRoll = true;
LK.effects.flashObject(boss, 0x00FF00, 200);
}
}
} else if (self.invulnerable && !self.rolling) {
if (currentVisualSprite) {
currentVisualSprite.alpha = Math.floor(Date.now() / 100) % 4 > 1 ? 0.3 : 1;
}
} else {
if (currentVisualSprite && currentVisualSprite.alpha !== 1) {
currentVisualSprite.alpha = 1;
}
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;
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;
}
}
if (self.idleAnimationSprite && self.idleAnimationSprite.visible && !self.idleAnimationSprite.destroyed && self.idleAnimationSprite.update) {
self.idleAnimationSprite.update();
}
}; // Koniec self.update
return self;
});
// Koniec Player
// --- KONIEC BLOKU DO SKOPIOWANIA ---
// Koniec Player
// <--- 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ę (źródło 186)
}
self.currentFrame++;
if (self.currentFrame >= self.frames.length) {
if (self.loop) {
self.currentFrame = 0;
self.addChild(self.frames[self.currentFrame]); // Dodajemy nową klatkę, jeśli zapętlamy
} else {
self.playing = false;
if (typeof self.onComplete === 'function') {
self.onComplete(); // ← wywołanie eventu zakończenia animacji
}
return;
}
} else {
// Dodajemy nową klatkę tylko wtedy, gdy animacja jeszcze trwa
self.addChild(self.frames[self.currentFrame]);
}
}
};
// Method to stop animation
self.stop = function () {
self.playing = false;
};
// Method to start/resume animation
self.play = function () {
self.playing = true;
};
// Method to set a specific frame
self.gotoFrame = function (frameIndex) {
if (frameIndex >= 0 && frameIndex < self.frames.length) {
self.removeChildren();
self.currentFrame = frameIndex;
self.addChild(self.frames[self.currentFrame]);
}
};
return self;
});
var UI = Container.expand(function () {
var self = Container.call(this);
// Create heart containers (wizualizacja zdrowia)
self.hearts = [];
self.heartContainer = new Container(); // Kontener na ikony serc
self.addChild(self.heartContainer);
// Title and messages (teksty na ekranach)
self.titleText = new Text2("ROLL SOULS", {
size: 150,
fill: 0xFFFFFF
});
self.titleText.anchor.set(0.5, 0.5);
self.addChild(self.titleText);
self.messageText = new Text2("", {
size: 60,
fill: 0xFFFFFF
});
self.messageText.anchor.set(0.5, 0.5);
self.addChild(self.messageText);
// Deaths counter (licznik śmierci)
self.deathsText = new Text2("Deaths: 0", {
size: 40,
fill: 0xFFFFFF
});
self.deathsText.anchor.set(1, 0); // Wyrównanie do prawego górnego rogu
self.deathsText.x = 2048 - 50; // Pozycja X od prawej krawędzi
self.deathsText.y = 50; // Pozycja Y od góry
self.addChild(self.deathsText);
// Tutorial text (teksty tutoriali)
self.tutorialText = new Text2("", {
size: 50,
fill: 0xFFFFFF
});
self.tutorialText.anchor.set(0.5, 0); // Wyrównanie do środka na górze
self.tutorialText.x = 2048 / 2; // Pozycja X na środku
self.tutorialText.y = 200; // Pozycja Y (może być różna w zależności od stanu)
self.addChild(self.tutorialText);
// --- Timer Text ---
self.timerText = new Text2("00:00", {
// Zmieniono tekst początkowy
size: 60,
fill: 0xFFFFFF // Biały kolor
});
self.timerText.x = 2048 / 2; // Wyśrodkuj w poziomie
self.timerText.y = 50; // Przysuń do góry
self.timerText.anchor.set(0.5, 0); // Pozycja Y od górnej krawędzi
self.timerText.alpha = 0; // Domyślnie ukryty
self.addChild(self.timerText);
// --- DODANY NOWY ELEMENT TEKSTOWY DLA REKORDU ---
self.highScoreText = new Text2("Best: 00:00", {
// Tekst początkowy
size: 50,
// Mniejszy rozmiar niż aktualny czas
fill: 0xFFFF00 // Żółty kolor dla odróżnienia
});
self.highScoreText.x = 2048 - 50; // Przysuń do prawej krawędzi (z marginesem 50)
self.highScoreText.y = 50; // Przysuń do góry (ta sama wysokość co timer)
self.highScoreText.anchor.set(1, 0); // Pozycja Y (poniżej timerText)
self.highScoreText.alpha = 0; // Domyślnie ukryty
self.addChild(self.highScoreText); // Dodaj do kontenera UI
// --- KONIEC DODAWANIA NOWEGO ELEMENTU ---
// --- Boss Health Bar (wizualizacja zdrowia bossa) ---
self.bossHealthBarContainer = new Container(); // Kontener na pasek zdrowia bossa
self.bossHealthBarContainer.x = 2048 / 2; // Wyśrodkuj poziomo
self.bossHealthBarContainer.y = 150; // Pozycja Y (poniżej timera)
self.bossHealthBarContainer.alpha = 0; // Domyślnie ukryty
self.addChild(self.bossHealthBarContainer);
var barWidth = 800; // Szerokość paska zdrowia (musi być taka sama jak szerokość assetu bossHpbar)
var barHeight = 30; // Wysokość paska zdrowia (musi być taka sama jak wysokość assetu bossHpbar)
// Właściwy pasek zdrowia bossa (czerwony) - UŻYWAMY TERAZ NOWEGO ASSETU bossHpbar
self.bossHealthBar = self.attachAsset('bossHpbar', {
// Użyj assetu 'bossHpbar'
anchorX: 0,
// Ustaw punkt odniesienia na lewą krawędź (0), środek pionowo (0.5)
anchorY: 0.5,
x: -barWidth / 2 // Przesuń w lewo o połowę szerokości tła
});
self.bossHealthBarContainer.addChild(self.bossHealthBar);
// --- METODY AKTUALIZACJI UI ---
// Aktualizuje wizualizację serc na UI
self.updateHearts = function (current, max) {
current = Math.max(0, current || 0);
max = Math.max(1, max || 1); // Max musi być co najmniej 1
if (self.hearts.length !== max) {
while (self.hearts.length > 0) {
var oldHeart = self.hearts.pop();
if (oldHeart && oldHeart.destroy && !oldHeart.destroyed) {
oldHeart.destroy();
}
}
self.heartContainer.removeChildren(); // Usuń wizualne obiekty serc
for (var i = 0; i < max; i++) {
var heart = LK.getAsset('heart', {
anchorX: 0.5,
anchorY: 0.5,
x: i * 50,
y: 0,
tint: i < current ? 0xFF0000 : 0x555555
});
self.hearts.push(heart);
self.heartContainer.addChild(heart);
}
self.heartContainer.x = (2048 - max * 50) / 2 + 25;
self.heartContainer.y = 100; // Zmieniono pozycję serc, aby zrobić miejsce
} else {
for (var j = 0; j < self.hearts.length; j++) {
if (self.hearts[j]) {
self.hearts[j].tint = j < current ? 0xFF0000 : 0x555555;
}
}
}
};
// Aktualizuje wizualizację paska zdrowia bossa
self.updateBossHealth = function (current, max) {
current = Math.max(0, current || 0);
max = Math.max(1, max || 1);
var barWidth = 800;
var currentWidth = current / max * barWidth;
if (self.bossHealthBar) {
self.bossHealthBar.width = currentWidth;
var shouldBeVisible = (gameState.currentState === "game" || gameState.currentState === "gameOver") && (boss && !boss.dead && current > 0 || gameState.currentState === "gameOver" && !gameOverReasonIsDeath && isNewBossPlusMode);
self.bossHealthBarContainer.alpha = shouldBeVisible ? 1 : 0;
}
if (self.bossHealthBarBg) {
// Jeśli używasz tła paska HP
self.bossHealthBarBg.alpha = self.bossHealthBarContainer.alpha;
}
};
// Wyświetla komunikat na środku ekranu
self.showMessage = function (message, duration) {
self.messageText.setText(message);
self.messageText.alpha = 1;
if (self.messageTimeout) {
LK.clearTimeout(self.messageTimeout);
self.messageTimeout = null;
}
if (duration && duration > 0) {
self.messageTimeout = LK.setTimeout(function () {
tween(self.messageText, {
alpha: 0
}, {
duration: 500,
onFinish: function onFinish() {
self.messageTimeout = null;
}
});
}, duration);
} else {
self.messageText.alpha = 1;
}
};
// Wyświetla tekst tutorialu
self.showTutorial = function (text) {
self.tutorialText.setText(text);
self.tutorialText.alpha = 1;
};
// Ukrywa tekst tutorialu (zanikając)
self.hideTutorial = function () {
tween(self.tutorialText, {
alpha: 0
}, {
duration: 500
});
};
// Aktualizuje licznik śmierci
self.updateDeathsCounter = function () {
var deaths = parseInt(storage.totalDeaths, 10) || 0;
self.deathsText.setText("Deaths: " + deaths);
self.deathsText.visible = gameState.currentState === "game";
};
// Aktualizuje wyświetlanie czasu timera
self.updateTimerDisplay = function (seconds) {
seconds = Math.max(0, seconds || 0);
var minutes = Math.floor(seconds / 60);
var remainingSeconds = seconds % 60;
var formattedSeconds = remainingSeconds < 10 ? '0' + remainingSeconds : remainingSeconds;
var formattedTime = String(minutes).padStart(2, '0') + ':' + formattedSeconds;
if (self.timerText) {
// W Roll Master wyświetlamy "Time:", w innych trybach tylko czas
var prefix = gameState.currentState === "rollMaster" ? "Time: " : "";
self.timerText.setText(prefix + formattedTime);
}
};
// --- DODANA NOWA FUNKCJA DO AKTUALIZACJI TEKSTU REKORDU ---
self.updateHighScoreDisplay = function (seconds) {
seconds = Math.max(0, seconds || 0);
var minutes = Math.floor(seconds / 60);
var remainingSeconds = seconds % 60;
var formattedSeconds = remainingSeconds < 10 ? '0' + remainingSeconds : remainingSeconds;
var formattedTime = String(minutes).padStart(2, '0') + ':' + formattedSeconds;
if (self.highScoreText) {
// Sprawdź, czy obiekt istnieje
self.highScoreText.setText("Best: " + formattedTime);
}
};
// --- KONIEC DODAWANIA NOWEJ FUNKCJI ---
// Pozycjonuje elementy UI w zależności od stanu gry
self.positionElements = function (state) {
// Pozycje stałe dla niektórych elementów
self.deathsText.x = 2048 - 150;
self.deathsText.y = 50;
self.heartContainer.y = 80; // Pozycja pionowa serc (może być nadpisywana)
self.bossHealthBarContainer.x = 2048 / 2;
self.bossHealthBarContainer.y = 160; // Pasek HP bossa poniżej timera/serc
// Resetuj widoczność przed ustawieniem dla danego stanu
self.titleText.alpha = 0;
self.messageText.alpha = 0;
self.tutorialText.alpha = 0;
self.timerText.alpha = 0;
self.heartContainer.alpha = 0;
self.bossHealthBarContainer.alpha = 0;
self.deathsText.alpha = 0; // Domyślnie ukryty
self.highScoreText.alpha = 0; // Domyślnie ukryty
// Przywróć domyślną pozycję i styl timerText
if (self.timerText) {
self.timerText.x = 350;
self.timerText.y = 100;
self.timerText.anchor.set(0, 0); // Upewnij się, że anchor jest resetowany
self.timerText.style = {
size: 60,
fill: 0xFFFFFF
};
}
switch (state) {
case "title":
self.titleText.x = 2048 / 2;
self.titleText.y = 800;
self.titleText.alpha = 1;
self.messageText.x = 2048 / 2;
self.messageText.y = 1000;
// self.messageText.alpha = 1; // Ustawiane przez showMessage
self.tutorialText.x = 2048 / 2;
self.tutorialText.y = 1200;
// self.tutorialText.alpha = 1; // Ustawiane przez showTutorial
break;
case "game":
self.messageText.x = 2048 / 2;
self.messageText.y = 1500;
// self.messageText.alpha = 0; // Ustawiane przez showMessage
self.tutorialText.x = 2048 / 2;
self.tutorialText.y = 200;
// self.tutorialText.alpha = 1; // Ustawiane przez showTutorial/hideTutorial
// Pokaż elementy walki
if (self.heartContainer) {
self.heartContainer.alpha = 1;
}
if (self.timerText) {
self.timerText.alpha = 1;
}
if (self.deathsText) {
self.deathsText.alpha = 1;
} // Pokaż licznik śmierci
// Pasek zdrowia bossa jest zarządzany przez updateBossHealth
break;
case "grillMenu":
self.messageText.x = 2048 / 2;
self.messageText.y = 500;
// self.messageText.alpha = 0; // Ustawiane przez showMessage
break;
case "gameOver":
self.titleText.x = 2048 / 2;
self.titleText.y = 800;
// self.titleText.alpha = 1; // Ustawiane w gameState.gameOver
self.messageText.x = 2048 / 2;
self.messageText.y = 1000;
// self.messageText.alpha = 0; // Ustawiane w gameState.gameOver przez showMessage
self.tutorialText.x = 2048 / 2;
self.tutorialText.y = 1200;
// Widoczność paska HP bossa zarządzana przez updateBossHealth
if (self.deathsText) {
self.deathsText.alpha = 1;
} // Pokaż licznik śmierci też na Game Over
break;
case "intro":
case "fakeTutorial":
case "realTutorial":
// Większość elementów ukryta (domyślnie)
break;
// --- DODANA OBSŁUGA WIDOCZNOŚCI W ROLL MASTER ---
case "rollMaster":
// Dostosuj pozycję timera specjalnie dla tego trybu
if (self.timerText) {
// --- POCZĄTEK ZMIAN ---
self.timerText.x = 2048 / 2; // Ustaw X na środek ekranu
self.timerText.y = 50; // Ustaw Y blisko góry
self.timerText.anchor.set(0.5, 0); // Lewy górny róg
self.timerText.style = {
size: 60,
fill: 0xFFFFFF
}; // Ustaw styl, jeśli trzeba
self.timerText.alpha = 1; // Pokaż timer
}
// Pokaż rekord
if (self.highScoreText) {
self.highScoreText.alpha = 1;
}
// Ukryj inne niepotrzebne elementy
if (self.heartContainer) {
self.heartContainer.alpha = 0;
}
if (self.bossHealthBarContainer) {
self.bossHealthBarContainer.alpha = 0;
}
if (self.deathsText) {
self.deathsText.alpha = 0;
}
if (self.tutorialText) {
self.tutorialText.alpha = 0;
}
if (self.titleText) {
self.titleText.alpha = 0;
}
break;
// --- KONIEC DODAWANIA OBSŁUGI ---
}
}; // Koniec positionElements
return self;
});
/****
* Initialize Game
****/
// Koniec var UI = Container.expand(...)
var game = new LK.Game({
backgroundColor: 0x111111 // Ciemne tło domyślne
});
/****
* Game Code
****/
// Globalny kontener na elementy scen intro/tutoriali i Grill Screena (do łatwego czyszczenia)
// Komentarze o assetach pominięte dla zwięzłości
var currentSceneElements = new Container();
// Zmienne gry
var player;
var boss;
var ui;
var walls = []; // Ściany areny bossa
// Zmienna do przechowywania aktywnego tła sceny
var currentBackground = null;
// Flaga do śledzenia, czy jesteśmy w trybie New Boss+
var isNewBossPlusMode = false;
// Flaga do śledzenia przyczyny Game Over (true = gracz zginął, false = czas minął)
var gameOverReasonIsDeath = false;
var coffinMemeImage = null;
// Funkcja do czyszczenia elementów z kontenera currentSceneElements (nie usuwa UI ani tła)
function clearScene() {
while (currentSceneElements.children.length > 0) {
var child = currentSceneElements.children[0];
if (child && child.destroy) {
child.destroy(); // Użyj destroy jeśli dostępne
} else if (child && child.parent) {
child.parent.removeChild(child); // Fallback
} else {
// Jeśli obiekt nie ma ani destroy ani parent, usuń go z tablicy (choć to nie powinno się zdarzyć)
currentSceneElements.children.shift();
}
}
// Usuń też dzieci bezpośrednio z 'game', które mogły zostać dodane poza 'currentSceneElements' w scenach,
// ale UWAŻAJ, aby nie usunąć stałych elementów jak UI, walls, currentSceneElements sam w sobie.
// Lepsze podejście: zawsze dodawaj elementy specyficzne dla sceny do currentSceneElements.
}
// Obiekt zarządzający stanami gry
var gameState = {
currentState: "title",
gameDuration: 120,
remainingTime: 0,
gameTimerInterval: null,
fakeTutorialTimerId: null,
bossSpeedIncreased: false,
currentIntroMusicInstance: null,
currentRollSoulsInstance: null,
currentRollMasterMusicInstance: null,
// Nowa właściwość
rollMasterTime: 0,
rollMasterDifficulty: 1,
rollMasterTimerInterval: null,
rollMasterAttacks: [],
attackSpawnTimer: 0,
attackSpawnInterval: 120,
explosionSpawnTimer: 0,
explosionSpawnInterval: 240,
rollMasterHighScore: 0,
isInputActive: false,
currentInputPos: {
x: 0,
y: 0
},
touchStart: {
x: 0,
y: 0
},
touchEnd: {
x: 0,
y: 0
},
grillMenuEffects: [],
swipeStartTime: 0,
init: function init() {
storage.totalDeaths = 0;
storage.maxHearts = 5;
isNewBossPlusMode = false;
gameOverReasonIsDeath = false;
this.rollMasterHighScore = storage.rollMasterHighScore || 0;
this.currentIntroMusicInstance = null;
this.currentRollSoulsInstance = null;
this.currentRollMasterMusicInstance = null;
game.setBackgroundColor(0x111111);
ui = game.addChild(new UI());
ui.updateDeathsCounter();
ui.updateHearts(storage.maxHearts, storage.maxHearts);
ui.positionElements("title");
game.addChild(currentSceneElements);
// Muzyka intro jest teraz uruchamiana i zarządzana w showTitleScreen
this.showTitleScreen();
},
// --- gameState.showTitleScreen ---
showTitleScreen: function showTitleScreen() {
isNewBossPlusMode = false;
this.rollMasterTime = 0;
console.log("[showTitleScreen] State: Title Screen");
var needsIntroMusic = true;
if (this.currentRollSoulsInstance && this.currentRollSoulsInstance.stop) {
console.log("[showTitleScreen] Zatrzymywanie currentRollSoulsInstance.");
if (typeof this.currentRollSoulsInstance.volume === 'number') {
this.currentRollSoulsInstance.volume = 0;
}
this.currentRollSoulsInstance.stop();
this.currentRollSoulsInstance = null;
} else {
var musicViaLK_RS = LK.music;
if (musicViaLK_RS && musicViaLK_RS.assetId === 'RollSouls' && musicViaLK_RS.playing && musicViaLK_RS.stop) {
console.log("[showTitleScreen] Fallback: Zatrzymywanie RollSouls przez LK.music.");
if (typeof musicViaLK_RS.volume === 'number') {
musicViaLK_RS.volume = 0;
}
musicViaLK_RS.stop();
}
}
if (this.currentRollMasterMusicInstance && this.currentRollMasterMusicInstance.stop) {
console.log("[showTitleScreen] Zatrzymywanie currentRollMasterMusicInstance.");
if (typeof this.currentRollMasterMusicInstance.volume === 'number') {
this.currentRollMasterMusicInstance.volume = 0;
}
this.currentRollMasterMusicInstance.stop();
this.currentRollMasterMusicInstance = null;
} else {
var musicViaLK_RM = LK.music;
if (musicViaLK_RM && musicViaLK_RM.assetId === 'rollmaster' && musicViaLK_RM.playing && musicViaLK_RM.stop) {
console.log("[showTitleScreen] Fallback: Zatrzymywanie 'rollmaster' przez LK.music.");
if (typeof musicViaLK_RM.volume === 'number') {
musicViaLK_RM.volume = 0;
}
musicViaLK_RM.stop();
}
}
if (this.currentIntroMusicInstance && this.currentIntroMusicInstance.playing) {
console.log("[showTitleScreen] currentIntroMusicInstance już gra.");
needsIntroMusic = false;
} else {
var lkMusicCheck = LK.music;
if (lkMusicCheck && lkMusicCheck.assetId === 'introMusic' && lkMusicCheck.playing) {
console.log("[showTitleScreen] introMusic już gra (wg LK.music). Zapisuję instancję.");
this.currentIntroMusicInstance = lkMusicCheck;
needsIntroMusic = false;
}
}
if (needsIntroMusic) {
console.log("[showTitleScreen] Uruchamianie introMusic...");
this.currentIntroMusicInstance = LK.playMusic('introMusic', {
loop: true,
volume: 0.7,
fade: {
start: 0,
end: 0.7,
duration: 1000
}
});
if (!this.currentIntroMusicInstance) {
console.error("[showTitleScreen] LK.playMusic('introMusic') nie zwróciło instancji!");
}
} else {
console.log("[showTitleScreen] introMusic nie jest ponownie uruchamiana.");
}
if (this.fakeTutorialTimerId) {
LK.clearTimeout(this.fakeTutorialTimerId);
this.fakeTutorialTimerId = null;
}
if (this.gameTimerInterval) {
LK.clearInterval(this.gameTimerInterval);
this.gameTimerInterval = null;
}
if (this.rollMasterTimerInterval) {
LK.clearInterval(this.rollMasterTimerInterval);
this.rollMasterTimerInterval = null;
}
clearScene();
if (currentBackground) {
tween.stop(currentBackground);
currentBackground.destroy();
currentBackground = null;
}
this.currentState = "title";
game.setBackgroundColor(0x1a1a1a);
currentBackground = LK.getAsset('titleBg', {
anchorX: 0,
anchorY: 0,
x: 0,
y: 0
});
game.addChildAt(currentBackground, 0);
if (typeof particleIntervalId !== 'undefined' && particleIntervalId) {
LK.clearInterval(particleIntervalId);
particleIntervalId = null;
}
if (typeof startScreenParticles !== 'undefined') {
startScreenParticles.forEach(function (p) {
if (p && p.destroy) {
p.destroy();
}
});
startScreenParticles = [];
}
var localStartScreenParticles = [];
function localSpawnBackgroundParticle() {/* ... logika cząsteczek ... */}
var localParticleIntervalId = LK.setInterval(localSpawnBackgroundParticle, 400);
// Aby uniknąć konfliktów z globalnymi zmiennymi, jeśli są, można przekazać je do gameState lub zarządzać nimi lokalnie.
// Dla uproszczenia, zakładam, że particleIntervalId i startScreenParticles są zarządzane w sposób, który nie koliduje.
if (player && player.destroy) {
player.destroy();
}
player = null;
if (boss && boss.destroy) {
boss.destroy();
}
boss = null;
walls.forEach(function (wall) {
if (wall) {
wall.alpha = 1;
}
});
ui.positionElements("title");
ui.titleText.setText("Welcome Unchosen");
ui.showMessage("Tap to Start", 0);
ui.showTutorial("Swipe to Roll - Death is Progress");
ui.updateHearts(storage.maxHearts, storage.maxHearts);
ui.updateDeathsCounter();
this.touchStart = {
x: 0,
y: 0
};
this.touchEnd = {
x: 0,
y: 0
};
game.off('down');
game.on('down', function () {
if (gameState.currentState === 'title') {
if (typeof localParticleIntervalId !== 'undefined' && localParticleIntervalId) {
LK.clearInterval(localParticleIntervalId);
}
if (localStartScreenParticles) {
localStartScreenParticles.forEach(function (p) {
if (p && p.destroy) {
p.destroy();
}
});
}
gameState.showIntro();
}
});
},
showIntro: function showIntro() {
var skipElement = null;
var skipTimerId = null;
var self = this; // self tutaj to gameState
if (this.fakeTutorialTimerId) {
LK.clearTimeout(this.fakeTutorialTimerId);
this.fakeTutorialTimerId = null;
}
if (typeof particleIntervalId !== 'undefined') {
LK.clearInterval(particleIntervalId);
particleIntervalId = null;
}
if (typeof startScreenParticles !== 'undefined') {
startScreenParticles.forEach(function (p) {
if (p && p.destroy) {
p.destroy();
}
});
startScreenParticles = [];
}
clearScene();
if (currentBackground) {
tween.stop(currentBackground);
currentBackground.destroy();
currentBackground = null;
}
this.currentState = "intro";
game.setBackgroundColor(0x111111);
currentBackground = LK.getAsset('introBg', {
anchorX: 0.5,
anchorY: 0.4,
x: 1000,
y: 1000,
scaleX: 1,
scaleY: 1
});
game.addChildAt(currentBackground, 0);
tween(currentBackground, {
scaleX: 1.5,
scaleY: 1.5
}, {
duration: 38000,
easing: tween.linear,
repeat: Infinity,
yoyo: true
});
if (player && player.destroy) {
player.destroy();
}
player = null;
if (boss && boss.destroy) {
boss.destroy();
}
boss = null;
walls.forEach(function (wall) {
if (wall) {
wall.alpha = 0;
}
});
ui.positionElements("intro");
skipTimerId = LK.setTimeout(function () {
if (self.currentState !== "intro") {
skipTimerId = null;
return;
}
skipElement = new Text2("Skip intro", {
size: 70,
fill: 0xFFFFFF
});
skipElement.x = 2048 - 50;
skipElement.y = 2732 - 50;
skipElement.anchor.set(1, 1);
skipElement.interactive = true;
skipElement.cursor = "pointer";
currentSceneElements.addChild(skipElement);
skipElement.down = function () {
if (self.currentState !== "intro") {
return;
}
console.log("[Skip Intro] Pominięto intro przez tekst!");
if (skipTimerId) {
LK.clearTimeout(skipTimerId);
skipTimerId = null;
}
if (currentBackground) {
tween.stop(currentBackground);
}
clearScene();
if (self.currentIntroMusicInstance && self.currentIntroMusicInstance.stop) {
console.log("[Skip Intro] Zatrzymywanie currentIntroMusicInstance.");
if (typeof self.currentIntroMusicInstance.volume === 'number') {
self.currentIntroMusicInstance.volume = 0;
}
self.currentIntroMusicInstance.stop();
self.currentIntroMusicInstance = null;
} else {
console.log("[Skip Intro] Brak currentIntroMusicInstance lub metody stop. Fallback na LK.music.");
var musicViaLK_I = LK.music;
if (musicViaLK_I && musicViaLK_I.assetId === 'introMusic' && musicViaLK_I.stop) {
if (typeof musicViaLK_I.volume === 'number') {
musicViaLK_I.volume = 0;
}
musicViaLK_I.stop();
}
}
if (self.currentRollMasterMusicInstance && self.currentRollMasterMusicInstance.stop) {
console.log("[Skip Intro] Zatrzymywanie currentRollMasterMusicInstance.");
if (typeof self.currentRollMasterMusicInstance.volume === 'number') {
self.currentRollMasterMusicInstance.volume = 0;
}
self.currentRollMasterMusicInstance.stop();
self.currentRollMasterMusicInstance = null;
}
console.log("[Skip Intro] Odtwarzanie RollSouls...");
self.currentRollSoulsInstance = LK.playMusic('RollSouls', {
loop: true,
volume: 0.7,
fade: {
start: 0,
end: 0.7,
duration: 1000
}
});
if (!self.currentRollSoulsInstance) {
console.error("[Skip Intro] LK.playMusic('RollSouls') nie zwróciło instancji!");
}
console.log("[Skip Intro] Przechodzenie do showRealTutorial...");
self.showRealTutorial();
};
skipTimerId = null;
}, 3000);
function showIntroText(text, delay, onComplete) {
var introText = new Text2(text, {
size: 90,
fill: 0xFFFFFF,
align: 'center',
wordWrap: true,
wordWrapWidth: 1800
});
introText.anchor.set(0.5, 0.5);
introText.x = 2048 / 2;
introText.y = 2232 / 2 - 400;
introText.alpha = 0;
currentSceneElements.addChild(introText);
LK.setTimeout(function () {
if (self.currentState !== "intro") {
return;
}
tween(introText, {
alpha: 1
}, {
duration: 2000,
easing: tween.easeInOut
});
LK.setTimeout(function () {
if (self.currentState !== "intro") {
return;
}
tween(introText, {
alpha: 0
}, {
duration: 2000,
easing: tween.easeInOut
});
LK.setTimeout(function () {
if (onComplete) {
onComplete();
}
}, 3000);
}, 3000);
}, delay);
}
showIntroText('From the authors of Dark Souls...', 2000, function () {
if (self.currentState !== "intro") {
return;
}
showIntroText('I have no information. I don’t know them.', 0, function () {
if (self.currentState !== "intro") {
return;
}
showIntroText('Silas GameStudio...', 0, function () {
if (self.currentState !== "intro") {
return;
}
showIntroText('Still looking for funding.', 0, function () {
if (self.currentState !== "intro") {
return;
}
showIntroText('Oh, and it doesn’t even exist.', 0, function () {
if (self.currentState !== "intro") {
return;
}
LK.setTimeout(function () {
if (self.currentState !== "intro") {
return;
}
if (skipTimerId) {
LK.clearTimeout(skipTimerId);
skipTimerId = null;
}
if (skipElement && skipElement.parent) {
currentSceneElements.removeChild(skipElement);
if (skipElement.destroy) {
skipElement.destroy();
}
skipElement = null;
}
if (typeof gameState.showFakeTutorial === 'function') {
gameState.showFakeTutorial();
} else {
console.error("Error: gameState.showFakeTutorial is not defined");
gameState.showTitleScreen();
}
}, 1000);
});
});
});
});
});
},
// Koniec funkcji showIntro
// --- KONIEC SEKWENCJI INTRO ---
// <--- Koniec funkcji showIntro
showFakeTutorial: function showFakeTutorial() {
clearScene();
if (this.fakeTutorialTimerId) {
LK.clearTimeout(this.fakeTutorialTimerId);
this.fakeTutorialTimerId = null;
}
if (this.currentIntroMusicInstance) {
console.log("[FakeTutorial] Próba wyciszenia i zatrzymania currentIntroMusicInstance.");
var instanceToFadeAndStop = this.currentIntroMusicInstance;
LK.tween(instanceToFadeAndStop, {
volume: 0
}, {
duration: 3000,
onFinish: function onFinish() {
if (instanceToFadeAndStop.stop) {
instanceToFadeAndStop.stop();
console.log("[FakeTutorial] currentIntroMusicInstance zatrzymane po tweenie.");
if (gameState.currentIntroMusicInstance === instanceToFadeAndStop) {
gameState.currentIntroMusicInstance = null;
}
}
}
});
} else {
console.log("[FakeTutorial] Nie znaleziono currentIntroMusicInstance. Sprawdzanie LK.music...");
var musicViaLK_I = LK.music;
if (musicViaLK_I && musicViaLK_I.assetId === 'introMusic') {
LK.tween(musicViaLK_I, {
volume: 0
}, {
duration: 3000,
onFinish: function onFinish() {
if (musicViaLK_I.stop) {
musicViaLK_I.stop();
}
}
});
}
}
if (this.currentRollMasterMusicInstance && this.currentRollMasterMusicInstance.stop) {
console.log("[FakeTutorial] Zatrzymywanie currentRollMasterMusicInstance.");
if (typeof this.currentRollMasterMusicInstance.volume === 'number') {
this.currentRollMasterMusicInstance.volume = 0;
}
this.currentRollMasterMusicInstance.stop();
this.currentRollMasterMusicInstance = null;
}
console.log("[FakeTutorial] Odtwarzanie RollSouls...");
this.currentRollSoulsInstance = LK.playMusic('RollSouls', {
loop: true,
volume: 0.7,
fade: {
start: 0,
end: 0.7,
duration: 3000
}
});
if (!this.currentRollSoulsInstance) {
console.error("[FakeTutorial] LK.playMusic('RollSouls') nie zwróciło instancji!");
}
this.currentState = "fakeTutorial";
ui.positionElements("fakeTutorial");
var instructionText = new Text2('Press LPM to block.. or don’t press.', {
size: 100,
fill: 0xFFFFFF,
align: 'center',
wordWrap: true,
wordWrapWidth: 1800,
dropShadow: true,
dropShadowColor: 0x000000,
dropShadowDistance: 3,
dropShadowBlur: 4,
dropShadowAngle: Math.PI / 4
});
instructionText.anchor.set(0.5, 0.5);
instructionText.x = 2048 / 2;
instructionText.y = 2732 / 2;
currentSceneElements.addChild(instructionText);
this.fakeTutorialTimerId = LK.setTimeout(function () {
try {
if (instructionText && instructionText.destroy) {
instructionText.destroy();
}
} catch (e) {}
gameState.fakeTutorialTimerId = null;
if (gameState && typeof gameState.showRealTutorial === 'function') {
gameState.showRealTutorial();
} else {
console.error("Error: gameState.showRealTutorial is not a function in fakeTutorial timeout!");
gameState.showTitleScreen();
}
}, 6000);
},
// *** NOWA FUNKCJA: Prawdziwy Tutorial (oddzielona od startGame) ***
showRealTutorial: function showRealTutorial() {
if (this.fakeTutorialTimerId) {
LK.clearTimeout(this.fakeTutorialTimerId);
}
this.fakeTutorialTimerId = null; // Wyczyść timer fake tutorialu
clearScene(); // Wyczyść elementy fake tutorialu/fake game over
// Zatrzymaj animację tła intro i usuń je
if (currentBackground) {
tween.stop(currentBackground);
currentBackground.destroy();
currentBackground = null;
}
this.currentState = "realTutorial";
// Ustaw tło tutoriala
if (currentBackground && currentBackground.destroy) {
currentBackground.destroy();
}
currentBackground = LK.getAsset('realtutorialbg', {});
game.addChildAt(currentBackground, 0);
// Pokaż ściany areny
walls.forEach(function (wall) {
if (wall) {
wall.alpha = 1;
}
});
ui.positionElements("realTutorial"); // Ustaw UI (głównie ukrywa elementy gry)
// --- NIE MA tutorialTitle ani tutorialDesc (tekst jest na grafice) ---
// Przycisk "Let's Roll!" do rozpoczęcia gry
var startButton = new Container();
startButton.interactive = true;
startButton.x = 2048 / 2;
startButton.y = 1250; // Zmniejszona pozycja Y, był 1500
currentSceneElements.addChild(startButton);
// Powiększone tło przycisku
var startButtonBg = LK.getAsset('button_bg', {
anchorX: 0.5,
anchorY: 0.5
});
startButtonBg.scale.set(1.3); // Skala tła przycisku zwiększona
startButton.addChild(startButtonBg);
// Powiększony i wyraźniejszy tekst przycisku
var startButtonText = new Text2("Let's Roll!", {
size: 80,
fill: 0xFFFFFF,
stroke: 0x000000,
strokeThickness: 6
});
startButtonText.anchor.set(0.5, 0.5);
startButton.addChild(startButtonText);
// Funkcja kliknięcia przycisku
startButton.down = function () {
gameState.startGame(); // Rozpocznij grę
};
},
// Obsługa inputu w stanie fakeTutorial (fałszywa śmierć)
handleFakeTutorialInput: function handleFakeTutorialInput() {
// Wyczyść timer, który miał prowadzić do prawdziwego tutorialu
if (this.fakeTutorialTimerId) {
LK.clearTimeout(this.fakeTutorialTimerId);
this.fakeTutorialTimerId = null; // Zresetuj ID timera
}
clearScene(); // Wyczyść ekran fałszywego tutorialu
// Ustaw stan tymczasowo (można by dodać dedykowany stan 'fakeGameOver')
this.currentState = "fakeGameOver"; // Zmieniono z "gameOver"
// Zatrzymaj animację tła intro, ale nie usuwaj go jeszcze
if (currentBackground) {
tween.stop(currentBackground);
}
ui.positionElements("gameOver"); // Użyj pozycjonowania gameOver dla tekstów "YOU DIED"
// Wyświetl "YOU DIED" na czerwono
var diedText = new Text2("YOU DIED", {
size: 150,
fill: 0xFF0000
}); // Czerwony kolor
diedText.anchor.set(0.5, 0.5);
diedText.x = 2048 / 2;
diedText.y = 600;
currentSceneElements.addChild(diedText);
// Wyświetl wyjaśnienie
var explanationText = new Text2("Did you check the title of the game?", {
size: 70,
fill: 0xFFFFFF,
align: 'center',
wordWrap: true,
wordWrapWidth: 1800,
dropShadow: true,
dropShadowColor: 0x000000,
// cień czarny
dropShadowDistance: 3,
// odległość cienia
dropShadowBlur: 4,
// lekkie rozmycie cienia
dropShadowAngle: Math.PI / 4 // kąt padania cienia (45 stopni)
});
explanationText.anchor.set(0.5, 0.5);
explanationText.x = 2048 / 2;
explanationText.y = 800;
currentSceneElements.addChild(explanationText);
// Dodaj przycisk "How to play again"
var howToPlayButtonContainer = new Container();
howToPlayButtonContainer.interactive = true;
howToPlayButtonContainer.x = 2048 / 2;
howToPlayButtonContainer.y = 1300; // Pozycja przycisku
currentSceneElements.addChild(howToPlayButtonContainer);
var buttonBg = LK.getAsset('button_bg', {
anchorX: 0.5,
anchorY: 0.5
});
howToPlayButtonContainer.addChild(buttonBg);
var buttonText = new Text2('How to play', {
size: 50,
fill: 0xFFFFFF
}); // Zmieniono tekst i rozmiar
buttonText.anchor.set(0.5, 0.5);
howToPlayButtonContainer.addChild(buttonText);
// Akcja przycisku: Przejdź do prawdziwego tutorialu
howToPlayButtonContainer.down = function () {
// *** POPRAWKA: Sprawdzenie przed wywołaniem ***
if (gameState && typeof gameState.showRealTutorial === 'function') {
gameState.showRealTutorial(); // Przejdź do prawdziwego tutorialu
} else {
console.error("Error: gameState.showRealTutorial is not a function in fake input handler!");
gameState.showTitleScreen(); // Awaryjny powrót do tytułu
}
};
},
// Przejście do stanu gry (walka z bossem) - wywoływane z Prawdziwego Tutorialu
startGame: function startGame() {
var _this = this;
console.log("[StartGame] Rozpoczynanie standardowej gry lub NewBoss+.");
if (this.currentIntroMusicInstance && this.currentIntroMusicInstance.stop) {
console.log("[StartGame] Zatrzymywanie currentIntroMusicInstance.");
if (typeof this.currentIntroMusicInstance.volume === 'number') {
this.currentIntroMusicInstance.volume = 0;
}
this.currentIntroMusicInstance.stop();
this.currentIntroMusicInstance = null;
}
if (this.currentRollMasterMusicInstance && this.currentRollMasterMusicInstance.stop) {
console.log("[StartGame] Zatrzymywanie currentRollMasterMusicInstance.");
if (typeof this.currentRollMasterMusicInstance.volume === 'number') {
this.currentRollMasterMusicInstance.volume = 0;
}
this.currentRollMasterMusicInstance.stop();
this.currentRollMasterMusicInstance = null;
}
if (this.currentRollSoulsInstance && this.currentRollSoulsInstance.stop) {
console.log("[StartGame] Zatrzymywanie poprzedniej currentRollSoulsInstance.");
if (typeof this.currentRollSoulsInstance.volume === 'number') {
this.currentRollSoulsInstance.volume = 0;
}
this.currentRollSoulsInstance.stop();
}
console.log("[StartGame] Uruchamianie RollSouls.");
this.currentRollSoulsInstance = LK.playMusic('RollSouls', {
loop: true,
volume: 0.7
});
if (!this.currentRollSoulsInstance) {
console.error("[StartGame] LK.playMusic('RollSouls') nie zwróciło instancji!");
}
if (this.fakeTutorialTimerId) {
LK.clearTimeout(this.fakeTutorialTimerId);
this.fakeTutorialTimerId = null;
}
if (this.gameTimerInterval) {
LK.clearInterval(this.gameTimerInterval);
this.gameTimerInterval = null;
}
clearScene();
if (currentBackground) {
tween.stop(currentBackground);
currentBackground.destroy();
currentBackground = null;
}
this.currentState = "game";
if (gameState.grillMenuEffects && gameState.grillMenuEffects.length > 0) {
gameState.grillMenuEffects.forEach(function (effect) {
if (effect && effect.parent) {
tween.stop(effect);
effect.parent.removeChild(effect);
if (effect.destroy && !effect.destroyed) {
effect.destroy();
}
} else if (effect && effect.destroy && !effect.destroyed) {
console.warn("Sparkle/Star object found without parent but has destroy method:", effect);
effect.destroy();
} else {
console.warn("Sparkle/Star object found without parent or destroy method:", effect);
}
});
gameState.grillMenuEffects = [];
}
game.setBackgroundColor(0x111111);
var arenaBg = LK.getAsset('arena', {
anchorX: 0.5,
anchorY: 0.5,
x: 2048 / 2,
y: 2732 / 2
});
game.addChildAt(arenaBg, 0);
if (player && player.destroy) {
player.destroy();
}
player = game.addChild(new Player());
// --- POCZĄTEK ZMIAN ---
if (player) {
player.health = parseInt(storage.maxHearts, 10) || 5;
console.log("DEBUG: Inicjalizacja zdrowia gracza. player.health:", player.health, "storage.maxHearts:", storage.maxHearts);
}
// --- KONIEC ZMIAN ---
player.rolling = false;
player.invulnerable = false;
player.invulnerabilityFrames = 0;
player.rollCooldown = 0;
if (player && typeof player.clearRollTimeouts === 'function') {
player.clearRollTimeouts();
}
player.hasRolledThroughBossThisRoll = false;
player.x = 2048 / 2;
player.y = 2732 / 2 + 400;
player.alpha = 1;
player.dead = false;
if (boss && boss.destroy) {
boss.destroy();
}
boss = game.addChild(new Boss());
boss.x = 2048 / 2;
boss.y = 2732 / 2 - 400;
boss.alpha = 1;
boss.attackCooldown = 90;
boss.attacks = [];
boss.attackSpeedMultiplier = 1;
boss.repositioning = false;
boss.dead = false;
boss.phase = 1;
boss.tint = 0xFFFFFF;
boss.scale.set(1, 1);
if (isNewBossPlusMode) {
boss.maxHealth = 700;
boss.health = boss.maxHealth;
this.gameDuration = 600;
boss.attackSpeedMultiplier = 0.6;
boss.speed = 7;
console.log("Starting NEW BOSS+ mode. HP:", boss.maxHealth, "Time:", this.gameDuration);
} else {
boss.maxHealth = 160;
boss.health = boss.maxHealth;
this.gameDuration = 120;
boss.speed = 5;
boss.attackSpeedMultiplier = 1;
console.log("Starting STANDARD mode. HP:", boss.maxHealth, "Time:", this.gameDuration);
}
walls.forEach(function (wall) {
if (wall) {
wall.alpha = 1;
}
});
ui.positionElements("game");
// --- POCZĄTEK ZMIAN ---
if (ui && player) {
ui.updateHearts(player.health, parseInt(storage.maxHearts, 10) || 5);
} else if (ui) {
ui.updateHearts(0, parseInt(storage.maxHearts, 10) || 5);
}
// --- KONIEC ZMIAN ---
ui.showTutorial("Swipe to Roll!");
ui.updateBossHealth(boss.health, boss.maxHealth);
if (coffinMemeImage && !coffinMemeImage.destroyed) {
coffinMemeImage.destroy();
coffinMemeImage = null;
}
try {
var assetMeta = null;
try {
assetMeta = LK.getAssetMeta('coffinDanceMeme');
} catch (metaError) {
console.warn("LK.getAssetMeta nie działa lub brak assetu 'coffinDanceMeme'. Używam domyślnej wysokości 200.");
}
var memeHeight = assetMeta ? assetMeta.height : 200;
coffinMemeImage = LK.getAsset('coffinDanceMeme', {
anchorX: 0.5,
anchorY: 1.0,
x: 2048 / 2,
y: 2732 + memeHeight,
alpha: 1
});
var uiIndex = game.getChildIndex(ui);
if (uiIndex > -1) {
game.addChildAt(coffinMemeImage, uiIndex);
} else {
game.addChild(coffinMemeImage);
}
console.log("Coffin dance meme załadowany i schowany, y start:", coffinMemeImage.y, "Height:", memeHeight);
} catch (e) {
console.error("Nie udało się stworzyć/załadować assetu coffinDanceMeme:", e);
coffinMemeImage = null;
}
this.remainingTime = this.gameDuration;
ui.updateTimerDisplay(this.remainingTime);
if (this.gameTimerInterval) {
LK.clearInterval(this.gameTimerInterval);
}
this.bossSpeedIncreased = false;
this.gameTimerInterval = LK.setInterval(function () {
if (_this.currentState === "game") {
_this.remainingTime--;
ui.updateTimerDisplay(_this.remainingTime);
if (!isNewBossPlusMode) {
var accelerationThreshold = _this.gameDuration - 60;
if (_this.remainingTime <= accelerationThreshold && !_this.bossSpeedIncreased) {
_this.bossSpeedIncreased = true;
if (boss && !boss.dead) {
boss.attackSpeedMultiplier *= 0.7;
boss.speed += 1;
ui.showMessage("Boss attacks faster!", 2000);
}
}
}
if (_this.remainingTime <= 0) {
LK.clearInterval(_this.gameTimerInterval);
_this.gameTimerInterval = null;
if (isNewBossPlusMode) {
gameOverReasonIsDeath = false;
gameState.gameOver(false);
} else {
gameState.showGrillScreen();
}
}
} else {
if (_this.gameTimerInterval) {
LK.clearInterval(_this.gameTimerInterval);
}
_this.gameTimerInterval = null;
}
}, 1000);
LK.setTimeout(function () {
if (_this.currentState === "game") {
ui.hideTutorial();
}
}, 3000);
},
// Koniec funkcji startGame
// victory() - nieużywane, logika w timerze i boss.die()
showGrillScreen: function showGrillScreen() {
this.rollMasterTime = 0; // <-- DODAJ TĘ LINIĘ
isNewBossPlusMode = false;
console.log("State: Grill Menu (isNewBossPlusMode reset to false)");
// Wyczyść timery (pozostaw ten fragment)
if (this.gameTimerInterval) {
LK.clearInterval(this.gameTimerInterval);
}
this.gameTimerInterval = null;
if (this.fakeTutorialTimerId) {
LK.clearTimeout(this.fakeTutorialTimerId);
}
this.fakeTutorialTimerId = null;
this.bossSpeedIncreased = false; // Reset flagi
// --- DODANY KOD: Tablica do przechowywania efektów z menu grilla ---
gameState.grillMenuEffects = []; // Tworzymy nową tablicę przy wejściu do menu
// --- KONIEC DODANEGO KODU ---
// --- Rozpoczęcie agresywnego czyszczenia sceny ---
// Stwórz listę obiektów (kontenerów, UI, ścian), które powinny pozostać w 'game'.
// Upewnij się, że 'ui', 'currentSceneElements' i wszystkie obiekty w tablicy 'walls' są tutaj.
var childrenToKeep = [ui, currentSceneElements].concat(walls);
// Przejdź przez wszystkie dzieci głównego obiektu 'game' od końca do początku
for (var i = game.children.length - 1; i >= 0; i--) {
var child = game.children[i];
// Sprawdź, czy obecne dziecko NIE jest na liście obiektów do zachowania
if (childrenToKeep.indexOf(child) === -1) {
// console.log("Usuwam obiekt:", child); // Możesz tymczasowo odkomentować do debugowania
// Usuń i zniszcz obiekt
if (child && child.destroy) {
child.destroy(); // Użyj destroy jeśli dostępne (zalecane w LK)
} else if (child && child.parent) {
child.parent.removeChild(child); // Fallback - usuń z rodzica
}
}
}
// Po agresywnym czyszczeniu, upewnij się, że globalne zmienne gracza, bossa i tła są null
// Te obiekty powinny zostać usunięte przez powyższą pętlę, ale warto zresetować zmienne.
currentBackground = null;
player = null;
boss = null;
// --- Koniec agresywnego czyszczenia sceny ---
this.currentState = "grillMenu";
game.setBackgroundColor(0x333333); // Tło grilla
// Teraz dodaj nowe tło dla ekranu Grilla (ten fragment zostaje taki, jak go poprawiliśmy ostatnio - JEDNO dodanie grillMenu)
currentBackground = LK.getAsset('grillMenu', {
anchorX: 0.5,
anchorY: 0.5,
x: 2048 / 2,
y: 2732 / 2
});
// Dodaj nowe tło na spód
if (currentBackground) {
// Sprawdź czy asset został poprawnie załadowany
game.addChildAt(currentBackground, 0);
}
var confirmRestButton = null;
// ✨ DODAJEMY 5 SPARKLINGÓW ✨
// Sparkle 1
var sparkle1 = game.addChild(LK.getAsset('sparkle', {
anchorX: 0.5,
anchorY: 0.5,
x: 2048 / 2 - 460,
y: 2732 / 2 + 690
}));
gameState.grillMenuEffects.push(sparkle1); // <--- DODANA LINIJA
// Sparkle 2
var sparkle2 = game.addChild(LK.getAsset('sparkle', {
anchorX: 0.5,
anchorY: 0.5,
x: 2048 / 2 - 520,
y: 2732 / 2 + 580
}));
gameState.grillMenuEffects.push(sparkle2); // <--- DODANA LINIJA
// Sparkle 3
var sparkle3 = game.addChild(LK.getAsset('sparkle', {
anchorX: 0.5,
anchorY: 0.5,
x: 2048 / 2 - 660,
y: 2732 / 2 + 550
}));
gameState.grillMenuEffects.push(sparkle3); // <--- DODANA LINIJA
// Sparkle 4
var sparkle4 = game.addChild(LK.getAsset('sparkle', {
anchorX: 0.5,
anchorY: 0.5,
x: 2048 / 2 - 500,
y: 2732 / 2 + 680
}));
gameState.grillMenuEffects.push(sparkle4); // <--- DODANA LINIJA
// Sparkle 5
var sparkle5 = game.addChild(LK.getAsset('sparkle', {
anchorX: 0.5,
anchorY: 0.5,
x: 2048 / 2 - 620,
y: 2732 / 2 + 720
}));
gameState.grillMenuEffects.push(sparkle5); // <--- DODANA LINIJA
// Funkcja animacji sparkle
function animateSparkle(s) {
// Sprawdzenie stanu (pozostaje bez zmian)
if (gameState.currentState !== "grillMenu" || !s || s.destroyed) {
return;
}
// ZAPISZ pozycję Y na początku TEGO cyklu animacji
var initialYThisCycle = s.y;
s.alpha = 0; // Rozpocznij od przezroczystości 0
// Pozycję Y ustawimy za chwilę, nie ma potrzeby resetowania jej tutaj od razu
// Animacja pojawienia się i ruchu w górę
tween(s, {
alpha: 1,
y: initialYThisCycle - 40 // Przesuń w górę WZGLĘDEM ZAPISANEJ pozycji
}, {
duration: 800,
easing: tween.easeOut,
onFinish: function onFinish() {
// Po zakończeniu ruchu w górę
if (gameState.currentState !== "grillMenu" || !s || s.destroyed) {
return;
}
// Animacja zanikania (na tej wyższej pozycji)
tween(s, {
alpha: 0
}, {
duration: 300,
easing: tween.easeIn,
onFinish: function onFinish() {
// Po zakończeniu zanikania
if (gameState.currentState !== "grillMenu" || !s || s.destroyed) {
return;
}
// *** KLUCZOWA ZMIANA: Resetuj pozycję Y PRZED następnym cyklem ***
s.y = initialYThisCycle; // <-- WRÓĆ do pozycji Y z początku tego cyklu
// Zaplanuj kolejny cykl animacji
LK.setTimeout(function () {
animateSparkle(s); // Wywołaj kolejny cykl (teraz zacznie z poprawnej pozycji Y)
}, Math.random() * 500 + 300);
}
});
}
});
}
// Start animacji sparklingów
// Upewnij się, że animacje startują z pewnym opóźnieniem po dodaniu do tablicy
LK.setTimeout(function () {
if (gameState.currentState === "grillMenu") {
animateSparkle(sparkle1);
}
}, Math.random() * 500);
LK.setTimeout(function () {
if (gameState.currentState === "grillMenu") {
animateSparkle(sparkle2);
}
}, Math.random() * 500 + 500);
LK.setTimeout(function () {
if (gameState.currentState === "grillMenu") {
animateSparkle(sparkle3);
}
}, Math.random() * 500 + 1000);
LK.setTimeout(function () {
if (gameState.currentState === "grillMenu") {
animateSparkle(sparkle4);
}
}, Math.random() * 500 + 1500);
LK.setTimeout(function () {
if (gameState.currentState === "grillMenu") {
animateSparkle(sparkle5);
}
}, Math.random() * 500 + 2000);
// 🌟 DODAJEMY 3 GWIAZDKI NA NIEBIE 🌟
// Star 1
var star1 = game.addChild(LK.getAsset('star', {
anchorX: 0.5,
anchorY: 0.5,
x: 2048 / 2 - 800,
y: 2732 / 2 - 1000
}));
gameState.grillMenuEffects.push(star1); // <--- DODANA LINIJA
// Star 2
var star2 = game.addChild(LK.getAsset('star', {
anchorX: 0.5,
anchorY: 0.5,
x: 2048 / 2 - 300,
y: 2732 / 2 - 1150
}));
gameState.grillMenuEffects.push(star2); // <--- DODANA LINIJA
// Star 3
var star3 = game.addChild(LK.getAsset('star', {
anchorX: 0.5,
anchorY: 0.5,
x: 2048 / 2 - 200,
y: 2732 / 2 - 900
}));
gameState.grillMenuEffects.push(star3); // <--- DODANA LINIJA
// Funkcja animacji gwiazdek
function animateStar(star) {
// --- DODANE SPRAWDZENIE STANU ---
if (gameState.currentState !== "grillMenu" || !star || star.destroyed) {
return;
}
// --- KONIEC DODANEGO SPRAWDZENIA ---
star.alpha = 0;
tween(star, {
alpha: 1
}, {
duration: 2000,
easing: tween.easeIn,
onFinish: function onFinish() {
// --- DODANE SPRAWDZENIE STANU W CALLBACKU ---
if (gameState.currentState !== "grillMenu" || !star || star.destroyed) {
return;
}
// --- KONIEC DODANEGO SPRAWDZENIA ---
tween(star, {
alpha: 0
}, {
duration: 2000,
easing: tween.easeOut,
onFinish: function onFinish() {
// --- DODANE SPRAWDZENIE STANU W WEWNĘTRZNYM CALLBACKU ---
if (gameState.currentState !== "grillMenu" || !star || star.destroyed) {
return;
}
// --- KONIEC DODANEGO SPRAWDZENIA ---
// Po całym cyklu czekamy losowo 2-5 sekund przed kolejnym pojawieniem się
LK.setTimeout(function () {
animateStar(star);
}, Math.random() * 3000 + 2000);
}
});
}
});
}
// Start animacji gwiazdek
// Upewnij się, że animacje startują z pewnym opóźnieniem po dodaniu do tablicy
LK.setTimeout(function () {
if (gameState.currentState === "grillMenu") {
animateStar(star1);
}
}, Math.random() * 1000);
LK.setTimeout(function () {
if (gameState.currentState === "grillMenu") {
animateStar(star2);
}
}, Math.random() * 1000 + 500);
LK.setTimeout(function () {
if (gameState.currentState === "grillMenu") {
animateStar(star3);
}
}, Math.random() * 1000 + 1000);
// --- KONIEC gwiazdek ---
// --- KONIEC sparklingów ---
// Ukryj ściany (jeśli są widoczne w menu)
walls.forEach(function (wall) {
if (wall) {
wall.alpha = 0;
}
});
// Ustaw UI dla Grill Menu
ui.positionElements("grillMenu");
ui.updateBossHealth(0, 1); // Ukryj pasek HP bossa
ui.updateHearts(0, storage.maxHearts); // Ukryj serca gracza
// --- Przyciski na ekranie Grilla ---
var buttonYStart = 1000;
var buttonYOffset = 150;
// Przycisk "Rest"
var restButton = new Container();
restButton.interactive = true;
restButton.cursor = "pointer";
restButton.x = 600;
restButton.y = 650;
currentSceneElements.addChild(restButton);
var restButtonBg = LK.getAsset('buttonRest', {
anchorX: 0.5,
anchorY: 0.5
});
restButton.addChild(restButtonBg);
restButton.down = function () {
if (!confirmRestButton || confirmRestButton && confirmRestButton.destroyed) {
confirmRestButton = new Container();
confirmRestButton.interactive = true;
confirmRestButton.cursor = "pointer";
// Zamieniamy tekst i tło na konkretny asset
var confirmButtonGraphic = LK.getAsset('confirmRestButton', {
anchorX: 0.5,
anchorY: 0.5
});
confirmRestButton.addChild(confirmButtonGraphic);
var restButtonActualWidth = restButtonBg.width || 500;
var confirmButtonActualWidth = confirmButtonGraphic.width || 550;
var paddingBetweenButtons = 30;
confirmRestButton.x = restButton.x + restButtonActualWidth / 2 + confirmButtonActualWidth / 2 + paddingBetweenButtons;
confirmRestButton.y = restButton.y;
currentSceneElements.addChild(confirmRestButton);
confirmRestButton.down = function () {
var farewellGraphic = null;
try {
farewellGraphic = LK.getAsset('grillMenuFarewellGraphic', {
anchorX: 0.5,
anchorY: 0.5,
x: 2048 / 2,
y: 2732 / 2,
alpha: 1 // od razu widoczna
});
currentSceneElements.addChild(farewellGraphic);
LK.setTimeout(function () {
if (gameState.currentState === "grillMenu" && farewellGraphic && !farewellGraphic.destroyed) {
tween(farewellGraphic, {
alpha: 0
}, {
duration: 500,
onFinish: function onFinish() {
if (farewellGraphic && !farewellGraphic.destroyed) {
currentSceneElements.removeChild(farewellGraphic);
farewellGraphic.destroy();
}
if (confirmRestButton && !confirmRestButton.destroyed) {
currentSceneElements.removeChild(confirmRestButton);
confirmRestButton.destroy();
confirmRestButton = null;
}
}
});
} else if (farewellGraphic && !farewellGraphic.destroyed) {
currentSceneElements.removeChild(farewellGraphic);
farewellGraphic.destroy();
}
}, 6000);
} catch (e) {
console.error("Błąd grillMenuFarewellGraphic:", e);
farewellGraphic = new Shape({
width: 2048,
height: 2732,
color: 0x101030
});
farewellGraphic.x = 2048 / 2;
farewellGraphic.y = 2732 / 2;
farewellGraphic.alpha = 0;
currentSceneElements.addChild(farewellGraphic);
tween(farewellGraphic, {
alpha: 0.8
}, {
duration: 500
});
LK.setTimeout(function () {
if (farewellGraphic && !farewellGraphic.destroyed) {
tween(farewellGraphic, {
alpha: 0
}, {
duration: 500,
onFinish: function onFinish() {
if (farewellGraphic && !farewellGraphic.destroyed) {
currentSceneElements.removeChild(farewellGraphic);
farewellGraphic.destroy();
}
if (confirmRestButton && !confirmRestButton.destroyed) {
currentSceneElements.removeChild(confirmRestButton);
confirmRestButton.destroy();
confirmRestButton = null;
}
}
});
}
}, 6000);
if (ui && ui.showMessage) {
ui.showMessage("Błąd: Brak assetu grafiki. Placeholder.", 3000);
}
}
};
} else {
console.log("Przycisk 'Potwierdź' już istnieje.");
}
};
// Przycisk "Upgrade Roll" (TEN I KOLEJNE PRZYCISKI POZOSTAJĄ BEZ ZMIAN)
var upgradeButton = new Container();
upgradeButton.interactive = true;
upgradeButton.x = 600;
upgradeButton.y = 850;
currentSceneElements.addChild(upgradeButton);
var upgradeButtonBg = LK.getAsset('buttonUpgrade', {
anchorX: 0.5,
anchorY: 0.5
});
upgradeButton.addChild(upgradeButtonBg);
upgradeButton.down = function () {
if (currentBackground) {
tween(currentBackground, {
alpha: 0
}, {
duration: 600,
easing: tween.easeIn,
onFinish: function onFinish() {
currentBackground.destroy();
currentBackground = LK.getAsset('upgradebg', {
anchorX: 0.5,
anchorY: 0.5,
x: 2048 / 2,
y: 2732 / 2,
alpha: 0
});
game.addChildAt(currentBackground, 0);
tween(currentBackground, {
alpha: 1
}, {
duration: 1200,
easing: tween.easeOut
});
}
});
}
};
// Przycisk "New Boss+"
var newBossButton = new Container();
newBossButton.interactive = true;
newBossButton.cursor = "pointer";
newBossButton.x = 600;
newBossButton.y = 1050; // jeszcze niżej
currentSceneElements.addChild(newBossButton); // Dodaj do kontenera sceny
var newBossButtonBg = LK.getAsset('buttonBoss', {
anchorX: 0.5,
anchorY: 0.5
});
newBossButton.addChild(newBossButtonBg);
newBossButton.down = function () {
isNewBossPlusMode = true;
// Poprzedni kod usuwający sparkle tutaj jest już niepotrzebny,
// ponieważ obsługa usuwania jest teraz w startGame z wykorzystaniem gameState.grillMenuEffects
gameState.startGame();
};
// Przycisk "Roll Master"
var rollMasterButton = new Container(); // Tworzymy kontener na przycisk
rollMasterButton.interactive = true; // Ustawiamy interaktywność
rollMasterButton.cursor = "pointer"; // Zmieniamy kursor po najechaniu
rollMasterButton.x = 600; // Ta sama pozycja X co inne przyciski
rollMasterButton.y = 2500; // Pozycja Y pod przyciskiem "New Boss+"
currentSceneElements.addChild(rollMasterButton); // Dodajemy kontener do sceny
// --- NOWY KOD TŁA ---
// Pobieramy asset obrazka dla przycisku Roll Master
var rollMasterButtonImage = LK.getAsset('buttonRollMaster', {
anchorX: 0.5,
// Ustawiamy punkt zaczepienia na środek obrazka
anchorY: 0.5
});
rollMasterButton.addChild(rollMasterButtonImage); // Dodajemy obrazek do kontenera przycisku
// --- KONIEC NOWEGO KODU TŁA ---
// Usunęliśmy kod tworzący i dodający obiekt Text2
// Akcja przycisku (kliknięcie) pozostaje bez zmian
rollMasterButton.down = function () {
gameState.startRollMasterMode(); // Uruchom tryb Roll Master
};
var creditsButton = new Container();
creditsButton.interactive = true;
creditsButton.cursor = "pointer";
// Ustaw pozycję dla prawego dolnego rogu
// Te wartości (X, Y) są odległościami od lewego górnego rogu ekranu (0,0)
// Musisz je dostosować do rozmiaru swojego przycisku i ekranu.
// Załóżmy, że Twój przycisk 'buttoncredits' ma np. 200px szerokości i 80px wysokości.
// Aby umieścić go w prawym dolnym rogu z marginesem 50px:
var creditsButtonAsset = LK.getAsset('buttoncredits', {}); // Pobierz asset, aby sprawdzić wymiary jeśli trzeba
var creditsButtonWidth = creditsButtonAsset.width || 200; // Domyślna szerokość, jeśli nie ma w assecie
var creditsButtonHeight = creditsButtonAsset.height || 80; // Domyślna wysokość
creditsButton.x = 2048 - creditsButtonWidth / 2 - 50; // Od prawej krawędzi (2048) odejmij połowę szerokości przycisku i margines
creditsButton.y = 2732 - creditsButtonHeight / 2 - 50; // Od dolnej krawędzi (2732) odejmij połowę wysokości przycisku i margines
currentSceneElements.addChild(creditsButton);
// Użyj swojego nowego assetu 'buttoncredits' jako tła/grafiki przycisku
var creditsButtonBg = LK.getAsset('buttoncredits', {
anchorX: 0.5,
// Anchor na środek, bo pozycjonujemy kontener
anchorY: 0.5
// Nie potrzebujesz tutaj scale, chyba że Twój asset jest za duży/mały
});
creditsButton.addChild(creditsButtonBg);
// Tekst na przycisku (jeśli Twój asset 'buttoncredits' nie ma już napisu)
// Jeśli 'buttoncredits' to już gotowy obrazek z napisem "Credits", ten obiekt Text2 nie jest potrzebny.
var creditsButtonText = new Text2("Credits", {
size: 30,
// Dostosuj rozmiar, aby pasował do Twojego przycisku
fill: 0xFFFFFF // Kolor tekstu
// Możesz dodać stroke, jeśli tekst jest na skomplikowanym tle
// stroke: 0x000000,
// strokeThickness: 2
});
creditsButtonText.anchor.set(0.5, 0.5); // Wyśrodkuj tekst na przycisku
creditsButton.addChild(creditsButtonText); // Dodaj tekst DO kontenera przycisku
creditsButton.down = function () {
if (gameState.currentState === "grillMenu") {
console.log("Przycisk Credits kliknięty!"); // Dodaj log dla testu
gameState.showCredits();
}
};
},
showCredits: function showCredits() {
var self = this;
var previousState = this.currentState;
var wasInputActive = self.isInputActive;
self.isInputActive = false; // Tymczasowo zablokuj input gry
// self.creditsPlaying = true; // Opcjonalna flaga, jeśli potrzebujesz
console.log("[Credits] Pokazywanie Creditsów z własnym tłem");
// 1. Stwórz tło 'creditsbg'
var creditsBackground; // Zmienna na nasze tło
try {
creditsBackground = LK.getAsset('creditsbg', {
anchorX: 0.5,
// Zakładamy, że chcesz je wyśrodkować
anchorY: 0.5,
x: 2048 / 2,
y: 2732 / 2,
alpha: 0 // Zacznij od przezroczystości
});
game.addChild(creditsBackground); // Dodaj do sceny
} catch (e) {
console.error("[Credits] Błąd podczas ładowania assetu 'creditsbg':", e);
// Awaryjnie: stwórz czarne tło, jeśli asset się nie załadował
creditsBackground = new Shape({
width: 2048,
height: 2732,
color: 0x111010
}); // Użyjmy Twojego działającego koloru
creditsBackground.x = 2048 / 2;
creditsBackground.y = 2732 / 2;
creditsBackground.alpha = 0;
game.addChild(creditsBackground);
}
var creditsText = null; // Zmienna na tekst creditsów
var creditsTextTween = null; // Zmienna na animację tekstu
// Handler do pomijania creditsów
var _skipCreditsHandler = function skipCreditsHandler() {
console.log("[Credits] Pominięto creditsy");
// Zatrzymaj animacje
tween.stop(creditsBackground);
if (creditsTextTween) {
tween.stop(creditsTextTween);
}
// Usuń obiekty
if (creditsText && !creditsText.destroyed) {
creditsText.destroy();
}
if (creditsBackground && !creditsBackground.destroyed) {
creditsBackground.destroy();
}
// Przywróć stan
self.isInputActive = wasInputActive;
// self.creditsPlaying = false;
game.off('down', _skipCreditsHandler); // Usuń listener
// Jeśli currentState było zmieniane na coś w stylu "creditsScreenActive", przywróć je teraz
// self.currentState = previousState;
// W tej wersji nie zmieniamy currentState, więc nie ma potrzeby przywracania
};
// Animacja pojawienia się tła
tween(creditsBackground, {
alpha: 1
}, {
duration: 500,
// Krótki fade-in
onFinish: function onFinish() {
// Stwórz tekst "Silas"
creditsText = new Text2("Silas", {
size: 150,
fill: 0xFFFFFF,
align: 'center'
});
creditsText.anchor.set(0.5, 0.5);
creditsText.x = 2048 / 2;
creditsText.y = 2732 + creditsText.height;
game.addChild(creditsText);
var textAnimationDuration = 8000;
var textEndY = -creditsText.height;
// Animacja tekstu w górę
creditsTextTween = tween(creditsText, {
y: textEndY
}, {
duration: textAnimationDuration,
easing: tween.linear,
onFinish: function onFinish() {
if (creditsText && !creditsText.destroyed) {
creditsText.destroy();
}
// Animacja zanikania tła 'creditsbg'
tween(creditsBackground, {
alpha: 0
}, {
duration: 500,
onFinish: function onFinish() {
if (creditsBackground && !creditsBackground.destroyed) {
creditsBackground.destroy();
}
console.log("[Credits] Koniec Creditsów, powrót do", previousState);
self.isInputActive = wasInputActive;
// self.creditsPlaying = false;
game.off('down', _skipCreditsHandler); // Usuń listener, jeśli nie został wcześniej wywołany
}
});
}
});
// Dodaj listener do pomijania dopiero po pojawieniu się tła i tekstu (lub od razu, jeśli wolisz)
game.once('down', _skipCreditsHandler);
}
});
},
// Koniec funkcji showGrillScreen (przecinek oddziela ją od następnej metody gameState)
// --- NOWA FUNKCJA: Start przejścia do trybu Roll Master ---
startRollMasterMode: function startRollMasterMode() {
console.log("Rozpoczynanie trybu Roll Master...");
this.currentState = "rollMaster"; // Ustawienie nowego stanu gry
// Wyczyszczenie elementów z poprzedniej sceny (np. Grill Menu)
// Upewnij się, że 'currentSceneElements' to poprawny kontener dla elementów Grill Menu
// lub użyj innej metody czyszczenia specyficznej dla Twojej konfiguracji.
// Poniżej przykład czyszczenia globalnego kontenera, jeśli go używasz:
if (typeof clearScene === 'function') {
// Sprawdź, czy funkcja clearScene istnieje
clearScene(); // Użyj globalnej funkcji clearScene, jeśli jest dostępna
} else if (currentSceneElements && typeof currentSceneElements.removeChildren === 'function') {
// Alternatywnie, jeśli currentSceneElements zawiera tylko elementy menu:
currentSceneElements.removeChildren();
} else {
console.warn("Nie można automatycznie wyczyścić sceny przed Roll Master!");
}
// Dodatkowo, usuń specyficzne efekty z Grill Menu, jeśli istnieją
if (this.grillMenuEffects && this.grillMenuEffects.length > 0) {
this.grillMenuEffects.forEach(function (effect) {
if (effect && effect.destroy) {
effect.destroy();
}
});
this.grillMenuEffects = [];
}
// Usuń tło Grill Menu
if (currentBackground && currentBackground.destroy) {
tween.stop(currentBackground); // Zatrzymaj animacje, jeśli były
currentBackground.destroy();
currentBackground = null;
}
// Wywołanie funkcji konfigurującej scenę tego trybu
this.setupRollMasterScene();
},
// <-- WAŻNE: Przecinek tutaj, bo następuje kolejna funkcja gameState
// --- NOWA FUNKCJA: Konfiguracja sceny Roll Master ---
setupRollMasterScene: function setupRollMasterScene() {
var _this2 = this; // _this2 dla callbacków w timerach poniżej
console.log("[SetupRollMaster] Konfiguracja sceny Roll Master...");
// --- POCZĄTEK SEKCJI ZARZĄDZANIA MUZYKĄ ---
if (this.currentIntroMusicInstance && this.currentIntroMusicInstance.stop) {
console.log("[SetupRollMaster] Zatrzymywanie currentIntroMusicInstance.");
if (typeof this.currentIntroMusicInstance.volume === 'number') {
this.currentIntroMusicInstance.volume = 0;
}
this.currentIntroMusicInstance.stop();
this.currentIntroMusicInstance = null;
}
if (this.currentRollSoulsInstance && this.currentRollSoulsInstance.stop) {
console.log("[SetupRollMaster] Zatrzymywanie currentRollSoulsInstance.");
if (typeof this.currentRollSoulsInstance.volume === 'number') {
this.currentRollSoulsInstance.volume = 0;
}
this.currentRollSoulsInstance.stop();
this.currentRollSoulsInstance = null;
}
// Zatrzymaj poprzednią instancję rollmaster music, jeśli jakimś cudem istnieje
if (this.currentRollMasterMusicInstance && this.currentRollMasterMusicInstance.stop) {
console.log("[SetupRollMaster] Zatrzymywanie poprzedniej currentRollMasterMusicInstance.");
if (typeof this.currentRollMasterMusicInstance.volume === 'number') {
this.currentRollMasterMusicInstance.volume = 0;
}
this.currentRollMasterMusicInstance.stop();
// this.currentRollMasterMusicInstance = null; // Zerujemy, bo zaraz przypiszemy nową
}
console.log("[SetupRollMaster] Odtwarzanie muzyki 'rollmaster'.");
this.currentRollMasterMusicInstance = LK.playMusic('rollmaster', {
loop: true,
volume: 0.7,
fade: {
start: 0,
end: 0.7,
duration: 1000
}
});
if (!this.currentRollMasterMusicInstance) {
console.error("[SetupRollMaster] LK.playMusic('rollmaster') nie zwróciło instancji!");
}
// --- KONIEC SEKCJI ZARZĄDZANIA MUZYKĄ ---
try {
currentBackground = LK.getAsset('rollMasterBg', {
anchorX: 0.5,
anchorY: 0.5,
x: 2048 / 2,
y: 2732 / 2
});
game.addChildAt(currentBackground, 0);
} catch (e) {
console.warn("Nie znaleziono assetu 'rollMasterBg', używam koloru tła.");
game.setBackgroundColor(0x222233);
}
if (typeof Player !== 'undefined') {
player = game.addChild(new Player());
player.x = 2048 / 2;
player.y = 2732 / 2 + 400;
player.health = 1;
player.rolling = false;
player.invulnerable = false;
player.invulnerabilityFrames = 0;
player.rollCooldown = 0;
if (player && typeof player.clearRollTimeouts === 'function') {
player.clearRollTimeouts();
}
player.hasRolledThroughBossThisRoll = false;
player.alpha = 1;
player.dead = false;
} else {
console.error("Klasa Player nie jest zdefiniowana!");
this.showGrillScreen();
return;
}
this.rollMasterTime = 0;
this.rollMasterDifficulty = 1;
this.rollMasterAttacks = [];
this.attackUnlockOrder = ['rmattack1', 'rmattack2', 'rmattack3', 'rmattack4'];
this.unlockedAttacks = ['rmattack1'];
this.nextUnlockTime = 15;
console.log("Ataki startowe:", this.unlockedAttacks, "Następne odblokowanie w:", this.nextUnlockTime + "s");
this.attackSpawnTimer = 0;
this.attackSpawnInterval = 120;
this.explosionSpawnTimer = 0;
this.explosionSpawnInterval = 240;
this.laserSpawnTimer = 0;
this.laserSpawnInterval = 300;
this.laserWarningTime = 1500;
this.spreaderSpawnTimer = 0;
this.spreaderSpawnInterval = 600;
this.spreaderSplitTime = 180;
if (this.randomRmattack1Timer) {
LK.clearTimeout(this.randomRmattack1Timer);
this.randomRmattack1Timer = null;
}
this.rollMasterHighScore = storage.rollMasterHighScore || 0;
if (ui && ui.updateHighScoreDisplay) {
ui.updateHighScoreDisplay(this.rollMasterHighScore);
} else {
console.error("Nie można zaktualizować wyświetlania rekordu - brak ui.updateHighScoreDisplay");
}
if (ui && ui.timerText) {
ui.updateTimerDisplay(this.rollMasterTime);
} else {
console.error("Nie można znaleźć ui.timerText do skonfigurowania!");
}
if (ui && ui.updateHearts) {
ui.updateHearts(1, 1);
}
if (ui && ui.updateBossHealth) {
ui.updateBossHealth(0, 1);
}
if (ui && ui.positionElements) {
ui.positionElements("rollMaster");
} else {
console.error("Nie można ustawić elementów UI dla Roll Master - brak ui.positionElements");
}
if (this.rollMasterTimerInterval) {
LK.clearInterval(this.rollMasterTimerInterval);
}
this.rollMasterTimerInterval = LK.setInterval(function () {
if (_this2.currentState !== "rollMaster") {
LK.clearInterval(_this2.rollMasterTimerInterval);
_this2.rollMasterTimerInterval = null;
return;
}
_this2.rollMasterTime++;
if (ui && ui.updateTimerDisplay) {
ui.updateTimerDisplay(_this2.rollMasterTime);
}
if (_this2.rollMasterTime > 0 && _this2.rollMasterTime % 20 === 0) {
var newAttackJustUnlocked = false;
var newAttackFriendlyName = "";
if (_this2.unlockedAttacks.length < _this2.attackUnlockOrder.length) {
var nextAttackIndex = _this2.unlockedAttacks.length;
var attackToUnlockTechnicalName = _this2.attackUnlockOrder[nextAttackIndex];
_this2.unlockedAttacks.push(attackToUnlockTechnicalName);
newAttackJustUnlocked = true;
newAttackFriendlyName = attackToUnlockTechnicalName.replace('rmattack1', 'Fart of the Forgotten Pyromancer').replace('rmattack2', 'Blood Ball Z').replace('rmattack3', 'Holy Wifi Beam').replace('rmattack4', 'Flame Skull.exe');
console.log("Odblokowano nowy atak:", attackToUnlockTechnicalName, "w", _this2.rollMasterTime + "s");
}
_this2.increaseRollMasterDifficulty();
if (ui && ui.showMessage) {
var messageToShow = "";
var currentDifficulty = _this2.rollMasterDifficulty;
if (newAttackJustUnlocked) {
messageToShow = "New Attack: " + newAttackFriendlyName + "! (Difficulty: " + currentDifficulty + ")";
} else {
switch (currentDifficulty) {
case 2:
case 3:
case 4:
messageToShow = "Even the pause button is afraid.";
break;
case 5:
messageToShow = "Alright, that’s probably enough difficulty... (Poziom: " + currentDifficulty + ")";
break;
case 6:
messageToShow = "Wait, are you still playing? I ran out of messages. (Poziom: " + currentDifficulty + ")";
break;
case 7:
messageToShow = "Okay seriously, stop. I didn’t test this far. (Poziom: " + currentDifficulty + ")";
break;
default:
messageToShow = "I stopped writing messages after level 7. You're on your own. Level: " + currentDifficulty;
break;
}
}
ui.showMessage(messageToShow, 2500);
}
}
}, 1000);
if (ui && ui.showMessage && this.unlockedAttacks.length > 0) {
var firstAttackName = this.unlockedAttacks[0];
var friendlyFirstName = firstAttackName.replace('rmattack1', 'Fart of the Forgotten Pyromancer').replace('rmattack2', 'Blood Ball Z').replace('rmattack3', 'Holy Wifi Beam').replace('rmattack4', 'Flame Skull.exe');
LK.setTimeout(function () {
if (gameState.currentState === "rollMaster") {
ui.showMessage("First Attack: " + friendlyFirstName, 3000);
}
}, 1000);
}
console.log("Scena Roll Master skonfigurowana i muzyka 'rollmaster' powinna grać.");
},
// <-- WAŻNE: Przecinek tutaj
// --- NOWA FUNKCJA: Zwiększanie trudności w Roll Master ---
increaseRollMasterDifficulty: function increaseRollMasterDifficulty() {
this.rollMasterDifficulty++;
console.log("Trudność Roll Master zwiększona do:", this.rollMasterDifficulty);
var decreaseAmountProjectile = 10;
var minIntervalProjectile = 30;
this.attackSpawnInterval = Math.max(minIntervalProjectile, this.attackSpawnInterval - decreaseAmountProjectile);
console.log("Nowy interwał projectile:", this.attackSpawnInterval);
var decreaseAmountExplosion = 15;
var minIntervalExplosion = 60;
this.explosionSpawnInterval = Math.max(minIntervalExplosion, this.explosionSpawnInterval - decreaseAmountExplosion);
console.log("Nowy interwał explosion:", this.explosionSpawnInterval);
// --- NOWA LOGIKA DLA LASERA ---
var decreaseAmountLaser = 20; // Jak szybko ma skracać się interwał lasera
var minIntervalLaser = 120; // Minimalny interwał lasera (np. 2 sekundy)
this.laserSpawnInterval = Math.max(minIntervalLaser, this.laserSpawnInterval - decreaseAmountLaser);
console.log("Nowy interwał laser:", this.laserSpawnInterval);
// --- NOWA LOGIKA DLA SPREADERA ---
var decreaseAmountSpreader = 30; // Jak szybko ma skracać się interwał spreadera
var minIntervalSpreader = 180; // Minimalny interwał spreadera (np. 3 sekundy)
this.spreaderSpawnInterval = Math.max(minIntervalSpreader, this.spreaderSpawnInterval - decreaseAmountSpreader);
console.log("Nowy interwał spreader:", this.spreaderSpawnInterval);
},
// Przejście do stanu Game Over
// isDeath: true (gracz zginął), false (czas minął w Boss+)
gameOver: function gameOver(isDeath) {
console.log("DEBUG: gameState.gameOver(", isDeath, ") called! State:", this.currentState, "Boss dead:", boss ? boss.dead : 'N/A', "Player dead:", player ? player.dead : 'N/A');
console.log("[GameOver] Stan gry Game Over. Przyczyna śmierć:", isDeath);
this.currentState = "gameOver";
gameOverReasonIsDeath = isDeath;
// Zatrzymaj timery i inne procesy gry
if (this.gameTimerInterval) {
LK.clearInterval(this.gameTimerInterval);
this.gameTimerInterval = null;
}
if (this.rollMasterTimerInterval) {
LK.clearInterval(this.rollMasterTimerInterval);
this.rollMasterTimerInterval = null;
}
this.bossSpeedIncreased = false;
// Zatrzymaj animację tła, jeśli istnieje
if (currentBackground) {
tween.stop(currentBackground);
}
game.setBackgroundColor(0x000000);
// Ukryj gracza i bossa (przez alpha)
if (player && player.alpha !== 0) {
player.alpha = 0;
}
if (boss && boss.alpha !== 0) {
boss.alpha = 0;
}
walls.forEach(function (wall) {
if (wall) {
wall.alpha = 0;
}
});
// Ustaw elementy UI dla stanu gameOver
ui.positionElements("gameOver");
ui.titleText.alpha = 0;
ui.messageText.alpha = 0;
ui.showTutorial("");
// --- POCZĄTEK ZMIAN: Animacje memów ---
// Rozpocznij DŁUGI FADE-OUT dla coffinDanceMeme
if (coffinMemeImage && !coffinMemeImage.destroyed) {
console.log("DEBUG: Rozpoczynanie długiego fade-out (5s) dla coffinDanceMeme w gameOver.");
// Upewnij się, że jest widoczny i na dole ekranu przed animacją
coffinMemeImage.alpha = 1.0;
coffinMemeImage.y = 2932;
tween.stop(coffinMemeImage); // Zatrzymaj poprzednie tweeny
tween(coffinMemeImage, {
alpha: 0
}, {
duration: 2500,
// Czas zanikania = czas trwania ekranu gameOver
easing: tween.easeIn,
onFinish: function onFinish() {
// Zniszcz po zakończeniu animacji
if (coffinMemeImage && !coffinMemeImage.destroyed) {
console.log("DEBUG: CoffinDanceMeme fade-out zakończony, niszczenie.");
if (coffinMemeImage.parent) {
coffinMemeImage.parent.removeChild(coffinMemeImage);
}
coffinMemeImage.destroy();
coffinMemeImage = null; // Zresetuj zmienną globalną
}
}
});
} else {
console.log("DEBUG: coffinDanceMeme nie istnieje lub zniszczony w gameOver - nie można animować fade-out.");
}
// Wyświetl losowy mem śmierci z FADE-IN
var memeAssets = ['deathMeme1', 'deathMeme2', 'deathMeme3', 'deathMeme4', 'deathMeme5'];
var randomMemeAssetName = memeAssets[Math.floor(Math.random() * memeAssets.length)];
console.log("[GameOver] Wybrano mema:", randomMemeAssetName);
try {
var memeImage = LK.getAsset(randomMemeAssetName, {
anchorX: 0.5,
anchorY: 0.5,
alpha: 0 // Start invisible
});
memeImage.x = 2048 / 2;
memeImage.y = 2732 / 2 - 200;
memeImage.scale.set(0.8);
currentSceneElements.addChild(memeImage); // Dodaj do currentSceneElements
// Animacja Fade-In (0.5 sekundy)
tween(memeImage, {
alpha: 1
}, {
duration: 500,
easing: tween.easeIn
});
} catch (e) {
console.error("[GameOver] Błąd podczas ładowania assetu mema:", randomMemeAssetName, e);
ui.titleText.setText("YOU DIED (anyway)");
ui.titleText.alpha = 1;
}
// --- KONIEC ZMIAN: Animacje memów ---
// Wyświetl dodatkowy tekst
var extraTextMessage = "";
if (!isDeath && isNewBossPlusMode) {
extraTextMessage = "You just lost 10 minutes of your life. For what?";
}
if (extraTextMessage) {
var extraText = new Text2(extraTextMessage, {
size: 60,
fill: 0xFFFFFF,
align: 'center',
stroke: 0x000000,
strokeThickness: 4
});
extraText.anchor.set(0.5, 0.5);
extraText.x = 2048 / 2;
extraText.y = 2732 / 2 + 250;
currentSceneElements.addChild(extraText);
}
// Timeout przed przejściem do następnego stanu
LK.setTimeout(function () {
console.log("DEBUG: Wykonuje się timeout w gameState.gameOver. isDeath:", isDeath, "isNewBossPlusMode:", isNewBossPlusMode);
clearScene(); // Czyści losowy mem śmierci i extraText z currentSceneElements
// Niszczenie coffinDanceMeme jest teraz w onFinish jego tweentu
// Zniszcz gracza, bossa, tło...
if (player && player.destroy && !player.destroyed) {
player.destroy();
}
player = null;
if (boss && boss.destroy && !boss.destroyed) {
boss.destroy();
}
boss = null;
if (currentBackground) {
currentBackground.destroy();
}
currentBackground = null;
// Resetuj UI
ui.titleText.setText("");
ui.titleText.alpha = 0;
ui.messageText.setText("");
ui.messageText.alpha = 0;
ui.tutorialText.setText("");
ui.tutorialText.alpha = 0;
ui.updateDeathsCounter();
ui.updateBossHealth(0, 1);
ui.updateHearts(0, parseInt(storage.maxHearts, 10) || 5);
// Logika przejścia do następnego stanu
if (isDeath && !isNewBossPlusMode) {
console.log("gameOver timeout: Standard death -> Restarting standard game...");
isNewBossPlusMode = false;
if (typeof gameState.startGame === 'function') {
gameState.startGame();
} else {
console.error("Nie można wywołać startGame po standardowej śmierci!");
}
} else if (isDeath && isNewBossPlusMode) {
console.log("gameOver timeout: Boss+ death -> Going to Grill Menu...");
isNewBossPlusMode = false;
if (typeof gameState.showGrillScreen === 'function') {
gameState.showGrillScreen();
} else {
console.error("Nie można wywołać showGrillScreen po śmierci w Boss+!");
}
} else if (!isDeath && isNewBossPlusMode) {
console.log("gameOver timeout: Boss+ timeout -> Going to Grill Menu...");
isNewBossPlusMode = false;
if (typeof gameState.showGrillScreen === 'function') {
gameState.showGrillScreen();
} else {
console.error("Nie można wywołać showGrillScreen po timeout w Boss+!");
}
} else {
console.log("gameOver timeout: Fallback case -> Going to Grill Menu...");
isNewBossPlusMode = false;
if (typeof gameState.showGrillScreen === 'function') {
gameState.showGrillScreen();
} else {
console.error("Nie można wywołać showGrillScreen w fallbacku gameOver!");
}
}
}, 5000);
},
// --- NOWA FUNKCJA: Zakończenie trybu Roll Master ---
endRollMasterMode: function endRollMasterMode(finalTime) {
var _this3 = this;
if (this.currentState === "rollMasterGameOver") {
console.log("DEBUG: Próba ponownego wejścia do endRollMasterMode. Zatrzymano.");
return;
}
console.log("[EndRollMaster] Zakończono tryb Roll Master. Czas:", finalTime);
this.currentState = "rollMasterGameOver";
// Zatrzymaj muzykę i timery etc.
if (this.currentRollMasterMusicInstance && this.currentRollMasterMusicInstance.playing) {/* ... log ... */}
if (this.rollMasterTimerInterval) {
LK.clearInterval(this.rollMasterTimerInterval);
this.rollMasterTimerInterval = null;
}
if (this.randomRmattack1Timer) {
LK.clearTimeout(this.randomRmattack1Timer);
this.randomRmattack1Timer = null;
}
if (this.rollMasterAttacks && this.rollMasterAttacks.length > 0) {/* ... czyszczenie ataków ... */}
if (player && player.clearRollTimeouts) {
player.clearRollTimeouts();
}
// Sprawdź rekord
var oldHighScore = this.rollMasterHighScore || 0;
if (finalTime > oldHighScore) {/* ... zapisz rekord ... */}
game.setBackgroundColor(0x000000); // Czarne tło
if (player && player.alpha !== 0) {
player.alpha = 0;
} // Ukryj gracza
// Wyświetl losowy mem śmierci z DŁUŻSZYM FADE-IN
var memeAssets = ['deathMeme1', 'deathMeme2', 'deathMeme3', 'deathMeme4', 'deathMeme5'];
var randomMemeAssetName = memeAssets[Math.floor(Math.random() * memeAssets.length)];
try {
var memeImage = LK.getAsset(randomMemeAssetName, {
anchorX: 0.5,
anchorY: 0.5,
alpha: 0
});
memeImage.x = 2048 / 2;
memeImage.y = 2732 / 2 - 200;
memeImage.scale.set(0.8);
currentSceneElements.addChild(memeImage);
// Wydłużony fade-in (1.5 sekundy)
tween(memeImage, {
alpha: 1
}, {
duration: 1500,
easing: tween.easeIn
});
} catch (e) {
console.error("[EndRollMaster] Błąd ładowania mema:", e);
}
// Wyświetl tekst z czasem
var timeMessage = "Your Time: " + String(Math.floor(finalTime / 60)).padStart(2, '0') + ':' + String(finalTime % 60).padStart(2, '0');
var timeText = new Text2(timeMessage, {
size: 60,
fill: 0xFFFFFF,
align: 'center',
stroke: 0x000000,
strokeThickness: 4
});
timeText.anchor.set(0.5, 0.5);
timeText.x = 2048 / 2;
timeText.y = 2732 / 2 + 250;
currentSceneElements.addChild(timeText);
// Timeout przed przejściem do grillMenu
LK.setTimeout(function () {
console.log("DEBUG: Wykonuje się timeout w gameState.endRollMasterMode.");
clearScene(); // Czyści losowy mem śmierci i timeText
// Zniszcz gracza, jeśli istnieje
if (player && player.destroy && !player.destroyed) {
player.destroy();
}
player = null;
// Przejdź do grillMenu
if (_this3 && typeof _this3.showGrillScreen === 'function') {
console.log("[EndRollMaster] Przechodzenie do showGrillScreen.");
_this3.showGrillScreen();
} else {/* ... obsługa błędu ... */}
}, 5000);
},
// Zamykający nawias klamrowy dla endRollMasterMode
// Koniec funkcji endRo
// Obsługa gestów dotykowych/myszy
processTouchGesture: function processTouchGesture() {
// ... (kod obsługi fake tutorial) ...
var dx = this.touchEnd.x - this.touchStart.x;
var dy = this.touchEnd.y - this.touchStart.y;
var distance = Math.sqrt(dx * dx + dy * dy);
var swipeThreshold = 50; // Minimalny dystans, by uznać za swipe
if (distance < swipeThreshold) {
// Logika Tapnięcia (bez zmian)
// ...
return;
}
// --- Obsługa Swipe (Turlania) ---
if ((this.currentState === "game" || this.currentState === "rollMaster") && player && !player.dead) {
// Normalizuj kierunek (bez zmian)
var direction = {
x: 0,
y: 0
};
if (distance > 0) {
direction.x = dx / distance;
direction.y = dy / distance;
}
// <-- NOWA LOGIKA: Obliczanie czasu trwania na podstawie dystansu swipe -->
var minSwipeDist = swipeThreshold; // Minimalny dystans swipe'a (np. 50)
var maxSwipeDist = 400; // <-- ZWIĘKSZONA dla większej skali
var minRollDuration = 50; // <-- LEKKO ZWIĘKSZONA dla płynności minimum
var maxRollDuration = 400; // Maksymalny czas uniku w ms (np. 0.45s - dostosuj!)
// Ogranicz dystans do zakresu min/max
var clampedDistance = Math.max(minSwipeDist, Math.min(distance, maxSwipeDist));
// Znormalizuj dystans do zakresu 0-1
var normalizedDistance = (clampedDistance - minSwipeDist) / (maxSwipeDist - minSwipeDist);
// Oblicz czas trwania uniku na podstawie znormalizowanego dystansu
var currentRollDuration = minRollDuration + normalizedDistance * (maxRollDuration - minRollDuration);
// <-- KONIEC NOWEJ LOGIKI -->
// Wywołaj turlanie, przekazując kierunek ORAZ obliczony czas trwania
player.roll(direction, currentRollDuration); // Przekazujemy nowy parametr!
}
}
};
// --- Obsługa inputu ---
game.down = function (x, y, obj) {
// Rejestruj początek dotyku/kliknięcia
// <-- ZMIANA: Natychmiastowa obsługa kliknięcia w fakeTutorial -->
if (gameState.currentState === "fakeTutorial") {
// Sprawdźmy dodatkowo, czy timer jeszcze istnieje, na wszelki wypadek
if (gameState.fakeTutorialTimerId) {
gameState.handleFakeTutorialInput(); // Wywołaj logikę "fałszywej śmierci"
return; // Zakończ, nie rób nic więcej w game.down
}
}
// Stany, w których śledzimy gesty lub kliknięcia przycisków:
var trackStates = ["title", "game", /* Usunięto "fakeTutorial" */"fakeGameOver", "realTutorial", "grillMenu", "gameOver", "rollMaster"]; // Można usunąć "fakeTutorial" z listy
if (trackStates.indexOf(gameState.currentState) !== -1) {
gameState.touchStart.x = x;
gameState.touchStart.y = y;
gameState.touchEnd.x = x;
gameState.touchEnd.y = y;
gameState.isInputActive = true;
gameState.currentInputPos.x = x;
gameState.currentInputPos.y = y;
gameState.swipeStartTime = Date.now();
}
// Obsługa kliknięć przycisków jest robiona przez ich własne handlery .down
// Note: If you have complex button logic that consumes clicks,
// you might need to check if 'obj' is null here before setting isInputActive.
// For simple cases, setting it always on 'down' might be fine,
// but make sure your player movement check in update respects the game state.
};
game.up = function (x, y, obj) {
// Rejestruj koniec dotyku/kliknięcia i przetwarzaj gest
var trackStates = ["title", "game", "fakeTutorial", "fakeGameOver", "realTutorial", "grillMenu", "gameOver", "rollMaster"];
if (trackStates.indexOf(gameState.currentState) !== -1) {
gameState.touchEnd.x = x;
gameState.touchEnd.y = y;
// Zawsze ustawiaj isInputActive na false przy puszczeniu, aby zatrzymać chodzenie
gameState.isInputActive = false;
// --- TUTAJ JEST KLUCZOWA ZMIANA ---
// Oblicz czas trwania gestu
var swipeDuration = Date.now() - gameState.swipeStartTime; // Odczytujemy startTime
var holdThreshold = 1000; // Próg w ms - PAMIĘTAJ O DOSTROJENIU!
// Wywołaj logikę rolla tylko jeśli gest był krótszy niż próg przytrzymania
if (swipeDuration < holdThreshold) {
// Gest był szybki (potencjalny swipe/tap)
console.log("game.up - GEST KRÓTKI (" + swipeDuration + "ms), przetwarzam..."); // Opcjonalny log
gameState.processTouchGesture(); // Wywołujemy tylko tutaj!
} else {
console.log("game.up - GEST DŁUGI (" + swipeDuration + "ms), IGNORUJĘ rolla"); // Opcjonalny log
}
// Jeśli gest był długi, nic więcej nie robimy.
// --- KONIEC KLUCZOWEJ ZMIANY ---
}
// Obsługa puszczenia przycisków (.up) może być dodana tutaj, jeśli potrzebna
};
game.move = function (x, y, obj) {
var trackStates = ["title", "game", "fakeTutorial", "fakeGameOver", "realTutorial", "grillMenu", "gameOver", "rollMaster"];
if (trackStates.indexOf(gameState.currentState) !== -1) {
gameState.touchEnd.x = x;
gameState.touchEnd.y = y;
if (gameState.isInputActive) {
gameState.currentInputPos.x = x;
gameState.currentInputPos.y = y;
}
}
};
function launchRmattack1() {
console.log("Launch rmattack1 (projectile)");
var projectileRadius = 45;
var startX, startY;
var edge = Math.floor(Math.random() * 4);
var gameWidth = 2048; // Szerokość pola gry
var gameHeight = 2732; // Wysokość pola gry
// Pozycjonowanie startowe (bez zmian)
if (edge === 0) {
// top
startX = Math.random() * gameWidth;
startY = -projectileRadius;
} else if (edge === 1) {
// right
startX = gameWidth + projectileRadius;
startY = Math.random() * gameHeight;
} else if (edge === 2) {
// bottom
startX = Math.random() * gameWidth;
startY = gameHeight + projectileRadius;
} else {
// left
startX = -projectileRadius;
startY = Math.random() * gameHeight;
}
// Celowanie w środek (bez zmian)
var targetX = gameWidth / 2;
var targetY = gameHeight / 2;
var dx = targetX - startX;
var dy = targetY - startY;
var angle = Math.atan2(dy, dx);
// --- ZMIANA: Obliczanie prędkości (vx, vy) zamiast endX, endY ---
var speed = 4 + gameState.rollMasterDifficulty * 0.65; // Prędkość pocisku (dostosuj wg potrzeb, może być zależna od trudności)
var vx = Math.cos(angle) * speed;
var vy = Math.sin(angle) * speed;
// --- KONIEC ZMIANY ---
var rotation = angle;
var scaleX = 1;
// Tworzenie animacji (bez zmian)
var frames = [LK.getAsset('rmattack1_0', {
anchorX: 0.5,
anchorY: 0.5
}), LK.getAsset('rmattack1_1', {
anchorX: 0.5,
anchorY: 0.5
}), LK.getAsset('rmattack1_2', {
anchorX: 0.5,
anchorY: 0.5
}), LK.getAsset('rmattack1_3', {
anchorX: 0.5,
anchorY: 0.5
}), LK.getAsset('rmattack1_4', {
anchorX: 0.5,
anchorY: 0.5
}), LK.getAsset('rmattack1_5', {
anchorX: 0.5,
anchorY: 0.5
}), LK.getAsset('rmattack1_6', {
anchorX: 0.5,
anchorY: 0.5
})];
var sprite = new SpriteAnimation({
frames: frames,
frameDuration: 120,
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)");
var bombWidth = 120;
var bombHeight = 120;
var halfBombWidth = bombWidth / 2;
var halfBombHeight = bombHeight / 2;
var minX_b = halfBombWidth;
var maxX_b = 2048 - halfBombWidth;
var minY_b = halfBombHeight;
var maxY_b = 2732 - halfBombHeight;
var x = minX_b + Math.random() * (maxX_b - minX_b);
var spawnHeight = maxY_b - topSafeMargin;
var y = spawnHeight > 0 ? topSafeMargin + Math.random() * spawnHeight : topSafeMargin;
var idleFrame = LK.getAsset('rmattack2_explode_0', {
anchorX: 0.5,
anchorY: 0.5
});
var bomb = new SpriteAnimation({
frames: [idleFrame],
frameDuration: 500,
loop: true,
x: x,
y: y,
anchorX: 0.5,
anchorY: 0.5
});
game.addChild(bomb);
bomb.idleTween = tween(bomb, {
y: y - 10
}, {
duration: 500,
yoyo: true,
repeat: Infinity,
easing: tween.inOutQuad
});
LK.setTimeout(function () {
if (bomb.destroyed) {
return;
}
try {
if (bomb.idleTween && typeof bomb.idleTween.stop === "function") {
bomb.idleTween.stop();
}
} catch (e) {
console.warn("idleTween stop error:", e);
}
var explosionFrames = [];
var finalScale = 0;
for (var i = 0; i < 11; i++) {
var scale = 1 + i * 0.4;
finalScale = scale;
var frame = LK.getAsset("rmattack2_explode_" + i, {
anchorX: 0.5,
anchorY: 0.5,
scaleX: scale,
scaleY: scale
});
explosionFrames.push(frame);
}
var explosion = new SpriteAnimation({
frames: explosionFrames,
frameDuration: 80,
loop: false,
x: bomb.x,
y: bomb.y,
anchorX: 0.5,
anchorY: 0.5
});
// Nowe: natychmiastowe niszczenie po zakończeniu animacji
explosion.onComplete = function () {
var index = gameState.rollMasterAttacks.indexOf(explosionData);
if (index > -1) {
gameState.rollMasterAttacks.splice(index, 1);
console.log("Usunięto eksplozję z listy przez onComplete.");
}
if (!explosion.destroyed) {
explosion.destroy();
}
};
bomb.destroy();
game.addChild(explosion);
var explosionDurationFrames = explosionFrames.length * (80 / (1000 / 60));
var explosionMaxRadius = bombWidth * finalScale / 2 * 0.8;
var explosionData = {
type: 'explosion',
visual: explosion,
radius: 0,
maxRadius: explosionMaxRadius,
currentFrame: 0,
totalFrames: explosionFrames.length,
frameDurationTicks: 80 / (1000 / 60),
timer: 0,
damageDealt: false
};
gameState.rollMasterAttacks.push(explosionData);
console.log("Dodano eksplozję do śledzenia, maxRadius:", explosionMaxRadius);
}, 2000);
}
// 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) {
ui.updateDeathsCounter();
if (boss) {
var maxHp = boss.maxHealth > 0 ? boss.maxHealth : 1;
ui.updateBossHealth(boss.health, maxHp);
} else {
var shouldShowBossHpOnGameOver = gameState.currentState === "gameOver" && !gameOverReasonIsDeath && isNewBossPlusMode;
if (!shouldShowBossHpOnGameOver) {
ui.updateBossHealth(0, 1);
}
}
if (player) {
if (gameState.currentState === "rollMaster") {
ui.updateHearts(player.health, 1);
} else {
ui.updateHearts(player.health, parseInt(storage.maxHearts, 10) || 5);
}
} else if (gameState.currentState !== "game" && gameState.currentState !== "gameOver") {
ui.updateHearts(0, parseInt(storage.maxHearts, 10) || 5);
}
}
if (coffinMemeImage && !coffinMemeImage.destroyed) {
if (gameState.currentState === "game" && player && !player.dead) {
var currentHp = player.health;
var maxHp = parseInt(storage.maxHearts, 10) || 5;
var visibilityFactor = 1 - currentHp / maxHp;
visibilityFactor = Math.max(0, Math.min(1, visibilityFactor));
var memeHeight = coffinMemeImage.height || 200;
var screenBottom = 2732;
var hiddenOffsetY = memeHeight;
var targetY = screenBottom + hiddenOffsetY * (1 - visibilityFactor) + 300;
var targetAlpha = visibilityFactor;
var positionChangeSpeed = 10;
var currentY = coffinMemeImage.y;
if (Math.abs(currentY - targetY) > 1) {
coffinMemeImage.y = currentY < targetY ? Math.min(targetY, currentY + positionChangeSpeed) : Math.max(targetY, currentY - positionChangeSpeed);
}
var alphaChangeSpeed = 0.05;
var currentAlpha = coffinMemeImage.alpha;
if (Math.abs(currentAlpha - targetAlpha) > 0.01) {
coffinMemeImage.alpha = currentAlpha < targetAlpha ? Math.min(targetAlpha, currentAlpha + alphaChangeSpeed) : Math.max(targetAlpha, currentAlpha - alphaChangeSpeed);
}
} else if (gameState.currentState === "game" && player && player.dead) {
coffinMemeImage.y = 2932;
coffinMemeImage.alpha = 1.0;
}
}
// Sterowanie tym memem odbywa się teraz tylko przy starcie gry (startGame)
// oraz podczas animacji zanikania w gameState.gameOver
// Logika dla różnych stanów gry
if (gameState.currentState === "game") {
if (player) {
player.update();
}
if (boss) {
boss.update();
}
} else if (gameState.currentState === "rollMaster") {
if (player) {
player.update();
}
// --- Logika spawnowania i aktualizacji ataków RollMaster ---
// Spawner Pocisków (rmattack1)
if (gameState.unlockedAttacks.includes('rmattack1')) {
if (!gameState.randomRmattack1Timer) {
var _spawnRandomRmattack = function spawnRandomRmattack1() {
if (gameState.currentState !== "rollMaster" || !gameState.unlockedAttacks.includes('rmattack1')) {
gameState.randomRmattack1Timer = null;
return;
}
launchRmattack1();
var nextDelay = 800 + Math.random() * 1500;
gameState.randomRmattack1Timer = LK.setTimeout(_spawnRandomRmattack, nextDelay);
};
_spawnRandomRmattack();
}
} else if (gameState.randomRmattack1Timer) {
LK.clearTimeout(gameState.randomRmattack1Timer);
gameState.randomRmattack1Timer = null;
}
// Spawner Eksplozji (rmattack2)
if (gameState.unlockedAttacks.includes('rmattack2') && gameState.explosionSpawnInterval > 0) {
gameState.explosionSpawnTimer++;
if (gameState.explosionSpawnTimer >= gameState.explosionSpawnInterval) {
gameState.explosionSpawnTimer = 0;
launchRmattack2();
}
}
// Spawner Lasera (rmattack3)
if (gameState.unlockedAttacks.includes('rmattack3') && gameState.laserSpawnInterval > 0) {
gameState.laserSpawnTimer++;
if (gameState.laserSpawnTimer >= gameState.laserSpawnInterval) {
gameState.laserSpawnTimer = 0;
launchRmattack3();
}
}
// Spawner Spreader Parent (rmattack4)
if (gameState.unlockedAttacks.includes('rmattack4') && gameState.spreaderSpawnInterval > 0) {
gameState.spreaderSpawnTimer++;
if (gameState.spreaderSpawnTimer >= gameState.spreaderSpawnInterval) {
gameState.spreaderSpawnTimer = 0;
console.log("Spawnuję Spreader Parent (rmattack4)!");
launchRmattack4();
}
}
// Pętla aktualizacji i kolizji dla ataków RollMaster
for (var i = gameState.rollMasterAttacks.length - 1; i >= 0; i--) {
var atk = gameState.rollMasterAttacks[i];
if (!atk || !atk.visual || atk.visual.destroyed) {
gameState.rollMasterAttacks.splice(i, 1);
continue;
}
var shouldRemove = false;
if (atk.visual.update && typeof atk.visual.update === 'function') {
atk.visual.update();
}
// Logika kolizji i ruchu dla różnych typów ataków ('projectile', 'explosion', 'laser', 'spreader_parent')
if (atk.type === 'projectile') {
atk.visual.x += atk.vx;
atk.visual.y += atk.vy;
if (atk.visual.x < -200 || atk.visual.x > 2248 || atk.visual.y < -200 || atk.visual.y > 2932) {
shouldRemove = true;
} else if (player && !player.dead && !player.rolling && !player.invulnerable) {
var dx_p = player.x - atk.visual.x;
var dy_p = player.y - atk.visual.y;
var dist_p = Math.sqrt(dx_p * dx_p + dy_p * dy_p);
var playerRadius = player.width / 2 * 0.8;
var projectileRadius = atk.radius || 45;
if (dist_p < playerRadius + projectileRadius) {
console.log("Kolizja: Pocisk!");
player.takeDamage(1);
shouldRemove = true;
}
}
} else if (atk.type === 'explosion') {
atk.timer++;
if (atk.timer >= atk.frameDurationTicks) {
atk.timer = 0;
atk.currentFrame++;
}
atk.radius = atk.currentFrame < atk.totalFrames ? atk.maxRadius * (atk.currentFrame / atk.totalFrames) : atk.maxRadius;
if (!atk.damageDealt && player && !player.dead && !player.rolling && !player.invulnerable) {
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!");
player.takeDamage(1);
atk.damageDealt = true;
}
}
} else if (atk.type === 'laser') {
if (player && !player.dead && !player.rolling && !player.invulnerable) {
var laserHalfWidth = (atk.width || 90) / 2;
var laserHalfHeight = (atk.height || 990) / 2;
var playerHalfWidth = player.width / 2 * 0.8;
var playerHalfHeight = player.height / 2 * 0.8;
var laserLeft = atk.visual.x - laserHalfWidth;
var laserRight = atk.visual.x + laserHalfWidth;
var laserTop = atk.visual.y - laserHalfHeight;
var laserBottom = atk.visual.y + laserHalfHeight;
var playerLeft = player.x - playerHalfWidth;
var playerRight = player.x + playerHalfWidth;
var playerTop = player.y - playerHalfHeight;
var playerBottom = player.y + playerHalfHeight;
if (laserLeft < playerRight && laserRight > playerLeft && laserTop < playerBottom && laserBottom > playerTop) {
console.log("Kolizja: Laser!");
player.takeDamage(1);
}
}
if (atk.lifeTime && atk.lifeTime > 0) {
atk.lifeTime--;
}
} else if (atk.type === 'spreader_parent') {
atk.timer--;
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 && !player.invulnerable) {
// [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
player.takeDamage(1); // [cite: 154]
// Nie usuwamy rodzica po kolizji, tylko po podziale
}
}
}
if (shouldRemove) {
if (atk.visual && atk.visual.destroy && !atk.visual.destroyed) {
atk.visual.destroy();
}
gameState.rollMasterAttacks.splice(i, 1);
continue;
}
}
// Sprawdzenie śmierci gracza w RollMaster
if (player && player.dead && gameState.currentState === "rollMaster") {
if (typeof gameState !== 'undefined' && typeof gameState.endRollMasterMode === 'function') {
gameState.endRollMasterMode(gameState.rollMasterTime);
} else {/* ... obsługa błędu ... */}
}
} // Koniec else if dla gameState.currentState === "rollMaster"
}; // 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
@@ -376,10 +376,10 @@
return;
}
var targetX = player.x;
var targetY = player.y;
- var count = isNewBossPlusMode ? 8 : 5;
- var attackLifeTime = isNewBossPlusMode ? 2500 : 3000;
+ var count = isNewBossPlusMode ? 12 : 12;
+ var attackLifeTime = isNewBossPlusMode ? 3000 : 3500;
var delayBetweenAttacks = isNewBossPlusMode ? 60 : 60;
for (var i = 0; i < count; i++) {
var t = i / (count - 1);
var posX = self.x + (targetX - self.x) * t;