/****
* 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);
self.isCharging = false;
self.ultimateAttackCooldownTimer = 0;
self.ultimateAttackMinInterval = 30 * 60;
self.ultimateAttackMaxInterval = 40 * 60;
self.nextUltimateAttackAvailableTime = 0;
self.circleAttackCooldownDuration = 11 * 60;
self.circleAttackActiveCooldown = self.circleAttackCooldownDuration;
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;
self.createBossAttackAnim = function () {
var frames = [];
for (var i = 0; i <= 6; i++) {
frames.push(LK.getAsset('bossAttack' + i, {}));
}
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;
};
self.playBossAttackAnim = function (attackType) {
if (self.bossAttackAnim) {
self.bossAttackAnim.stop();
if (self.bossAttackAnim.parent) {
self.bossAttackAnim.parent.removeChild(self.bossAttackAnim);
}
self.bossAttackAnim.destroy();
self.bossAttackAnim = null;
}
if (attackType !== 2 && attackType !== 3) {
if (self.bossGraphics && self.bossGraphics.parent) {
self.bossGraphics.parent.removeChild(self.bossGraphics);
}
self.bossGraphics = null;
self.bossAttackAnim = self.addChild(self.createBossAttackAnim());
self.bossAttackAnim.update = function () {
if (self.bossAttackAnim !== this || !this.playing || !this.frames || this.frames.length === 0) {
return;
}
this.frameTimer = (this.frameTimer || 0) + 1;
if (this.frameTimer >= this.frameDuration / (1000 / 60)) {
this.frameTimer = 0;
this.removeChildren();
this.currentFrame = (this.currentFrame || 0) + 1;
if (this.currentFrame >= this.frames.length) {
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;
if (this.parent) {
this.parent.removeChild(this);
}
this.destroy();
self.bossAttackAnim = null;
} else {
if (this.frames[this.currentFrame]) {
this.addChild(this.frames[this.currentFrame]);
}
}
}
};
}
};
self.createAttack = function (x, y, duration, type) {
var framesToUse = [];
var scaleMultiplier = 1;
var attackRadius = 60;
if (type === 'circle') {
framesToUse = [LK.getAsset('fireball0', {
anchorX: 0.5,
anchorY: 0.5,
clone: true
}), LK.getAsset('fireball00', {
anchorX: 0.5,
anchorY: 0.5,
clone: true
}), LK.getAsset('fireball01', {
anchorX: 0.5,
anchorY: 0.5,
clone: true
}), LK.getAsset('fireball02', {
anchorX: 0.5,
anchorY: 0.5,
clone: true
}), LK.getAsset('fireball03', {
anchorX: 0.5,
anchorY: 0.5,
clone: true
}), LK.getAsset('fireball04', {
anchorX: 0.5,
anchorY: 0.5,
clone: true
}), LK.getAsset('fireball05', {
anchorX: 0.5,
anchorY: 0.5,
clone: true
}), LK.getAsset('fireball06', {
anchorX: 0.5,
anchorY: 0.5,
clone: true
}), LK.getAsset('fireball07', {
anchorX: 0.5,
anchorY: 0.5,
clone: true
}), LK.getAsset('fireball08', {
anchorX: 0.5,
anchorY: 0.5,
clone: true
}), LK.getAsset('fireball09', {
anchorX: 0.5,
anchorY: 0.5,
clone: true
}), LK.getAsset('fireball1', {
anchorX: 0.5,
anchorY: 0.5,
clone: true
}), LK.getAsset('fireball10', {
anchorX: 0.5,
anchorY: 0.5,
clone: true
}), LK.getAsset('fireball11', {
anchorX: 0.5,
anchorY: 0.5,
clone: true
}), LK.getAsset('fireball12', {
anchorX: 0.5,
anchorY: 0.5,
clone: true
}), LK.getAsset('fireball13', {
anchorX: 0.5,
anchorY: 0.5,
clone: true
}), LK.getAsset('fireball14', {
anchorX: 0.5,
anchorY: 0.5,
clone: true
}), LK.getAsset('fireballnew1', {
anchorX: 0.5,
anchorY: 0.5,
clone: true
}), LK.getAsset('fireballnew2', {
anchorX: 0.5,
anchorY: 0.5,
clone: true
}), LK.getAsset('fireballnew3', {
anchorX: 0.5,
anchorY: 0.5,
clone: true
}), LK.getAsset('fireballnew4', {
anchorX: 0.5,
anchorY: 0.5,
clone: true
})];
} else if (type === 'line') {
framesToUse = [LK.getAsset('fireball2', {
anchorX: 0.5,
anchorY: 0.5,
clone: true
}), LK.getAsset('fireball3', {
anchorX: 0.5,
anchorY: 0.5,
clone: true
}), LK.getAsset('fireball4', {
anchorX: 0.5,
anchorY: 0.5,
clone: true
}), LK.getAsset('fireball5', {
anchorX: 0.5,
anchorY: 0.5,
clone: true
}), LK.getAsset('fireball6', {
anchorX: 0.5,
anchorY: 0.5,
clone: true
}), LK.getAsset('fireball7', {
anchorX: 0.5,
anchorY: 0.5,
clone: true
}), LK.getAsset('fireball8', {
anchorX: 0.5,
anchorY: 0.5,
clone: true
}), LK.getAsset('fireball9', {
anchorX: 0.5,
anchorY: 0.5,
clone: true
}), LK.getAsset('fireball15', {
anchorX: 0.5,
anchorY: 0.5,
clone: true
}), LK.getAsset('fireball16', {
anchorX: 0.5,
anchorY: 0.5,
clone: true
}), LK.getAsset('linearattack1', {
anchorX: 0.5,
anchorY: 0.5,
clone: true
}), LK.getAsset('linearattack2', {
anchorX: 0.5,
anchorY: 0.5,
clone: true
}), LK.getAsset('linearattack3', {
anchorX: 0.5,
anchorY: 0.5,
clone: true
}), LK.getAsset('linearattack4', {
anchorX: 0.5,
anchorY: 0.5,
clone: true
}), LK.getAsset('linearattack5', {
anchorX: 0.5,
anchorY: 0.5,
clone: true
})];
scaleMultiplier = 1.3;
attackRadius = 60 * scaleMultiplier;
} else {
framesToUse = [LK.getAsset('fireball0', {
anchorX: 0.5,
anchorY: 0.5,
clone: true
})];
}
var currentFrameDuration = type === 'line' ? 250 : type === 'circle' ? 150 : 100;
var spriteAnim = game.addChild(new SpriteAnimation({
frames: framesToUse,
frameDuration: currentFrameDuration,
loop: false,
anchorX: 0.5,
anchorY: 0.5,
x: x,
y: y
}));
spriteAnim.scaleX = 1.6 * scaleMultiplier;
spriteAnim.scaleY = 1.6 * scaleMultiplier;
spriteAnim.play();
var attackData = {
x: x,
y: y,
radius: attackRadius,
visual: spriteAnim,
lifeTime: Math.floor(duration / (1000 / 60)),
isActive: true,
attackObjectType: type
};
self.attacks.push(attackData);
var animationTotalDurationMs = framesToUse.length * currentFrameDuration;
LK.setTimeout(function () {
var index = self.attacks.indexOf(attackData);
if (index !== -1) {
self.attacks.splice(index, 1);
}
if (spriteAnim && !spriteAnim.destroyed) {
if (spriteAnim.parent) {
spriteAnim.parent.removeChild(spriteAnim);
}
spriteAnim.destroy();
}
}, animationTotalDurationMs);
};
self.circleAttack = function () {
LK.getSound('bossAttack').play();
var count = isNewBossPlusMode ? 8 : 4;
var radius = 300;
var orbitDurationFrames = 230;
var flightDurationMs = isNewBossPlusMode ? 3500 : 4500;
var baseAngle = Math.random() * Math.PI * 2;
var angularSpeed = 0.02;
var attackOriginX = self.x;
var attackOriginY = self.y;
for (var i = 0; i < count; i++) {
var angleOffset = i / count * Math.PI * 2;
var initialAngle = baseAngle + angleOffset;
var x = attackOriginX + Math.cos(initialAngle) * radius;
var y = attackOriginY + Math.sin(initialAngle) * radius;
var orbFrames = [LK.getAsset('fireball0', {
anchorX: 0.5,
anchorY: 0.5,
clone: true
}), LK.getAsset('fireball00', {
anchorX: 0.5,
anchorY: 0.5,
clone: true
}), LK.getAsset('fireball01', {
anchorX: 0.5,
anchorY: 0.5,
clone: true
}), LK.getAsset('fireball02', {
anchorX: 0.5,
anchorY: 0.5,
clone: true
}), LK.getAsset('fireball03', {
anchorX: 0.5,
anchorY: 0.5,
clone: true
}), LK.getAsset('fireball04', {
anchorX: 0.5,
anchorY: 0.5,
clone: true
}), LK.getAsset('fireball05', {
anchorX: 0.5,
anchorY: 0.5,
clone: true
}), LK.getAsset('fireball06', {
anchorX: 0.5,
anchorY: 0.5,
clone: true
})];
var spriteAnim = new SpriteAnimation({
frames: orbFrames,
frameDuration: 100,
loop: true,
anchorX: 0.5,
anchorY: 0.5,
x: x,
y: y
});
spriteAnim.scaleX = 1.6;
spriteAnim.scaleY = 1.6;
spriteAnim.play();
spriteAnim.update = function () {
this.frameTimer = (this.frameTimer || 0) + 1;
if (this.frameTimer >= this.frameDuration / (1000 / 60)) {
this.frameTimer = 0;
this.removeChildren();
this.currentFrame = ((this.currentFrame || 0) + 1) % this.frames.length;
if (this.frames[this.currentFrame]) {
this.addChild(this.frames[this.currentFrame]);
}
}
};
game.addChild(spriteAnim);
self.attacks.push({
type: 'circle_orbiting',
angleOffset: angleOffset,
baseAngle: baseAngle,
currentAngle: initialAngle,
angularSpeed: angularSpeed,
radius: radius,
collisionRadius: 60,
centerX: attackOriginX,
centerY: attackOriginY,
detachCounter: orbitDurationFrames,
lifeTime: Math.floor(flightDurationMs / (1000 / 60)),
isActive: true,
visual: spriteAnim,
x: x,
y: y,
detached: false,
vx: 0,
vy: 0
});
}
};
self.takeDamage = function (amount) {
console.log("DEBUG: Boss.takeDamage CALLED. Amount:", amount, "Boss dead:", self.dead, "Current state:", gameState.currentState, "Boss health BEFORE:", self.health);
if (self.dead || typeof gameState !== 'undefined' && gameState.currentState !== "game") {
console.log("DEBUG: Boss.takeDamage REJECTED. Dead or wrong game state.");
return;
}
self.health -= amount;
self.health = Math.max(0, self.health);
console.log("DEBUG: Boss health AFTER:", self.health, "/", self.maxHealth);
LK.effects.flashObject(self, 0xFFFFFF, 200);
if (typeof isNewBossPlusMode !== 'undefined' && !isNewBossPlusMode) {
if (self.health <= self.maxHealth / 2 && self.phase === 1) {
console.log("DEBUG: Boss entering phase 2.");
self.phase = 2;
self.speed += 2;
self.attackSpeedMultiplier = (self.attackSpeedMultiplier || 1) * 0.8;
tween(self, {
tint: 0xFF3300
}, {
duration: 1000,
easing: tween.easeInOut
});
}
}
if (self.health <= 0) {
console.log("DEBUG: Boss health <= 0, calling self.die().");
self.die();
}
};
// NOWA METODA DO CZYSZCZENIA ATAKÓW
self.clearAllAttacks = function () {
console.log("DEBUG: Boss.clearAllAttacks CALLED. Clearing " + self.attacks.length + " attacks.");
var attacksToClear = self.attacks.slice(); // Iteruj po kopii
attacksToClear.forEach(function (attack) {
if (attack.visual && !attack.visual.destroyed) {
if (attack.visual.parent) {
attack.visual.parent.removeChild(attack.visual); // Usuń z rodzica (np. 'game')
}
attack.visual.destroy();
}
});
self.attacks = []; // Wyczyść tablicę ataków bossa
console.log("DEBUG: Boss.clearAllAttacks FINISHED. Attacks remaining: " + self.attacks.length);
};
self.die = function () {
var currentGameState = typeof gameState !== 'undefined' ? gameState.currentState : "gameState undefined";
console.log("DEBUG: Boss.die() called. Current state:", currentGameState, "Is boss already dead?", self.dead, "Is NewBossPlusMode?", isNewBossPlusMode);
if (self.dead && currentGameState !== "game") {
console.log("DEBUG: Boss.die() - Already dead and not in 'game' state. Attempting cleanup again just in case.");
self.clearAllAttacks("Die - Already dead, not in game state");
return;
}
if (self.dead) {
console.log("DEBUG: Boss.die() - Already dead. Exiting.");
return;
}
self.dead = true;
console.log("DEBUG: Boss.die() - Boss marked as dead. Clearing attacks.");
self.clearAllAttacks("Die - Normal death sequence");
// Dźwięk zwycięstwa tylko w trybie standardowym
if (typeof isNewBossPlusMode !== 'undefined' && !isNewBossPlusMode) {
LK.getSound('victory').play();
}
if (typeof gameState !== 'undefined' && gameState.currentState === "game") {
console.log("DEBUG: Boss.die - In 'game' state.");
// ZMIANA TUTAJ: Boss+ będzie miał animację śmierci i przejdzie do Grill Menu
// Niezależnie od tego, czy to Boss+ czy standardowy, wykonaj animację śmierci
console.log("DEBUG: Boss.die - Starting death animation tween for boss (standard or Boss+).");
tween(self, {
alpha: 0,
scaleX: self.scaleX * 1.2,
scaleY: self.scaleY * 1.2
}, {
duration: 2000,
easing: tween.easeOut,
onFinish: function onFinish() {
console.log("DEBUG: Boss.die tween onFinish reached.");
// Zwiększ licznik pokonanych bossów (możesz chcieć osobny licznik dla Boss+)
storage.bossesDefeated = (parseInt(storage.bossesDefeated, 10) || 0) + 1;
console.log("DEBUG: Boss.die onFinish - Bosses defeated:", storage.bossesDefeated);
if (typeof gameState !== 'undefined' && typeof gameState.showGrillScreen === 'function') {
// Dla Boss+ możesz przekazać specjalną flagę, jeśli chcesz inny komunikat na Grill Menu
var wasBossPlusDefeated = typeof isNewBossPlusMode !== 'undefined' && isNewBossPlusMode;
gameState.showGrillScreen(false, wasBossPlusDefeated); // (survivedBossPlusChallenge = false, defeatedBossPlus = wasBossPlusDefeated)
}
}
});
} else {
console.log("DEBUG: Boss.die - Not in 'game' state (e.g., during restart), skipping animations. Attacks should be cleared.");
}
};
self.update = function () {
if (typeof gameState !== 'undefined' && gameState.currentState !== "game" && gameState.currentState !== "rollMaster") {
if (self.rolling) {
self.rolling = false;
}
return;
}
if (self.dead) {
return;
}
self.ultimateAttackCooldownTimer++;
if (self.circleAttackActiveCooldown < self.circleAttackCooldownDuration) {
self.circleAttackActiveCooldown++;
}
if (self.attackCooldown > 30 && typeof player !== 'undefined' && player && !player.dead && !self.isCharging) {
var dx = player.x - self.x;
var dy = player.y - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
var moveSpeed = self.speed || 2;
if (distance > 150) {
var moveX = dx / distance * moveSpeed;
var moveY = dy / distance * moveSpeed;
var nextX = self.x + moveX;
var nextY = self.y + moveY;
var halfWidth = (self.width || 100) * (self.scaleX || 1) / 2;
var halfHeight = (self.height || 100) * (self.scaleY || 1) / 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));
}
}
for (var i = self.attacks.length - 1; i >= 0; i--) {
var attack = self.attacks[i];
var shouldRemove = false;
if (attack.type !== 'line_controller' && (!attack || !attack.visual || attack.visual.destroyed)) {
self.attacks.splice(i, 1);
continue;
}
if (attack.visual && typeof attack.visual.update === 'function') {
attack.visual.update();
}
if (attack.type === 'circle_orbiting') {
if (!attack.detached) {
attack.baseAngle += attack.angularSpeed;
var currentOrbAngle = attack.baseAngle + attack.angleOffset;
attack.x = attack.centerX + Math.cos(currentOrbAngle) * attack.radius;
attack.y = attack.centerY + Math.sin(currentOrbAngle) * attack.radius;
if (attack.visual) {
attack.visual.x = attack.x;
attack.visual.y = attack.y;
}
attack.detachCounter--;
if (attack.detachCounter <= 0) {
attack.detached = true;
var tangentialAngle = currentOrbAngle + Math.PI / 2;
var launchSpeed = 6;
attack.vx = Math.cos(tangentialAngle) * launchSpeed;
attack.vy = Math.sin(tangentialAngle) * launchSpeed;
}
} else {
attack.x += attack.vx;
attack.y += attack.vy;
if (attack.visual) {
attack.visual.x = attack.x;
attack.visual.y = attack.y;
}
attack.lifeTime--;
if (attack.x < -200 || attack.x > 2248 || attack.y < -200 || attack.y > 2932) {
shouldRemove = true;
}
if (attack.lifeTime <= 0) {
shouldRemove = true;
}
}
} else if (attack.type === 'ultimate_orb') {
attack.x += attack.vx;
attack.y += attack.vy;
if (attack.visual) {
attack.visual.x = attack.x;
attack.visual.y = attack.y;
}
attack.lifeTime--;
if (attack.x < -200 || attack.x > 2048 + 200 || attack.y < -200 || attack.y > 2732 + 200) {
shouldRemove = true;
}
if (attack.lifeTime <= 0) {
shouldRemove = true;
}
} else if (attack.type === 'line_controller') {
attack.x += attack.directionX * attack.speed;
attack.y += attack.directionY * attack.speed;
var currentTime = LK.getGameTime();
if (attack.currentSegmentIndex < attack.totalSegmentsToSpawn && currentTime - attack.lastSpawnTime >= attack.segmentDelay) {
self.createAttack(attack.x, attack.y, attack.segmentLifeTime, 'line');
attack.currentSegmentIndex++;
attack.lastSpawnTime = currentTime;
}
var controllerLifeTimeAfterSpawning = attack.segmentLifeTime;
if (attack.currentSegmentIndex >= attack.totalSegmentsToSpawn && LK.getGameTime() - attack.lastSpawnTime > controllerLifeTimeAfterSpawning) {
shouldRemove = true;
}
} else if (attack.attackObjectType && attack.type !== 'line_controller') {
attack.lifeTime--;
if (attack.lifeTime <= 0) {
shouldRemove = true;
}
}
if (!shouldRemove && attack.isActive && typeof player !== 'undefined' && player && !player.dead && !player.invulnerable) {
if (attack.type !== 'line_controller') {
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 * 0.8;
var colRadius = attack.collisionRadius || attack.radius || 60;
if (distance_p < playerRadius_p + colRadius) {
player.takeDamage(1);
shouldRemove = true;
}
}
}
if (shouldRemove) {
if (attack.visual && !attack.visual.destroyed) {
if (attack.visual.parent) {
attack.visual.parent.removeChild(attack.visual);
}
attack.visual.destroy();
}
self.attacks.splice(i, 1);
}
}
if (self.attackCooldown > 0) {
self.attackCooldown--;
}
if (self.attackCooldown <= 0 && !self.isCharging) {
self.startAttackPattern();
}
}; // Koniec self.update dla Bossa
// Pełne implementacje funkcji lineAttack, chargeAttack, ultimateAttack, startAttackPattern
// powinny być tutaj wklejone z Twojej poprzedniej, kompletnej wersji.
// Poniżej skrócone wersje dla kompletności struktury.
self.lineAttack = function () {
LK.getSound('bossAttack').play();
if (typeof player === 'undefined' || !player) {
return;
}
var startX = self.x;
var startY = self.y;
var targetX = player.x;
var targetY = player.y;
var dx = targetX - startX;
var dy = targetY - startY;
var distance = Math.sqrt(dx * dx + dy * dy);
var normalizedDx = 0;
var normalizedDy = 0;
if (distance > 0) {
normalizedDx = dx / distance;
normalizedDy = dy / distance;
}
var wallSpeed = typeof isNewBossPlusMode !== 'undefined' && isNewBossPlusMode ? 3 : 2;
var numberOfSegments = typeof isNewBossPlusMode !== 'undefined' && isNewBossPlusMode ? 10 : 8;
var segmentSpawnDuration = typeof isNewBossPlusMode !== 'undefined' && isNewBossPlusMode ? 2500 : 3000;
var segmentLifeTime = typeof isNewBossPlusMode !== 'undefined' && isNewBossPlusMode ? 2000 : 2500;
var delayPerSegment = segmentSpawnDuration / numberOfSegments;
var lineAttackController = {
type: 'line_controller',
currentSegmentIndex: 0,
lastSpawnTime: LK.getGameTime(),
totalSegmentsToSpawn: numberOfSegments,
segmentDelay: delayPerSegment,
segmentLifeTime: segmentLifeTime,
directionX: normalizedDx,
directionY: normalizedDy,
speed: wallSpeed,
x: startX,
y: startY,
targetX: targetX,
targetY: targetY
};
self.attacks.push(lineAttackController);
};
self.chargeAttack = function () {
LK.getSound('bossAttack').play();
if (typeof player === 'undefined' || !player) {
return;
}
if (self.bossAttackAnim) {
self.bossAttackAnim.stop();
if (self.bossAttackAnim.parent) {
self.bossAttackAnim.parent.removeChild(self.bossAttackAnim);
}
self.bossAttackAnim.destroy();
self.bossAttackAnim = null;
}
if (!self.bossGraphics || self.bossGraphics && self.bossGraphics.destroyed) {
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;
}
self.isCharging = true;
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 chargeDistance = typeof isNewBossPlusMode !== 'undefined' && isNewBossPlusMode ? 700 : 500;
var chargeDuration = typeof isNewBossPlusMode !== 'undefined' && isNewBossPlusMode ? 600 : 800;
tween(self, {
x: self.x + dx * chargeDistance,
y: self.y + dy * chargeDistance
}, {
duration: chargeDuration * (self.attackSpeedMultiplier || 1),
easing: tween.easeIn,
onFinish: function onFinish() {
self.isCharging = false;
}
});
};
self.ultimateAttack = function () {
if (self.dead || typeof gameState !== 'undefined' && gameState.currentState !== "game" || typeof isNewBossPlusMode !== 'undefined' && !isNewBossPlusMode) {
return;
}
if (self.bossAttackAnim) {
self.bossAttackAnim.stop();
if (self.bossAttackAnim.parent) {
self.bossAttackAnim.parent.removeChild(self.bossAttackAnim);
}
self.bossAttackAnim.destroy();
self.bossAttackAnim = null;
}
if (!self.bossGraphics || self.bossGraphics && self.bossGraphics.destroyed) {
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 screenWidth = 2048;
var screenHeight = 2732;
var iconWidth = 700;
var iconHeight = 700;
var startX = -iconWidth;
var middleX = screenWidth / 2;
var endX = screenWidth + iconWidth;
var targetY = 250;
var hoverAmplitudeY = 20;
var hoverDurationY = 1000;
var rotationAmplitude = 0.05;
var rotationDuration = 1500;
var moveInDuration = 400;
var hoverDurationTotal = 2000;
var moveOutDuration = 400;
var attackIcon = game.addChild(LK.getAsset('ultimateBossAttack_icon', {
anchorX: 0.5,
anchorY: 0.5,
x: startX,
y: targetY,
alpha: 0,
scaleX: 0.8,
scaleY: 0.8,
rotation: 0
}));
tween(attackIcon, {
alpha: 1,
scaleX: 1,
scaleY: 1,
x: middleX
}, {
duration: moveInDuration,
easing: tween.easeOutQuad,
onFinish: function onFinish() {
if (attackIcon.destroyed) {
return;
}
var hoverTweenY = tween(attackIcon, {
y: attackIcon.y + hoverAmplitudeY
}, {
duration: hoverDurationY / 2,
easing: tween.easeInOut,
yoyo: true,
repeat: Infinity
});
var rotationTween = tween(attackIcon, {
rotation: attackIcon.rotation + rotationAmplitude
}, {
duration: rotationDuration / 2,
easing: tween.easeInOut,
yoyo: true,
repeat: Infinity
});
LK.setTimeout(function () {
if (attackIcon.destroyed) {
if (hoverTweenY) {
hoverTweenY.stop();
}
if (rotationTween) {
rotationTween.stop();
}
return;
}
if (hoverTweenY) {
hoverTweenY.stop();
}
if (rotationTween) {
rotationTween.stop();
}
tween(attackIcon, {
y: targetY,
rotation: 0
}, {
duration: 100,
onFinish: function onFinish() {
tween(attackIcon, {
alpha: 0,
x: endX
}, {
duration: moveOutDuration,
easing: tween.easeInQuad,
onFinish: function onFinish() {
if (attackIcon && !attackIcon.destroyed) {
if (attackIcon.parent) {
attackIcon.parent.removeChild(attackIcon);
}
attackIcon.destroy();
}
}
});
}
});
}, hoverDurationTotal);
}
});
var originalOrbWidth = 300;
var originalOrbHeight = 300;
var baseOrbRadius = Math.max(originalOrbWidth, originalOrbHeight) / 2;
var growAndFadeInDuration = 6000;
var finalScale = 2.6;
var orbSpeed = 8;
var travelDurationMs = 6000;
function createAndLaunchOrb(spawnX, spawnDelay) {
LK.setTimeout(function () {
if (self.dead || typeof gameState !== 'undefined' && gameState.currentState !== "game") {
return;
}
var orbFrames = [];
for (var i = 0; i <= 7; i++) {
orbFrames.push(LK.getAsset('ultimatebossattack_orb_' + i, {
anchorX: 0.5,
anchorY: 0.5,
clone: true
}));
}
var orbAnim = new SpriteAnimation({
frames: orbFrames,
frameDuration: 70,
loop: true,
x: spawnX,
y: 300,
anchorX: 0.5,
anchorY: 0.5,
scaleX: 0.1,
scaleY: 0.1,
alpha: 0
});
game.addChild(orbAnim);
tween(orbAnim, {
scaleX: finalScale,
scaleY: finalScale,
alpha: 1
}, {
duration: growAndFadeInDuration,
easing: tween.easeOutQuad,
onFinish: function onFinish() {
if (orbAnim.destroyed) {
return;
}
var targetXPlayer = typeof player !== 'undefined' && player ? player.x : screenWidth / 2;
var targetYPlayer = typeof player !== 'undefined' && player ? player.y : screenHeight / 2;
var actualOrbRadius = baseOrbRadius * finalScale;
var dxOrb = targetXPlayer - orbAnim.x;
var dyOrb = targetYPlayer - orbAnim.y;
var distOrb = Math.sqrt(dxOrb * dxOrb + dyOrb * dyOrb);
var vxOrb = distOrb > 0 ? dxOrb / distOrb * orbSpeed : 0;
var vyOrb = distOrb > 0 ? dyOrb / distOrb * orbSpeed : 0;
self.attacks.push({
type: 'ultimate_orb',
visual: orbAnim,
vx: vxOrb,
vy: vyOrb,
radius: actualOrbRadius,
lifeTime: travelDurationMs / (1000 / 60),
x: orbAnim.x,
y: orbAnim.y,
isActive: true
});
}
});
}, spawnDelay);
}
var spawnDelayBetweenOrbs = 1000;
createAndLaunchOrb(screenWidth / 2, 0);
createAndLaunchOrb(screenWidth / 2 - 600, spawnDelayBetweenOrbs);
createAndLaunchOrb(screenWidth / 2 + 600, spawnDelayBetweenOrbs * 2);
};
self.startAttackPattern = function () {
self.attackCooldown = typeof isNewBossPlusMode !== 'undefined' && isNewBossPlusMode ? 150 : 190;
var attackType;
var availableAttacks = [];
if (self.circleAttackActiveCooldown >= self.circleAttackCooldownDuration) {
availableAttacks.push(0);
}
availableAttacks.push(1);
availableAttacks.push(2);
var isUltimateAttackReady = self.ultimateAttackCooldownTimer >= self.nextUltimateAttackAvailableTime;
if (typeof isNewBossPlusMode !== 'undefined' && isNewBossPlusMode && isUltimateAttackReady) {
availableAttacks.push(3);
}
if (availableAttacks.length === 0) {
return;
}
attackType = availableAttacks[Math.floor(Math.random() * availableAttacks.length)];
self.playBossAttackAnim(attackType);
if (attackType === 0) {
self.circleAttack();
self.circleAttackActiveCooldown = 0;
} else if (attackType === 1) {
self.lineAttack();
} else if (attackType === 2) {
self.chargeAttack();
} else if (attackType === 3 && typeof isNewBossPlusMode !== 'undefined' && isNewBossPlusMode) {
self.ultimateAttack();
self.ultimateAttackCooldownTimer = 0;
self.nextUltimateAttackAvailableTime = self.ultimateAttackMinInterval + Math.floor(Math.random() * (self.ultimateAttackMaxInterval - self.ultimateAttackMinInterval + 1));
self.attackCooldown = 6 * 60;
}
};
// Inicjalizacja zmiennych bossa
self.nextUltimateAttackAvailableTime = self.ultimateAttackMinInterval + Math.floor(Math.random() * (self.ultimateAttackMaxInterval - self.ultimateAttackMinInterval + 1));
self.health = typeof isNewBossPlusMode !== 'undefined' && isNewBossPlusMode ? 300 : 150;
self.maxHealth = self.health;
self.speed = typeof isNewBossPlusMode !== 'undefined' && isNewBossPlusMode ? 3 : 2;
self.attackSpeedMultiplier = 1;
self.phase = 1;
self.attacks = [];
self.attackCooldown = 120; // Początkowy cooldown przed pierwszym atakiem
self.dead = false;
return self;
});
var MinibossCC = Container.expand(function (options) {
var self = Container.call(this);
options = options || {};
self.maxHp = options.maxHp || 200;
self.hp = self.maxHp;
self.speed = options.speed || 0;
self.isDead = false;
self.attackPattern = ['explosiveProjectile', 'laserWall'];
self.currentAttackIndex = 0;
self.attackCooldowns = {
'explosiveProjectile': 8 * 60,
'laserWall': 5 * 60
};
self.attackCooldown = 120;
self.currentAttackName = '';
self.isCurrentlyPlayingAttackAnim = false;
self.activeSkillAnimationInstance = null;
self.isTeleporting = false;
self.teleportCooldownTimer = 0;
self.teleportIntervalMin = 9 * 60;
self.teleportIntervalMax = 16 * 60;
self.nextTeleportTime = self.teleportIntervalMin + Math.floor(Math.random() * (self.teleportIntervalMax - self.teleportIntervalMin + 1));
self.activeClones = [];
self.maxClones = 2;
self.canTeleportWithClones = true;
self.teleportCloneCooldownDuration = 10 * 60;
self.isWaitingForClonesToDespawn = false;
self.cloneTeleportTimeoutId = null;
self.creepSpeed = 0.9;
self.creepDirectionX = 0;
self.creepDirectionY = 0;
self.creepDirectionChangeIntervalMin = 3 * 60;
self.creepDirectionChangeIntervalMax = 7 * 60;
self.creepDirectionChangeTimer = 0;
self.actualWidth = 120;
self.actualHeight = 120;
try {
self.graphics = self.attachAsset('miniboss_cc_asset', {
anchorX: 0.5,
anchorY: 0.5
});
} catch (e) {
console.warn("Nie udało się załadować 'miniboss_cc_asset', używam awaryjnego Shape:", e);
self.graphics = new Shape({
width: 120,
height: 120,
color: 0xFF8C00,
shape: 'box'
});
self.addChild(self.graphics);
}
if (self.graphics && typeof self.graphics.width !== 'undefined') {
self.width = self.graphics.width * (self.graphics.scaleX || 1);
self.height = self.graphics.height * (self.graphics.scaleY || 1);
} else {
self.width = 120;
self.height = 120;
}
if (self.graphics && typeof self.graphics.width === 'number' && typeof self.graphics.height === 'number') {
self.actualWidth = self.graphics.width * (self.graphics.scaleX || 1);
self.actualHeight = self.graphics.height * (self.graphics.scaleY || 1);
} else {
self.actualWidth = self.width;
self.actualHeight = self.height;
}
self.pickNewCreepDirection = function () {
var angle = Math.random() * 2 * Math.PI;
self.creepDirectionX = Math.cos(angle);
self.creepDirectionY = Math.sin(angle);
self.creepDirectionChangeTimer = self.creepDirectionIntervalMin + Math.floor(Math.random() * (self.creepDirectionChangeIntervalMax - self.creepDirectionIntervalMin + 1));
};
self.createMinibossSkillAnim = function () {
var frames = [];
var frameBaseName = 'bossSkillAnim0';
var totalSkillFrames = 9;
for (var i = 0; i < totalSkillFrames; i++) {
var assetName = frameBaseName + (i === 0 ? '' : String(i));
try {
frames.push(LK.getAsset(assetName, {
anchorX: 0.5,
anchorY: 0.5,
clone: true
}));
} catch (e) {
console.error("Błąd ładowania klatki animacji Klona: " + assetName, e);
if (i > 0 && frames.length > 0) {
frames.push(frames[0]);
} else {
var placeholderFrame = new Shape({
width: 120,
height: 120,
color: 0xFF00FF,
shape: 'box'
});
placeholderFrame.anchor.set(0.5, 0.5);
frames.push(placeholderFrame);
}
}
}
for (var i = totalSkillFrames - 2; i >= 1; i--) {
var assetName = frameBaseName + (i === 0 ? '' : String(i));
try {
frames.push(LK.getAsset(assetName, {
anchorX: 0.5,
anchorY: 0.5,
clone: true
}));
} catch (e) {
console.error("Błąd ładowania klatki powrotnej animacji Klona: " + assetName, e);
if (frames.length > 0) {
frames.push(frames[0]);
}
}
}
var skillAnimationObject = new SpriteAnimation({
frames: frames,
frameDuration: 120,
loop: false,
anchorX: 0.5,
anchorY: 0.5,
x: 0,
y: 0
});
return skillAnimationObject;
};
self.playMinibossSkillAnim = function (onAnimationCompleteCallback) {
if (self.isDead || self.isTeleporting) {
self.isCurrentlyPlayingAttackAnim = false;
if (typeof onAnimationCompleteCallback === 'function') {
onAnimationCompleteCallback();
}
return;
}
if (self.activeSkillAnimationInstance && self.activeSkillAnimationInstance.parent) {
self.activeSkillAnimationInstance.stop();
self.removeChild(self.activeSkillAnimationInstance);
self.activeSkillAnimationInstance.destroy();
self.activeSkillAnimationInstance = null;
}
if (self.graphics && !self.graphics.destroyed) {
self.graphics.visible = false;
}
var newAnimation = self.createMinibossSkillAnim();
self.activeSkillAnimationInstance = self.addChild(newAnimation);
self.activeSkillAnimationInstance.currentFrameIndex = 0;
self.activeSkillAnimationInstance.animationTimer = 0;
self.activeSkillAnimationInstance.framesArray = newAnimation.frames;
self.activeSkillAnimationInstance.singleFrameDuration = newAnimation.frameDuration || 120;
self.activeSkillAnimationInstance.playing = true;
self.activeSkillAnimationInstance.removeChildren();
if (self.activeSkillAnimationInstance.framesArray && self.activeSkillAnimationInstance.framesArray.length > 0) {
self.activeSkillAnimationInstance.addChild(self.activeSkillAnimationInstance.framesArray[0]);
} else {
self.isCurrentlyPlayingAttackAnim = false;
if (self.graphics && !self.graphics.destroyed) {
self.graphics.visible = true;
}
if (typeof onAnimationCompleteCallback === 'function') {
onAnimationCompleteCallback();
}
if (self.activeSkillAnimationInstance) {
if (self.activeSkillAnimationInstance.parent) {
self.removeChild(self.activeSkillAnimationInstance);
}
self.activeSkillAnimationInstance.destroy();
self.activeSkillAnimationInstance = null;
}
return;
}
self.activeSkillAnimationInstance.update = function () {
if (!this.playing || !this.framesArray || this.framesArray.length === 0) {
return;
}
this.animationTimer++;
if (this.animationTimer * (1000 / 60) >= this.singleFrameDuration) {
this.animationTimer = 0;
this.currentFrameIndex++;
if (this.currentFrameIndex >= this.framesArray.length) {
this.playing = false;
if (self.graphics && !self.graphics.destroyed) {
self.graphics.visible = true;
}
if (this.parent) {
this.parent.removeChild(this);
}
this.destroy();
if (self.activeSkillAnimationInstance === this) {
self.activeSkillAnimationInstance = null;
}
if (typeof onAnimationCompleteCallback === 'function') {
onAnimationCompleteCallback();
} else {
self.isCurrentlyPlayingAttackAnim = false;
}
} else {
this.removeChildren();
if (this.framesArray[this.currentFrameIndex]) {
this.addChild(this.framesArray[this.currentFrameIndex]);
}
}
}
};
};
self.update = function () {
if (self.isDead || gameState.currentState !== "cursedCrystal") {
return;
}
if (self.isTeleporting || self.activeSkillAnimationInstance) {} else {
self.creepDirectionChangeTimer--;
if (self.creepDirectionChangeTimer <= 0) {
self.pickNewCreepDirection();
}
var nextX = self.x + self.creepDirectionX * self.creepSpeed;
var nextY = self.y + self.creepDirectionY * self.creepSpeed;
var halfWidth = self.actualWidth / 2;
var halfHeight = self.actualHeight / 2;
var arenaMinX = 50 + halfWidth;
var arenaMaxX = 2048 - 50 - halfWidth;
var arenaMinY = 50 + halfHeight;
var arenaMaxY = 2732 - 50 - halfHeight;
if (nextX >= arenaMinX && nextX <= arenaMaxX) {
self.x = nextX;
} else {
self.creepDirectionX *= -1;
self.creepDirectionChangeTimer = self.creepDirectionIntervalMin;
}
if (nextY >= arenaMinY && nextY <= arenaMaxY) {
self.y = nextY;
} else {
self.creepDirectionY *= -1;
self.creepDirectionChangeTimer = self.creepDirectionIntervalMin;
}
}
if (self.canTeleportWithClones && !self.isTeleporting) {
self.teleportCooldownTimer++;
if (self.teleportCooldownTimer >= self.nextTeleportTime) {
self.teleport();
return;
}
}
if (!self.isTeleporting) {
if (self.attackCooldown > 0) {
self.attackCooldown--;
} else if (player && !player.dead && !self.isCurrentlyPlayingAttackAnim) {
self.isCurrentlyPlayingAttackAnim = true;
self.performAttack();
}
}
};
self.performAttack = function () {
if (!player || player.dead || self.isTeleporting) {
self.isCurrentlyPlayingAttackAnim = false;
return;
}
var executeAttackLogic = function executeAttackLogic() {
if (self.isDead) {
self.isCurrentlyPlayingAttackAnim = false;
return;
}
self.currentAttackName = self.attackPattern[self.currentAttackIndex];
if (self.currentAttackName === 'explosiveProjectile') {
var createProjectileWithDelay = function createProjectileWithDelay(delayMs) {
LK.setTimeout(function () {
if (self.isDead || !player || player.dead) {
return;
}
var projectile = new MinibossExplosiveProjectile({
x: self.x,
y: self.y,
target: player,
speed: 7,
explosionRadius: 150
});
game.addChild(projectile);
if (gameState.cursedCrystalActiveProjectiles === undefined) {
gameState.cursedCrystalActiveProjectiles = [];
}
gameState.cursedCrystalActiveProjectiles.push(projectile);
console.log("MinibossCC: Wystrzelono pocisk eksplozywny (opóźnienie: " + delayMs + "ms)");
}, delayMs);
};
createProjectileWithDelay(0); // Pierwszy pocisk natychmiast
if (gameState.minibossEnhancedProjectile) {
// Załóżmy, że ta flaga istnieje w gameState
console.log("MinibossCC (Ulepszony): Wystrzeliwuje drugi pocisk!");
createProjectileWithDelay(2000); // Drugi pocisk po 2000ms (2 sekundy)
}
} else if (self.currentAttackName === 'laserWall') {
var numTotalSegments = 7;
var middleSegmentIndex = Math.floor(numTotalSegments / 2);
var scytheWidth = 160;
var scytheHeight = 160;
var spacingBetweenSegments = 20;
var totalWallEffectiveHeight = numTotalSegments * scytheHeight + (numTotalSegments - 1) * spacingBetweenSegments;
var wallPivotX = self.x;
var wallPivotY = self.y;
var warningDuration = 1500;
var activeDurationStandard = 4000; // Dla 2 obrotów
var rotationSpeedStandard = 2 * Math.PI / 120; // 1 obrót na 120 klatek (2s)
var activeDurationEnhanced = 4500; // Czas na 3 obroty (3 * 1.5s)
var rotationSpeedEnhanced = 2 * Math.PI / 90; // 1 obrót na 90 klatek (1.5s)
var currentActiveDuration = activeDurationStandard;
var currentRotationSpeed = rotationSpeedStandard;
if (gameState.minibossEnhancedLaserWall) {
// Zakładamy dostęp do gameState
console.log("Miniboss: Ulepszony LaserWall - szybszy i 3 obroty!");
currentActiveDuration = activeDurationEnhanced;
currentRotationSpeed = rotationSpeedEnhanced;
}
var activeDuration = 4000;
var rotationSpeed = 2 * Math.PI / 120;
var laserWallInstance = {
pivotX: wallPivotX,
pivotY: wallPivotY,
segments: [],
currentAngle: 0,
rotationSpeed: currentRotationSpeed,
warningTimer: warningDuration,
activeTimer: currentActiveDuration,
isDead: false
};
var initialSegmentOffsetY = -(totalWallEffectiveHeight / 2) + scytheHeight / 2;
var frameDurationAction = 100;
var frameDurationLoop = 100;
for (var i = 0; i < numTotalSegments; i++) {
var segmentOffsetY = initialSegmentOffsetY + i * (scytheHeight + spacingBetweenSegments);
var spawnPosX = wallPivotX;
var spawnPosY = wallPivotY + segmentOffsetY;
if (i === middleSegmentIndex) {
laserWallInstance.segments.push({
isPlaceholder: true,
initialOffsetY: segmentOffsetY,
width: scytheWidth,
height: scytheHeight
});
continue;
}
var scytheAppearVisual = null;
var FADE_IN_DURATION = 500; // Czas trwania fade in w milisekundach - dostosuj!
try {
scytheAppearVisual = LK.getAsset('scythe_appear_0', {
anchorX: 0.5,
anchorY: 0.5,
clone: true,
alpha: 0 // Zacznij od przezroczystości 0
});
scytheAppearVisual.x = spawnPosX;
scytheAppearVisual.y = spawnPosY;
game.addChild(scytheAppearVisual);
tween(scytheAppearVisual, {
alpha: 1
}, {
duration: FADE_IN_DURATION,
easing: tween.easeInQuad
});
} catch (e) {
console.error("Błąd ładowania assetu scythe_appear_0: ", e);
scytheAppearVisual = new Shape({
width: scytheWidth,
height: scytheHeight,
color: 0xCCCCCC,
shape: 'box',
alpha: 0 // Zacznij od przezroczystości 0
});
scytheAppearVisual.anchor.set(0.5, 0.5);
scytheAppearVisual.x = spawnPosX;
scytheAppearVisual.y = spawnPosY;
game.addChild(scytheAppearVisual);
tween(scytheAppearVisual, {
alpha: 1
}, {
duration: FADE_IN_DURATION,
easing: tween.easeInQuad
});
}
laserWallInstance.segments.push({
isPlaceholder: false,
initialOffsetX: 0,
initialOffsetY: segmentOffsetY,
width: scytheWidth,
height: scytheHeight,
visual: scytheAppearVisual,
currentX: spawnPosX,
currentY: spawnPosY,
animationPhase: 'appearing_static'
});
}
LK.setTimeout(function () {
if (laserWallInstance.isDead) {
return;
}
laserWallInstance.segments.forEach(function (segment) {
if (segment.isPlaceholder || segment.animationPhase !== 'appearing_static') {
return;
}
if (segment.visual && segment.visual.parent) {
segment.visual.parent.removeChild(segment.visual);
segment.visual.destroy();
}
var actionFrames = [];
var actionAssetNames = ['scythe_action_0', 'scythe_action_1', 'scythe_action_2', 'scythe_action_3', 'scythe_action_4', 'scythe_action_5'];
try {
actionAssetNames.forEach(function (name) {
actionFrames.push(LK.getAsset(name, {
anchorX: 0.5,
anchorY: 0.5,
clone: true
}));
});
} catch (e) {
console.error("Błąd ładowania klatek akcji kosy: ", e);
}
if (actionFrames.length === 0) {
console.error("Brak klatek dla actionScytheAnim segmentu", segment);
segment.visual = new Shape({
width: scytheWidth,
height: scytheHeight,
color: 0xFF0000,
shape: 'box'
});
segment.visual.anchor.set(0.5, 0.5);
segment.visual.x = segment.currentX;
segment.visual.y = segment.currentY;
game.addChild(segment.visual);
segment.animationPhase = 'error_action';
return;
}
var actionScytheAnim = new SpriteAnimation({
frames: actionFrames,
frameDuration: frameDurationAction,
loop: false,
x: segment.currentX,
y: segment.currentY,
anchorX: 0.5,
anchorY: 0.5
});
game.addChild(actionScytheAnim);
segment.visual = actionScytheAnim;
segment.animationPhase = 'action_once';
actionScytheAnim.play();
actionScytheAnim.onComplete = function () {
if (segment.isPlaceholder || segment.animationPhase !== 'action_once' || laserWallInstance && laserWallInstance.isDead) {
return;
}
if (segment.visual && segment.visual.parent) {
segment.visual.parent.removeChild(segment.visual);
segment.visual.destroy();
}
var loopFrames = [];
var loopAssetIndices = [2, 3, 4, 5];
var loopAssetNames = loopAssetIndices.map(function (idx) {
return idx < actionAssetNames.length ? actionAssetNames[idx] : null;
}).filter(function (name) {
return name !== null;
});
try {
loopAssetNames.forEach(function (name) {
loopFrames.push(LK.getAsset(name, {
anchorX: 0.5,
anchorY: 0.5,
clone: true
}));
});
} catch (e) {
console.error("Błąd ładowania klatek pętli kosy: ", e);
}
if (loopFrames.length === 0) {
console.error("Brak klatek dla loopingScytheAnim segmentu", segment);
segment.visual = new Shape({
width: scytheWidth,
height: scytheHeight,
color: 0xFF0000,
shape: 'box'
});
segment.visual.anchor.set(0.5, 0.5);
segment.visual.x = segment.currentX;
segment.visual.y = segment.currentY;
game.addChild(segment.visual);
segment.animationPhase = 'error_loop';
return;
}
var loopingScytheAnim = new SpriteAnimation({
frames: loopFrames,
frameDuration: frameDurationLoop,
loop: true,
x: segment.currentX,
y: segment.currentY,
anchorX: 0.5,
anchorY: 0.5
});
game.addChild(loopingScytheAnim);
segment.visual = loopingScytheAnim;
segment.animationPhase = 'looping_cut';
loopingScytheAnim.play();
};
});
}, warningDuration);
if (gameState.cursedCrystalActiveLaserWalls === undefined) {
gameState.cursedCrystalActiveLaserWalls = [];
}
gameState.cursedCrystalActiveLaserWalls.push(laserWallInstance);
}
self.currentAttackIndex = (self.currentAttackIndex + 1) % self.attackPattern.length;
self.attackCooldown = self.attackCooldowns[self.currentAttackName] || 180;
self.isCurrentlyPlayingAttackAnim = false;
};
self.playMinibossSkillAnim(executeAttackLogic);
};
self.teleport = function () {
if (self.isDead || self.isTeleporting) {
console.log("MinibossCC: Próba teleportacji, ale już martwy lub teleportuje. Dead: " + self.isDead + ", Teleporting: " + self.isTeleporting);
return;
}
if (!self.canTeleportWithClones) {
console.log("MinibossCC: Próba teleportacji z klonami, ale jest na cooldownie po klonach lub czeka na klony.");
return;
}
self.canTeleportWithClones = false;
self.isWaitingForClonesToDespawn = true;
console.log("MinibossCC: TELEPORT START. Pozycja: X=" + self.x.toFixed(0) + ", Y=" + self.y.toFixed(0) + ", Alpha: " + self.alpha);
self.isTeleporting = true;
self.attackCooldown = 120 + Math.floor(Math.random() * 60);
tween(self, {
alpha: 0
}, {
duration: 600,
easing: tween.easeInQuad,
onFinish: function onFinish() {
if (self.isDead) {
self.alpha = 1;
self.isTeleporting = false;
self.isWaitingForClonesToDespawn = false;
self.canTeleportWithClones = true;
console.log("MinibossCC: Teleport przerwany, boss martwy w trakcie fade-out.");
return;
}
var halfWidth = self.actualWidth / 2;
var halfHeight = self.actualHeight / 2;
var arenaMinX = 50 + halfWidth;
var arenaMaxX = 2048 - 50 - halfWidth;
var arenaMinY = 50 + halfHeight;
var arenaMaxY = 2732 - 50 - halfHeight;
var newX, newY, distanceToPlayer;
var attempts = 0;
var minDistanceToPlayer = 300;
do {
newX = arenaMinX + Math.random() * (arenaMaxX - arenaMinX);
newY = arenaMinY + Math.random() * (arenaMaxY - arenaMinY);
if (typeof player !== 'undefined' && player && !player.dead) {
distanceToPlayer = Math.sqrt(Math.pow(newX - player.x, 2) + Math.pow(newY - player.y, 2));
} else {
distanceToPlayer = minDistanceToPlayer + 1;
}
attempts++;
} while (distanceToPlayer < minDistanceToPlayer && attempts < 10);
if (attempts >= 10 && distanceToPlayer < minDistanceToPlayer) {
console.warn("MinibossCC: Nie udało się znaleźć pozycji wystarczająco daleko od gracza po 10 próbach.");
}
self.x = newX;
self.y = newY;
self.pickNewCreepDirection();
var clonesActuallySpawned = 0;
var minClonesToSpawn = 1;
var maxClonesToSpawn = 3; // Domyślnie 1-2 klony
// Użyj jednej flagi, np. gameState.minibossIsEnhanced, lub sprawdź jedną z istniejących
// Zakładam, że masz flagę np. gameState.minibossEnhancedProjectile lub stworzysz ogólną gameState.minibossIsEnhanced
if (gameState.minibossEnhancedProjectile) {
// Zmień ten warunek na swoją flagę ulepszenia bossa
console.log("MinibossCC (Ulepszony): Tworzy więcej klonów przy teleportacji!");
minClonesToSpawn = 3;
maxClonesToSpawn = 5;
}
var clonesToAttemptSpawn = minClonesToSpawn + Math.floor(Math.random() * (maxClonesToSpawn - minClonesToSpawn + 1));
console.log("MinibossCC będzie próbował stworzyć klonów:", clonesToAttemptSpawn);
for (var i = 0; i < clonesToAttemptSpawn; i++) {
if (self.activeClones.length < self.maxClones) {
// Nadal respektujemy self.maxClones
var cloneSpawnAttempts = 0;
var cloneX, cloneY, distToBoss, distToPlayerClone;
var minDistanceToBoss = 200;
var minDistanceToPlayerForClone = 150;
// Logika do...while do znajdowania pozycji klona POZOSTAJE BEZ ZMIAN
do {
cloneX = arenaMinX + Math.random() * (arenaMaxX - arenaMinX);
cloneY = arenaMinY + Math.random() * (arenaMaxY - arenaMinY);
distToBoss = Math.sqrt(Math.pow(cloneX - self.x, 2) + Math.pow(cloneY - self.y, 2));
if (typeof player !== 'undefined' && player && !player.dead) {
distToPlayerClone = Math.sqrt(Math.pow(cloneX - player.x, 2) + Math.pow(cloneY - player.y, 2));
} else {
distToPlayerClone = minDistanceToPlayerForClone + 1;
}
cloneSpawnAttempts++;
} while ((distToBoss < minDistanceToBoss || distToPlayerClone < minDistanceToPlayerForClone) && cloneSpawnAttempts < 10);
var clone = new MinibossCCClone({
x: cloneX,
y: cloneY,
owner: self
});
game.addChild(clone);
self.activeClones.push(clone);
clonesActuallySpawned++;
} else {
console.log("MinibossCC: Osiągnięto maksymalną liczbę klonów (" + self.maxClones + "), nie można stworzyć więcej.");
break;
}
}
if (clonesActuallySpawned === 0 && self.isWaitingForClonesToDespawn) {
console.log("MinibossCC: No clones were spawned, starting teleport cooldown immediately.");
self.startTeleportCooldownAfterClones();
}
self.teleportCooldownTimer = 0;
self.nextTeleportTime = self.teleportIntervalMin + Math.floor(Math.random() * (self.teleportIntervalMax - self.teleportIntervalMin + 1));
tween(self, {
alpha: 1
}, {
duration: 600,
easing: tween.easeOutQuad,
onFinish: function onFinishFadeIn() {
self.isTeleporting = false;
console.log("MinibossCC: TELEPORT FADE_IN_FINISH. Następna normalna teleportacja za: " + (self.nextTeleportTime / 60).toFixed(1) + "s");
}
});
}
});
};
self.takeDamage = function (amount) {
if (self.isDead) {
return;
}
self.hp -= amount;
gameState.cursedCrystalMinibossHP = self.hp;
LK.effects.flashObject(self.graphics || self, 0xFF0000, 200);
if (self.hp <= 0) {
self.hp = 0;
gameState.cursedCrystalMinibossHP = self.hp;
self.die();
}
if (ui && ui.updateMinibossHealthCC) {
ui.updateMinibossHealthCC(self.hp, self.maxHp, true);
}
};
self.die = function () {
if (self.isDead) {
return;
}
if (self.cloneTeleportTimeoutId) {
LK.clearTimeout(self.cloneTeleportTimeoutId);
self.cloneTeleportTimeoutId = null;
}
self.isWaitingForClonesToDespawn = false;
self.canTeleportWithClones = true;
if (self.activeClones && self.activeClones.length > 0) {
console.log("MinibossCC dying, clearing " + self.activeClones.length + " active clones.");
var clonesToKill = self.activeClones.slice();
clonesToKill.forEach(function (clone) {
if (clone && !clone.isDead) {
clone.die(true);
}
});
self.activeClones = [];
}
self.isDead = true;
console.log("MinibossCC has been defeated!");
if (gameState.cursedCrystalActiveProjectiles) {
gameState.cursedCrystalActiveProjectiles.forEach(function (proj) {});
gameState.cursedCrystalActiveProjectiles = [];
}
if (gameState.cursedCrystalActiveExplosions) {
gameState.cursedCrystalActiveExplosions = [];
}
if (gameState.cursedCrystalActiveLaserWalls) {
gameState.cursedCrystalActiveLaserWalls.forEach(function (wall) {});
gameState.cursedCrystalActiveLaserWalls = [];
}
if (typeof gameState.endCursedCrystalMode === 'function') {
gameState.endCursedCrystalMode(true);
}
if (self.parent) {
self.parent.removeChild(self);
}
if (self.destroy && !self.destroyed) {
self.destroy();
}
gameState.cursedCrystalMinibossObject = null;
gameState.isMinibossActiveCC = false;
};
self.startTeleportCooldownAfterClones = function () {
if (!self.isWaitingForClonesToDespawn && self.activeClones.length > 0) {
return;
}
if (!self.isWaitingForClonesToDespawn && self.activeClones.length === 0) {
return;
}
console.log("MinibossCC: All clones despawned. Starting " + self.teleportCloneCooldownDuration / 60 + "-second cooldown for clone teleport.");
self.isWaitingForClonesToDespawn = false;
if (self.cloneTeleportTimeoutId) {
LK.clearTimeout(self.cloneTeleportTimeoutId);
}
self.cloneTeleportTimeoutId = LK.setTimeout(function () {
self.cloneTeleportTimeoutId = null;
if (!self.isDead) {
self.canTeleportWithClones = true;
self.nextTeleportTime = self.teleportCooldownTimer;
console.log("MinibossCC: Clone teleport is OFF COOLDOWN. Boss can attempt teleport. nextTeleportTime now: " + self.nextTeleportTime);
}
}, self.teleportCloneCooldownDuration * (1000 / 60));
};
self.x = options.x || 2048 / 2;
self.y = options.y || 300;
self.pickNewCreepDirection();
return self;
});
// -------- KONIEC ZAKTUALIZOWANEJ KLASY MinibossCC --------
// NOWA KLASA DLA KLONA MINIBOSSA CC
var MinibossCCClone = Container.expand(function (options) {
var self = Container.call(this);
options = options || {};
self.owner = options.owner;
self.x = options.x || 2048 / 2;
self.y = options.y || 300;
self.isDead = false;
self.lifeTimer = 30 * 60;
self.attackCooldown = 4 * 60 + Math.floor(Math.random() * (2 * 60));
self.activeSkillAnimationInstance = null;
self.isCurrentlyPlayingAttackAnim = false;
self.creepSpeed = 0.9;
self.creepDirectionX = 0;
self.creepDirectionY = 0;
self.creepDirectionChangeIntervalMin = 2 * 60;
self.creepDirectionChangeIntervalMax = 5 * 60;
self.creepDirectionChangeTimer = 0;
// --- DEFINICJA METODY pickNewCreepDirection PRZED PIERWSZYM UŻYCIEM ---
self.pickNewCreepDirection = function () {
var angle = Math.random() * 2 * Math.PI;
self.creepDirectionX = Math.cos(angle);
self.creepDirectionY = Math.sin(angle);
self.creepDirectionChangeTimer = self.creepDirectionIntervalMin + Math.floor(Math.random() * (self.creepDirectionChangeIntervalMax - self.creepDirectionIntervalMin + 1));
};
// --- PIERWSZE WYWOŁANIE METODY ---
self.pickNewCreepDirection();
try {
self.graphics = self.attachAsset('miniboss_cc_asset', {
anchorX: 0.5,
anchorY: 0.5
});
} catch (e) {
console.warn("Nie udało się załadować 'miniboss_cc_asset' dla Klona, używam Shape.", e);
self.graphics = new Shape({
width: 120,
height: 120,
color: 0xFF8C00,
shape: 'box'
});
self.addChild(self.graphics);
}
self.width = self.graphics && typeof self.graphics.width !== 'undefined' ? self.graphics.width : 120;
self.height = self.graphics && typeof self.graphics.height !== 'undefined' ? self.graphics.height : 120;
self.createMinibossSkillAnim = function () {
var frames = [];
var frameBaseName = 'bossSkillAnim0';
var totalSkillFrames = 9;
for (var i = 0; i < totalSkillFrames; i++) {
var assetName = frameBaseName + (i === 0 ? '' : String(i));
try {
frames.push(LK.getAsset(assetName, {
anchorX: 0.5,
anchorY: 0.5,
clone: true
}));
} catch (e) {
console.error("Błąd ładowania klatki animacji Klona: " + assetName, e);
if (i > 0 && frames.length > 0) {
frames.push(frames[0]);
} else {
var placeholderFrame = new Shape({
width: 120,
height: 120,
color: 0xFF00FF,
shape: 'box'
});
placeholderFrame.anchor.set(0.5, 0.5);
frames.push(placeholderFrame);
}
}
}
for (var i = totalSkillFrames - 2; i >= 1; i--) {
var assetName = frameBaseName + (i === 0 ? '' : String(i));
try {
frames.push(LK.getAsset(assetName, {
anchorX: 0.5,
anchorY: 0.5,
clone: true
}));
} catch (e) {
console.error("Błąd ładowania klatki powrotnej animacji Klona: " + assetName, e);
if (frames.length > 0) {
frames.push(frames[0]);
}
}
}
var skillAnimationObject = new SpriteAnimation({
frames: frames,
frameDuration: 120,
loop: false,
anchorX: 0.5,
anchorY: 0.5,
x: 0,
y: 0
});
return skillAnimationObject;
};
self.playMinibossSkillAnim = function (onAnimationCompleteCallback) {
if (self.isDead) {
self.isCurrentlyPlayingAttackAnim = false;
if (typeof onAnimationCompleteCallback === 'function') {
onAnimationCompleteCallback();
}
return;
}
if (self.activeSkillAnimationInstance && self.activeSkillAnimationInstance.parent) {
self.activeSkillAnimationInstance.stop();
self.removeChild(self.activeSkillAnimationInstance);
self.activeSkillAnimationInstance.destroy();
self.activeSkillAnimationInstance = null;
}
if (self.graphics && !self.graphics.destroyed) {
self.graphics.visible = false;
}
var newAnimation = self.createMinibossSkillAnim();
self.activeSkillAnimationInstance = self.addChild(newAnimation);
self.activeSkillAnimationInstance.currentFrameIndex = 0;
self.activeSkillAnimationInstance.animationTimer = 0;
self.activeSkillAnimationInstance.framesArray = newAnimation.frames;
self.activeSkillAnimationInstance.singleFrameDuration = newAnimation.frameDuration || 120;
self.activeSkillAnimationInstance.playing = true;
self.activeSkillAnimationInstance.removeChildren();
if (self.activeSkillAnimationInstance.framesArray && self.activeSkillAnimationInstance.framesArray.length > 0) {
self.activeSkillAnimationInstance.addChild(self.activeSkillAnimationInstance.framesArray[0]);
} else {
self.isCurrentlyPlayingAttackAnim = false;
if (self.graphics && !self.graphics.destroyed) {
self.graphics.visible = true;
}
if (typeof onAnimationCompleteCallback === 'function') {
onAnimationCompleteCallback();
}
if (self.activeSkillAnimationInstance) {
if (self.activeSkillAnimationInstance.parent) {
self.removeChild(self.activeSkillAnimationInstance);
}
self.activeSkillAnimationInstance.destroy();
self.activeSkillAnimationInstance = null;
}
return;
}
self.activeSkillAnimationInstance.update = function () {
if (!this.playing || !this.framesArray || this.framesArray.length === 0) {
return;
}
this.animationTimer++;
if (this.animationTimer * (1000 / 60) >= this.singleFrameDuration) {
this.animationTimer = 0;
this.currentFrameIndex++;
if (this.currentFrameIndex >= this.framesArray.length) {
this.playing = false;
if (self.graphics && !self.graphics.destroyed) {
self.graphics.visible = true;
}
if (this.parent) {
this.parent.removeChild(this);
}
this.destroy();
if (self.activeSkillAnimationInstance === this) {
self.activeSkillAnimationInstance = null;
}
if (typeof onAnimationCompleteCallback === 'function') {
onAnimationCompleteCallback();
} else {
self.isCurrentlyPlayingAttackAnim = false;
}
} else {
this.removeChildren();
if (this.framesArray[this.currentFrameIndex]) {
this.addChild(this.framesArray[this.currentFrameIndex]);
}
}
}
};
};
self.takeDamage = function (amount) {
if (self.isDead) {
return;
}
LK.effects.flashObject(self.graphics || self, 0xFFFFFF, 150);
self.die();
};
self.die = function (isTimeout) {
if (self.isDead) {
return;
}
self.isDead = true;
if (!isTimeout) {
LK.effects.flashObject(self.graphics || self, 0xFFFFFF, 150);
}
if (self.owner && self.owner.activeClones) {
var index = self.owner.activeClones.indexOf(self);
if (index > -1) {
self.owner.activeClones.splice(index, 1);
if (self.owner.activeClones.length === 0 && self.owner.isWaitingForClonesToDespawn) {
self.owner.startTeleportCooldownAfterClones();
}
}
}
var deathTween = tween(self, {
alpha: 0
}, {
duration: 800,
easing: tween.easeOutQuad,
onFinish: function onFinish() {
if (self.parent) {
self.parent.removeChild(self);
}
if (self.destroy && !self.destroyed) {
self.destroy();
}
}
});
};
self.performSimpleAttack = function () {
if (self.isDead || !player || player.dead) {
self.isCurrentlyPlayingAttackAnim = false;
return;
}
var executeCloneAttackLogic = function executeCloneAttackLogic() {
if (self.isDead) {
self.isCurrentlyPlayingAttackAnim = false;
return;
}
var projectile = new MinibossExplosiveProjectile({
x: self.x,
y: self.y,
target: player,
speed: 4,
explosionRadius: 150
});
game.addChild(projectile);
if (gameState.cursedCrystalActiveProjectiles === undefined) {
gameState.cursedCrystalActiveProjectiles = [];
}
gameState.cursedCrystalActiveProjectiles.push(projectile);
self.attackCooldown = 8 * 60 + Math.floor(Math.random() * (2 * 60));
self.isCurrentlyPlayingAttackAnim = false;
};
self.playMinibossSkillAnim(executeCloneAttackLogic);
};
self.update = function () {
if (self.isDead || gameState.currentState !== "cursedCrystal") {
return;
}
self.lifeTimer--;
if (self.lifeTimer <= 0) {
self.die(true);
return;
}
if (self.activeSkillAnimationInstance) {} else {
self.creepDirectionChangeTimer--;
if (self.creepDirectionChangeTimer <= 0) {
self.pickNewCreepDirection();
}
var nextX = self.x + self.creepDirectionX * self.creepSpeed;
var nextY = self.y + self.creepDirectionY * self.creepSpeed;
var halfWidth = (self.width || 120) / 2;
var halfHeight = (self.height || 120) / 2;
var arenaMinX = 50 + halfWidth;
var arenaMaxX = 2048 - 50 - halfWidth;
var arenaMinY = 50 + halfHeight;
var arenaMaxY = 2732 - 50 - halfHeight;
if (nextX >= arenaMinX && nextX <= arenaMaxX) {
self.x = nextX;
} else {
self.creepDirectionX *= -1;
self.creepDirectionChangeTimer = self.creepDirectionIntervalMin;
}
if (nextY >= arenaMinY && nextY <= arenaMaxY) {
self.y = nextY;
} else {
self.creepDirectionY *= -1;
self.creepDirectionChangeTimer = self.creepDirectionIntervalMin;
}
}
if (self.attackCooldown > 0) {
self.attackCooldown--;
} else if (!self.isCurrentlyPlayingAttackAnim && !self.activeSkillAnimationInstance) {
self.isCurrentlyPlayingAttackAnim = true;
self.performSimpleAttack();
}
};
self.alpha = 0;
self.scaleX = 0.5;
self.scaleY = 0.5;
tween(self, {
alpha: 1,
scaleX: 1,
scaleY: 1
}, {
duration: 500,
easing: tween.easeOutQuad
});
return self;
});
// -------- KONIEC ZAKTUALIZOWANEJ KLASY MinibossCC --------
var MinibossExplosiveProjectile = Container.expand(function (options) {
var self = Container.call(this);
options = options || {};
self.x = options.x || 0;
self.y = options.y || 0;
self.speed = options.speed || 6;
self.target = options.target;
self.lifeTimer = 240;
self.isDead = false;
self.hasExploded = false;
self.explosionRadius = options.explosionRadius || 100;
self.explosionDamage = 1;
try {
self.graphics = self.attachAsset('projectile_spinning_asset', {
// UŻYJ NOWEGO ASSETU DLA POCISKU
anchorX: 0.5,
anchorY: 0.5,
width: 140,
height: 140
});
} catch (e) {
console.warn("Nie udało się załadować 'projectile_spinning_asset', używam Shape.", e);
self.graphics = new Shape({
width: 40,
height: 40,
color: 0xFF4500,
shape: 'ellipse'
});
self.addChild(self.graphics);
}
self.width = self.graphics.width;
self.height = self.graphics.height;
self.update = function () {
if (self.isDead || self.hasExploded || !self.target || self.target.dead) {
if (!self.hasExploded && !self.isDead) {
self.isDead = true;
}
return;
}
self.lifeTimer--;
var dx = self.target.x - self.x;
var dy = self.target.y - self.y;
var distanceToTarget = Math.sqrt(dx * dx + dy * dy);
if (distanceToTarget > 0) {
var moveX = dx / distanceToTarget * self.speed;
var moveY = dy / distanceToTarget * self.speed;
self.x += moveX;
self.y += moveY;
}
if (self.graphics) {
// Dodajemy rotację pocisku
self.graphics.rotation += 0.1; // Dostosuj prędkość obrotu
}
var playerRadius = (self.target.width || 150) / 2 * 0.7;
var projectileRadius = self.width / 2;
if (distanceToTarget < playerRadius + projectileRadius) {
self.explode();
return;
}
if (self.lifeTimer <= 0) {
self.explode();
return;
}
};
self.explode = function () {
if (self.hasExploded) {
return;
}
self.hasExploded = true;
self.isDead = true;
if (self.graphics && self.graphics.parent) {
self.graphics.parent.removeChild(self.graphics);
self.graphics.destroy();
self.graphics = null;
}
var explosionFramesAssets = [];
for (var i = 0; i < 7; i++) {
try {
explosionFramesAssets.push(LK.getAsset('explosion_frame_' + i, {
anchorX: 0.5,
anchorY: 0.5,
clone: true
}));
} catch (e) {
console.error("Błąd ładowania klatki eksplozji: explosion_frame_" + i, e);
var placeholderExplosionFrame = new Shape({
width: 50,
height: 50,
color: 0xFF8C00,
shape: 'ellipse'
});
placeholderExplosionFrame.anchor.set(0.5, 0.5);
explosionFramesAssets.push(placeholderExplosionFrame);
}
}
if (explosionFramesAssets.length < 7) {
// Potrzebujemy wszystkich 7 klatek do tej logiki
console.error("Nie załadowano wystarczającej liczby klatek eksplozji.");
if (gameState && gameState.cursedCrystalActiveExplosions) {
gameState.cursedCrystalActiveExplosions.push({
x: self.x,
y: self.y,
radius: self.explosionRadius,
damage: self.explosionDamage,
durationTimer: 30,
hitPlayerThisFrame: false
});
}
return;
}
var explosionDisplay = game.addChild(new Container());
explosionDisplay.x = self.x;
explosionDisplay.y = self.y;
var currentFrameGfx = null;
var INITIAL_SCALE = 0.2;
var MAX_SCALE = self.explosionRadius * 3.0 / (explosionFramesAssets[0] ? explosionFramesAssets[0].width : 50); // ZMIENIONA LINIA (1.5 -> 3.0)
var EXP_FRAME_DURATION_MS = 70;
var LOOP_FRAME_DURATION_MS = 90;
var SHRINK_FADE_DURATION_MS = 600;
var totalDurationMs = 2000;
var expansionPhaseDuration = 7 * EXP_FRAME_DURATION_MS; // 7 klatek * 70ms = 490ms
var loopPhaseDuration = totalDurationMs - expansionPhaseDuration - SHRINK_FADE_DURATION_MS;
if (loopPhaseDuration < 0) {
// Jeśli nie ma czasu na pętlę, skróć inne fazy lub ustaw min. czas pętli
loopPhaseDuration = Math.max(0, LOOP_FRAME_DURATION_MS * 4); // np. przynajmniej jedna pełna pętla 4 klatek
SHRINK_FADE_DURATION_MS = Math.max(200, totalDurationMs - expansionPhaseDuration - loopPhaseDuration);
}
var displayFrame = function displayFrame(frameAsset, scale, alpha) {
if (currentFrameGfx && currentFrameGfx.parent) {
explosionDisplay.removeChild(currentFrameGfx);
// Nie niszczymy frameAsset, bo to klon z puli LK.getAsset
}
if (frameAsset) {
currentFrameGfx = explosionDisplay.addChild(frameAsset);
currentFrameGfx.scale.set(scale);
currentFrameGfx.alpha = alpha === undefined ? 1 : alpha;
} else {
currentFrameGfx = null;
}
};
var currentExpansionFrame = 0;
function expandAnimation() {
if (currentExpansionFrame < 7) {
var progress = currentExpansionFrame / 6.0;
var scale = INITIAL_SCALE + (MAX_SCALE - INITIAL_SCALE) * progress;
displayFrame(explosionFramesAssets[currentExpansionFrame], Math.max(INITIAL_SCALE, scale));
currentExpansionFrame++;
LK.setTimeout(expandAnimation, EXP_FRAME_DURATION_MS);
} else {
startLoopingPhase();
}
}
var loopFramesIndices = [3, 4, 5, 6]; // Klatki 3, 4, 5, 6 do pętli
var currentLoopArrayIndex = 0;
var loopEndTime;
function startLoopingPhase() {
if (loopPhaseDuration <= 0) {
startShrinkingPhase();
return;
}
loopEndTime = Date.now() + loopPhaseDuration;
currentLoopArrayIndex = 0; // Zacznij od pierwszej klatki pętli (indeks 3 globalnie)
loopAnimation();
}
function loopAnimation() {
if (Date.now() < loopEndTime && explosionDisplay && !explosionDisplay.destroyed) {
displayFrame(explosionFramesAssets[loopFramesIndices[currentLoopArrayIndex % loopFramesIndices.length]], MAX_SCALE);
currentLoopArrayIndex++;
LK.setTimeout(loopAnimation, LOOP_FRAME_DURATION_MS);
} else {
startShrinkingPhase();
}
}
function startShrinkingPhase() {
if (!currentFrameGfx || !currentFrameGfx.parent || explosionDisplay && explosionDisplay.destroyed) {
if (explosionDisplay && !explosionDisplay.destroyed) {
explosionDisplay.destroy();
}
return;
}
// Aby zmniejszać ostatnio wyświetloną klatkę (lub konkretną np. klatkę 6 jako bazę)
// Dla pewności, że mamy co skalować, możemy ponownie wyświetlić ostatnią klatkę pętli (np. klatkę 6)
// jeśli currentFrameGfx mógł zostać usunięty lub jest nieoczekiwany.
// Jeśli currentFrameGfx jest już ostatnią klatką pętli, to dobrze.
// Jeśli chcemy zawsze zmniejszać np. klatkę nr 6:
// displayFrame(explosionFramesAssets[6], MAX_SCALE);
tween(currentFrameGfx, {
scaleX: 0.01,
scaleY: 0.01,
alpha: 0
}, {
duration: SHRINK_FADE_DURATION_MS,
easing: tween.easeInQuad,
onFinish: function onFinish() {
if (explosionDisplay && !explosionDisplay.destroyed) {
explosionDisplay.destroy();
}
}
});
}
expandAnimation(); // Rozpocznij pierwszą fazę
if (gameState && gameState.cursedCrystalActiveExplosions) {
gameState.cursedCrystalActiveExplosions.push({
x: self.x,
y: self.y,
radius: self.explosionRadius,
damage: self.explosionDamage,
durationTimer: Math.round(totalDurationMs / (1000 / 60)),
// Czas trwania logicznej eksplozji = ~2 sekundy
hitPlayerThisFrame: false
});
}
};
return self;
});
// Zamykająca klamra dla Container.expand klasy Boss
// --- POCZĄTEK BLOKU DO SKOPIOWANIA ---
var Player = Container.expand(function () {
var self = Container.call(this);
if (gameState.currentState === "cursedCrystal") {
console.log("Player Update - Cursed Crystal Tick, Rolling: " + this.rolling + ", Cooldown: " + this.rollCooldown);
} else if (gameState.currentState === "game") {
console.log("Player Update - Game Tick, Rolling: " + this.rolling + ", Cooldown: " + this.rollCooldown);
}
// --- 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.hasRolledThroughMinibossCCThisRoll = false;
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) {
var timestamp = Date.now();
var currentHealthBeforeDamage = this.health;
console.log("[Player.takeDamage] Called at: " + timestamp + " | Amount: " + amount + " | Invulnerable: " + this.invulnerable + " | Dead: " + this.dead + " | Health BEFORE: " + currentHealthBeforeDamage + " | Current State: " + gameState.currentState);
if (!this.invulnerable && !this.dead) {
if (gameState.currentState === "cursedCrystal") {
gameState.cursedCrystalPlayerHealth -= amount;
gameState.cursedCrystalPlayerHealth = Math.max(0, gameState.cursedCrystalPlayerHealth);
this.health = gameState.cursedCrystalPlayerHealth;
console.log(" [CursedCrystal DMG] New Health (gameState & this.health): " + this.health + " at " + timestamp);
if (ui && ui.updateHearts) {
ui.updateHearts(gameState.cursedCrystalPlayerHealth, gameState.cursedCrystalPlayerMaxHealth);
}
} else {
this.health -= amount;
this.health = Math.max(0, this.health);
console.log(" [DMG in " + gameState.currentState + "] New Health (this.health): " + this.health + " at " + timestamp);
}
if (this.health < currentHealthBeforeDamage) {
LK.effects.flashObject(this, 0xFF0000, 200);
}
if (this.health <= 0) {
console.log(" Player health is <= 0 at " + timestamp + ". Calling this.die().");
this.die();
return;
}
this.invulnerable = true;
console.log(" Player.invulnerable SET TO TRUE at " + timestamp + ". IMMEDIATELY CHECKING: player.invulnerable is " + this.invulnerable);
if (this.postHitInvulnerabilityTimer) {
LK.clearTimeout(this.postHitInvulnerabilityTimer);
this.postHitInvulnerabilityTimer = null;
}
var playerInstance = this;
this.postHitInvulnerabilityTimer = LK.setTimeout(function () {
var endInvulnerableTimestamp = Date.now();
if (!playerInstance || playerInstance.destroyed) {
console.log(" Player invulnerability timeout: player destroyed or null at " + endInvulnerableTimestamp);
return;
}
playerInstance.invulnerable = false;
console.log(" Player.invulnerable SET TO FALSE at " + endInvulnerableTimestamp + " (after 2000ms)");
var currentVisualSprite = null;
if (playerInstance.rolling && playerInstance.children.length > 0 && playerInstance.children[0] !== playerInstance.idleAnimationSprite) {} else if (playerInstance.idleAnimationSprite && playerInstance.idleAnimationSprite.visible && playerInstance.idleAnimationSprite.children.length > 0) {
currentVisualSprite = playerInstance.idleAnimationSprite.children[0];
}
if (currentVisualSprite && currentVisualSprite.alpha !== 1) {
currentVisualSprite.alpha = 1;
}
playerInstance.postHitInvulnerabilityTimer = null;
}, 2000);
} else {
console.log("[Player.takeDamage] SKIPPED at: " + timestamp + " (Invulnerable: " + this.invulnerable + ", Dead: " + this.dead + ")");
}
};
// --- Śmierć (z poprawkami dla storage i onFinish) ---
self.die = function () {
console.log("!!!! Player.die() called! State:", gameState.currentState, "Current Health:", self.health);
if (self.dead) {
console.log("Player.die() exited - already dead.");
return;
}
self.dead = true;
try {
self.clearRollTimeouts();
} catch (e) {
console.error("!!! Błąd podczas wywoływania self.clearRollTimeouts w Player.die:", e);
}
if (gameState.currentState === "cursedCrystal") {
console.log("Player died in Cursed Crystal mode. Calling gameState.endCursedCrystalMode(false) via tween.");
tween(self, {
alpha: 0,
scaleX: self.scaleX * 1.2,
scaleY: self.scaleY * 1.2
}, {
duration: 1000,
easing: tween.easeOut,
onFinish: function onFinish() {
console.log("Player.die tween (Cursed Crystal) onFinish.");
if (typeof gameState.endCursedCrystalMode === 'function') {
gameState.endCursedCrystalMode(false);
} else {
console.error("gameState.endCursedCrystalMode is not defined!");
if (typeof gameState.showGrillScreen === 'function') {
gameState.showGrillScreen();
}
}
}
});
return;
}
var currentDeaths = parseInt(storage.totalDeaths, 10) || 0;
storage.totalDeaths = currentDeaths + 1;
console.log("Updated totalDeaths to:", 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("Updated maxHearts to:", storage.maxHearts);
tween(self, {
alpha: 0,
scaleX: self.scaleX * 1.2,
scaleY: self.scaleY * 1.2
}, {
duration: 1000,
easing: tween.easeOut,
onFinish: function onFinish() {
console.log("Player.die tween (Non-CC) onFinish. gameState.currentState:", gameState.currentState);
if (gameState.currentState === "game") {
console.log("Player.die (game mode) onFinish - Calling gameState.gameOver(true).");
if (typeof boss !== 'undefined' && boss && typeof boss.clearAllAttacks === 'function') {
boss.clearAllAttacks("Player.die -> tutorial gameOver");
}
if (typeof gameState.gameOver === 'function') {
gameOverReasonIsDeath = true;
gameState.gameOver(true);
} else {
console.error("gameState.gameOver() is not defined for 'game' mode death!");
if (typeof gameState.showGrillScreen === 'function') {
gameState.showGrillScreen();
}
}
} else if (gameState.currentState === "rollMaster" || gameState.currentState === "rollMasterGameOver") {
console.log("Player.die (RollMaster or rollMasterGameOver state) onFinish. No further action needed from Player.die, endRollMasterMode handles the rest.");
} else if (gameState.currentState === "gameOver") {
console.log("Player.die onFinish while gameState.currentState is already 'gameOver'. No further action needed from Player.die.");
}
}
});
};
// Koniec self.die
// --- Aktualizacja (Update) ---
self.update = function () {
if (gameState.currentState === "cursedCrystal") {
//console.log("Player Update Tick - CC - Health: " + self.health + ", RollCD: " + self.rollCooldown + ", Rolling: " + self.rolling + ", Invuln: " + self.invulnerable + ", InvulnFrames: " + self.invulnerabilityFrames);
}
if (gameState.currentState !== "game" && gameState.currentState !== "rollMaster" && gameState.currentState !== "cursedCrystal") {
if (self.rolling) {
self.rolling = false;
self.invulnerabilityFrames = 0;
if (self.idleAnimationSprite && !self.idleAnimationSprite.destroyed) {
self.idleAnimationSprite.visible = true;
self.idleAnimationSprite.play();
if (self.children.length > 1) {
for (var i = self.children.length - 1; i >= 0; i--) {
if (self.children[i] !== self.idleAnimationSprite && self.children[i].destroy) {
self.children[i].destroy();
}
}
}
}
}
self.clearRollTimeouts();
return;
}
if (self.dead) {
return;
}
if (self.rollCooldown > 0) {
self.rollCooldown--;
}
var currentVisualSprite = null;
if (self.rolling) {
for (var i = 0; i < self.children.length; i++) {
if (self.children[i] !== self.idleAnimationSprite) {
currentVisualSprite = self.children[i];
break;
}
}
} 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 % 10 < 5 ? 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 pWidth = currentVisualSprite ? currentVisualSprite.width * Math.abs(currentVisualSprite.scaleX || 1) : self.width;
var pHeight = currentVisualSprite ? currentVisualSprite.height * (currentVisualSprite.scaleY || 1) : self.height;
var halfWidth = pWidth / 2;
var halfHeight = pHeight / 2;
var minXBoundary = halfWidth;
var maxXBoundary = 2048 - halfWidth;
var minYBoundary = halfHeight;
var maxYBoundary = 2732 - halfHeight;
nextX = Math.max(minXBoundary, Math.min(nextX, maxXBoundary));
nextY = Math.max(minYBoundary, Math.min(nextY, maxYBoundary));
if (gameState.currentState === "cursedCrystal" && typeof crystalCoreObject !== 'undefined' && crystalCoreObject && !crystalCoreObject.destroyed) {
var crystalCX = crystalCoreObject.x;
var crystalCY = crystalCoreObject.y;
var crystalRadius = crystalCoreObject.collisionRadius || 50;
var distToCrystalHorizontal = Math.abs(nextX - crystalCX);
var distToCrystalVertical = Math.abs(nextY - crystalCY);
if (distToCrystalHorizontal < halfWidth + crystalRadius && distToCrystalVertical < halfHeight + crystalRadius) {
if (self.x < crystalCX && nextX > self.x) {
nextX = crystalCX - crystalRadius - halfWidth - 1;
} else if (self.x > crystalCX && nextX < self.x) {
nextX = crystalCX + crystalRadius + halfWidth + 1;
}
if (self.y < crystalCY && nextY > self.y) {
nextY = crystalCY - crystalRadius - halfHeight - 1;
} else if (self.y > crystalCY && nextY < self.y) {
nextY = crystalCY + crystalRadius + halfHeight + 1;
}
}
}
self.x = nextX;
self.y = nextY;
if (gameState.currentState === "game" && 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 = pWidth / 2 * 0.8;
var hitboxMultiplier = 1.2;
var bossCollisionRadius = (boss.width || 180) * (boss.scaleX || 1) / 2 * hitboxMultiplier;
if (distance_b < playerCollisionRadius + bossCollisionRadius) {
boss.takeDamage(10);
self.hasRolledThroughBossThisRoll = true;
LK.effects.flashObject(boss, 0x00FF00, 200);
}
}
if (gameState.currentState === "cursedCrystal" && gameState.isMinibossActiveCC === true && gameState.cursedCrystalMinibossObject && !gameState.cursedCrystalMinibossObject.isDead && !self.hasRolledThroughMinibossCCThisRoll) {
var minibossCC = gameState.cursedCrystalMinibossObject;
var dx_mcc = self.x - minibossCC.x;
var dy_mcc = self.y - minibossCC.y;
var distance_mcc = Math.sqrt(dx_mcc * dx_mcc + dy_mcc * dy_mcc);
var playerRollRadius = pWidth / 2 * 0.8;
var minibossCCRadius = (minibossCC.width || 120) / 2 * 0.9;
if (distance_mcc < playerRollRadius + minibossCCRadius) {
minibossCC.takeDamage(10);
self.hasRolledThroughMinibossCCThisRoll = true;
console.log("Player rolled through Miniboss CC and dealt damage!");
}
}
} 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;
}
if (gameState.isInputActive && (gameState.currentState === "game" || gameState.currentState === "rollMaster" || gameState.currentState === "cursedCrystal") && 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);
var moveSpeed = 3;
if (distance_m > moveSpeed) {
var normalizedX = dx_m / distance_m;
var normalizedY = dy_m / distance_m;
var nextPlayerX = self.x + normalizedX * moveSpeed;
var nextPlayerY = self.y + normalizedY * moveSpeed;
var pWidthIdle = self.idleAnimationSprite.children[0] ? self.idleAnimationSprite.children[0].width * (self.idleAnimationSprite.scaleX || 1) : self.width;
var pHeightIdle = self.idleAnimationSprite.children[0] ? self.idleAnimationSprite.children[0].height * (self.idleAnimationSprite.scaleY || 1) : self.height;
var halfWidthIdle = pWidthIdle / 2;
var halfHeightIdle = pHeightIdle / 2;
var minXBoundaryIdle = halfWidthIdle;
var maxXBoundaryIdle = 2048 - halfWidthIdle;
var minYBoundaryIdle = halfHeightIdle;
var maxYBoundaryIdle = 2732 - halfHeightIdle;
nextPlayerX = Math.max(minXBoundaryIdle, Math.min(nextPlayerX, maxXBoundaryIdle));
nextPlayerY = Math.max(minYBoundaryIdle, Math.min(nextPlayerY, maxYBoundaryIdle));
if (gameState.currentState === "cursedCrystal" && typeof crystalCoreObject !== 'undefined' && crystalCoreObject && !crystalCoreObject.destroyed) {
var crystalCXIdle = crystalCoreObject.x;
var crystalCYIdle = crystalCoreObject.y;
var crystalRadiusIdle = crystalCoreObject.collisionRadius || 50;
var distToCrystalHorizontalIdle = Math.abs(nextPlayerX - crystalCXIdle);
var distToCrystalVerticalIdle = Math.abs(nextPlayerY - crystalCYIdle);
if (distToCrystalHorizontalIdle < halfWidthIdle + crystalRadiusIdle && distToCrystalVerticalIdle < halfHeightIdle + crystalRadiusIdle) {} else {
self.x = nextPlayerX;
self.y = nextPlayerY;
}
} else {
self.x = nextPlayerX;
self.y = nextPlayerY;
}
if (self.idleAnimationSprite && !self.idleAnimationSprite.destroyed) {
if (normalizedX > 0.1) {
self.idleAnimationSprite.scaleX = 1;
} else if (normalizedX < -0.1) {
self.idleAnimationSprite.scaleX = -1;
}
}
}
}
}
if (self.idleAnimationSprite && self.idleAnimationSprite.visible && !self.idleAnimationSprite.destroyed && typeof self.idleAnimationSprite.update === 'function') {
if (gameState.currentState === "cursedCrystal") {}
self.idleAnimationSprite.update();
}
}; // Ten nawias klamrowy zamyka funkcję self.update = function () { ... };
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 SoulEnemy = Container.expand(function (options) {
var self = Container.call(this);
options = options || {};
self.hp = options.hp || 1;
self.speed = options.speed || 2;
self.isDead = false;
self.targetX = options.targetX || 2048 / 2;
self.targetY = options.targetY || 2732 / 2;
var animFrames = [];
for (var i = 0; i < 6; i++) {
// 6 klatek animacji
try {
animFrames.push(LK.getAsset('soul_anim_' + i, {
anchorX: 0.5,
anchorY: 0.5,
clone: true
}));
} catch (e) {
console.error("Błąd ładowania klatki animacji duszy: soul_anim_" + i, e);
// Awaryjny placeholder, jeśli klatka się nie załaduje
var placeholderFrame = new Shape({
width: options.width || 30,
height: options.height || 30,
color: 0xFF00FF,
shape: 'ellipse'
});
placeholderFrame.anchor.set(0.5, 0.5);
animFrames.push(placeholderFrame);
}
}
if (animFrames.length > 0) {
self.animation = new SpriteAnimation({
frames: animFrames,
frameDuration: 120,
// Czas trwania klatki w ms - dostosuj
loop: true,
anchorX: 0.5,
anchorY: 0.5
});
self.addChild(self.animation);
self.animation.play();
} else {
// Jeśli nie udało się załadować żadnej klatki, stwórz awaryjny kształt
console.error("Nie udało się załadować żadnej klatki dla SoulEnemy. Tworzenie awaryjnego kształtu.");
self.fallbackGraphics = new Shape({
width: options.width || 30,
height: options.height || 30,
color: 0xADD8E6,
shape: 'ellipse'
});
self.fallbackGraphics.anchor.set(0.5, 0.5);
self.addChild(self.fallbackGraphics);
}
// Ustawienie self.width i self.height na podstawie pierwszej klatki animacji lub opcji
if (animFrames.length > 0 && animFrames[0] && typeof animFrames[0].width !== 'undefined') {
self.width = animFrames[0].width;
self.height = animFrames[0].height;
} else {
self.width = options.width || 30;
self.height = options.height || 30;
}
self.update = function () {
if (self.isDead || gameState.currentState !== "cursedCrystal") {
return;
}
var currentTargetX = self.targetX;
var currentTargetY = self.targetY;
if (typeof crystalCoreObject !== 'undefined' && crystalCoreObject && !crystalCoreObject.destroyed) {
currentTargetX = crystalCoreObject.x;
currentTargetY = crystalCoreObject.y;
}
var dx = currentTargetX - self.x;
var dy = currentTargetY - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (self.animation) {
// Obracanie animacji w kierunku ruchu
var angle = Math.atan2(dy, dx);
self.animation.rotation = angle;
// Jeśli chcesz, aby sprite "patrzył" w prawo, gdy kąt jest 0, możesz dodać:
// self.animation.rotation = angle + Math.PI / 2; // Jeśli sprite jest domyślnie skierowany w górę
} else if (self.fallbackGraphics) {
// Obracanie awaryjnej grafiki
var angle = Math.atan2(dy, dx);
self.fallbackGraphics.rotation = angle;
}
if (distance > self.speed) {
var moveX = dx / distance * self.speed;
var moveY = dy / distance * self.speed;
self.x += moveX;
self.y += moveY;
} else {
self.x = currentTargetX;
self.y = currentTargetY;
if (typeof crystalCoreObject !== 'undefined' && crystalCoreObject && !crystalCoreObject.destroyed && self.x === crystalCoreObject.x && self.y === crystalCoreObject.y) {
self.onHitCrystal();
} else if (!crystalCoreObject || crystalCoreObject.destroyed) {
self.isDead = true;
if (self.parent) {
self.parent.removeChild(self);
}
self.destroy();
}
}
// SpriteAnimation ma własną metodę update, która jest wywoływana automatycznie, jeśli jest dzieckiem
// Nie ma potrzeby wywoływania self.animation.update() ręcznie tutaj.
};
self.takeDamage = function (amount) {
if (self.isDead) {
return;
}
self.hp -= amount;
// Efekt flash na całym obiekcie SoulEnemy (który zawiera animację)
LK.effects.flashObject(self, 0xFFFFFF, 150);
if (self.hp <= 0) {
self.die();
}
};
self.die = function () {
if (self.isDead) {
return;
}
self.isDead = true;
gameState.cursedCrystalScore += 10;
if (ui && ui.updateScoreCC) {
ui.updateScoreCC(gameState.cursedCrystalScore);
}
if (self.parent) {
self.parent.removeChild(self);
}
self.destroy(); // To powinno zniszczyć również animację jako dziecko
};
self.onHitCrystal = function () {
if (self.isDead) {
return;
}
self.isDead = true; // Oznacz jako martwą, aby zatrzymać inne logiki (np. update)
gameState.cursedCrystalSoulsHitCrystal++;
gameState.cursedCrystalChargeLevel = Math.min(gameState.cursedCrystalTargetCharge, gameState.cursedCrystalChargeLevel + 3);
if (ui && ui.updateCrystalCharge) {
ui.updateCrystalCharge(gameState.cursedCrystalChargeLevel, gameState.cursedCrystalTargetCharge);
}
if (typeof gameState.updateCrystalVisual === 'function') {
gameState.updateCrystalVisual();
}
var FADE_OUT_DURATION = 300; // Czas trwania fade out w ms - dostosuj!
// Zatrzymaj animację SpriteAnimation, jeśli istnieje i gra
if (self.animation && typeof self.animation.stop === 'function') {
self.animation.stop();
}
tween(self, {
alpha: 0
}, {
duration: FADE_OUT_DURATION,
easing: tween.easeOutQuad,
// Możesz wybrać inną funkcję easing
onFinish: function onFinish() {
if (self.parent) {
self.parent.removeChild(self);
}
if (self.destroy && !self.destroyed) {
// Dodatkowe sprawdzenie !self.destroyed
self.destroy();
}
}
});
};
self.x = options.x || 0;
self.y = options.y || 0;
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;
// Dodaj tę linię do konstruktora, aby SpriteAnimation pamiętało własne alpha
// i mogło przekazywać je do klatek.
self.alpha = options.alpha !== undefined ? options.alpha : 1;
// 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);
}
// TUTAJ JEST KLUCZOWA ZMIANA W KONSTRUKTORZE:
// Ustaw alpha dla pierwszej klatki na podstawie alpha kontenera SpriteAnimation.
if (firstFrame) {
firstFrame.alpha = self.alpha;
}
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]);
}
self.currentFrame++;
if (self.currentFrame >= self.frames.length) {
if (self.loop) {
self.currentFrame = 0;
// TUTAJ JEST KLUCZOWA ZMIANA W UPDATE DLA PĘTLI:
var nextFrame = self.frames[self.currentFrame];
if (nextFrame) {
nextFrame.alpha = self.alpha; // Zastosuj alpha kontenera
}
self.addChild(nextFrame);
} else {
self.playing = false;
if (typeof self.onComplete === 'function') {
self.onComplete();
}
return;
}
} else {
// Dodajemy nową klatkę tylko wtedy, gdy animacja jeszcze trwa
// TUTAJ JEST KLUCZOWA ZMIANA W UPDATE DLA DALSZYCH KLATEK:
var nextFrame = self.frames[self.currentFrame];
if (nextFrame) {
nextFrame.alpha = self.alpha; // Zastosuj alpha kontenera
}
self.addChild(nextFrame);
}
}
};
// 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);
}
};
self.ccScoreText = new Text2("Score: 0", {
size: 50,
fill: 0xFFFF00,
// Żółty, dla odróżnienia
align: 'left'
});
self.ccScoreText.anchor.set(0, 0); // Lewy górny róg tego tekstu
self.addChild(self.ccScoreText);
// Rekord dla Cursed Crystal
self.ccHighScoreText = new Text2("Best: 0", {
size: 40,
fill: 0xFFFFFF,
// Biały
align: 'left'
});
self.ccHighScoreText.anchor.set(0, 0); // Lewy górny róg tego tekstu
self.addChild(self.ccHighScoreText);
// Pasek Naładowania Klejnotu
self.crystalChargeBarContainer = new Container();
self.addChild(self.crystalChargeBarContainer);
var baseAssetID = 'cursedenergybar'; // Nazwa Twojego assetu
var chargeBarAssetFullWidth; // Zmienna na pełną szerokość assetu
var chargeBarAssetHeight; // Zmienna na wysokość assetu
// Tło paska (ten sam asset, ale z większą przezroczystością)
try {
self.crystalChargeBarBg = self.attachAsset(baseAssetID, {
anchorX: 0.5,
anchorY: 0.5,
alpha: 0.3,
// Ustaw przezroczystość tła (np. 30%)
clone: true
});
chargeBarAssetFullWidth = self.crystalChargeBarBg.width; // Pobierz szerokość z assetu
chargeBarAssetHeight = self.crystalChargeBarBg.height; // Pobierz wysokość z assetu
self.crystalChargeBarContainer.addChild(self.crystalChargeBarBg);
} catch (e) {
console.error("Błąd ładowania assetu tła paska energii ('" + baseAssetID + "'): ", e);
chargeBarAssetFullWidth = 400; // Awaryjna szerokość
chargeBarAssetHeight = 25; // Awaryjna wysokość
self.crystalChargeBarBg = new Shape({
width: chargeBarAssetFullWidth,
height: chargeBarAssetHeight,
color: 0xCCCCCC,
alpha: 0.3
});
self.crystalChargeBarBg.anchor.set(0, 0.5);
self.crystalChargeBarContainer.addChild(self.crystalChargeBarBg);
}
// Wypełnienie paska (ten sam asset, w pełni widoczny)
try {
self.crystalChargeBarFill = self.attachAsset(baseAssetID, {
anchorX: 0.5,
anchorY: 0.5,
alpha: 1,
// Pełna widoczność
clone: true
});
self.crystalChargeBarFill.width = 0; // Zacznij od zerowej szerokości
// Jeśli Twoje tło i wypełnienie mają być idealnie na sobie, a oba mają anchorX:0, anchorY:0.5,
// to ich .x i .y względem kontenera powinny być takie same (np. 0,0).
// self.crystalChargeBarFill.x = 0;
// self.crystalChargeBarFill.y = 0;
self.crystalChargeBarContainer.addChild(self.crystalChargeBarFill);
} catch (e) {
console.error("Błąd ładowania assetu wypełnienia paska energii ('" + baseAssetID + "'): ", e);
self.crystalChargeBarFill = new Shape({
width: 0,
height: chargeBarAssetHeight ? chargeBarAssetHeight : 25,
color: 0x8A2BE2
});
self.crystalChargeBarFill.anchor.set(0, 0.5);
self.crystalChargeBarContainer.addChild(self.crystalChargeBarFill);
}
// --- DODANA NOWA FUNKCJA DO AKTUALIZACJI TEKSTU REKORDU ---
self.updateHighScoreDisplay = function (seconds) {
// 'seconds' to rekord czasu dla Roll Master
seconds = Math.max(0, seconds || 0);
var minutes = Math.floor(seconds / 60);
var remainingSeconds = seconds % 60;
var formattedSeconds = String(remainingSeconds).padStart(2, '0'); // Zawsze dwie cyfry dla sekund
var formattedTime = String(minutes).padStart(2, '0') + ':' + formattedSeconds;
if (this.highScoreText) {
// this.highScoreText to element UI dla rekordu Roll Master
// Wyświetlaj rekord Roll Master tylko, gdy jesteśmy w tym trybie lub na jego ekranie końca
if (gameState.currentState === "rollMaster" || gameState.currentState === "rollMasterGameOver") {
this.highScoreText.setText("Best Time: " + formattedTime);
// Pozycja i widoczność (alpha) tego elementu są zarządzane przez UI.prototype.positionElements
}
// W innych stanach gry nie modyfikujemy tekstu; positionElements powinno go ukryć.
}
};
self.updateScoreCC = function (currentScore) {
if (this.ccScoreText) {
// this.ccScoreText to element dla "Score: X" w Cursed Crystal
if (gameState.currentState === "cursedCrystal") {
this.ccScoreText.setText("Score: " + (currentScore || 0));
// Pozycja i widoczność (alpha) są zarządzane przez UI.prototype.positionElements("cursedCrystal")
}
}
};
self.updateHighScoreCC = function (highScore) {
// 'highScore' to rekord dla Cursed Crystal
if (this.ccHighScoreText) {
// this.ccHighScoreText to element dla "Best: Y" w Cursed Crystal
if (gameState.currentState === "cursedCrystal") {
this.ccHighScoreText.setText("Best: " + (highScore || 0));
// Pozycja i widoczność (alpha) są zarządzane przez UI.prototype.positionElements("cursedCrystal")
}
}
};
// Aktualizuje wizualizację paska naładowania Klejnotu
self.updateCrystalCharge = function (currentCharge, maxCharge) {
if (self.crystalChargeBarFill && self.crystalChargeBarBg) {
maxCharge = Math.max(1, maxCharge || 1); // Max musi być co najmniej 1
currentCharge = Math.max(0, Math.min(currentCharge, maxCharge));
var chargeBarBaseWidth = self.crystalChargeBarBg.width; // Szerokość tła paska
var fillWidth = currentCharge / maxCharge * chargeBarBaseWidth;
self.crystalChargeBarFill.width = fillWidth;
}
};
// Funkcja do pokazywania/ukrywania i aktualizacji paska HP Minibossa CC
// Wykorzysta istniejący self.bossHealthBar i self.bossHealthBarContainer
self.updateMinibossHealthCC = function (currentHP, maxHP, isActive) {
if (self.bossHealthBarContainer && self.bossHealthBar) {
if (isActive && currentHP > 0) {
self.bossHealthBarContainer.alpha = 1;
maxHP = Math.max(1, maxHP || 1);
currentHP = Math.max(0, Math.min(currentHP, maxHP));
var barWidth = 800; // Upewnij się, że to ta sama szerokość co w updateBossHealth
// lub pobierz z self.bossHealthBarBg.width jeśli masz tło
var currentBarWidth = currentHP / maxHP * barWidth;
self.bossHealthBar.width = currentBarWidth;
} else {
self.bossHealthBarContainer.alpha = 0;
}
}
};
// --- 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 (mogą być nadpisywane w case'ach)
if (self.deathsText) {
// Sprawdzenie, czy element istnieje
self.deathsText.x = 2048 - 150;
self.deathsText.y = 50;
}
if (self.heartContainer) {
self.heartContainer.y = 80;
}
if (self.bossHealthBarContainer) {
self.bossHealthBarContainer.x = 2048 / 2;
self.bossHealthBarContainer.y = 160;
}
// Resetuj widoczność wszystkich głównych elementów UI przed ustawieniem dla danego stanu
if (self.titleText) {
self.titleText.alpha = 0;
}
if (self.messageText) {
self.messageText.alpha = 0;
}
if (self.tutorialText) {
self.tutorialText.alpha = 0;
}
if (self.timerText) {
self.timerText.alpha = 0;
}
if (self.heartContainer) {
self.heartContainer.alpha = 0;
}
if (self.bossHealthBarContainer) {
self.bossHealthBarContainer.alpha = 0;
}
if (self.deathsText) {
self.deathsText.alpha = 0;
}
// Ten highScoreText jest specyficzny dla Roll Master, więc domyślnie go ukrywamy
// chyba że jesteśmy w stanie rollMaster lub rollMasterGameOver (obsłużone w case)
if (self.highScoreText) {
self.highScoreText.alpha = 0;
}
// NOWE ELEMENTY UI DLA CURSED CRYSTAL - też domyślnie ukryte
if (self.ccScoreText) {
self.ccScoreText.alpha = 0;
}
if (self.ccHighScoreText) {
self.ccHighScoreText.alpha = 0;
}
if (self.crystalChargeBarContainer) {
self.crystalChargeBarContainer.alpha = 0;
}
// Przywróć domyślną pozycję i styl timerText (może być nadpisane w case "rollMaster")
if (self.timerText) {
self.timerText.x = 350; // Domyślna pozycja X dla trybu "game"
self.timerText.y = 100; // Domyślna pozycja Y dla trybu "game"
self.timerText.anchor.set(0, 0);
self.timerText.style = {
size: 60,
fill: 0xFFFFFF
};
self.timerText.setText("00:00"); // Resetuj tekst na wszelki wypadek
}
// Logika dla highScoreText (Roll Master) - resetowanie tekstu poza trybem RM
if (self.highScoreText && state !== "rollMaster" && state !== "rollMasterGameOver") {
// self.highScoreText.setText("Best RM: 00:00"); // Możesz zresetować tekst lub zostawić ostatni znany
}
switch (state) {
case "title":
if (self.titleText) {
self.titleText.x = 2048 / 2;
self.titleText.y = 800;
self.titleText.alpha = 1;
}
if (self.messageText) {
self.messageText.x = 2048 / 2;
self.messageText.y = 1000;
// self.messageText.alpha jest zarządzane przez showMessage
}
if (self.tutorialText) {
self.tutorialText.x = 2048 / 2;
self.tutorialText.y = 1200;
// self.tutorialText.alpha jest zarządzane przez showTutorial
}
break;
case "game":
// Tryb walki z bossem (tutorial)
if (self.heartContainer) {
self.heartContainer.alpha = 1;
// Pozycja serc jest aktualizowana w updateHearts
}
if (self.timerText) {
// Timer dla standardowego bossa (jeśli jest)
self.timerText.alpha = 1;
// Ustaw x, y, anchor, style dla tego timera
self.timerText.x = 350; // Przykładowa pozycja z Twojego kodu
self.timerText.y = 100;
self.timerText.anchor.set(0, 0);
self.timerText.style = {
size: 60,
fill: 0xFFFFFF
};
}
if (self.deathsText) {
self.deathsText.alpha = 1;
}
if (self.bossHealthBarContainer && typeof boss !== 'undefined' && boss && !boss.dead && boss.health > 0) {
self.bossHealthBarContainer.alpha = 1;
} else if (self.bossHealthBarContainer) {
self.bossHealthBarContainer.alpha = 0;
}
if (self.tutorialText) {
// self.tutorialText.alpha jest zarządzane przez showTutorial/hideTutorial
self.tutorialText.x = 2048 / 2;
self.tutorialText.y = 200;
}
break;
case "grillMenu":
if (self.messageText) {
// Dla komunikatów w Grill Menu
self.messageText.x = 2048 / 2;
self.messageText.y = 500;
// alpha zarządzane przez showMessage
}
// Wszystkie inne elementy typowo rozgrywkowe powinny być ukryte przez reset na początku funkcji
break;
case "gameOver":
// Standardowy Game Over (po Boss+ lub tutorialu, jeśli tak zdecydujesz)
// Teksty i przyciski dla tego stanu są głównie zarządzane przez gameState.gameOver
if (self.titleText) {// Dla "DEFEAT!" lub "TIME'S UP!"
// alpha, text, x, y są ustawiane w gameState.gameOver
}
if (self.messageText) {// Dla dodatkowych wiadomości
// alpha, text, x, y są ustawiane w gameState.gameOver
}
if (self.deathsText) {
// Najpierw sprawdź, czy deathsText istnieje
// Warunek, kiedy pokazać licznik śmierci:
// Tylko jeśli gra się zakończyła (co wiemy, bo state === "gameOver"),
// przyczyną była śmierć gracza,
// ORAZ NIE był to tryb Boss+ (czyli isNewBossPlusMode jest false).
if (gameOverReasonIsDeath && !isNewBossPlusMode) {
self.deathsText.alpha = 1;
self.deathsText.x = 2048 - 150; // Twoja standardowa pozycja
self.deathsText.y = 50; // Twoja standardowa pozycja
} else {
self.deathsText.alpha = 0; // W innych przypadkach "gameOver" (np. Boss+) ukryj licznik śmierci
}
}
break;
// ... (case'y intro, fakeTutorial, realTutorial - zakładam, że są OK) ...
case "intro": //
case "fakeTutorial": //
case "realTutorial":
//
// Większość elementów UI jest zazwyczaj ukryta, zarządzane przez logikę tych stanów
break;
//
case "rollMaster":
// Pokaż elementy UI specyficzne dla aktywnego trybu Roll Master
if (self.timerText) {
// Timer/wynik dla Roll Master
self.timerText.alpha = 1;
self.timerText.x = 2048 / 2; // Środek-góra
self.timerText.y = 50;
self.timerText.anchor.set(0.5, 0);
self.timerText.style = {
size: 60,
fill: 0xFFFFFF
};
// Tekst jest aktualizowany w ui.updateTimerDisplay
}
if (self.highScoreText) {
// Rekord dla Roll Master (ten właściwy, nie ccHighScoreText)
self.highScoreText.alpha = 1;
self.highScoreText.x = 2048 - 50; // Prawy górny róg
self.highScoreText.y = 50;
self.highScoreText.anchor.set(1, 0);
// Tekst jest aktualizowany w ui.updateHighScoreDisplay
}
// Jawnie ukryj elementy UI z Cursed Crystal
if (self.ccScoreText) {
self.ccScoreText.alpha = 0;
}
if (self.ccHighScoreText) {
self.ccHighScoreText.alpha = 0;
} // <--- BARDZO WAŻNE
if (self.crystalChargeBarContainer) {
self.crystalChargeBarContainer.alpha = 0;
}
// Ukryj inne standardowe elementy gry
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;
} // Główny tytuł gry
break;
case "rollMasterGameOver":
// Jawnie ukryj elementy UI z Cursed Crystal
if (self.ccScoreText) {
self.ccScoreText.alpha = 0;
}
if (self.ccHighScoreText) {
self.ccHighScoreText.alpha = 0;
}
if (self.crystalChargeBarContainer) {
self.crystalChargeBarContainer.alpha = 0;
}
// Ukryj elementy aktywnej gry Roll Master (timer na żywo)
if (self.timerText) {
self.timerText.alpha = 0;
}
if (self.highScoreText) {
self.highScoreText.alpha = 0;
}
// Ukryj inne standardowe elementy gry
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;
case "cursedCrystal":
// TYLKO JEDNO WYSTĄPIENIE TEGO CASE
if (self.timerText) {
self.timerText.alpha = 0;
}
if (self.highScoreText) {
// To jest highScoreText dla Roll Master, więc ukrywamy
self.highScoreText.alpha = 0;
}
if (self.deathsText) {
// Licznik śmierci też ukrywamy w tym trybie
self.deathsText.alpha = 0;
}
if (self.tutorialText) {
// Tutorial też ukrywamy
self.tutorialText.alpha = 0;
}
// --- Elementy specyficzne dla Cursed Crystal ---
if (self.ccScoreText) {
self.ccScoreText.alpha = 1;
self.ccScoreText.x = 50;
self.ccScoreText.y = 20;
self.ccScoreText.anchor.set(0, 0);
}
if (self.ccHighScoreText) {
self.ccHighScoreText.alpha = 1;
self.ccHighScoreText.x = 2048 - 50;
self.ccHighScoreText.y = 20;
self.ccHighScoreText.anchor.set(1, 0);
}
// --- Serca Gracza ---
if (self.heartContainer) {
self.heartContainer.alpha = 1; // UPEWNIJ SIĘ, ŻE SERCA SĄ WIDOCZNE
var ccMaxHearts = typeof gameState !== 'undefined' && gameState.cursedCrystalPlayerMaxHealth ? gameState.cursedCrystalPlayerMaxHealth : 10;
var heartsWidth = ccMaxHearts * 50;
self.heartContainer.x = (2048 - heartsWidth) / 2 + 25;
self.heartContainer.y = 70; // Stała pozycja Y dla serc
}
// --- Pasek Ładowania Kryształu ---
if (self.crystalChargeBarContainer && self.crystalChargeBarBg) {
if (typeof gameState !== 'undefined' && gameState.isMinibossActiveCC) {
self.crystalChargeBarContainer.alpha = 0;
} else {
self.crystalChargeBarContainer.alpha = 1;
var chargeBarWidth = self.crystalChargeBarBg.width || 400;
self.crystalChargeBarContainer.anchorX = 0.5;
self.crystalChargeBarContainer.x = 2048 / 2;
self.crystalChargeBarContainer.y = (self.heartContainer ? self.heartContainer.y + (self.heartContainer.height || 40) : 70 + 40) + 50;
}
}
// --- Pasek HP Minibossa ---
if (self.bossHealthBarContainer) {
var heartsBottomY = self.heartContainer ? self.heartContainer.y + (self.heartContainer.height || 40) : 70 + 40;
// Poprzednio było: heartsBottomY + 20 (ok. 130), potem heartsBottomY + 70 (ok. 180)
// Aby przesunąć o kolejne 50px w dół od Y=180, potrzebujemy Y = 230
// Więc offset od heartsBottomY (ok. 110) musi być 120.
var desiredOffsetYBelowHearts = 120;
self.bossHealthBarContainer.y = heartsBottomY + desiredOffsetYBelowHearts;
// Widoczność paska HP bossa jest zarządzana przez updateMinibossHealthCC,
// więc tutaj tylko pozycjonujemy.
}
break;
case "cursedCrystalGameOver":
// Ukryj elementy rozgrywki CC (oprócz score i highscore, które mogą być częścią ekranu końca)
if (self.timerText) {
self.timerText.alpha = 0;
}
if (self.highScoreText) {
self.highScoreText.alpha = 0;
} // highScoreText dla RollMaster
if (self.crystalChargeBarContainer) {
self.crystalChargeBarContainer.alpha = 0;
}
if (self.heartContainer) {
self.heartContainer.alpha = 0;
} // Ukryj serca gracza na ekranie końca gry CC
if (self.bossHealthBarContainer) {
self.bossHealthBarContainer.alpha = 0;
}
if (self.deathsText) {
self.deathsText.alpha = 0;
}
if (self.tutorialText) {
self.tutorialText.alpha = 0;
}
break;
}
}; // 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
****/
// Jasnoniebieska elipsa dla Duszy
// Placeholder, zmień ID
// Dodana funkcja pomocnicza do pobierania czasu gry w milisekundach
// Dodana funkcja pomocnicza do pobierania czasu gry w milisekundach
// Globalny kontener na elementy scen intro/tutoriali i Grill Screena (do łatwego czyszczenia)
// Komentarze o assetach pominięte dla zwięzłości
// Ciemnofioletowe tło
// Fioletowy klejnot
// Pomarańczowy kwadrat dla Minibossa
function _typeof(o) {
"@babel/helpers - typeof";
return _typeof = "function" == typeof Symbol && "symbol" == typeof Symbol.iterator ? function (o) {
return typeof o;
} : function (o) {
return o && "function" == typeof Symbol && o.constructor === Symbol && o !== Symbol.prototype ? "symbol" : typeof o;
}, _typeof(o);
}
function _defineProperty(e, r, t) {
return (r = _toPropertyKey(r)) in e ? Object.defineProperty(e, r, {
value: t,
enumerable: !0,
configurable: !0,
writable: !0
}) : e[r] = t, e;
}
function _toPropertyKey(t) {
var i = _toPrimitive(t, "string");
return "symbol" == _typeof(i) ? i : i + "";
}
function _toPrimitive(t, r) {
if ("object" != _typeof(t) || !t) {
return t;
}
var e = t[Symbol.toPrimitive];
if (void 0 !== e) {
var i = e.call(t, r || "default");
if ("object" != _typeof(i)) {
return i;
}
throw new TypeError("@@toPrimitive must return a primitive value.");
}
return ("string" === r ? String : Number)(t);
}
if (typeof LK.getGameTime === 'undefined') {
LK.getGameTime = function () {
return Date.now(); // Zwraca aktualny czas w milisekundach
};
}
if (typeof LK.getGameTime === 'undefined') {
LK.getGameTime = function () {
return Date.now(); // Zwraca aktualny czas w milisekundach
};
}
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 = _defineProperty(_defineProperty(_defineProperty({
currentState: "title",
gameDuration: 120,
remainingTime: 0,
crystalExploding: false,
gameTimerInterval: null,
fakeTutorialTimerId: null,
bossSpeedIncreased: false,
currentIntroMusicInstance: null,
cursedCrystalMusicTracks: ['CursedCrystal', 'cursedcrystal2'],
currentRollSoulsInstance: null,
currentRollMasterMusicInstance: null,
currentCursedCrystalMusicInstance: null,
// Nowa właściwość
cursedCrystalPlayerHealth: 10,
// Startowe HP gracza w tym trybie
cursedCrystalPlayerMaxHealth: 10,
cursedCrystalChargeLevel: 0,
// Aktualny poziom naładowania Klejnotu (0-100%)
cursedCrystalTargetCharge: 100,
// Wartość naładowania do przywołania Minibossa
cursedCrystalSoulsHitCrystal: 0,
// Licznik dusz, które dotarły do kryształu (do skalowania Minibossa)
cursedCrystalScore: 0,
cursedCrystalHighScore: 0,
// Ładowane z localStorage
cursedCrystalEnemies: [],
// Tablica na Dusze i pociski Minibossa CC
cursedCrystalEnemySpawnTimer: 0,
cursedCrystalBaseSpawnInterval: 120,
// Początkowy interwał spawnu (np. 2 sekundy)
cursedCrystalCurrentSpawnInterval: 120,
cursedCrystalMinSpawnInterval: 30,
// Minimalny interwał spawnu (np. 0.5 sekundy)
cursedCrystalDifficultyTimer: 0,
cursedCrystalDifficultyInterval: 600,
// Co ile klatek (np. 10 sekund) zwiększać trudność
cursedCrystalEnemyBaseSpeed: 2,
cursedCrystalEnemyCurrentMaxSpeed: 4,
cursedCrystalTimeSurvived: 0,
// Czas przetrwania w klatkach lub sekundach
isMinibossActiveCC: false,
cursedCrystalMinibossObject: null,
cursedCrystalMinibossHP: 0,
cursedCrystalMinibossMaxHP: 200,
cursedCrystalActiveProjectiles: [],
// Dla pocisków Minibossa
cursedCrystalActiveExplosions: [],
cursedCrystalActiveLasers: [],
cursedCrystalActiveLaserWalls: [],
// Dla aktywnych obszarów eksplozji
// Bazowe HP Minibossa, będzie skalowane
cursedCrystalMinibossAttackTimer: 0,
cursedCrystalMinibossAttackInterval: 180,
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() {
// Zapewnienie, że LK.getGameTime jest zdefiniowane
if (typeof LK.getGameTime === 'undefined') {
LK.getGameTime = function () {
return Date.now(); // Zwraca aktualny czas w milisekundach
};
}
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);
this.showTitleScreen();
},
// --- gameState.showTitleScreen ---
showTitleScreen: function showTitleScreen() {
isNewBossPlusMode = false;
this.rollMasterTime = 0;
// Normalne działanie - bez flagi testowej
console.log("[showTitleScreen] State: Title Screen");
var needsIntroMusic = true;
// Zatrzymywanie muzyki z innych trybów
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();
}
}
// Logika muzyki intro
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.");
}
// Czyszczenie timerów
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(); // Usuwa elementy z currentSceneElements
// Ustawienie tła i cząsteczek
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);
// Reset gracza i bossa
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;
}
});
// Ustawienie UI
ui.positionElements("title");
ui.titleText.setText("Welcome Unchosen");
ui.showMessage("Tap to Start", 0); // Normalny tekst
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
};
// Usuwamy testowy przycisk i jego logikę
game.off('down');
game.on('down', function (x, y, obj) {
if (gameState.currentState === 'title') {
// Logika dla normalnego przejścia do intro
console.log("Title screen clicked - Proceeding to Intro.");
if (localParticleIntervalId) {
LK.clearInterval(localParticleIntervalId);
localParticleIntervalId = null;
}
if (localStartScreenParticles) {
localStartScreenParticles.forEach(function (p) {
if (p && p.destroy) {
p.destroy();
}
});
localStartScreenParticles = [];
}
// Muzyka intro powinna już grać lub zacząć grać, nie zatrzymujemy jej tutaj
gameState.showIntro(); // <<< KLUCZOWA ZMIANA - PRZYWRÓCENIE NORMALNEGO PRZEJŚCIA
}
});
},
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 (this.currentCursedCrystalMusicInstance && this.currentCursedCrystalMusicInstance.stop) {
console.log("[showTitleScreen] Zatrzymywanie currentCursedCrystalMusicInstance.");
if (typeof this.currentCursedCrystalMusicInstance.volume === 'number') {
this.currentCursedCrystalMusicInstance.volume = 0;
}
this.currentCursedCrystalMusicInstance.stop();
this.currentCursedCrystalMusicInstance = null;
} else {
// Opcjonalny fallback, jeśli chcesz (choć jeśli zawsze ustawiasz currentCursedCrystalMusicInstance, może nie być potrzebny)
var musicViaLK_CC = LK.music;
if (musicViaLK_CC && musicViaLK_CC.assetId === 'cursedcrystal' && musicViaLK_CC.playing && musicViaLK_CC.stop) {
console.log("[showTitleScreen] Fallback: Zatrzymywanie muzyki Cursed Crystal przez LK.music.");
if (typeof musicViaLK_CC.volume === 'number') {
musicViaLK_CC.volume = 0;
}
musicViaLK_CC.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();
}
}, 4000);
}, 4000);
}, delay);
}
showIntroText('From the authors of Dark Souls...', 3000, 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();
}
}, 1200);
});
});
});
});
});
},
// 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.currentCursedCrystalMusicInstance && this.currentCursedCrystalMusicInstance.stop) {
// DODANA LOGIKA
console.log("[StartGame] Zatrzymywanie currentCursedCrystalMusicInstance.");
if (typeof this.currentCursedCrystalMusicInstance.volume === 'number') {
this.currentCursedCrystalMusicInstance.volume = 0;
}
this.currentCursedCrystalMusicInstance.stop();
this.currentCursedCrystalMusicInstance = 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();
this.currentRollSoulsInstance = null; // Upewnij się, że zerujesz po zatrzymaniu, aby odtworzyć nową
}
console.log("[StartGame] Uruchamianie RollSouls.");
this.currentRollSoulsInstance = LK.playMusic('RollSouls', {
// Zakładam, że 'RollSouls' to muzyka dla tego trybu
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());
if (player) {
player.health = parseInt(storage.maxHearts, 10) || 5;
console.log("DEBUG: Inicjalizacja zdrowia gracza. player.health:", player.health, "storage.maxHearts:", storage.maxHearts);
}
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 = 600;
boss.health = boss.maxHealth;
this.gameDuration = 600;
boss.attackSpeedMultiplier = 0.6;
boss.speed = 7;
boss.ultimateAttackCooldownTimer = 0;
boss.nextUltimateAttackAvailableTime = boss.ultimateAttackMinInterval;
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");
if (ui && player) {
ui.updateHearts(player.health, parseInt(storage.maxHearts, 10) || 5);
} else if (ui) {
ui.updateHearts(0, parseInt(storage.maxHearts, 10) || 5);
}
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
};
// Przycisk "Cursed Crystal"
var cursedCrystalButton = new Container();
cursedCrystalButton.interactive = true;
cursedCrystalButton.cursor = "pointer";
cursedCrystalButton.x = 600; // Ta sama kolumna co inne główne przyciski
// Ustaw pozycję Y pod przyciskiem "New Boss+"
// Zakładamy, że newBossButton i newBossButtonBg istnieją i są zdefiniowane powyżej
var newBossButtonHeight = newBossButtonBg && newBossButtonBg.height ? newBossButtonBg.height : 200; // Domyślna wysokość, jeśli nieznana
var verticalSpacing = 700; // Odstęp między przyciskami
// --- POCZĄTEK POPRAWKI dla LK.getAssetMeta ---
var cursedCrystalButtonAssetForSizing;
var cursedCrystalButtonHeight = 250; // Domyślna wysokość assetu buttonCursedCrystal (zgodnie z definicją w LK.init.image)
try {
// Pobierz asset, aby spróbować odczytać jego rzeczywistą wysokość
cursedCrystalButtonAssetForSizing = LK.getAsset('buttonCursedCrystal', {});
if (cursedCrystalButtonAssetForSizing && typeof cursedCrystalButtonAssetForSizing.height === 'number') {
cursedCrystalButtonHeight = cursedCrystalButtonAssetForSizing.height;
}
} catch (e) {
// Jeśli wystąpi błąd przy pobieraniu assetu (np. jeszcze niezaładowany lub błąd ID),
// użyjemy domyślnej wysokości i wyświetlimy ostrzeżenie.
console.warn("Nie można pobrać assetu 'buttonCursedCrystal' do ustalenia wysokości, używam domyślnej: " + cursedCrystalButtonHeight, e);
}
// Oblicz pozycję Y przycisku Cursed Crystal
cursedCrystalButton.y = newBossButton.y + newBossButtonHeight / 2 + verticalSpacing + cursedCrystalButtonHeight / 2;
// --- KONIEC POPRAWKI dla LK.getAssetMeta ---
currentSceneElements.addChild(cursedCrystalButton);
var cursedCrystalButtonImage = LK.getAsset('buttonCursedCrystal', {
anchorX: 0.5,
anchorY: 0.5
});
cursedCrystalButton.addChild(cursedCrystalButtonImage);
cursedCrystalButton.down = function () {
if (gameState.currentState === "grillMenu") {
console.log("Przycisk Cursed Crystal kliknięty!");
gameState.startCursedCrystalMode();
}
};
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.currentCursedCrystalMusicInstance && this.currentCursedCrystalMusicInstance.stop) {
this.currentCursedCrystalMusicInstance.stop();
this.currentCursedCrystalMusicInstance = null;
} // <<< DODAJ TUTAJ
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(isDeathParam) {
// Krok 1: Zabezpieczenie parametru isDeathParam i przechwycenie aktualnego stanu isNewBossPlusMode
var localIsDeath = typeof isDeathParam === 'boolean' ? isDeathParam : true;
var calledDuringBossPlusMode = isNewBossPlusMode; // Przechwyć stan na początku wywołania!
console.log("gameState.gameOver CALLED. isDeathParam:", isDeathParam, "=> localIsDeath:", localIsDeath, ". Flag isNewBossPlusMode at call time (captured as calledDuringBossPlusMode):", calledDuringBossPlusMode, ". Current global isNewBossPlusMode:", isNewBossPlusMode // Dla porównania, jeśli coś by go zmieniło w międzyczasie
);
this.currentState = "gameOver"; // Ustaw stan gry
gameOverReasonIsDeath = localIsDeath; // Ustaw globalną flagę (jeśli nadal jej używasz)
// Krok 2: Zatrzymaj timery i inne procesy specyficzne dla trybu "game"
if (this.gameTimerInterval) {
LK.clearInterval(this.gameTimerInterval);
this.gameTimerInterval = null;
}
// if (this.rollMasterTimerInterval) { // To jest dla Roll Master, nie powinno być tutaj zatrzymywane
// LK.clearInterval(this.rollMasterTimerInterval);
// this.rollMasterTimerInterval = null;
// }
this.bossSpeedIncreased = false; // Reset flagi dla standardowego bossa
// Krok 3: Ukryj aktywne elementy gry (gracz, boss, ściany)
if (player && player.alpha !== 0) {
// Nie niszczymy gracza tutaj, jego animacja śmierci powinna się zakończyć,
// a obiekt player zostanie zniszczony przed pokazaniem przycisków lub restartem.
player.alpha = 0;
}
if (boss && boss.alpha !== 0) {
// Podobnie dla bossa, jego animacja śmierci (jeśli jest) powinna się zakończyć.
// Zostanie zniszczony później.
boss.alpha = 0;
}
walls.forEach(function (wall) {
if (wall) {
wall.alpha = 0;
}
});
// Krok 4: Wstępne ustawienie UI (czyszczenie tekstów)
if (ui) {
ui.positionElements("gameOver"); // Może ukryć niepotrzebne elementy, pokazać stałe dla gameOver
ui.titleText.setText("");
ui.titleText.alpha = 0;
ui.messageText.setText("");
ui.messageText.alpha = 0;
ui.tutorialText.setText("");
ui.tutorialText.alpha = 0; // Ukryj tutorial, jeśli był widoczny
}
// Krok 5: Wyświetl mema śmierci (jeśli localIsDeath) lub komunikat "Time's Up"
var memeDisplayDuration = 3000; // Czas wyświetlania mema/komunikatu przed przyciskami
var deathMemeVisual = null; // Zmienna do przechowywania obiektu mema
if (localIsDeath) {
var memeAssets = ['deathMeme1', 'deathMeme2', 'deathMeme3', 'deathMeme4', 'deathMeme5'];
var randomMemeAssetName = memeAssets[Math.floor(Math.random() * memeAssets.length)];
console.log("[GameOver] Wyświetlanie mema:", randomMemeAssetName);
try {
deathMemeVisual = LK.getAsset(randomMemeAssetName, {
anchorX: 0.5,
anchorY: 0.5,
alpha: 0,
x: 2048 / 2,
y: 2732 / 2 - 150,
// Trochę wyżej, żeby zrobić miejsce na przyciski
scaleX: 0.8,
scaleY: 0.8
});
currentSceneElements.addChild(deathMemeVisual); // Dodaj do kontenera, który będzie czyszczony
tween(deathMemeVisual, {
alpha: 1
}, {
duration: 500,
easing: tween.easeIn
});
} catch (e) {
console.error("[GameOver] Błąd ładowania assetu mema:", randomMemeAssetName, e);
if (ui) {
// Awaryjny tekst, jeśli mem się nie załaduje
ui.titleText.setText("DEFEAT!"); // Tłumaczenie
ui.titleText.style = {
size: 150,
fill: 0xFF0000,
align: 'center'
};
ui.titleText.alpha = 1;
}
}
} else if (calledDuringBossPlusMode && !localIsDeath) {
// Boss+ Time's Up
if (ui) {
ui.titleText.setText("TIME'S UP!"); // Tłumaczenie
ui.titleText.style = {
size: 150,
fill: 0xFFA500,
align: 'center',
stroke: 0x000000,
strokeThickness: 6
};
ui.titleText.alpha = 1;
// Można dodać dodatkowy komunikat poniżej tytułu
// ui.messageText.setText("The challenge ends.");
// ui.messageText.alpha = 1;
}
}
// Krok 6: Timeout, po którym wykonają się akcje końcowe (przyciski lub przejście)
LK.setTimeout(function () {
// Ten console.log był źródłem błędu, teraz używamy localIsDeath i calledDuringBossPlusMode
console.log("DEBUG: Timeout #1 (po memie) w gameState.gameOver. localIsDeath:", localIsDeath, "Captured BossPlus state (calledDuringBossPlusMode):", calledDuringBossPlusMode);
// Usuń mema, jeśli był wyświetlony
if (deathMemeVisual && !deathMemeVisual.destroyed) {
tween(deathMemeVisual, {
alpha: 0
}, {
duration: 300,
onFinish: function onFinish() {
if (deathMemeVisual && deathMemeVisual.parent) {
deathMemeVisual.parent.removeChild(deathMemeVisual);
}
if (deathMemeVisual && deathMemeVisual.destroy) {
deathMemeVisual.destroy();
}
deathMemeVisual = null;
}
});
}
// Wyczyść tytuł/wiadomość, która była z memem, aby przygotować miejsce na nowy tytuł z przyciskami
if (ui) {
ui.titleText.alpha = 0;
ui.messageText.alpha = 0;
}
// Ostateczne czyszczenie obiektów gry (gracz, boss, tło areny)
// clearScene() może być zbyt agresywne, jeśli currentSceneElements zawiera UI. Lepiej ręcznie.
if (player && player.destroy && !player.destroyed) {
player.destroy();
player = null;
}
if (boss && boss.destroy && !boss.destroyed) {
boss.destroy();
boss = null;
}
if (currentBackground) {
// Usuń tło areny, jeśli istnieje
if (currentBackground.parent) {
currentBackground.parent.removeChild(currentBackground);
}
if (currentBackground.destroy && !currentBackground.destroyed) {
currentBackground.destroy();
}
currentBackground = null;
}
game.setBackgroundColor(0x1a1a1a); // Ciemne tło dla ekranu z przyciskami
// Usuń poprzednie aktywne przyciski (np. z Cursed Crystal)
if (gameState.activeButtons && gameState.activeButtons.length > 0) {
gameState.activeButtons.forEach(function (btn) {
if (btn && btn.destroy && !btn.destroyed) {
// Dodatkowe sprawdzenie !btn.destroyed
if (btn.parent) {
btn.parent.removeChild(btn);
} // Usuń z rodzica przed zniszczeniem
btn.destroy();
}
});
}
gameState.activeButtons = []; // Wyczyść tablicę na nowe przyciski
// Logika przycisków lub restartu
if (calledDuringBossPlusMode) {
// Przypadek Boss+ (śmierć LUB koniec czasu) - wyświetl przyciski
console.log("gameOver: Scenariusz Boss+. Wyświetlanie tytułu i przycisków.");
var titleTextForBossPlus = localIsDeath ? "DEFEAT!" : "TIME'S UP!";
if (ui) {
ui.titleText.setText(titleTextForBossPlus);
ui.titleText.style = {
size: 120,
fill: localIsDeath ? 0xFF0000 : 0xFFA500,
align: 'center',
stroke: 0x000000,
strokeThickness: 6
};
ui.titleText.alpha = 1;
ui.titleText.x = 2048 / 2;
ui.titleText.y = 700; // Przykładowa pozycja Y dla tytułu
}
var buttonYStart = (ui && ui.titleText ? ui.titleText.y : 700) + 200;
var buttonSpacing = 220;
// Przycisk "Main Menu"
var menuButton = new Container();
menuButton.interactive = true;
menuButton.cursor = "pointer";
try {
var menuButtonAsset = LK.getAsset('mainmenu', {
anchorX: 0.5,
anchorY: 0.5
});
menuButton.addChild(menuButtonAsset);
} catch (e) {
console.error("Błąd ładowania assetu przycisku 'mainmenu':", e);
// Prosty fallback - tylko kształt, bez tekstu, bo zakładamy, że go nie potrzebujemy,
// skoro główny asset ma tekst. Jeśli chcesz, możesz dodać tu tekst awaryjny.
var menuButtonBgFallback = new Shape({
width: 400,
height: 100,
color: 0x555555,
shape: 'box'
});
menuButton.addChild(menuButtonBgFallback);
}
menuButton.x = 2048 / 2;
menuButton.y = buttonYStart;
menuButton.down = function () {
if (gameState.currentState !== "gameOver") {
return;
} // Dodatkowe zabezpieczenie
gameState.activeButtons.forEach(function (btn) {
if (btn && btn.destroy && !btn.destroyed) {
if (btn.parent) {
btn.parent.removeChild(btn);
}
btn.destroy();
}
});
gameState.activeButtons = [];
isNewBossPlusMode = false;
gameState.showGrillScreen();
};
currentSceneElements.addChild(menuButton); // Dodajemy do currentSceneElements
gameState.activeButtons.push(menuButton);
// Przycisk "Restart Boss+"
var restartButton = new Container();
restartButton.interactive = true;
restartButton.cursor = "pointer";
try {
var restartButtonAsset = LK.getAsset('restartgame', {
anchorX: 0.5,
anchorY: 0.5
});
restartButton.addChild(restartButtonAsset);
} catch (e) {
console.error("Błąd ładowania assetu przycisku 'restartgame':", e);
// Prosty fallback
var restartButtonBgFallback = new Shape({
width: 400,
height: 100,
color: 0x4477FF,
shape: 'box'
}); // Dostosuj wymiary i kolor
restartButton.addChild(restartButtonBgFallback);
}
restartButton.x = 2048 / 2;
restartButton.y = buttonYStart + buttonSpacing;
restartButton.down = function () {
if (gameState.currentState !== "gameOver") {
return;
}
gameState.activeButtons.forEach(function (btn) {
if (btn && btn.destroy && !btn.destroyed) {
if (btn.parent) {
btn.parent.removeChild(btn);
}
btn.destroy();
}
});
gameState.activeButtons = [];
isNewBossPlusMode = true;
gameState.startGame();
};
currentSceneElements.addChild(restartButton);
gameState.activeButtons.push(restartButton);
} else if (localIsDeath && !calledDuringBossPlusMode) {
// Śmierć w standardowym trybie (tutorial) - BEZ PRZYCISKÓW, od razu restart
console.log("gameOver: Standardowa śmierć (tutorial). Restartowanie poziomu.");
isNewBossPlusMode = false;
gameState.startGame();
} else {
// Inny, nieoczekiwany scenariusz (np. !localIsDeath i !calledDuringBossPlusMode)
// To jest fallback, np. jeśli jakimś cudem czas minął w trybie non-Boss+ (co nie powinno się zdarzyć)
console.warn("gameOver: Nieoczekiwany scenariusz (np. timeout w trybie non-Boss+). Przejście do Grill Menu.");
isNewBossPlusMode = false;
gameState.showGrillScreen();
}
}, memeDisplayDuration); // Czas na wyświetlenie mema/komunikatu przed pokazaniem przycisków/przejściem
},
// --- NOWA FUNKCJA: Zakończenie trybu Roll Master ---
// Wewnątrz obiektu gameState:
endRollMasterMode: function endRollMasterMode(finalTime) {
if (this.currentState === "rollMasterGameOver") {
return;
}
console.log("[EndRollMasterMode] Zakończono tryb Roll Master. Czas:", finalTime);
this.currentState = "rollMasterGameOver";
if (this.currentRollMasterMusicInstance && this.currentRollMasterMusicInstance.stop) {
this.currentRollMasterMusicInstance.stop();
this.currentRollMasterMusicInstance = null;
}
// ... (zatrzymanie timerów, czyszczenie ataków RM, logika rekordu - jak w poprzedniej odpowiedzi) ...
if (this.rollMasterTimerInterval) {
LK.clearInterval(this.rollMasterTimerInterval);
this.rollMasterTimerInterval = null;
}
if (this.randomRmattack1Timer) {
LK.clearTimeout(this.randomRmattack1Timer);
this.randomRmattack1Timer = null;
}
if (this.rollMasterAttacks && this.rollMasterAttacks.length > 0) {
this.rollMasterAttacks.forEach(function (atk) {
if (atk.visual && atk.visual.destroy && !atk.visual.destroyed) {
if (atk.visual.parent) {
atk.visual.parent.removeChild(atk.visual);
}
atk.visual.destroy();
}
});
this.rollMasterAttacks = [];
}
if (player && typeof player.clearRollTimeouts === 'function') {
player.clearRollTimeouts();
}
var oldHighScore = parseInt(storage.rollMasterHighScore, 10) || 0;
var newRecordRM = false;
if (finalTime > oldHighScore) {
storage.rollMasterHighScore = finalTime;
this.rollMasterHighScore = finalTime;
newRecordRM = true;
}
if (player && player.alpha !== 0) {
player.alpha = 0; // Ukryj gracza, ale nie niszcz od razu
}
var memeAndScoreDisplayDuration = 3000;
var deathMemeVisualRM = null;
var scoreTextVisualRM = null;
var memeAssets = ['deathMeme1', 'deathMeme2', 'deathMeme3', 'deathMeme4', 'deathMeme5'];
var randomMemeAssetName = memeAssets[Math.floor(Math.random() * memeAssets.length)];
try {
deathMemeVisualRM = LK.getAsset(randomMemeAssetName, {
anchorX: 0.5,
anchorY: 0.5,
alpha: 0,
x: 2048 / 2,
y: 2732 / 2 - 200,
scaleX: 0.8,
scaleY: 0.8
});
// Dodaj mema NAD istniejącym tłem (rollMasterBg), jeśli currentSceneElements jest nad nim
// lub bezpośrednio do 'game' na odpowiedniej warstwie.
// Jeśli currentSceneElements jest używane dla elementów pop-up, to jest dobre miejsce.
game.addChild(deathMemeVisualRM); // Lub currentSceneElements.addChild(deathMemeVisualRM);
tween(deathMemeVisualRM, {
alpha: 1
}, {
duration: 500,
easing: tween.easeIn
});
} catch (e) {
console.error("[EndRollMasterMode] Błąd ładowania mema:", e);
}
var timeMessage = "Your Time: " + String(Math.floor(finalTime / 60)).padStart(2, '0') + ':' + String(finalTime % 60).padStart(2, '0');
if (newRecordRM) {
timeMessage += "\n(NEW HIGH SCORE!)";
} else {
timeMessage += "\nBest: " + String(Math.floor(this.rollMasterHighScore / 60)).padStart(2, '0') + ':' + String(this.rollMasterHighScore % 60).padStart(2, '0');
}
scoreTextVisualRM = new Text2(timeMessage, {
size: 70,
fill: 0xFFFFFF,
align: 'center',
stroke: 0x000000,
strokeThickness: 4,
alpha: 0
});
scoreTextVisualRM.anchor.set(0.5, 0.5);
scoreTextVisualRM.x = 2048 / 2;
var memeHeightEstimate = deathMemeVisualRM && deathMemeVisualRM.height ? deathMemeVisualRM.height * (deathMemeVisualRM.scaleY || 1) : 560; // 700*0.8
scoreTextVisualRM.y = (deathMemeVisualRM ? deathMemeVisualRM.y + memeHeightEstimate / 2 : 2732 / 2 - 200 + 300) + 100;
game.addChild(scoreTextVisualRM); // Lub currentSceneElements.addChild(scoreTextVisualRM);
tween(scoreTextVisualRM, {
alpha: 1
}, {
duration: 500,
delay: 300,
easing: tween.easeIn
});
if (ui && ui.positionElements) {
ui.positionElements("rollMasterGameOver");
}
if (ui && ui.updateHighScoreDisplay) {
ui.updateHighScoreDisplay(this.rollMasterHighScore);
} // Pokaż rekord w UI
// Usuń stare aktywne przyciski (jeśli jakieś były)
if (gameState.activeButtons && gameState.activeButtons.length > 0) {
gameState.activeButtons.forEach(function (btn) {
if (btn && btn.destroy && !btn.destroyed) {
if (btn.parent) {
btn.parent.removeChild(btn);
}
btn.destroy();
}
});
}
gameState.activeButtons = [];
LK.setTimeout(function () {
console.log("DEBUG: Timeout #1 (po memie/wyniku) w endRollMasterMode. Pokazywanie przycisków.");
if (deathMemeVisualRM && !deathMemeVisualRM.destroyed) {
tween(deathMemeVisualRM, {
alpha: 0
}, {
duration: 300,
onFinish: function onFinish() {
if (deathMemeVisualRM.parent) {
deathMemeVisualRM.parent.removeChild(deathMemeVisualRM);
}
if (deathMemeVisualRM.destroy) {
deathMemeVisualRM.destroy();
}
deathMemeVisualRM = null;
}
});
}
if (scoreTextVisualRM && !scoreTextVisualRM.destroyed) {
tween(scoreTextVisualRM, {
alpha: 0
}, {
duration: 300,
onFinish: function onFinish() {
if (scoreTextVisualRM.parent) {
scoreTextVisualRM.parent.removeChild(scoreTextVisualRM);
}
if (scoreTextVisualRM.destroy) {
scoreTextVisualRM.destroy();
}
scoreTextVisualRM = null;
}
});
}
// Zniszcz obiekt gracza teraz, gdy już nie jest potrzebny
if (player && player.destroy && !player.destroyed) {
player.destroy();
player = null;
}
// TŁO: currentBackground (czyli rollMasterBg) POWINNO POZOSTAĆ. Nie niszczymy go.
// NIE wywołuj game.setBackgroundColor, aby nie nadpisać rollMasterBg.
// if (currentBackground) {
// // Jeśli currentBackground to było coś specyficznego dla mema, a nie rollMasterBg,
// // to tutaj trzeba by je usunąć i ewentualnie przywrócić rollMasterBg.
// // Ale zakładamy, że currentBackground to rollMasterBg od początku tego trybu.
// }
// game.setBackgroundColor(0x1a1a1a); // <--- USUWAMY TĘ LINIĘ (lub podobną)
var buttonYStart = 900;
var buttonSpacing = 220;
// Przycisk "Main Menu"
var menuButton = new Container(); /* ... reszta definicji bez zmian ... */
menuButton.interactive = true;
menuButton.cursor = "pointer";
var menuButtonBg = new Shape({
width: 400,
height: 100,
color: 0x555555,
shape: 'box'
});
try {
var menuButtonAsset = LK.getAsset('mainmenu', {
anchorX: 0.5,
anchorY: 0.5
});
menuButton.addChild(menuButtonAsset);
} catch (e) {
console.error("Błąd ładowania assetu przycisku 'mainmenu':", e);
var menuButtonBgFallback = new Shape({
width: 400,
height: 100,
color: 0x555555,
shape: 'box'
});
menuButton.addChild(menuButtonBgFallback);
// Możesz dodać awaryjny Text2, jeśli chcesz, gdyby asset się nie załadował
// var menuButtonTextFallback = new Text2("Main Menu", { size: 40, fill: 0xFFFFFF, anchor: { x: 0.5, y: 0.5 } });
// menuButton.addChild(menuButtonTextFallback);
}
menuButton.x = 2048 / 2;
menuButton.y = buttonYStart;
menuButton.down = function () {
if (gameState.currentState !== "rollMasterGameOver") {
return;
}
gameState.activeButtons.forEach(function (btn) {
if (btn && btn.destroy && !btn.destroyed) {
if (btn.parent) {
btn.parent.removeChild(btn);
}
btn.destroy();
}
});
gameState.activeButtons = [];
// WAŻNE: Zanim przejdziesz do GrillMenu, zniszcz currentBackground (rollMasterBg)
if (currentBackground && currentBackground.destroy && !currentBackground.destroyed) {
if (currentBackground.parent) {
currentBackground.parent.removeChild(currentBackground);
}
currentBackground.destroy();
currentBackground = null;
}
gameState.showGrillScreen();
};
game.addChild(menuButton); // Lub currentSceneElements.addChild(menuButton);
gameState.activeButtons.push(menuButton);
// Przycisk "Restart Mode"
var restartButton = new Container(); /* ... reszta definicji bez zmian ... */
try {
var restartButtonAsset = LK.getAsset('restartgame', {
anchorX: 0.5,
anchorY: 0.5
});
restartButton.addChild(restartButtonAsset);
} catch (e) {
console.error("Błąd ładowania assetu przycisku 'restartgame':", e);
var restartButtonBgFallback = new Shape({
width: 400,
height: 100,
color: 0x4477FF,
shape: 'box'
});
restartButton.addChild(restartButtonBgFallback);
// Możesz dodać awaryjny Text2
// var restartButtonTextFallback = new Text2("Restart Mode", { size: 40, fill: 0xFFFFFF, anchor: { x: 0.5, y: 0.5 } });
// restartButton.addChild(restartButtonTextFallback);
}
restartButton.x = 2048 / 2;
restartButton.y = buttonYStart + buttonSpacing;
restartButton.down = function () {
if (gameState.currentState !== "rollMasterGameOver") {
return;
}
gameState.activeButtons.forEach(function (btn) {
if (btn && btn.destroy && !btn.destroyed) {
if (btn.parent) {
btn.parent.removeChild(btn);
}
btn.destroy();
}
});
gameState.activeButtons = [];
// WAŻNE: Zanim zrestartujesz tryb, zniszcz currentBackground (rollMasterBg)
if (currentBackground && currentBackground.destroy && !currentBackground.destroyed) {
if (currentBackground.parent) {
currentBackground.parent.removeChild(currentBackground);
}
currentBackground.destroy();
currentBackground = null;
}
gameState.startRollMasterMode();
};
game.addChild(restartButton); // Lub currentSceneElements.addChild(restartButton);
gameState.activeButtons.push(restartButton);
}, memeAndScoreDisplayDuration);
},
startCursedCrystalMode: function startCursedCrystalMode() {
if (this.currentIntroMusicInstance && this.currentIntroMusicInstance.stop) {
this.currentIntroMusicInstance.stop();
this.currentIntroMusicInstance = null;
}
if (this.currentRollSoulsInstance && this.currentRollSoulsInstance.stop) {
this.currentRollSoulsInstance.stop();
this.currentRollSoulsInstance = null;
}
if (this.currentRollMasterMusicInstance && this.currentRollMasterMusicInstance.stop) {
this.currentRollMasterMusicInstance.stop();
this.currentRollMasterMusicInstance = null;
}
if (this.currentCursedCrystalMusicInstance && this.currentCursedCrystalMusicInstance.stop) {
this.currentCursedCrystalMusicInstance.stop();
this.currentCursedCrystalMusicInstance = null;
}
var randomIndex = Math.floor(Math.random() * this.cursedCrystalMusicTracks.length);
var selectedTrackId = this.cursedCrystalMusicTracks[randomIndex];
console.log(Date.now() + " [StartCursedCrystal] Próba odtworzenia: " + selectedTrackId);
this.currentCursedCrystalMusicInstance = LK.playMusic(selectedTrackId, {
loop: true,
volume: 0.7
});
// Logowanie wartości zwróconej przez LK.playMusic
console.log(Date.now() + " [StartCursedCrystal] LK.playMusic zwróciło dla '" + selectedTrackId + "':", this.currentCursedCrystalMusicInstance);
if (!this.currentCursedCrystalMusicInstance) {
console.error(Date.now() + " [StartCursedCrystal] Nie udało się odtworzyć muzyki (instancja jest falsy): " + selectedTrackId);
} else {
console.log(Date.now() + " [StartCursedCrystal] Instancja muzyki wydaje się być utworzona dla: " + selectedTrackId);
}
console.log("[StartCursedCrystal] Rozpoczynanie trybu Cursed Crystal...");
console.log("[StartCursedCrystal] Rozpoczynanie trybu Cursed Crystal...");
this.currentState = "cursedCrystal";
// 1. Zatrzymaj muzykę z innych trybów
if (this.currentIntroMusicInstance && this.currentIntroMusicInstance.stop) {
this.currentIntroMusicInstance.stop();
this.currentIntroMusicInstance = null;
}
if (this.currentRollSoulsInstance && this.currentRollSoulsInstance.stop) {
this.currentRollSoulsInstance.stop();
this.currentRollSoulsInstance = null;
}
if (this.currentRollMasterMusicInstance && this.currentRollMasterMusicInstance.stop) {
this.currentRollMasterMusicInstance.stop();
this.currentRollMasterMusicInstance = null;
}
// TODO: Odtwórz muzykę specyficzną dla Cursed Crystal, jeśli ją masz
// np. this.currentCursedCrystalMusic = LK.playMusic('cursedCrystalMusic_asset', { loop: true, volume: 0.7 });
// 2. Wyczyść scenę z elementów poprzedniego trybu
if (typeof clearScene === 'function') {
clearScene(); // Usuwa elementy z currentSceneElements
} else {
console.warn("Funkcja clearScene nie jest zdefiniowana globalnie!");
}
// Dokładne czyszczenie specyficznych elementów z innych trybów:
// Czyszczenie timerów i ataków z Roll Master
if (this.rollMasterTimerInterval) {
LK.clearInterval(this.rollMasterTimerInterval);
this.rollMasterTimerInterval = null;
}
if (this.randomRmattack1Timer) {
// Timer dla rmattack1 w Roll Master
LK.clearTimeout(this.randomRmattack1Timer);
this.randomRmattack1Timer = null;
}
if (this.rollMasterAttacks && this.rollMasterAttacks.length > 0) {
console.log("[StartCursedCrystal] Czyszczenie " + this.rollMasterAttacks.length + " ataków z Roll Master.");
this.rollMasterAttacks.forEach(function (atk) {
if (atk.visual && atk.visual.destroy && !atk.visual.destroyed) {
if (atk.visual.parent) {
// Dodatkowe zabezpieczenie
atk.visual.parent.removeChild(atk.visual);
}
atk.visual.destroy();
}
// Jeśli atak miał inne zasoby (np. własne timery), też powinny być tu czyszczone
});
this.rollMasterAttacks = [];
}
// Usuń specyficzne efekty z Grill Menu, jeśli istnieją
if (this.grillMenuEffects && this.grillMenuEffects.length > 0) {
console.log("[StartCursedCrystal] Czyszczenie " + this.grillMenuEffects.length + " efektów z Grill Menu.");
this.grillMenuEffects.forEach(function (effect) {
if (effect && effect.destroy && !effect.destroyed) {
if (effect.parent) {
// Dodatkowe zabezpieczenie
effect.parent.removeChild(effect);
}
effect.destroy();
}
});
this.grillMenuEffects = [];
}
// Usuń globalne obiekty gracza i bossa, jeśli istnieją
if (player && player.destroy && !player.destroyed) {
// Jeśli gracz ma jakieś specyficzne timery/interwały, które nie są czyszczone w jego .destroy() lub .clearRollTimeouts()
// to tutaj byłoby miejsce na ich wyczyszczenie przed zniszczeniem obiektu player.
// player.clearRollTimeouts(); // To powinno być wywoływane w Player.die() lub przed destroy()
player.destroy();
}
player = null;
if (boss && boss.destroy && !boss.destroyed) {
if (typeof boss.clearAllAttacks === 'function') {
boss.clearAllAttacks("startCursedCrystalMode"); // Upewnij się, że ataki bossa są czyszczone
}
// Podobnie, jeśli boss ma jakieś globalne timery nieczyszczone w .destroy()
boss.destroy();
}
boss = null;
// Usuń istniejące tło, jeśli jest
if (currentBackground && currentBackground.destroy) {
tween.stop(currentBackground); // Zatrzymaj animacje tła, jeśli były
currentBackground.destroy();
currentBackground = null;
}
// Usuń obiekt Klejnotu, jeśli istniał z poprzedniej sesji tego trybu
if (typeof crystalCoreObject !== 'undefined' && crystalCoreObject && crystalCoreObject.destroy && !crystalCoreObject.destroyed) {
crystalCoreObject.destroy();
}
crystalCoreObject = null;
// 3. Wywołaj konfigurację sceny dla Cursed Crystal
// Zamiast this.setupCursedCrystalScene_MinimalTest(); wywołaj normalną funkcję:
this.setupCursedCrystalScene();
// Aby użyć minimalnej sceny testowej, odkomentuj poniższe i zakomentuj powyższe:
// this.setupCursedCrystalScene_MinimalTest();
},
updateCrystalVisual: function updateCrystalVisual() {
if (!crystalCoreObject || crystalCoreObject.destroyed || this.isMinibossActiveCC || this.cursedCrystalChargeLevel >= this.cursedCrystalTargetCharge || this.crystalExploding) {
return;
}
var newAssetId = 'crystal_state_0';
var charge = this.cursedCrystalChargeLevel;
var targetCharge = this.cursedCrystalTargetCharge;
var thresholds = {
state1: 0.15 * targetCharge,
state2: 0.30 * targetCharge,
state3: 0.55 * targetCharge,
state4: 0.80 * targetCharge
};
if (charge >= thresholds.state4) {
newAssetId = 'crystal_state_4';
} else if (charge >= thresholds.state3) {
newAssetId = 'crystal_state_3';
} else if (charge >= thresholds.state2) {
newAssetId = 'crystal_state_2';
} else if (charge >= thresholds.state1) {
newAssetId = 'crystal_state_1';
}
if (crystalCoreObject.currentVisual && crystalCoreObject.currentVisual.assetId === newAssetId) {
return;
}
if (crystalCoreObject.currentVisual) {
crystalCoreObject.removeChild(crystalCoreObject.currentVisual);
if (crystalCoreObject.currentVisual.destroy && !crystalCoreObject.currentVisual.destroyed) {
crystalCoreObject.currentVisual.destroy();
}
crystalCoreObject.currentVisual = null;
}
try {
var newVisual = LK.getAsset(newAssetId, {
anchorX: 0.5,
anchorY: 0.5
});
crystalCoreObject.addChild(newVisual);
crystalCoreObject.currentVisual = newVisual;
crystalCoreObject.currentVisual.assetId = newAssetId;
console.log("Wizualizacja kryształu zmieniona na: " + newAssetId + " (naładowanie: " + charge + ")");
} catch (e) {
console.error("Błąd ładowania assetu kryształu: " + newAssetId, e);
if (newAssetId !== 'crystal_state_0' && (!crystalCoreObject.currentVisual || crystalCoreObject.currentVisual.assetId !== 'crystal_state_0')) {
try {
var baseVisual = LK.getAsset('crystal_state_0', {
anchorX: 0.5,
anchorY: 0.5
});
crystalCoreObject.addChild(baseVisual);
crystalCoreObject.currentVisual = baseVisual;
crystalCoreObject.currentVisual.assetId = 'crystal_state_0';
} catch (e2) {
console.error("Błąd ładowania awaryjnego assetu 'crystal_state_0'", e2);
}
}
}
},
playCrystalExplosionAnimation: function playCrystalExplosionAnimation() {
var crystalOriginalX = 2048 / 2; // Domyślna pozycja X na środek ekranu
var crystalOriginalY = 2732 / 2; // Domyślna pozycja Y na środek ekranu
if (crystalCoreObject && !crystalCoreObject.destroyed) {
// Jeśli crystalCoreObject istnieje, użyj jego aktualnej pozycji
crystalOriginalX = crystalCoreObject.x;
crystalOriginalY = crystalCoreObject.y;
console.log("Odtwarzanie animacji eksplozji kryształu w pozycji: X=" + crystalOriginalX + ", Y=" + crystalOriginalY);
// Zatrzymaj istniejące tweeny na kontenerze kryształu (lewitacja, pulsowanie)
if (crystalCoreObject.levitationTween && crystalCoreObject.levitationTween.stop) {
crystalCoreObject.levitationTween.stop();
crystalCoreObject.levitationTween = null; // Wyczyść referencję
}
if (crystalCoreObject.scaleTween && crystalCoreObject.scaleTween.stop) {
crystalCoreObject.scaleTween.stop();
crystalCoreObject.scaleTween = null; // Wyczyść referencję
}
// Można dodać reset pozycji/skali, jeśli tweeny mogły je zmienić w sposób niepożądany dla animacji
crystalCoreObject.scale.set(1); // Przywróć domyślną skalę kontenera
// crystalCoreObject.y = crystalCoreObject.originalY; // Jeśli chcesz przywrócić oryginalną Y przed animacją
// Usuń obecną statyczną grafikę kryształu (dziecko kontenera crystalCoreObject)
if (crystalCoreObject.currentVisual) {
crystalCoreObject.removeChild(crystalCoreObject.currentVisual);
if (crystalCoreObject.currentVisual.destroy && !crystalCoreObject.currentVisual.destroyed) {
crystalCoreObject.currentVisual.destroy();
}
crystalCoreObject.currentVisual = null;
}
var explosionFramesAssets = [];
for (var i = 0; i < 4; i++) {
// Załóżmy 4 klatki animacji
try {
// Upewnij się, że anchorX i anchorY są ustawione, jeśli Twoje klatki tego wymagają
// dla poprawnego pozycjonowania wewnątrz SpriteAnimation
explosionFramesAssets.push(LK.getAsset('crystal_explosion_f' + i, {
anchorX: 0.5,
anchorY: 0.5,
clone: true
}));
} catch (e) {
console.error("Błąd ładowania klatki eksplozji: crystal_explosion_f" + i, e);
// Awaryjny placeholder jeśli klatki brakuje, aby animacja nadal miała odpowiednią długość
var placeholderFrame = new Shape({
width: 150,
height: 150,
color: 0xFF8C00,
shape: 'ellipse'
});
placeholderFrame.anchor.set(0.5, 0.5); // Ustaw anchor dla Shape
explosionFramesAssets.push(placeholderFrame);
}
}
var explosionAnim = new SpriteAnimation({
frames: explosionFramesAssets,
frameDuration: 150,
// Czas trwania klatki w ms (np. 150ms * 4 klatki = 0.6s) - dostosuj
loop: false,
anchorX: 0.5,
// Anchor samej animacji SpriteAnimation, jeśli ma być inaczej pozycjonowana w crystalCoreObject
anchorY: 0.5
// x i y będą 0,0 względem kontenera crystalCoreObject, jeśli animacja ma być na jego środku
});
crystalCoreObject.addChild(explosionAnim); // Dodaj animację eksplozji do kontenera kryształu
explosionAnim.play();
// TODO: Dodaj tutaj swój dźwięk eksplozji, jeśli masz
// np. LK.getSound('twoj_dzwiek_eksplozji_krysztalu').play();
} else {
// Ten blok jest awaryjny, jeśli crystalCoreObject nie istnieje, gdy funkcja jest wołana
console.warn("Próba eksplozji, ale crystalCoreObject nie istnieje lub został zniszczony przed rozpoczęciem animacji. Bezpośrednie spawnowanie Minibossa w domyślnej pozycji.");
this.spawnCursedCrystalMiniboss(crystalOriginalX, crystalOriginalY);
this.crystalExploding = false; // Zresetuj flagę
return; // Zakończ funkcję, jeśli nie ma kryształu do animowania
}
var selfGameState = this; // Zachowaj referencję 'this' (gameState) dla callbacku onComplete
explosionAnim.onComplete = function () {
console.log("Animacja eksplozji kryształu zakończona.");
// Zniszcz kontener crystalCoreObject po zakończeniu animacji
if (crystalCoreObject && !crystalCoreObject.destroyed) {
if (crystalCoreObject.parent) {
crystalCoreObject.parent.removeChild(crystalCoreObject);
}
crystalCoreObject.destroy();
}
crystalCoreObject = null; // Wyczyść globalną/dostępną referencję
// Wywołaj logikę spawnowania Minibossa, przekazując zapisaną pozycję
selfGameState.spawnCursedCrystalMiniboss(crystalOriginalX, crystalOriginalY);
selfGameState.crystalExploding = false; // Zresetuj flagę po zakończeniu całego procesu
};
},
spawnCursedCrystalMiniboss: function spawnCursedCrystalMiniboss(spawnX, spawnY) {
// Argumenty to spawnX i spawnY
console.log("MINIBOSS SPAWNING SEQUENCE INITIATED! Pozycja: X=" + spawnX + ", Y=" + spawnY);
var self = this;
self.isMinibossActiveCC = true;
if (ui && ui.crystalChargeBarContainer) {
ui.crystalChargeBarContainer.alpha = 0;
}
var timeSurvivedInMinutes = Math.floor(self.cursedCrystalTimeSurvived / (60 * 60));
var baseMinibossHp = 200;
var hpPerMinute = 100;
var scaledMaxHp = baseMinibossHp + timeSurvivedInMinutes * hpPerMinute;
scaledMaxHp = Math.max(baseMinibossHp, scaledMaxHp);
self.cursedCrystalMinibossMaxHP = scaledMaxHp;
self.cursedCrystalMinibossHP = scaledMaxHp;
var twoMinutesInFrames = 1 * 60 * 60; // 2 minuty * 60 sekund * 60 klatek/sekundę
if (self.cursedCrystalTimeSurvived >= twoMinutesInFrames) {
console.log("GRACZ PRZETRWAŁ PONAD 2 MINUTY! Miniboss otrzymuje ulepszone ataki!");
self.minibossEnhancedProjectile = true;
self.minibossEnhancedLaserWall = true;
} else {
self.minibossEnhancedProjectile = false;
self.minibossEnhancedLaserWall = false;
}
self.cursedCrystalEnemies.forEach(function (soul) {
if (soul && !soul.isDead) {
if (soul.parent) {
soul.parent.removeChild(soul);
}
if (soul.destroy && !soul.destroyed) {
soul.destroy();
}
}
});
self.cursedCrystalEnemies = [];
self.cursedCrystalChargeLevel = 0;
if (ui && ui.updateCrystalCharge) {
ui.updateCrystalCharge(self.cursedCrystalChargeLevel, self.cursedCrystalTargetCharge);
}
LK.setTimeout(function () {
if (self.currentState !== "cursedCrystal" || !self.isMinibossActiveCC) {
console.log("Spawn Minibossa anulowany - zmiana stanu gry lub isMinibossActiveCC jest false.");
return;
}
console.log("FAZA SPAWNU MINIBOSSA PO OPÓŹNIENIU");
var minibossOptions = {
maxHp: scaledMaxHp,
x: spawnX,
// ***** POPRAWKA: Używamy argumentu funkcji spawnX *****
y: spawnY // ***** POPRAWKA: Używamy argumentu funkcji spawnY *****
};
self.cursedCrystalMinibossObject = game.addChild(new MinibossCC(minibossOptions));
if (self.cursedCrystalMinibossObject) {
self.cursedCrystalMinibossObject.alpha = 0;
tween(self.cursedCrystalMinibossObject, {
alpha: 1
}, {
duration: 1500,
easing: tween.easeInQuad,
onFinish: function onFinish() {
console.log("Miniboss w pełni widoczny.");
}
});
if (ui && ui.updateMinibossHealthCC) {
ui.updateMinibossHealthCC(self.cursedCrystalMinibossHP, self.cursedCrystalMinibossMaxHP, true);
}
if (ui && ui.showMessage) {
ui.showMessage("Demon Pepe is here", 3000);
}
} else {
console.error("Nie udało się stworzyć obiektu MinibossCC!");
}
}, 3000);
},
setupCursedCrystalScene: function setupCursedCrystalScene() {
console.log("[SetupCursedCrystal] Konfiguracja sceny Cursed Crystal...");
// 1. Ustaw tło dla areny
try {
// Upewnij się, że stary currentBackground jest niszczony, jeśli istnieje
if (typeof currentBackground !== 'undefined' && currentBackground && currentBackground.destroy && !currentBackground.destroyed) {
currentBackground.destroy();
}
currentBackground = LK.getAsset('cursedCrystalArenaBg_asset', {
anchorX: 0,
// Zgodnie z Twoim kodem
anchorY: 0,
// Zgodnie z Twoim kodem
x: 0,
// Zgodnie z Twoim kodem
y: 0 // Zgodnie z Twoim kodem
});
game.addChildAt(currentBackground, 0);
} catch (e) {
console.warn("Nie znaleziono assetu tła Cursed Crystal, używam domyślnego koloru.", e);
game.setBackgroundColor(0x100510);
if (typeof currentBackground !== 'undefined' && currentBackground && currentBackground.destroy && !currentBackground.destroyed) {
currentBackground.destroy();
}
currentBackground = null;
}
// 2. Stwórz obiekt Klejnotu Dusz (CrystalCore) jako KONTENER
if (typeof crystalCoreObject !== 'undefined' && crystalCoreObject) {
if (crystalCoreObject.levitationTween && crystalCoreObject.levitationTween.stop) {
crystalCoreObject.levitationTween.stop();
}
if (crystalCoreObject.scaleTween && crystalCoreObject.scaleTween.stop) {
crystalCoreObject.scaleTween.stop();
}
if (crystalCoreObject.destroy && !crystalCoreObject.destroyed) {
crystalCoreObject.destroy();
}
}
crystalCoreObject = new Container(); // *** NOWOŚĆ: crystalCoreObject jest teraz kontenerem ***
crystalCoreObject.x = 2048 / 2;
crystalCoreObject.y = 2732 / 2;
crystalCoreObject.collisionRadius = 60;
game.addChild(crystalCoreObject);
// Dodaj początkową wizualizację kryształu (0% naładowania)
try {
var initialCrystalVisual = LK.getAsset('crystal_state_0', {
anchorX: 0.5,
anchorY: 0.5
});
crystalCoreObject.addChild(initialCrystalVisual);
crystalCoreObject.currentVisual = initialCrystalVisual; // Referencja do aktualnej grafiki
crystalCoreObject.currentVisual.assetId = 'crystal_state_0'; // Do sprawdzania, co jest wyświetlane
} catch (e) {
console.error("KRYTYCZNY BŁĄD: Nie można załadować 'crystal_state_0'!", e);
// Awaryjny kształt, jeśli assetu nie ma
var fallbackVisual = new Shape({
width: 100,
height: 100,
color: 0x8A2BE2,
shape: 'ellipse'
});
fallbackVisual.anchor.set(0.5);
crystalCoreObject.addChild(fallbackVisual);
crystalCoreObject.currentVisual = fallbackVisual;
crystalCoreObject.currentVisual.assetId = 'fallback_crystal';
}
// Animacje lewitacji i pulsowania dla kontenera crystalCoreObject
if (crystalCoreObject && !crystalCoreObject.destroyed) {
crystalCoreObject.originalY = crystalCoreObject.y;
var levitationHeight = 30;
var oneWayDuration = 1250;
var levitateCrystalUp = function levitateCrystalUp(targetObject) {
if (!targetObject || targetObject.destroyed) {
return;
}
if (targetObject.levitationTween && targetObject.levitationTween.stop) {
targetObject.levitationTween.stop();
}
targetObject.y = targetObject.originalY;
targetObject.levitationTween = tween(targetObject, {
y: targetObject.originalY - levitationHeight
}, {
duration: oneWayDuration,
easing: tween.easeInOut,
onFinish: function onFinish() {
if (targetObject && !targetObject.destroyed) {
levitateCrystalDown(targetObject);
}
}
});
};
var levitateCrystalDown = function levitateCrystalDown(targetObject) {
if (!targetObject || targetObject.destroyed) {
return;
}
if (targetObject.levitationTween && targetObject.levitationTween.stop) {
targetObject.levitationTween.stop();
}
targetObject.levitationTween = tween(targetObject, {
y: targetObject.originalY
}, {
duration: oneWayDuration,
easing: tween.easeInOut,
onFinish: function onFinish() {
if (targetObject && !targetObject.destroyed) {
levitateCrystalUp(targetObject);
}
}
});
};
levitateCrystalUp(crystalCoreObject);
if (crystalCoreObject.scaleTween && crystalCoreObject.scaleTween.stop) {
crystalCoreObject.scaleTween.stop();
}
crystalCoreObject.baseScaleX = crystalCoreObject.scaleX || 1; // Kontener zwykle ma scale 1
crystalCoreObject.baseScaleY = crystalCoreObject.scaleY || 1;
crystalCoreObject.scaleTween = tween(crystalCoreObject, {
scaleX: crystalCoreObject.baseScaleX * 1.05,
scaleY: crystalCoreObject.baseScaleY * 1.05
}, {
duration: oneWayDuration * 2 * 0.75,
easing: tween.easeInOut,
yoyo: true,
repeat: Infinity
});
}
// 3. Stwórz nowy obiekt gracza (bez zmian w tej sekcji)
if (typeof player !== 'undefined' && player && player.destroy && !player.destroyed) {
player.destroy();
}
player = game.addChild(new Player());
player.health = this.cursedCrystalPlayerMaxHealth;
player.x = 2048 / 2;
player.y = (crystalCoreObject && crystalCoreObject.y ? crystalCoreObject.y : 2732 / 2) + (crystalCoreObject && crystalCoreObject.currentVisual && crystalCoreObject.currentVisual.height ? crystalCoreObject.currentVisual.height / 2 : 50) + player.height / 2 + 50; // Pozycjonowanie względem środka/dolnej krawędzi kryształu
player.dead = false;
player.rolling = false;
player.invulnerable = false;
player.rollCooldown = 0;
if (player.clearRollTimeouts) {
player.clearRollTimeouts();
}
this.currentInputPos.x = player.x;
this.currentInputPos.y = player.y;
this.isInputActive = false;
this.cursedCrystalPlayerHealth = this.cursedCrystalPlayerMaxHealth; // Zakładam, że this.cursedCrystalPlayerMaxHealth jest zdefiniowane (np. w gameState lub przekazane)
this.cursedCrystalChargeLevel = 0;
this.cursedCrystalSoulsHitCrystal = 0;
this.cursedCrystalScore = 0;
this.cursedCrystalHighScore = parseInt(storage.cursedCrystalHighScore, 10) || 0;
this.cursedCrystalEnemies = [];
this.cursedCrystalEnemySpawnTimer = 0;
this.cursedCrystalSoulsPerGroupMin = 1;
this.cursedCrystalSoulsPerGroupMax = 5;
this.cursedCrystalGroupSpawnSpread = 60;
this.cursedCrystalBaseSpawnInterval = 90; // Interwał między grupami (np. 3 sekundy)
this.cursedCrystalCurrentSpawnInterval = this.cursedCrystalBaseSpawnInterval;
this.cursedCrystalDifficultyTimer = 0;
this.cursedCrystalEnemyBaseSpeed = 0.1; // Ustawiamy niższą bazową prędkość
this.cursedCrystalEnemyCurrentMaxSpeed = this.cursedCrystalEnemyBaseSpeed; // Aktualna prędkość startuje od bazowej
this.cursedCrystalTimeSurvived = 0;
this.isMinibossActiveCC = false;
this.crystalExploding = false;
if (this.cursedCrystalMinibossObject && this.cursedCrystalMinibossObject.destroy && !this.cursedCrystalMinibossObject.destroyed) {
this.cursedCrystalMinibossObject.destroy();
}
this.cursedCrystalMinibossObject = null;
this.cursedCrystalMinibossHP = 0;
this.cursedCrystalActiveProjectiles = [];
this.cursedCrystalActiveExplosions = [];
this.cursedCrystalActiveLaserWalls = [];
// 5. Konfiguracja UI (bez zmian w tej sekcji)
if (ui) {
ui.positionElements("cursedCrystal");
if (ui.updateScoreCC) {
ui.updateScoreCC(this.cursedCrystalScore);
}
if (ui.updateHighScoreCC) {
ui.updateHighScoreCC(this.cursedCrystalHighScore);
}
if (ui.updateCrystalCharge) {
ui.updateCrystalCharge(this.cursedCrystalChargeLevel, this.cursedCrystalTargetCharge);
}
if (ui.updateHearts) {
ui.updateHearts(this.cursedCrystalPlayerHealth, this.cursedCrystalPlayerMaxHealth);
}
if (ui.updateMinibossHealthCC) {
ui.updateMinibossHealthCC(0, this.cursedCrystalMinibossMaxHP, false);
}
if (ui.bossHealthBarContainer) {
ui.bossHealthBarContainer.alpha = 0;
}
if (ui.timerText) {
ui.timerText.alpha = 0;
}
if (ui.highScoreText) {
ui.highScoreText.alpha = 0;
}
if (ui.deathsText) {
ui.deathsText.alpha = 0;
}
}
// 6. Wyświetl startowy komunikat (bez zmian)
if (ui && ui.showMessage) {
ui.showMessage("Protect the Soul Gem!", 3000);
}
this.gameActive = true;
console.log("[SetupCursedCrystal] Scena Cursed Crystal skonfigurowana. Gracz HP: " + player.health);
}
}, "setupCursedCrystalScene", function setupCursedCrystalScene() {
// <<< NOWY KOD setupCursedCrystalScene ZACZYNA SIĘ TUTAJ >>>
console.log("[SetupCursedCrystal] Konfiguracja sceny Cursed Crystal...");
// 1. Ustaw tło dla areny
try {
if (typeof currentBackground !== 'undefined' && currentBackground && currentBackground.destroy && !currentBackground.destroyed) {
currentBackground.destroy();
}
currentBackground = LK.getAsset('cursedCrystalArenaBg_asset', {
// Podmień na swój asset tła
anchorX: 0,
anchorY: 0,
x: 0,
y: -100
});
game.addChildAt(currentBackground, 0);
} catch (e) {
console.warn("Nie znaleziono assetu tła Cursed Crystal, używam domyślnego koloru.", e);
game.setBackgroundColor(0x100510);
if (typeof currentBackground !== 'undefined' && currentBackground && currentBackground.destroy && !currentBackground.destroyed) {
currentBackground.destroy();
}
currentBackground = null;
}
// 2. Stwórz obiekt Klejnotu Dusz (CrystalCore) jako KONTENER
if (typeof crystalCoreObject !== 'undefined' && crystalCoreObject) {
if (crystalCoreObject.levitationTween && crystalCoreObject.levitationTween.stop) {
crystalCoreObject.levitationTween.stop();
}
if (crystalCoreObject.scaleTween && crystalCoreObject.scaleTween.stop) {
crystalCoreObject.scaleTween.stop();
}
if (crystalCoreObject.destroy && !crystalCoreObject.destroyed) {
crystalCoreObject.destroy();
}
}
crystalCoreObject = new Container(); // *** NOWOŚĆ: crystalCoreObject jest teraz kontenerem ***
crystalCoreObject.x = 2048 / 2;
crystalCoreObject.y = 2732 / 2;
game.addChild(crystalCoreObject);
try {
var initialCrystalVisual = LK.getAsset('crystal_state_0', {
anchorX: 0.5,
anchorY: 0.5
});
crystalCoreObject.addChild(initialCrystalVisual);
crystalCoreObject.currentVisual = initialCrystalVisual;
crystalCoreObject.currentVisual.assetId = 'crystal_state_0';
} catch (e) {
console.error("KRYTYCZNY BŁĄD: Nie można załadować 'crystal_state_0'!", e);
var fallbackVisual = new Shape({
width: 100,
height: 100,
color: 0x8A2BE2,
shape: 'ellipse'
});
fallbackVisual.anchor.set(0.5);
crystalCoreObject.addChild(fallbackVisual);
crystalCoreObject.currentVisual = fallbackVisual;
crystalCoreObject.currentVisual.assetId = 'fallback_crystal';
}
if (crystalCoreObject && !crystalCoreObject.destroyed) {
crystalCoreObject.originalY = crystalCoreObject.y;
var levitationHeight = 30;
var oneWayDuration = 1250;
var levitateCrystalUp = function levitateCrystalUp(targetObject) {
if (!targetObject || targetObject.destroyed) {
return;
}
if (targetObject.levitationTween && targetObject.levitationTween.stop) {
targetObject.levitationTween.stop();
}
targetObject.y = targetObject.originalY;
targetObject.levitationTween = tween(targetObject, {
y: targetObject.originalY - levitationHeight
}, {
duration: oneWayDuration,
easing: tween.easeInOut,
onFinish: function onFinish() {
if (targetObject && !targetObject.destroyed) {
levitateCrystalDown(targetObject);
}
}
});
};
var levitateCrystalDown = function levitateCrystalDown(targetObject) {
if (!targetObject || targetObject.destroyed) {
return;
}
if (targetObject.levitationTween && targetObject.levitationTween.stop) {
targetObject.levitationTween.stop();
}
targetObject.levitationTween = tween(targetObject, {
y: targetObject.originalY
}, {
duration: oneWayDuration,
easing: tween.easeInOut,
onFinish: function onFinish() {
if (targetObject && !targetObject.destroyed) {
levitateCrystalUp(targetObject);
}
}
});
};
levitateCrystalUp(crystalCoreObject);
if (crystalCoreObject.scaleTween && crystalCoreObject.scaleTween.stop) {
crystalCoreObject.scaleTween.stop();
}
crystalCoreObject.baseScaleX = crystalCoreObject.scaleX || 1;
crystalCoreObject.baseScaleY = crystalCoreObject.scaleY || 1;
crystalCoreObject.scaleTween = tween(crystalCoreObject, {
scaleX: crystalCoreObject.baseScaleX * 1.05,
scaleY: crystalCoreObject.baseScaleY * 1.05
}, {
duration: oneWayDuration * 2 * 0.75,
easing: tween.easeInOut,
yoyo: true,
repeat: Infinity
});
}
if (typeof player !== 'undefined' && player && player.destroy && !player.destroyed) {
player.destroy();
}
player = game.addChild(new Player());
player.health = this.cursedCrystalPlayerMaxHealth; // Użyj this
player.x = 2048 / 2;
player.y = (crystalCoreObject && crystalCoreObject.y ? crystalCoreObject.y : 2732 / 2) + (crystalCoreObject && crystalCoreObject.currentVisual && crystalCoreObject.currentVisual.height ? crystalCoreObject.currentVisual.height / 2 : 50) + player.height / 2 + 50;
player.dead = false;
player.rolling = false;
player.invulnerable = false;
player.rollCooldown = 0;
if (player.clearRollTimeouts) {
player.clearRollTimeouts();
}
this.currentInputPos.x = player.x; // Użyj this
this.currentInputPos.y = player.y; // Użyj this
this.isInputActive = false; // Użyj this
this.cursedCrystalPlayerHealth = this.cursedCrystalPlayerMaxHealth; // Użyj this
this.cursedCrystalChargeLevel = 0; // Użyj this
this.cursedCrystalSoulsHitCrystal = 0; // Użyj this
this.cursedCrystalScore = 0; // Użyj this
this.cursedCrystalHighScore = parseInt(storage.cursedCrystalHighScore, 10) || 0; // Użyj this
this.cursedCrystalEnemies = []; // Użyj this
this.cursedCrystalEnemySpawnTimer = 0; // Użyj this
this.cursedCrystalCurrentSpawnInterval = this.cursedCrystalBaseSpawnInterval; // Użyj this
this.cursedCrystalDifficultyTimer = 0; // Użyj this
this.cursedCrystalEnemyCurrentMaxSpeed = this.cursedCrystalEnemyBaseSpeed; // Użyj this
this.cursedCrystalTimeSurvived = 0; // Użyj this
this.isMinibossActiveCC = false; // Użyj this
this.crystalExploding = false; // *** NOWA FLAGA, użyj this ***
if (this.cursedCrystalMinibossObject && this.cursedCrystalMinibossObject.destroy && !this.cursedCrystalMinibossObject.destroyed) {
// Użyj this
this.cursedCrystalMinibossObject.destroy();
}
this.cursedCrystalMinibossObject = null; // Użyj this
this.cursedCrystalMinibossHP = 0; // Użyj this
this.cursedCrystalActiveProjectiles = []; // Użyj this
this.cursedCrystalActiveExplosions = []; // Użyj this
this.cursedCrystalActiveLaserWalls = []; // Użyj this
if (ui) {
ui.positionElements("cursedCrystal");
if (ui.updateScoreCC) {
ui.updateScoreCC(this.cursedCrystalScore);
}
if (ui.updateHighScoreCC) {
ui.updateHighScoreCC(this.cursedCrystalHighScore);
}
if (ui.updateCrystalCharge) {
ui.updateCrystalCharge(this.cursedCrystalChargeLevel, this.cursedCrystalTargetCharge);
}
if (ui.updateHearts) {
ui.updateHearts(this.cursedCrystalPlayerHealth, this.cursedCrystalPlayerMaxHealth);
}
if (ui.updateMinibossHealthCC) {
ui.updateMinibossHealthCC(0, this.cursedCrystalMinibossMaxHP, false);
}
if (ui.bossHealthBarContainer) {
ui.bossHealthBarContainer.alpha = 0;
}
if (ui.timerText) {
ui.timerText.alpha = 0;
}
if (ui.highScoreText) {
ui.highScoreText.alpha = 0;
}
if (ui.deathsText) {
ui.deathsText.alpha = 0;
}
}
if (ui && ui.showMessage) {
ui.showMessage("Catch them all!", 3000);
}
console.log("[SetupCursedCrystal] Scena Cursed Crystal skonfigurowana. Gracz HP: " + player.health);
// <<< NOWY KOD setupCursedCrystalScene KOŃCZY SIĘ TUTAJ >>>
}), "endCursedCrystalMode", function endCursedCrystalMode(isVictory) {
console.log("DEBUG: gameState.endCursedCrystalMode called. Victory: " + isVictory);
this.currentState = "cursedCrystalGameOver";
// --- CZYSZCZENIE AKTYWNYCH ELEMENTÓW TRYBU CC ---
// 1. Dusze (SoulEnemy)
if (this.cursedCrystalEnemies && this.cursedCrystalEnemies.length > 0) {
console.log("DEBUG: Clearing " + this.cursedCrystalEnemies.length + " remaining SoulEnemies.");
this.cursedCrystalEnemies.forEach(function (enemy) {
if (enemy && enemy.destroy && !enemy.destroyed) {
if (enemy.parent) {
enemy.parent.removeChild(enemy);
}
enemy.destroy();
}
});
}
this.cursedCrystalEnemies = [];
// 2. Pociski Minibossa CC (jeśli gracz zginął, a boss jeszcze strzelał)
if (this.cursedCrystalActiveProjectiles && this.cursedCrystalActiveProjectiles.length > 0) {
console.log("DEBUG: Clearing " + this.cursedCrystalActiveProjectiles.length + " remaining projectiles.");
this.cursedCrystalActiveProjectiles.forEach(function (proj) {
if (proj && proj.destroy && !proj.destroyed) {
if (proj.graphics && proj.graphics.parent) {
proj.graphics.parent.removeChild(proj.graphics);
}
if (proj.graphics && proj.graphics.destroy) {
proj.graphics.destroy();
}
proj.destroy();
}
});
}
this.cursedCrystalActiveProjectiles = [];
// 3. Eksplozje Minibossa CC
if (this.cursedCrystalActiveExplosions && this.cursedCrystalActiveExplosions.length > 0) {
console.log("DEBUG: Clearing " + this.cursedCrystalActiveExplosions.length + " active explosions.");
// Wizualizacje eksplozji są zarządzane przez tweeny i powinny same zniknąć,
// ale czyścimy tablicę logiczną.
}
this.cursedCrystalActiveExplosions = [];
// 4. Ściany Laserowe Minibossa CC
if (this.cursedCrystalActiveLaserWalls && this.cursedCrystalActiveLaserWalls.length > 0) {
console.log("DEBUG: Clearing " + this.cursedCrystalActiveLaserWalls.length + " laser walls.");
this.cursedCrystalActiveLaserWalls.forEach(function (wall) {
if (wall && wall.segments) {
wall.segments.forEach(function (seg) {
if (seg.visual && seg.visual.destroy && !seg.visual.destroyed) {
if (seg.visual.parent) {
seg.visual.parent.removeChild(seg.visual);
}
seg.visual.destroy();
}
});
}
if (wall && wall.warningVisuals && wall.warningVisuals.length > 0) {
wall.warningVisuals.forEach(function (warnVis) {
if (warnVis && warnVis.destroy && !warnVis.destroyed) {
if (warnVis.parent) {
warnVis.parent.removeChild(warnVis);
}
warnVis.destroy();
}
});
}
});
}
this.cursedCrystalActiveLaserWalls = [];
// 5. Sam Miniboss CC (jeśli gracz zginął, a Miniboss nie został pokonany)
// Metoda MinibossCC.die() czyści po sobie, gdy jest pokonany.
// Jeśli gracz umiera, obiekt Minibossa może nadal istnieć.
if (!isVictory && this.cursedCrystalMinibossObject && !this.cursedCrystalMinibossObject.isDead) {
console.log("DEBUG: Player died, clearing active MinibossCC.");
if (this.cursedCrystalMinibossObject.parent) {
this.cursedCrystalMinibossObject.parent.removeChild(this.cursedCrystalMinibossObject);
}
if (this.cursedCrystalMinibossObject.destroy) {
this.cursedCrystalMinibossObject.destroy();
}
}
this.cursedCrystalMinibossObject = null;
this.isMinibossActiveCC = false;
// 6. Obiekt Klejnotu (jeśli był i nie został zniszczony przy spawnie minibossa)
if (typeof crystalCoreObject !== 'undefined' && crystalCoreObject && crystalCoreObject.destroy && !crystalCoreObject.destroyed) {
if (crystalCoreObject.parent) {
crystalCoreObject.parent.removeChild(crystalCoreObject);
}
crystalCoreObject.destroy();
}
crystalCoreObject = null;
// 7. Zatrzymaj muzykę Cursed Crystal, jeśli jest
// if (this.currentCursedCrystalMusic && this.currentCursedCrystalMusic.stop) {
// this.currentCursedCrystalMusic.stop();
// this.currentCursedCrystalMusic = null;
// }
// Uruchom muzykę menu/grilla (jeśli jest taka potrzeba, np. showGrillScreen to zrobi)
// --- LOGIKA WYNIKU I REKORDU ---
var finalScore = this.cursedCrystalScore;
if (isVictory) {
var minutesSurvived = Math.floor(this.cursedCrystalTimeSurvived / (60 * 60)); // Zakładając 60 FPS
var bonusPoints = 1000 + minutesSurvived * 100;
finalScore += bonusPoints;
console.log("DEBUG: Victory! Base score: " + this.cursedCrystalScore + ", Time survived (frames): " + this.cursedCrystalTimeSurvived + " (" + minutesSurvived + " min), Bonus: " + bonusPoints + ", Final score: " + finalScore);
}
this.cursedCrystalScore = finalScore; // Zaktualizuj wynik w gameState
var highScore = parseInt(storage.cursedCrystalHighScore, 10) || 0;
var newRecord = false;
if (finalScore > highScore) {
storage.cursedCrystalHighScore = finalScore;
highScore = finalScore;
newRecord = true;
console.log("DEBUG: New High Score for Cursed Crystal: " + highScore);
}
if (ui) {
ui.titleText.setText(isVictory ? "VICTORY!" : "DEFEAT!"); // Przetłumaczone
ui.titleText.style = {
size: 120,
fill: isVictory ? 0x00FF00 : 0xFF0000,
align: 'center',
stroke: 0x000000,
strokeThickness: 6
};
var message = "Your Score: " + finalScore + "\n"; // Przetłumaczone
message += "Best Score: " + highScore; // Przetłumaczone
if (newRecord && isVictory) {
message += "\n(NEW HIGH SCORE!)"; // Przetłumaczone
} else if (newRecord && !isVictory) {
message += "\n(New High Score... Pepe sad)"; // Przetłumaczone (z lekką adaptacją)
}
ui.messageText.setText(message);
ui.messageText.style = {
size: 70,
fill: 0xFFFFFF,
align: 'center',
stroke: 0x000000,
strokeThickness: 4
};
// Usunięcie starych przycisków, jeśli istnieją (bez zmian)
if (this.activeButtons && this.activeButtons.length > 0) {
this.activeButtons.forEach(function (btn) {
if (btn && btn.destroy) {
btn.destroy();
}
});
}
this.activeButtons = [];
// Upewnij się, że teksty tytułu i wiadomości są od razu widoczne
ui.titleText.alpha = 1;
ui.messageText.alpha = 1;
// Ustaw pozycje dla titleText i messageText (przeniesione z ui.positionElements dla jasności)
// Zakładam, że te pozycje są poprawne dla ekranu końca gry
ui.titleText.x = 2048 / 2;
ui.titleText.y = 600;
ui.titleText.anchor.set(0.5);
ui.messageText.x = 2048 / 2;
ui.messageText.y = 800;
ui.messageText.anchor.set(0.5, 0.5);
// Opóźnienie tworzenia i wyświetlania przycisków
LK.setTimeout(function () {
// Sprawdź, czy nadal jesteśmy w tym samym stanie gry, aby uniknąć dodawania przycisków, jeśli stan się zmienił
if (gameState.currentState !== "cursedCrystalGameOver") {
return;
}
// Definiujemy oczekiwane wysokości na początku, aby były dostępne dla obu przycisków
var mainMenuButtonExpectedHeight = 100;
var restartButtonExpectedHeight = 100;
var spacingBetweenButtons = 60;
var menuButton = new Container();
menuButton.interactive = true;
menuButton.cursor = "pointer";
var menuButtonVisualHeight = mainMenuButtonExpectedHeight; // Użyjemy tej zmiennej
try {
var menuButtonAsset = LK.getAsset('mainmenu', {
anchorX: 0.5,
anchorY: 0.5
});
menuButton.addChild(menuButtonAsset);
if (menuButtonAsset && typeof menuButtonAsset.height === 'number') {
menuButtonVisualHeight = menuButtonAsset.height; // Użyj rzeczywistej wysokości assetu, jeśli dostępna
}
} catch (e) {
console.error("Błąd ładowania assetu przycisku 'mainmenu':", e);
var menuButtonBgFallback = new Shape({
width: 400,
height: mainMenuButtonExpectedHeight,
color: 0x555555,
shape: 'box'
});
menuButton.addChild(menuButtonBgFallback);
}
menuButton.x = 2048 / 2;
// Poprawiona linia dla menuButton.y:
menuButton.y = (ui.messageText.y || 800) + (ui.messageText.height || 70) / 2 + menuButtonVisualHeight / 2 + 80;
menuButton.down = function () {
if (gameState.currentState === "cursedCrystalGameOver") {
gameState.activeButtons.forEach(function (btn) {
if (btn && btn.destroy && !btn.destroyed) {
if (btn.parent) {
btn.parent.removeChild(btn);
}
btn.destroy();
}
});
gameState.activeButtons = [];
gameState.showGrillScreen();
}
};
currentSceneElements.addChild(menuButton);
gameState.activeButtons.push(menuButton);
// --- Przycisk "Restart Mode" ---
var restartButton = new Container();
restartButton.interactive = true;
restartButton.cursor = "pointer";
var restartButtonVisualHeight = restartButtonExpectedHeight; // Użyjemy tej zmiennej
try {
var restartButtonAsset = LK.getAsset('restartgame', {
anchorX: 0.5,
anchorY: 0.5
});
restartButton.addChild(restartButtonAsset);
if (restartButtonAsset && typeof restartButtonAsset.height === 'number') {
restartButtonVisualHeight = restartButtonAsset.height; // Użyj rzeczywistej wysokości assetu
}
} catch (e) {
console.error("Błąd ładowania assetu przycisku 'restartgame':", e);
var restartButtonBgFallback = new Shape({
width: 400,
height: restartButtonExpectedHeight,
color: 0x555555,
shape: 'box'
});
restartButton.addChild(restartButtonBgFallback);
}
restartButton.x = 2048 / 2;
// Poprawiona linia dla restartButton.y, z większym odstępem:
restartButton.y = menuButton.y + menuButtonVisualHeight / 2 + restartButtonVisualHeight / 2 + spacingBetweenButtons;
restartButton.down = function () {
if (gameState.currentState === "cursedCrystalGameOver") {
gameState.activeButtons.forEach(function (btn) {
if (btn && btn.destroy && !btn.destroyed) {
if (btn.parent) {
btn.parent.removeChild(btn);
}
btn.destroy();
}
});
gameState.activeButtons = [];
gameState.startCursedCrystalMode();
}
};
currentSceneElements.addChild(restartButton);
gameState.activeButtons.push(restartButton);
// ui.positionElements("cursedCrystalGameOver");
}, 3000); // Opóźnienie 3000 ms (3 sekundy)
}
}), "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" || this.currentState === "cursedCrystal") && 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", "cursedCrystal"];
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", "cursedCrystal"];
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", "cursedCrystal"];
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
});
}
function spawnCursedSoul() {
console.log("--- spawnCursedSoul (COMPACT GROUP) CALLED ---");
if (gameState.currentState !== "cursedCrystal" || typeof gameState.gameActive === 'boolean' && !gameState.gameActive) {
console.log("spawnCursedSoul (COMPACT GROUP): Exiting - wrong state or game not active. State:", gameState.currentState, "GameActive:", gameState.gameActive);
return;
}
if (typeof crystalCoreObject === 'undefined' || !crystalCoreObject || crystalCoreObject.destroyed) {
console.log("spawnCursedSoul (COMPACT GROUP): Exiting - crystalCoreObject missing or destroyed.");
return;
}
var minSouls = gameState.cursedCrystalSoulsPerGroupMin || 1; // Domyślnie 4, jeśli niezdefiniowane
var maxSouls = gameState.cursedCrystalSoulsPerGroupMax || 5; // Domyślnie 5
if (maxSouls < minSouls) {
maxSouls = minSouls;
}
var numSoulsInGroup = minSouls + Math.floor(Math.random() * (maxSouls - minSouls + 1));
console.log("numSoulsInGroup to spawn:", numSoulsInGroup);
if (numSoulsInGroup <= 0) {
console.warn("spawnCursedSoul (COMPACT GROUP): numSoulsInGroup is 0 or less.");
return;
}
var soulSpeed = gameState.cursedCrystalEnemyCurrentMaxSpeed;
console.log("Soul speed for group:", soulSpeed); // Powinno być niskie na początku
var soulHP = 1;
var soulAssetWidth = 30;
var soulAssetHeight = 30;
var gameWidth = 2048;
var gameHeight = 2732;
var edge = Math.floor(Math.random() * 4);
var groupBaseSpawnX, groupBaseSpawnY;
// Zmniejszamy rozrzut dla bardziej zwartej grupy
var groupSpawnTightnessFactor = 400; // Mniejsza wartość = ciaśniejsza grupa wzdłuż krawędzi
var depthSpreadFactor = gameState.cursedCrystalGroupSpawnSpread / 3 || 140; // Mniejszy rozrzut w głąb
switch (edge) {
case 0:
// Góra
groupBaseSpawnX = Math.random() * (gameWidth - 400) + 200; // Szerszy zakres, ale nadal unika rogów
groupBaseSpawnY = -soulAssetHeight;
break;
case 1:
// Prawo
groupBaseSpawnX = gameWidth + soulAssetWidth;
groupBaseSpawnY = Math.random() * (gameHeight - 400) + 200;
break;
case 2:
// Dół
groupBaseSpawnX = Math.random() * (gameWidth - 400) + 200;
groupBaseSpawnY = gameHeight + soulAssetHeight;
break;
default:
// Lewo (case 3)
groupBaseSpawnX = -soulAssetWidth;
groupBaseSpawnY = Math.random() * (gameHeight - 400) + 200;
break;
}
console.log("Spawning group from edge:", edge, "BaseX:", groupBaseSpawnX.toFixed(0), "BaseY:", groupBaseSpawnY.toFixed(0));
for (var i = 0; i < numSoulsInGroup; i++) {
var offsetX = 0;
var offsetY = 0;
// Ustawienie dusz blisko siebie, z lekkim losowym przesunięciem
if (edge === 0 || edge === 2) {
// Spawn z góry lub dołu - rozrzut poziomy
offsetX = (Math.random() - 0.5) * groupSpawnTightnessFactor;
offsetY = (Math.random() - 0.5) * depthSpreadFactor; // Mniejszy rozrzut w głąb
} else {
// Spawn z lewej lub prawej - rozrzut pionowy
offsetY = (Math.random() - 0.5) * groupSpawnTightnessFactor;
offsetX = (Math.random() - 0.5) * depthSpreadFactor; // Mniejszy rozrzut w głąb
}
var finalSpawnX = groupBaseSpawnX + offsetX;
var finalSpawnY = groupBaseSpawnY + offsetY;
// console.log("Creating soul " + (i + 1) + " at X:", finalSpawnX.toFixed(0), "Y:", finalSpawnY.toFixed(0));
var newSoul = new SoulEnemy({
x: finalSpawnX,
y: finalSpawnY,
hp: soulHP,
speed: soulSpeed,
// Wszystkie duszki w grupie mają tę samą (niską na początku) prędkość
targetX: crystalCoreObject.x,
targetY: crystalCoreObject.y
});
game.addChild(newSoul);
gameState.cursedCrystalEnemies.push(newSoul);
}
console.log("Finished spawning group of " + numSoulsInGroup + " souls.");
}
// --- Główna pętla aktualizacji gry ---
game.update = function () {
if (gameState.currentState === "cursedCrystal") {
// console.log("Game Update Tick - CURSED CRYSTAL");
}
if (ui) {
if (gameState.currentState !== "cursedCrystal" && gameState.currentState !== "cursedCrystalGameOver") {
ui.updateDeathsCounter();
}
if (boss && (gameState.currentState === "game" || gameState.currentState === "gameOver" && !gameOverReasonIsDeath && isNewBossPlusMode)) {
var maxHpBoss = boss.maxHealth > 0 ? boss.maxHealth : 1;
ui.updateBossHealth(boss.health, maxHpBoss);
} else if (gameState.currentState !== "cursedCrystal" && gameState.currentState !== "cursedCrystalGameOver") {
ui.updateBossHealth(0, 1);
}
if (player && gameState.currentState !== "cursedCrystal" && gameState.currentState !== "cursedCrystalGameOver") {
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" && gameState.currentState !== "rollMaster" && gameState.currentState !== "cursedCrystal" && gameState.currentState !== "cursedCrystalGameOver") {
ui.updateHearts(parseInt(storage.maxHearts, 10) || 5, parseInt(storage.maxHearts, 10) || 5);
}
}
if (coffinMemeImage && !coffinMemeImage.destroyed) {
if (gameState.currentState === "game" && player) {
if (!player.dead) {
var currentHp = player.health;
var maxHp = parseInt(storage.maxHearts, 10) || 5;
var visibilityFactor = 1 - Math.max(0, 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 > 0.1 ? visibilityFactor * 0.8 : 0;
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 {
coffinMemeImage.y = 2932;
coffinMemeImage.alpha = 1.0;
}
} else if (gameState.currentState !== "gameOver") {
if (coffinMemeImage.alpha > 0) {
coffinMemeImage.alpha = Math.max(0, coffinMemeImage.alpha - 0.05);
}
}
}
if (gameState.currentState === "game") {
if (player) {
player.update();
}
if (boss) {
boss.update();
}
} else if (gameState.currentState === "rollMaster") {
if (player) {
player.update();
}
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;
}
if (gameState.unlockedAttacks.includes('rmattack2') && gameState.explosionSpawnInterval > 0) {
gameState.explosionSpawnTimer++;
if (gameState.explosionSpawnTimer >= gameState.explosionSpawnInterval) {
gameState.explosionSpawnTimer = 0;
launchRmattack2();
}
}
if (gameState.unlockedAttacks.includes('rmattack3') && gameState.laserSpawnInterval > 0) {
gameState.laserSpawnTimer++;
if (gameState.laserSpawnTimer >= gameState.laserSpawnInterval) {
gameState.laserSpawnTimer = 0;
launchRmattack3();
}
}
if (gameState.unlockedAttacks.includes('rmattack4') && gameState.spreaderSpawnInterval > 0) {
gameState.spreaderSpawnTimer++;
if (gameState.spreaderSpawnTimer >= gameState.spreaderSpawnInterval) {
gameState.spreaderSpawnTimer = 0;
launchRmattack4();
}
}
for (var i = gameState.rollMasterAttacks.length - 1; i >= 0; i--) {
var atk = gameState.rollMasterAttacks[i];
var shouldRemove = false;
if (!atk || !atk.visual || atk.visual.destroyed) {
gameState.rollMasterAttacks.splice(i, 1);
continue;
}
if (atk.visual.update && typeof atk.visual.update === 'function') {
atk.visual.update();
}
switch (atk.type) {
case 'projectile':
atk.visual.x += atk.vx;
atk.visual.y += atk.vy;
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_p = (player.width || 150) / 2 * 0.8;
var projectileRadius = atk.radius || 45;
if (dist_p < playerRadius_p + projectileRadius) {
player.takeDamage(1);
shouldRemove = true;
}
}
var screenMargin = 200;
if (atk.visual.x < -screenMargin || atk.visual.x > 2048 + screenMargin || atk.visual.y < -screenMargin || atk.visual.y > 2732 + screenMargin) {
shouldRemove = true;
}
break;
case '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 || 150) / 2 * 0.8;
if (dist_e < playerRadius_e + atk.radius) {
player.takeDamage(1);
atk.damageDealt = true;
}
}
break;
case 'laser':
if (player && !player.dead && !player.rolling && !player.invulnerable) {
var laserHalfWidth = (atk.width || 100) / 2;
var laserHalfHeight = (atk.height || 100) / 2;
var playerHalfWidth_l = (player.width || 150) / 2 * 0.8;
var playerHalfHeight_l = (player.height || 150) / 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_l;
var playerRight = player.x + playerHalfWidth_l;
var playerTop = player.y - playerHalfHeight_l;
var playerBottom = player.y + playerHalfHeight_l;
if (laserLeft < playerRight && laserRight > playerLeft && laserTop < playerBottom && laserBottom > playerTop) {
player.takeDamage(1);
}
}
if (atk.lifeTime && atk.lifeTime > 0) {
atk.lifeTime--;
}
break;
case 'spreader_parent':
atk.timer--;
if (atk.timer <= 0) {
var originX = atk.visual.x;
var originY = atk.visual.y;
var projectileSpeed_s = 5 + gameState.rollMasterDifficulty * 0.5;
var projectileRadius_s = 45;
var numProjectiles_s = 12;
for (var j = 0; j < numProjectiles_s; j++) {
var angle_s = Math.PI * 2 / numProjectiles_s * j;
var vx_s = Math.cos(angle_s) * projectileSpeed_s;
var vy_s = Math.sin(angle_s) * projectileSpeed_s;
var projectileFrames_s = [];
for (var k_s = 0; k_s <= 6; k_s++) {
projectileFrames_s.push(LK.getAsset('rmattack1_' + k_s, {
anchorX: 0.5,
anchorY: 0.5
}));
}
var projectileVisual_s = new SpriteAnimation({
frames: projectileFrames_s,
frameDuration: 100,
loop: true,
x: originX,
y: originY,
anchorX: 0.5,
anchorY: 0.5,
scaleX: 1.2,
scaleY: 1.2
});
projectileVisual_s.rotation = Math.atan2(vy_s, vx_s);
var projectileObject_s = game.addChild(projectileVisual_s);
gameState.rollMasterAttacks.push({
type: 'projectile',
visual: projectileObject_s,
vx: vx_s,
vy: vy_s,
radius: projectileRadius_s
});
}
shouldRemove = true;
}
break;
default:
console.warn("Nieznany typ ataku w RollMaster: ", atk.type, atk);
break;
}
if (shouldRemove) {
if (atk.visual && atk.visual.destroy && !atk.visual.destroyed) {
if (atk.visual.parent) {
atk.visual.parent.removeChild(atk.visual);
}
atk.visual.destroy();
}
gameState.rollMasterAttacks.splice(i, 1);
}
}
if (player && player.dead && gameState.currentState === "rollMaster") {
if (typeof gameState.endRollMasterMode === 'function') {
gameState.endRollMasterMode(gameState.rollMasterTime);
} else {
console.error("Cannot call endRollMasterMode, function undefined");
}
}
} else if (gameState.currentState === "cursedCrystal") {
if (player) {
player.update();
}
if (!gameState.isMinibossActiveCC) {
gameState.cursedCrystalTimeSurvived++;
gameState.cursedCrystalEnemySpawnTimer++;
if (gameState.cursedCrystalEnemySpawnTimer >= gameState.cursedCrystalCurrentSpawnInterval) {
gameState.cursedCrystalEnemySpawnTimer = 0;
spawnCursedSoul();
}
gameState.cursedCrystalDifficultyTimer++;
if (gameState.cursedCrystalDifficultyTimer >= gameState.cursedCrystalDifficultyInterval) {
gameState.cursedCrystalDifficultyTimer = 0;
var spawnIntervalDecrease = 5;
gameState.cursedCrystalCurrentSpawnInterval = Math.max(gameState.cursedCrystalMinSpawnInterval, gameState.cursedCrystalCurrentSpawnInterval - spawnIntervalDecrease);
var speedIncrease = 0.25;
gameState.cursedCrystalEnemyCurrentMaxSpeed += speedIncrease;
}
}
for (var i = gameState.cursedCrystalEnemies.length - 1; i >= 0; i--) {
var enemy = gameState.cursedCrystalEnemies[i];
if (!enemy) {
gameState.cursedCrystalEnemies.splice(i, 1);
continue;
}
if (enemy.isDead) {
gameState.cursedCrystalEnemies.splice(i, 1);
continue;
}
enemy.update();
if (enemy.isDead) {
gameState.cursedCrystalEnemies.splice(i, 1);
continue;
}
if (player && player.rolling) {
var dx_soul = player.x - enemy.x;
var dy_soul = player.y - enemy.y;
var distance_soul = Math.sqrt(dx_soul * dx_soul + dy_soul * dy_soul);
var playerRollRadius_soul = (player.width || 150) / 2 * 0.7;
var soulRadius_coll = (enemy.width || 30) / 2 * 0.8;
if (distance_soul < playerRollRadius_soul + soulRadius_coll) {
enemy.takeDamage(1);
}
}
}
if (gameState.currentState === "cursedCrystal" && !gameState.isMinibossActiveCC && gameState.cursedCrystalChargeLevel >= gameState.cursedCrystalTargetCharge && !gameState.crystalExploding) {
gameState.crystalExploding = true; // Ustaw flagę, że proces eksplozji/spawnu się rozpoczął
// Zdefiniuj współrzędne spawnu minibossa na podstawie crystalCoreObject lub domyślnych
var tempCrystalSpawnX, tempCrystalSpawnY;
if (typeof crystalCoreObject !== 'undefined' && crystalCoreObject && !crystalCoreObject.destroyed) {
tempCrystalSpawnX = crystalCoreObject.x;
tempCrystalSpawnY = crystalCoreObject.y;
} else {
tempCrystalSpawnX = 2048 / 2; // Domyślna pozycja X na środek ekranu
tempCrystalSpawnY = 2732 / 2; // Domyślna pozycja Y na środek ekranu
console.warn("crystalCoreObject nie istnieje podczas próby spawnu minibossa, używam domyślnych współrzędnych.");
}
if (typeof gameState.playCrystalExplosionAnimation === 'function') {
// Ta funkcja powinna teraz poprawnie używać wewnętrznych crystalOriginalX/Y
// i przekazywać je do spawnCursedCrystalMiniboss
gameState.playCrystalExplosionAnimation();
} else {
// Awaryjne spawnowanie, jeśli playCrystalExplosionAnimation nie istnieje
console.error("gameState.playCrystalExplosionAnimation is not defined! Spawning miniboss directly.");
// Przekaż zdefiniowane wyżej współrzędne
gameState.spawnCursedCrystalMiniboss(tempCrystalSpawnX, tempCrystalSpawnY);
gameState.crystalExploding = false; // Resetuj flagę, jeśli spawn był bezpośredni i synchroniczny
}
// UWAGA: Poniższy blok kodu, który oryginalnie tworzył minibossOptions i spawnował minibossa
// w tym miejscu, został usunięty. Logika ta jest teraz w całości obsługiwana przez
// gameState.playCrystalExplosionAnimation() -> gameState.spawnCursedCrystalMiniboss()
// lub przez awaryjne, bezpośrednie wywołanie gameState.spawnCursedCrystalMiniboss(tempCrystalSpawnX, tempCrystalSpawnY) powyżej.
// To eliminuje redundancję i błąd ReferenceError.
} // Koniec if'a spawującego Minibossa
// Aktualizacja Minibossa CC, jeśli jest aktywny
if (gameState.isMinibossActiveCC && gameState.cursedCrystalMinibossObject && !gameState.cursedCrystalMinibossObject.isDead) {
gameState.cursedCrystalMinibossObject.update();
}
if (gameState.cursedCrystalMinibossObject && gameState.cursedCrystalMinibossObject.activeClones) {
for (var cIdx = gameState.cursedCrystalMinibossObject.activeClones.length - 1; cIdx >= 0; cIdx--) {
var cloneInstance = gameState.cursedCrystalMinibossObject.activeClones[cIdx];
if (cloneInstance.isDead) {
// Już obsłużone w cloneInstance.die(), ale dla pewności
gameState.cursedCrystalMinibossObject.activeClones.splice(cIdx, 1);
continue;
}
cloneInstance.update();
// Kolizja gracza (roll) z klonem
if (player && player.rolling && !cloneInstance.isDead) {
var dx_clone_roll = player.x - cloneInstance.x;
var dy_clone_roll = player.y - cloneInstance.y;
var distance_clone_roll = Math.sqrt(dx_clone_roll * dx_clone_roll + dy_clone_roll * dy_clone_roll);
var playerRollRadius_clone = (player.width || 150) / 2 * 0.7;
var cloneRadius_coll = (cloneInstance.width || 120) / 2 * 0.9;
if (distance_clone_roll < playerRollRadius_clone + cloneRadius_coll) {
cloneInstance.takeDamage(1); // Klon ginie po jednym trafieniu rollem
}
}
}
}
// --- Aktualizacja Pocisków Minibossa ---
if (typeof gameState.cursedCrystalActiveProjectiles === 'undefined' || !gameState.cursedCrystalActiveProjectiles) {
gameState.cursedCrystalActiveProjectiles = [];
}
for (var k = gameState.cursedCrystalActiveProjectiles.length - 1; k >= 0; k--) {
var projectile = gameState.cursedCrystalActiveProjectiles[k];
if (!projectile || projectile.isDead) {
if (projectile && projectile.graphics && projectile.graphics.parent) {
projectile.graphics.parent.removeChild(projectile.graphics);
if (projectile.graphics.destroy) {
projectile.graphics.destroy();
}
}
gameState.cursedCrystalActiveProjectiles.splice(k, 1);
continue;
}
projectile.update();
if (projectile.isDead) {
if (projectile.graphics && projectile.graphics.parent) {
projectile.graphics.parent.removeChild(projectile.graphics);
if (projectile.graphics.destroy) {
projectile.graphics.destroy();
}
}
gameState.cursedCrystalActiveProjectiles.splice(k, 1);
}
}
// --- Sprawdzanie Obrażeń od Eksplozji Minibossa ---
if (typeof gameState.cursedCrystalActiveExplosions === 'undefined' || !gameState.cursedCrystalActiveExplosions) {
gameState.cursedCrystalActiveExplosions = [];
}
for (var expIdx = gameState.cursedCrystalActiveExplosions.length - 1; expIdx >= 0; expIdx--) {
var explosion = gameState.cursedCrystalActiveExplosions[expIdx];
if (!explosion) {
gameState.cursedCrystalActiveExplosions.splice(expIdx, 1);
continue;
}
explosion.durationTimer--;
if (player && !player.dead && !player.invulnerable && !explosion.hitPlayerThisFrame) {
var distToExplosion = Math.sqrt(Math.pow(player.x - explosion.x, 2) + Math.pow(player.y - explosion.y, 2));
if (distToExplosion < (player.width || 150) / 2 * 0.7 + explosion.radius) {
player.takeDamage(explosion.damage);
explosion.hitPlayerThisFrame = true;
}
}
if (explosion.durationTimer <= 0) {
gameState.cursedCrystalActiveExplosions.splice(expIdx, 1);
}
}
// --- LOGIKA DLA AKTYWNYCH ŚCIAN LASEROWYCH MINIBOSSA ---
if (typeof gameState.cursedCrystalActiveLaserWalls === 'undefined' || !gameState.cursedCrystalActiveLaserWalls) {
gameState.cursedCrystalActiveLaserWalls = [];
}
if (gameState.cursedCrystalActiveLaserWalls && gameState.cursedCrystalActiveLaserWalls.length > 0) {
for (var wallIdx = gameState.cursedCrystalActiveLaserWalls.length - 1; wallIdx >= 0; wallIdx--) {
var wall = gameState.cursedCrystalActiveLaserWalls[wallIdx];
if (!wall || wall.isDead) {
if (wall && wall.segments) {
wall.segments.forEach(function (seg) {
if (seg.isPlaceholder) {
return;
}
if (seg.visual && seg.visual.parent) {
seg.visual.parent.removeChild(seg.visual);
if (seg.visual.destroy && !seg.visual.destroyed) {
seg.visual.destroy();
}
}
});
}
gameState.cursedCrystalActiveLaserWalls.splice(wallIdx, 1);
continue;
}
if (gameState.cursedCrystalMinibossObject && !gameState.cursedCrystalMinibossObject.isDead) {
wall.pivotX = gameState.cursedCrystalMinibossObject.x;
wall.pivotY = gameState.cursedCrystalMinibossObject.y;
}
if (wall.warningTimer > 0) {
wall.warningTimer -= 1000 / 60;
wall.segments.forEach(function (segment) {
if (segment.isPlaceholder) {
return;
}
var initialAngleForStaticDisplay = 0;
var rotatedOffsetX = segment.initialOffsetX * Math.cos(initialAngleForStaticDisplay) - segment.initialOffsetY * Math.sin(initialAngleForStaticDisplay);
var rotatedOffsetY = segment.initialOffsetX * Math.sin(initialAngleForStaticDisplay) + segment.initialOffsetY * Math.cos(initialAngleForStaticDisplay);
segment.currentX = wall.pivotX + rotatedOffsetX;
segment.currentY = wall.pivotY + rotatedOffsetY;
if (segment.visual && !segment.visual.destroyed) {
segment.visual.x = segment.currentX;
segment.visual.y = segment.currentY;
segment.visual.rotation = initialAngleForStaticDisplay;
}
});
} else {
if (wall.activeTimer > 0) {
wall.activeTimer -= 1000 / 60;
wall.currentAngle += wall.rotationSpeed;
wall.segments.forEach(function (segment) {
if (segment.isPlaceholder) {
return;
}
var rotatedOffsetX = segment.initialOffsetX * Math.cos(wall.currentAngle) - segment.initialOffsetY * Math.sin(wall.currentAngle);
var rotatedOffsetY = segment.initialOffsetX * Math.sin(wall.currentAngle) + segment.initialOffsetY * Math.cos(wall.currentAngle);
segment.currentX = wall.pivotX + rotatedOffsetX;
segment.currentY = wall.pivotY + rotatedOffsetY;
if (segment.visual && !segment.visual.destroyed) {
segment.visual.x = segment.currentX;
segment.visual.y = segment.currentY;
var baseRotation = wall.currentAngle;
if (segment.animationPhase === 'action_once' || segment.animationPhase === 'looping_cut') {
if (segment.currentIndividualSpinAngle === undefined) {
segment.currentIndividualSpinAngle = 0;
}
var RAPID_SPIN_SPEED = 0.4;
segment.currentIndividualSpinAngle += RAPID_SPIN_SPEED;
segment.visual.rotation = baseRotation + segment.currentIndividualSpinAngle;
} else {
segment.visual.rotation = baseRotation;
}
if (player && !player.dead && !player.invulnerable) {
var playerHalfWidth_lw = (player.width || 150) / 2 * 0.7;
var playerHalfHeight_lw = (player.height || 150) / 2 * 0.7;
var segmentHalfWidth_lw = (segment.width || 120) / 2;
var segmentHalfHeight_lw = (segment.height || 120) / 2;
if (Math.abs(player.x - segment.currentX) < playerHalfWidth_lw + segmentHalfWidth_lw && Math.abs(player.y - segment.currentY) < playerHalfHeight_lw + segmentHalfHeight_lw) {
player.takeDamage(1);
}
}
}
});
if (wall.activeTimer <= 0) {
wall.isDead = true;
}
} else {
wall.isDead = true;
}
}
}
}
if (player && player.dead) {/* Obsługiwane przez player.die() */}
}
};
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!");
} /****
* 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);
self.isCharging = false;
self.ultimateAttackCooldownTimer = 0;
self.ultimateAttackMinInterval = 30 * 60;
self.ultimateAttackMaxInterval = 40 * 60;
self.nextUltimateAttackAvailableTime = 0;
self.circleAttackCooldownDuration = 11 * 60;
self.circleAttackActiveCooldown = self.circleAttackCooldownDuration;
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;
self.createBossAttackAnim = function () {
var frames = [];
for (var i = 0; i <= 6; i++) {
frames.push(LK.getAsset('bossAttack' + i, {}));
}
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;
};
self.playBossAttackAnim = function (attackType) {
if (self.bossAttackAnim) {
self.bossAttackAnim.stop();
if (self.bossAttackAnim.parent) {
self.bossAttackAnim.parent.removeChild(self.bossAttackAnim);
}
self.bossAttackAnim.destroy();
self.bossAttackAnim = null;
}
if (attackType !== 2 && attackType !== 3) {
if (self.bossGraphics && self.bossGraphics.parent) {
self.bossGraphics.parent.removeChild(self.bossGraphics);
}
self.bossGraphics = null;
self.bossAttackAnim = self.addChild(self.createBossAttackAnim());
self.bossAttackAnim.update = function () {
if (self.bossAttackAnim !== this || !this.playing || !this.frames || this.frames.length === 0) {
return;
}
this.frameTimer = (this.frameTimer || 0) + 1;
if (this.frameTimer >= this.frameDuration / (1000 / 60)) {
this.frameTimer = 0;
this.removeChildren();
this.currentFrame = (this.currentFrame || 0) + 1;
if (this.currentFrame >= this.frames.length) {
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;
if (this.parent) {
this.parent.removeChild(this);
}
this.destroy();
self.bossAttackAnim = null;
} else {
if (this.frames[this.currentFrame]) {
this.addChild(this.frames[this.currentFrame]);
}
}
}
};
}
};
self.createAttack = function (x, y, duration, type) {
var framesToUse = [];
var scaleMultiplier = 1;
var attackRadius = 60;
if (type === 'circle') {
framesToUse = [LK.getAsset('fireball0', {
anchorX: 0.5,
anchorY: 0.5,
clone: true
}), LK.getAsset('fireball00', {
anchorX: 0.5,
anchorY: 0.5,
clone: true
}), LK.getAsset('fireball01', {
anchorX: 0.5,
anchorY: 0.5,
clone: true
}), LK.getAsset('fireball02', {
anchorX: 0.5,
anchorY: 0.5,
clone: true
}), LK.getAsset('fireball03', {
anchorX: 0.5,
anchorY: 0.5,
clone: true
}), LK.getAsset('fireball04', {
anchorX: 0.5,
anchorY: 0.5,
clone: true
}), LK.getAsset('fireball05', {
anchorX: 0.5,
anchorY: 0.5,
clone: true
}), LK.getAsset('fireball06', {
anchorX: 0.5,
anchorY: 0.5,
clone: true
}), LK.getAsset('fireball07', {
anchorX: 0.5,
anchorY: 0.5,
clone: true
}), LK.getAsset('fireball08', {
anchorX: 0.5,
anchorY: 0.5,
clone: true
}), LK.getAsset('fireball09', {
anchorX: 0.5,
anchorY: 0.5,
clone: true
}), LK.getAsset('fireball1', {
anchorX: 0.5,
anchorY: 0.5,
clone: true
}), LK.getAsset('fireball10', {
anchorX: 0.5,
anchorY: 0.5,
clone: true
}), LK.getAsset('fireball11', {
anchorX: 0.5,
anchorY: 0.5,
clone: true
}), LK.getAsset('fireball12', {
anchorX: 0.5,
anchorY: 0.5,
clone: true
}), LK.getAsset('fireball13', {
anchorX: 0.5,
anchorY: 0.5,
clone: true
}), LK.getAsset('fireball14', {
anchorX: 0.5,
anchorY: 0.5,
clone: true
}), LK.getAsset('fireballnew1', {
anchorX: 0.5,
anchorY: 0.5,
clone: true
}), LK.getAsset('fireballnew2', {
anchorX: 0.5,
anchorY: 0.5,
clone: true
}), LK.getAsset('fireballnew3', {
anchorX: 0.5,
anchorY: 0.5,
clone: true
}), LK.getAsset('fireballnew4', {
anchorX: 0.5,
anchorY: 0.5,
clone: true
})];
} else if (type === 'line') {
framesToUse = [LK.getAsset('fireball2', {
anchorX: 0.5,
anchorY: 0.5,
clone: true
}), LK.getAsset('fireball3', {
anchorX: 0.5,
anchorY: 0.5,
clone: true
}), LK.getAsset('fireball4', {
anchorX: 0.5,
anchorY: 0.5,
clone: true
}), LK.getAsset('fireball5', {
anchorX: 0.5,
anchorY: 0.5,
clone: true
}), LK.getAsset('fireball6', {
anchorX: 0.5,
anchorY: 0.5,
clone: true
}), LK.getAsset('fireball7', {
anchorX: 0.5,
anchorY: 0.5,
clone: true
}), LK.getAsset('fireball8', {
anchorX: 0.5,
anchorY: 0.5,
clone: true
}), LK.getAsset('fireball9', {
anchorX: 0.5,
anchorY: 0.5,
clone: true
}), LK.getAsset('fireball15', {
anchorX: 0.5,
anchorY: 0.5,
clone: true
}), LK.getAsset('fireball16', {
anchorX: 0.5,
anchorY: 0.5,
clone: true
}), LK.getAsset('linearattack1', {
anchorX: 0.5,
anchorY: 0.5,
clone: true
}), LK.getAsset('linearattack2', {
anchorX: 0.5,
anchorY: 0.5,
clone: true
}), LK.getAsset('linearattack3', {
anchorX: 0.5,
anchorY: 0.5,
clone: true
}), LK.getAsset('linearattack4', {
anchorX: 0.5,
anchorY: 0.5,
clone: true
}), LK.getAsset('linearattack5', {
anchorX: 0.5,
anchorY: 0.5,
clone: true
})];
scaleMultiplier = 1.3;
attackRadius = 60 * scaleMultiplier;
} else {
framesToUse = [LK.getAsset('fireball0', {
anchorX: 0.5,
anchorY: 0.5,
clone: true
})];
}
var currentFrameDuration = type === 'line' ? 250 : type === 'circle' ? 150 : 100;
var spriteAnim = game.addChild(new SpriteAnimation({
frames: framesToUse,
frameDuration: currentFrameDuration,
loop: false,
anchorX: 0.5,
anchorY: 0.5,
x: x,
y: y
}));
spriteAnim.scaleX = 1.6 * scaleMultiplier;
spriteAnim.scaleY = 1.6 * scaleMultiplier;
spriteAnim.play();
var attackData = {
x: x,
y: y,
radius: attackRadius,
visual: spriteAnim,
lifeTime: Math.floor(duration / (1000 / 60)),
isActive: true,
attackObjectType: type
};
self.attacks.push(attackData);
var animationTotalDurationMs = framesToUse.length * currentFrameDuration;
LK.setTimeout(function () {
var index = self.attacks.indexOf(attackData);
if (index !== -1) {
self.attacks.splice(index, 1);
}
if (spriteAnim && !spriteAnim.destroyed) {
if (spriteAnim.parent) {
spriteAnim.parent.removeChild(spriteAnim);
}
spriteAnim.destroy();
}
}, animationTotalDurationMs);
};
self.circleAttack = function () {
LK.getSound('bossAttack').play();
var count = isNewBossPlusMode ? 8 : 4;
var radius = 300;
var orbitDurationFrames = 230;
var flightDurationMs = isNewBossPlusMode ? 3500 : 4500;
var baseAngle = Math.random() * Math.PI * 2;
var angularSpeed = 0.02;
var attackOriginX = self.x;
var attackOriginY = self.y;
for (var i = 0; i < count; i++) {
var angleOffset = i / count * Math.PI * 2;
var initialAngle = baseAngle + angleOffset;
var x = attackOriginX + Math.cos(initialAngle) * radius;
var y = attackOriginY + Math.sin(initialAngle) * radius;
var orbFrames = [LK.getAsset('fireball0', {
anchorX: 0.5,
anchorY: 0.5,
clone: true
}), LK.getAsset('fireball00', {
anchorX: 0.5,
anchorY: 0.5,
clone: true
}), LK.getAsset('fireball01', {
anchorX: 0.5,
anchorY: 0.5,
clone: true
}), LK.getAsset('fireball02', {
anchorX: 0.5,
anchorY: 0.5,
clone: true
}), LK.getAsset('fireball03', {
anchorX: 0.5,
anchorY: 0.5,
clone: true
}), LK.getAsset('fireball04', {
anchorX: 0.5,
anchorY: 0.5,
clone: true
}), LK.getAsset('fireball05', {
anchorX: 0.5,
anchorY: 0.5,
clone: true
}), LK.getAsset('fireball06', {
anchorX: 0.5,
anchorY: 0.5,
clone: true
})];
var spriteAnim = new SpriteAnimation({
frames: orbFrames,
frameDuration: 100,
loop: true,
anchorX: 0.5,
anchorY: 0.5,
x: x,
y: y
});
spriteAnim.scaleX = 1.6;
spriteAnim.scaleY = 1.6;
spriteAnim.play();
spriteAnim.update = function () {
this.frameTimer = (this.frameTimer || 0) + 1;
if (this.frameTimer >= this.frameDuration / (1000 / 60)) {
this.frameTimer = 0;
this.removeChildren();
this.currentFrame = ((this.currentFrame || 0) + 1) % this.frames.length;
if (this.frames[this.currentFrame]) {
this.addChild(this.frames[this.currentFrame]);
}
}
};
game.addChild(spriteAnim);
self.attacks.push({
type: 'circle_orbiting',
angleOffset: angleOffset,
baseAngle: baseAngle,
currentAngle: initialAngle,
angularSpeed: angularSpeed,
radius: radius,
collisionRadius: 60,
centerX: attackOriginX,
centerY: attackOriginY,
detachCounter: orbitDurationFrames,
lifeTime: Math.floor(flightDurationMs / (1000 / 60)),
isActive: true,
visual: spriteAnim,
x: x,
y: y,
detached: false,
vx: 0,
vy: 0
});
}
};
self.takeDamage = function (amount) {
console.log("DEBUG: Boss.takeDamage CALLED. Amount:", amount, "Boss dead:", self.dead, "Current state:", gameState.currentState, "Boss health BEFORE:", self.health);
if (self.dead || typeof gameState !== 'undefined' && gameState.currentState !== "game") {
console.log("DEBUG: Boss.takeDamage REJECTED. Dead or wrong game state.");
return;
}
self.health -= amount;
self.health = Math.max(0, self.health);
console.log("DEBUG: Boss health AFTER:", self.health, "/", self.maxHealth);
LK.effects.flashObject(self, 0xFFFFFF, 200);
if (typeof isNewBossPlusMode !== 'undefined' && !isNewBossPlusMode) {
if (self.health <= self.maxHealth / 2 && self.phase === 1) {
console.log("DEBUG: Boss entering phase 2.");
self.phase = 2;
self.speed += 2;
self.attackSpeedMultiplier = (self.attackSpeedMultiplier || 1) * 0.8;
tween(self, {
tint: 0xFF3300
}, {
duration: 1000,
easing: tween.easeInOut
});
}
}
if (self.health <= 0) {
console.log("DEBUG: Boss health <= 0, calling self.die().");
self.die();
}
};
// NOWA METODA DO CZYSZCZENIA ATAKÓW
self.clearAllAttacks = function () {
console.log("DEBUG: Boss.clearAllAttacks CALLED. Clearing " + self.attacks.length + " attacks.");
var attacksToClear = self.attacks.slice(); // Iteruj po kopii
attacksToClear.forEach(function (attack) {
if (attack.visual && !attack.visual.destroyed) {
if (attack.visual.parent) {
attack.visual.parent.removeChild(attack.visual); // Usuń z rodzica (np. 'game')
}
attack.visual.destroy();
}
});
self.attacks = []; // Wyczyść tablicę ataków bossa
console.log("DEBUG: Boss.clearAllAttacks FINISHED. Attacks remaining: " + self.attacks.length);
};
self.die = function () {
var currentGameState = typeof gameState !== 'undefined' ? gameState.currentState : "gameState undefined";
console.log("DEBUG: Boss.die() called. Current state:", currentGameState, "Is boss already dead?", self.dead, "Is NewBossPlusMode?", isNewBossPlusMode);
if (self.dead && currentGameState !== "game") {
console.log("DEBUG: Boss.die() - Already dead and not in 'game' state. Attempting cleanup again just in case.");
self.clearAllAttacks("Die - Already dead, not in game state");
return;
}
if (self.dead) {
console.log("DEBUG: Boss.die() - Already dead. Exiting.");
return;
}
self.dead = true;
console.log("DEBUG: Boss.die() - Boss marked as dead. Clearing attacks.");
self.clearAllAttacks("Die - Normal death sequence");
// Dźwięk zwycięstwa tylko w trybie standardowym
if (typeof isNewBossPlusMode !== 'undefined' && !isNewBossPlusMode) {
LK.getSound('victory').play();
}
if (typeof gameState !== 'undefined' && gameState.currentState === "game") {
console.log("DEBUG: Boss.die - In 'game' state.");
// ZMIANA TUTAJ: Boss+ będzie miał animację śmierci i przejdzie do Grill Menu
// Niezależnie od tego, czy to Boss+ czy standardowy, wykonaj animację śmierci
console.log("DEBUG: Boss.die - Starting death animation tween for boss (standard or Boss+).");
tween(self, {
alpha: 0,
scaleX: self.scaleX * 1.2,
scaleY: self.scaleY * 1.2
}, {
duration: 2000,
easing: tween.easeOut,
onFinish: function onFinish() {
console.log("DEBUG: Boss.die tween onFinish reached.");
// Zwiększ licznik pokonanych bossów (możesz chcieć osobny licznik dla Boss+)
storage.bossesDefeated = (parseInt(storage.bossesDefeated, 10) || 0) + 1;
console.log("DEBUG: Boss.die onFinish - Bosses defeated:", storage.bossesDefeated);
if (typeof gameState !== 'undefined' && typeof gameState.showGrillScreen === 'function') {
// Dla Boss+ możesz przekazać specjalną flagę, jeśli chcesz inny komunikat na Grill Menu
var wasBossPlusDefeated = typeof isNewBossPlusMode !== 'undefined' && isNewBossPlusMode;
gameState.showGrillScreen(false, wasBossPlusDefeated); // (survivedBossPlusChallenge = false, defeatedBossPlus = wasBossPlusDefeated)
}
}
});
} else {
console.log("DEBUG: Boss.die - Not in 'game' state (e.g., during restart), skipping animations. Attacks should be cleared.");
}
};
self.update = function () {
if (typeof gameState !== 'undefined' && gameState.currentState !== "game" && gameState.currentState !== "rollMaster") {
if (self.rolling) {
self.rolling = false;
}
return;
}
if (self.dead) {
return;
}
self.ultimateAttackCooldownTimer++;
if (self.circleAttackActiveCooldown < self.circleAttackCooldownDuration) {
self.circleAttackActiveCooldown++;
}
if (self.attackCooldown > 30 && typeof player !== 'undefined' && player && !player.dead && !self.isCharging) {
var dx = player.x - self.x;
var dy = player.y - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
var moveSpeed = self.speed || 2;
if (distance > 150) {
var moveX = dx / distance * moveSpeed;
var moveY = dy / distance * moveSpeed;
var nextX = self.x + moveX;
var nextY = self.y + moveY;
var halfWidth = (self.width || 100) * (self.scaleX || 1) / 2;
var halfHeight = (self.height || 100) * (self.scaleY || 1) / 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));
}
}
for (var i = self.attacks.length - 1; i >= 0; i--) {
var attack = self.attacks[i];
var shouldRemove = false;
if (attack.type !== 'line_controller' && (!attack || !attack.visual || attack.visual.destroyed)) {
self.attacks.splice(i, 1);
continue;
}
if (attack.visual && typeof attack.visual.update === 'function') {
attack.visual.update();
}
if (attack.type === 'circle_orbiting') {
if (!attack.detached) {
attack.baseAngle += attack.angularSpeed;
var currentOrbAngle = attack.baseAngle + attack.angleOffset;
attack.x = attack.centerX + Math.cos(currentOrbAngle) * attack.radius;
attack.y = attack.centerY + Math.sin(currentOrbAngle) * attack.radius;
if (attack.visual) {
attack.visual.x = attack.x;
attack.visual.y = attack.y;
}
attack.detachCounter--;
if (attack.detachCounter <= 0) {
attack.detached = true;
var tangentialAngle = currentOrbAngle + Math.PI / 2;
var launchSpeed = 6;
attack.vx = Math.cos(tangentialAngle) * launchSpeed;
attack.vy = Math.sin(tangentialAngle) * launchSpeed;
}
} else {
attack.x += attack.vx;
attack.y += attack.vy;
if (attack.visual) {
attack.visual.x = attack.x;
attack.visual.y = attack.y;
}
attack.lifeTime--;
if (attack.x < -200 || attack.x > 2248 || attack.y < -200 || attack.y > 2932) {
shouldRemove = true;
}
if (attack.lifeTime <= 0) {
shouldRemove = true;
}
}
} else if (attack.type === 'ultimate_orb') {
attack.x += attack.vx;
attack.y += attack.vy;
if (attack.visual) {
attack.visual.x = attack.x;
attack.visual.y = attack.y;
}
attack.lifeTime--;
if (attack.x < -200 || attack.x > 2048 + 200 || attack.y < -200 || attack.y > 2732 + 200) {
shouldRemove = true;
}
if (attack.lifeTime <= 0) {
shouldRemove = true;
}
} else if (attack.type === 'line_controller') {
attack.x += attack.directionX * attack.speed;
attack.y += attack.directionY * attack.speed;
var currentTime = LK.getGameTime();
if (attack.currentSegmentIndex < attack.totalSegmentsToSpawn && currentTime - attack.lastSpawnTime >= attack.segmentDelay) {
self.createAttack(attack.x, attack.y, attack.segmentLifeTime, 'line');
attack.currentSegmentIndex++;
attack.lastSpawnTime = currentTime;
}
var controllerLifeTimeAfterSpawning = attack.segmentLifeTime;
if (attack.currentSegmentIndex >= attack.totalSegmentsToSpawn && LK.getGameTime() - attack.lastSpawnTime > controllerLifeTimeAfterSpawning) {
shouldRemove = true;
}
} else if (attack.attackObjectType && attack.type !== 'line_controller') {
attack.lifeTime--;
if (attack.lifeTime <= 0) {
shouldRemove = true;
}
}
if (!shouldRemove && attack.isActive && typeof player !== 'undefined' && player && !player.dead && !player.invulnerable) {
if (attack.type !== 'line_controller') {
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 * 0.8;
var colRadius = attack.collisionRadius || attack.radius || 60;
if (distance_p < playerRadius_p + colRadius) {
player.takeDamage(1);
shouldRemove = true;
}
}
}
if (shouldRemove) {
if (attack.visual && !attack.visual.destroyed) {
if (attack.visual.parent) {
attack.visual.parent.removeChild(attack.visual);
}
attack.visual.destroy();
}
self.attacks.splice(i, 1);
}
}
if (self.attackCooldown > 0) {
self.attackCooldown--;
}
if (self.attackCooldown <= 0 && !self.isCharging) {
self.startAttackPattern();
}
}; // Koniec self.update dla Bossa
// Pełne implementacje funkcji lineAttack, chargeAttack, ultimateAttack, startAttackPattern
// powinny być tutaj wklejone z Twojej poprzedniej, kompletnej wersji.
// Poniżej skrócone wersje dla kompletności struktury.
self.lineAttack = function () {
LK.getSound('bossAttack').play();
if (typeof player === 'undefined' || !player) {
return;
}
var startX = self.x;
var startY = self.y;
var targetX = player.x;
var targetY = player.y;
var dx = targetX - startX;
var dy = targetY - startY;
var distance = Math.sqrt(dx * dx + dy * dy);
var normalizedDx = 0;
var normalizedDy = 0;
if (distance > 0) {
normalizedDx = dx / distance;
normalizedDy = dy / distance;
}
var wallSpeed = typeof isNewBossPlusMode !== 'undefined' && isNewBossPlusMode ? 3 : 2;
var numberOfSegments = typeof isNewBossPlusMode !== 'undefined' && isNewBossPlusMode ? 10 : 8;
var segmentSpawnDuration = typeof isNewBossPlusMode !== 'undefined' && isNewBossPlusMode ? 2500 : 3000;
var segmentLifeTime = typeof isNewBossPlusMode !== 'undefined' && isNewBossPlusMode ? 2000 : 2500;
var delayPerSegment = segmentSpawnDuration / numberOfSegments;
var lineAttackController = {
type: 'line_controller',
currentSegmentIndex: 0,
lastSpawnTime: LK.getGameTime(),
totalSegmentsToSpawn: numberOfSegments,
segmentDelay: delayPerSegment,
segmentLifeTime: segmentLifeTime,
directionX: normalizedDx,
directionY: normalizedDy,
speed: wallSpeed,
x: startX,
y: startY,
targetX: targetX,
targetY: targetY
};
self.attacks.push(lineAttackController);
};
self.chargeAttack = function () {
LK.getSound('bossAttack').play();
if (typeof player === 'undefined' || !player) {
return;
}
if (self.bossAttackAnim) {
self.bossAttackAnim.stop();
if (self.bossAttackAnim.parent) {
self.bossAttackAnim.parent.removeChild(self.bossAttackAnim);
}
self.bossAttackAnim.destroy();
self.bossAttackAnim = null;
}
if (!self.bossGraphics || self.bossGraphics && self.bossGraphics.destroyed) {
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;
}
self.isCharging = true;
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 chargeDistance = typeof isNewBossPlusMode !== 'undefined' && isNewBossPlusMode ? 700 : 500;
var chargeDuration = typeof isNewBossPlusMode !== 'undefined' && isNewBossPlusMode ? 600 : 800;
tween(self, {
x: self.x + dx * chargeDistance,
y: self.y + dy * chargeDistance
}, {
duration: chargeDuration * (self.attackSpeedMultiplier || 1),
easing: tween.easeIn,
onFinish: function onFinish() {
self.isCharging = false;
}
});
};
self.ultimateAttack = function () {
if (self.dead || typeof gameState !== 'undefined' && gameState.currentState !== "game" || typeof isNewBossPlusMode !== 'undefined' && !isNewBossPlusMode) {
return;
}
if (self.bossAttackAnim) {
self.bossAttackAnim.stop();
if (self.bossAttackAnim.parent) {
self.bossAttackAnim.parent.removeChild(self.bossAttackAnim);
}
self.bossAttackAnim.destroy();
self.bossAttackAnim = null;
}
if (!self.bossGraphics || self.bossGraphics && self.bossGraphics.destroyed) {
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 screenWidth = 2048;
var screenHeight = 2732;
var iconWidth = 700;
var iconHeight = 700;
var startX = -iconWidth;
var middleX = screenWidth / 2;
var endX = screenWidth + iconWidth;
var targetY = 250;
var hoverAmplitudeY = 20;
var hoverDurationY = 1000;
var rotationAmplitude = 0.05;
var rotationDuration = 1500;
var moveInDuration = 400;
var hoverDurationTotal = 2000;
var moveOutDuration = 400;
var attackIcon = game.addChild(LK.getAsset('ultimateBossAttack_icon', {
anchorX: 0.5,
anchorY: 0.5,
x: startX,
y: targetY,
alpha: 0,
scaleX: 0.8,
scaleY: 0.8,
rotation: 0
}));
tween(attackIcon, {
alpha: 1,
scaleX: 1,
scaleY: 1,
x: middleX
}, {
duration: moveInDuration,
easing: tween.easeOutQuad,
onFinish: function onFinish() {
if (attackIcon.destroyed) {
return;
}
var hoverTweenY = tween(attackIcon, {
y: attackIcon.y + hoverAmplitudeY
}, {
duration: hoverDurationY / 2,
easing: tween.easeInOut,
yoyo: true,
repeat: Infinity
});
var rotationTween = tween(attackIcon, {
rotation: attackIcon.rotation + rotationAmplitude
}, {
duration: rotationDuration / 2,
easing: tween.easeInOut,
yoyo: true,
repeat: Infinity
});
LK.setTimeout(function () {
if (attackIcon.destroyed) {
if (hoverTweenY) {
hoverTweenY.stop();
}
if (rotationTween) {
rotationTween.stop();
}
return;
}
if (hoverTweenY) {
hoverTweenY.stop();
}
if (rotationTween) {
rotationTween.stop();
}
tween(attackIcon, {
y: targetY,
rotation: 0
}, {
duration: 100,
onFinish: function onFinish() {
tween(attackIcon, {
alpha: 0,
x: endX
}, {
duration: moveOutDuration,
easing: tween.easeInQuad,
onFinish: function onFinish() {
if (attackIcon && !attackIcon.destroyed) {
if (attackIcon.parent) {
attackIcon.parent.removeChild(attackIcon);
}
attackIcon.destroy();
}
}
});
}
});
}, hoverDurationTotal);
}
});
var originalOrbWidth = 300;
var originalOrbHeight = 300;
var baseOrbRadius = Math.max(originalOrbWidth, originalOrbHeight) / 2;
var growAndFadeInDuration = 6000;
var finalScale = 2.6;
var orbSpeed = 8;
var travelDurationMs = 6000;
function createAndLaunchOrb(spawnX, spawnDelay) {
LK.setTimeout(function () {
if (self.dead || typeof gameState !== 'undefined' && gameState.currentState !== "game") {
return;
}
var orbFrames = [];
for (var i = 0; i <= 7; i++) {
orbFrames.push(LK.getAsset('ultimatebossattack_orb_' + i, {
anchorX: 0.5,
anchorY: 0.5,
clone: true
}));
}
var orbAnim = new SpriteAnimation({
frames: orbFrames,
frameDuration: 70,
loop: true,
x: spawnX,
y: 300,
anchorX: 0.5,
anchorY: 0.5,
scaleX: 0.1,
scaleY: 0.1,
alpha: 0
});
game.addChild(orbAnim);
tween(orbAnim, {
scaleX: finalScale,
scaleY: finalScale,
alpha: 1
}, {
duration: growAndFadeInDuration,
easing: tween.easeOutQuad,
onFinish: function onFinish() {
if (orbAnim.destroyed) {
return;
}
var targetXPlayer = typeof player !== 'undefined' && player ? player.x : screenWidth / 2;
var targetYPlayer = typeof player !== 'undefined' && player ? player.y : screenHeight / 2;
var actualOrbRadius = baseOrbRadius * finalScale;
var dxOrb = targetXPlayer - orbAnim.x;
var dyOrb = targetYPlayer - orbAnim.y;
var distOrb = Math.sqrt(dxOrb * dxOrb + dyOrb * dyOrb);
var vxOrb = distOrb > 0 ? dxOrb / distOrb * orbSpeed : 0;
var vyOrb = distOrb > 0 ? dyOrb / distOrb * orbSpeed : 0;
self.attacks.push({
type: 'ultimate_orb',
visual: orbAnim,
vx: vxOrb,
vy: vyOrb,
radius: actualOrbRadius,
lifeTime: travelDurationMs / (1000 / 60),
x: orbAnim.x,
y: orbAnim.y,
isActive: true
});
}
});
}, spawnDelay);
}
var spawnDelayBetweenOrbs = 1000;
createAndLaunchOrb(screenWidth / 2, 0);
createAndLaunchOrb(screenWidth / 2 - 600, spawnDelayBetweenOrbs);
createAndLaunchOrb(screenWidth / 2 + 600, spawnDelayBetweenOrbs * 2);
};
self.startAttackPattern = function () {
self.attackCooldown = typeof isNewBossPlusMode !== 'undefined' && isNewBossPlusMode ? 150 : 190;
var attackType;
var availableAttacks = [];
if (self.circleAttackActiveCooldown >= self.circleAttackCooldownDuration) {
availableAttacks.push(0);
}
availableAttacks.push(1);
availableAttacks.push(2);
var isUltimateAttackReady = self.ultimateAttackCooldownTimer >= self.nextUltimateAttackAvailableTime;
if (typeof isNewBossPlusMode !== 'undefined' && isNewBossPlusMode && isUltimateAttackReady) {
availableAttacks.push(3);
}
if (availableAttacks.length === 0) {
return;
}
attackType = availableAttacks[Math.floor(Math.random() * availableAttacks.length)];
self.playBossAttackAnim(attackType);
if (attackType === 0) {
self.circleAttack();
self.circleAttackActiveCooldown = 0;
} else if (attackType === 1) {
self.lineAttack();
} else if (attackType === 2) {
self.chargeAttack();
} else if (attackType === 3 && typeof isNewBossPlusMode !== 'undefined' && isNewBossPlusMode) {
self.ultimateAttack();
self.ultimateAttackCooldownTimer = 0;
self.nextUltimateAttackAvailableTime = self.ultimateAttackMinInterval + Math.floor(Math.random() * (self.ultimateAttackMaxInterval - self.ultimateAttackMinInterval + 1));
self.attackCooldown = 6 * 60;
}
};
// Inicjalizacja zmiennych bossa
self.nextUltimateAttackAvailableTime = self.ultimateAttackMinInterval + Math.floor(Math.random() * (self.ultimateAttackMaxInterval - self.ultimateAttackMinInterval + 1));
self.health = typeof isNewBossPlusMode !== 'undefined' && isNewBossPlusMode ? 300 : 150;
self.maxHealth = self.health;
self.speed = typeof isNewBossPlusMode !== 'undefined' && isNewBossPlusMode ? 3 : 2;
self.attackSpeedMultiplier = 1;
self.phase = 1;
self.attacks = [];
self.attackCooldown = 120; // Początkowy cooldown przed pierwszym atakiem
self.dead = false;
return self;
});
var MinibossCC = Container.expand(function (options) {
var self = Container.call(this);
options = options || {};
self.maxHp = options.maxHp || 200;
self.hp = self.maxHp;
self.speed = options.speed || 0;
self.isDead = false;
self.attackPattern = ['explosiveProjectile', 'laserWall'];
self.currentAttackIndex = 0;
self.attackCooldowns = {
'explosiveProjectile': 8 * 60,
'laserWall': 5 * 60
};
self.attackCooldown = 120;
self.currentAttackName = '';
self.isCurrentlyPlayingAttackAnim = false;
self.activeSkillAnimationInstance = null;
self.isTeleporting = false;
self.teleportCooldownTimer = 0;
self.teleportIntervalMin = 9 * 60;
self.teleportIntervalMax = 16 * 60;
self.nextTeleportTime = self.teleportIntervalMin + Math.floor(Math.random() * (self.teleportIntervalMax - self.teleportIntervalMin + 1));
self.activeClones = [];
self.maxClones = 2;
self.canTeleportWithClones = true;
self.teleportCloneCooldownDuration = 10 * 60;
self.isWaitingForClonesToDespawn = false;
self.cloneTeleportTimeoutId = null;
self.creepSpeed = 0.9;
self.creepDirectionX = 0;
self.creepDirectionY = 0;
self.creepDirectionChangeIntervalMin = 3 * 60;
self.creepDirectionChangeIntervalMax = 7 * 60;
self.creepDirectionChangeTimer = 0;
self.actualWidth = 120;
self.actualHeight = 120;
try {
self.graphics = self.attachAsset('miniboss_cc_asset', {
anchorX: 0.5,
anchorY: 0.5
});
} catch (e) {
console.warn("Nie udało się załadować 'miniboss_cc_asset', używam awaryjnego Shape:", e);
self.graphics = new Shape({
width: 120,
height: 120,
color: 0xFF8C00,
shape: 'box'
});
self.addChild(self.graphics);
}
if (self.graphics && typeof self.graphics.width !== 'undefined') {
self.width = self.graphics.width * (self.graphics.scaleX || 1);
self.height = self.graphics.height * (self.graphics.scaleY || 1);
} else {
self.width = 120;
self.height = 120;
}
if (self.graphics && typeof self.graphics.width === 'number' && typeof self.graphics.height === 'number') {
self.actualWidth = self.graphics.width * (self.graphics.scaleX || 1);
self.actualHeight = self.graphics.height * (self.graphics.scaleY || 1);
} else {
self.actualWidth = self.width;
self.actualHeight = self.height;
}
self.pickNewCreepDirection = function () {
var angle = Math.random() * 2 * Math.PI;
self.creepDirectionX = Math.cos(angle);
self.creepDirectionY = Math.sin(angle);
self.creepDirectionChangeTimer = self.creepDirectionIntervalMin + Math.floor(Math.random() * (self.creepDirectionChangeIntervalMax - self.creepDirectionIntervalMin + 1));
};
self.createMinibossSkillAnim = function () {
var frames = [];
var frameBaseName = 'bossSkillAnim0';
var totalSkillFrames = 9;
for (var i = 0; i < totalSkillFrames; i++) {
var assetName = frameBaseName + (i === 0 ? '' : String(i));
try {
frames.push(LK.getAsset(assetName, {
anchorX: 0.5,
anchorY: 0.5,
clone: true
}));
} catch (e) {
console.error("Błąd ładowania klatki animacji Klona: " + assetName, e);
if (i > 0 && frames.length > 0) {
frames.push(frames[0]);
} else {
var placeholderFrame = new Shape({
width: 120,
height: 120,
color: 0xFF00FF,
shape: 'box'
});
placeholderFrame.anchor.set(0.5, 0.5);
frames.push(placeholderFrame);
}
}
}
for (var i = totalSkillFrames - 2; i >= 1; i--) {
var assetName = frameBaseName + (i === 0 ? '' : String(i));
try {
frames.push(LK.getAsset(assetName, {
anchorX: 0.5,
anchorY: 0.5,
clone: true
}));
} catch (e) {
console.error("Błąd ładowania klatki powrotnej animacji Klona: " + assetName, e);
if (frames.length > 0) {
frames.push(frames[0]);
}
}
}
var skillAnimationObject = new SpriteAnimation({
frames: frames,
frameDuration: 120,
loop: false,
anchorX: 0.5,
anchorY: 0.5,
x: 0,
y: 0
});
return skillAnimationObject;
};
self.playMinibossSkillAnim = function (onAnimationCompleteCallback) {
if (self.isDead || self.isTeleporting) {
self.isCurrentlyPlayingAttackAnim = false;
if (typeof onAnimationCompleteCallback === 'function') {
onAnimationCompleteCallback();
}
return;
}
if (self.activeSkillAnimationInstance && self.activeSkillAnimationInstance.parent) {
self.activeSkillAnimationInstance.stop();
self.removeChild(self.activeSkillAnimationInstance);
self.activeSkillAnimationInstance.destroy();
self.activeSkillAnimationInstance = null;
}
if (self.graphics && !self.graphics.destroyed) {
self.graphics.visible = false;
}
var newAnimation = self.createMinibossSkillAnim();
self.activeSkillAnimationInstance = self.addChild(newAnimation);
self.activeSkillAnimationInstance.currentFrameIndex = 0;
self.activeSkillAnimationInstance.animationTimer = 0;
self.activeSkillAnimationInstance.framesArray = newAnimation.frames;
self.activeSkillAnimationInstance.singleFrameDuration = newAnimation.frameDuration || 120;
self.activeSkillAnimationInstance.playing = true;
self.activeSkillAnimationInstance.removeChildren();
if (self.activeSkillAnimationInstance.framesArray && self.activeSkillAnimationInstance.framesArray.length > 0) {
self.activeSkillAnimationInstance.addChild(self.activeSkillAnimationInstance.framesArray[0]);
} else {
self.isCurrentlyPlayingAttackAnim = false;
if (self.graphics && !self.graphics.destroyed) {
self.graphics.visible = true;
}
if (typeof onAnimationCompleteCallback === 'function') {
onAnimationCompleteCallback();
}
if (self.activeSkillAnimationInstance) {
if (self.activeSkillAnimationInstance.parent) {
self.removeChild(self.activeSkillAnimationInstance);
}
self.activeSkillAnimationInstance.destroy();
self.activeSkillAnimationInstance = null;
}
return;
}
self.activeSkillAnimationInstance.update = function () {
if (!this.playing || !this.framesArray || this.framesArray.length === 0) {
return;
}
this.animationTimer++;
if (this.animationTimer * (1000 / 60) >= this.singleFrameDuration) {
this.animationTimer = 0;
this.currentFrameIndex++;
if (this.currentFrameIndex >= this.framesArray.length) {
this.playing = false;
if (self.graphics && !self.graphics.destroyed) {
self.graphics.visible = true;
}
if (this.parent) {
this.parent.removeChild(this);
}
this.destroy();
if (self.activeSkillAnimationInstance === this) {
self.activeSkillAnimationInstance = null;
}
if (typeof onAnimationCompleteCallback === 'function') {
onAnimationCompleteCallback();
} else {
self.isCurrentlyPlayingAttackAnim = false;
}
} else {
this.removeChildren();
if (this.framesArray[this.currentFrameIndex]) {
this.addChild(this.framesArray[this.currentFrameIndex]);
}
}
}
};
};
self.update = function () {
if (self.isDead || gameState.currentState !== "cursedCrystal") {
return;
}
if (self.isTeleporting || self.activeSkillAnimationInstance) {} else {
self.creepDirectionChangeTimer--;
if (self.creepDirectionChangeTimer <= 0) {
self.pickNewCreepDirection();
}
var nextX = self.x + self.creepDirectionX * self.creepSpeed;
var nextY = self.y + self.creepDirectionY * self.creepSpeed;
var halfWidth = self.actualWidth / 2;
var halfHeight = self.actualHeight / 2;
var arenaMinX = 50 + halfWidth;
var arenaMaxX = 2048 - 50 - halfWidth;
var arenaMinY = 50 + halfHeight;
var arenaMaxY = 2732 - 50 - halfHeight;
if (nextX >= arenaMinX && nextX <= arenaMaxX) {
self.x = nextX;
} else {
self.creepDirectionX *= -1;
self.creepDirectionChangeTimer = self.creepDirectionIntervalMin;
}
if (nextY >= arenaMinY && nextY <= arenaMaxY) {
self.y = nextY;
} else {
self.creepDirectionY *= -1;
self.creepDirectionChangeTimer = self.creepDirectionIntervalMin;
}
}
if (self.canTeleportWithClones && !self.isTeleporting) {
self.teleportCooldownTimer++;
if (self.teleportCooldownTimer >= self.nextTeleportTime) {
self.teleport();
return;
}
}
if (!self.isTeleporting) {
if (self.attackCooldown > 0) {
self.attackCooldown--;
} else if (player && !player.dead && !self.isCurrentlyPlayingAttackAnim) {
self.isCurrentlyPlayingAttackAnim = true;
self.performAttack();
}
}
};
self.performAttack = function () {
if (!player || player.dead || self.isTeleporting) {
self.isCurrentlyPlayingAttackAnim = false;
return;
}
var executeAttackLogic = function executeAttackLogic() {
if (self.isDead) {
self.isCurrentlyPlayingAttackAnim = false;
return;
}
self.currentAttackName = self.attackPattern[self.currentAttackIndex];
if (self.currentAttackName === 'explosiveProjectile') {
var createProjectileWithDelay = function createProjectileWithDelay(delayMs) {
LK.setTimeout(function () {
if (self.isDead || !player || player.dead) {
return;
}
var projectile = new MinibossExplosiveProjectile({
x: self.x,
y: self.y,
target: player,
speed: 7,
explosionRadius: 150
});
game.addChild(projectile);
if (gameState.cursedCrystalActiveProjectiles === undefined) {
gameState.cursedCrystalActiveProjectiles = [];
}
gameState.cursedCrystalActiveProjectiles.push(projectile);
console.log("MinibossCC: Wystrzelono pocisk eksplozywny (opóźnienie: " + delayMs + "ms)");
}, delayMs);
};
createProjectileWithDelay(0); // Pierwszy pocisk natychmiast
if (gameState.minibossEnhancedProjectile) {
// Załóżmy, że ta flaga istnieje w gameState
console.log("MinibossCC (Ulepszony): Wystrzeliwuje drugi pocisk!");
createProjectileWithDelay(2000); // Drugi pocisk po 2000ms (2 sekundy)
}
} else if (self.currentAttackName === 'laserWall') {
var numTotalSegments = 7;
var middleSegmentIndex = Math.floor(numTotalSegments / 2);
var scytheWidth = 160;
var scytheHeight = 160;
var spacingBetweenSegments = 20;
var totalWallEffectiveHeight = numTotalSegments * scytheHeight + (numTotalSegments - 1) * spacingBetweenSegments;
var wallPivotX = self.x;
var wallPivotY = self.y;
var warningDuration = 1500;
var activeDurationStandard = 4000; // Dla 2 obrotów
var rotationSpeedStandard = 2 * Math.PI / 120; // 1 obrót na 120 klatek (2s)
var activeDurationEnhanced = 4500; // Czas na 3 obroty (3 * 1.5s)
var rotationSpeedEnhanced = 2 * Math.PI / 90; // 1 obrót na 90 klatek (1.5s)
var currentActiveDuration = activeDurationStandard;
var currentRotationSpeed = rotationSpeedStandard;
if (gameState.minibossEnhancedLaserWall) {
// Zakładamy dostęp do gameState
console.log("Miniboss: Ulepszony LaserWall - szybszy i 3 obroty!");
currentActiveDuration = activeDurationEnhanced;
currentRotationSpeed = rotationSpeedEnhanced;
}
var activeDuration = 4000;
var rotationSpeed = 2 * Math.PI / 120;
var laserWallInstance = {
pivotX: wallPivotX,
pivotY: wallPivotY,
segments: [],
currentAngle: 0,
rotationSpeed: currentRotationSpeed,
warningTimer: warningDuration,
activeTimer: currentActiveDuration,
isDead: false
};
var initialSegmentOffsetY = -(totalWallEffectiveHeight / 2) + scytheHeight / 2;
var frameDurationAction = 100;
var frameDurationLoop = 100;
for (var i = 0; i < numTotalSegments; i++) {
var segmentOffsetY = initialSegmentOffsetY + i * (scytheHeight + spacingBetweenSegments);
var spawnPosX = wallPivotX;
var spawnPosY = wallPivotY + segmentOffsetY;
if (i === middleSegmentIndex) {
laserWallInstance.segments.push({
isPlaceholder: true,
initialOffsetY: segmentOffsetY,
width: scytheWidth,
height: scytheHeight
});
continue;
}
var scytheAppearVisual = null;
var FADE_IN_DURATION = 500; // Czas trwania fade in w milisekundach - dostosuj!
try {
scytheAppearVisual = LK.getAsset('scythe_appear_0', {
anchorX: 0.5,
anchorY: 0.5,
clone: true,
alpha: 0 // Zacznij od przezroczystości 0
});
scytheAppearVisual.x = spawnPosX;
scytheAppearVisual.y = spawnPosY;
game.addChild(scytheAppearVisual);
tween(scytheAppearVisual, {
alpha: 1
}, {
duration: FADE_IN_DURATION,
easing: tween.easeInQuad
});
} catch (e) {
console.error("Błąd ładowania assetu scythe_appear_0: ", e);
scytheAppearVisual = new Shape({
width: scytheWidth,
height: scytheHeight,
color: 0xCCCCCC,
shape: 'box',
alpha: 0 // Zacznij od przezroczystości 0
});
scytheAppearVisual.anchor.set(0.5, 0.5);
scytheAppearVisual.x = spawnPosX;
scytheAppearVisual.y = spawnPosY;
game.addChild(scytheAppearVisual);
tween(scytheAppearVisual, {
alpha: 1
}, {
duration: FADE_IN_DURATION,
easing: tween.easeInQuad
});
}
laserWallInstance.segments.push({
isPlaceholder: false,
initialOffsetX: 0,
initialOffsetY: segmentOffsetY,
width: scytheWidth,
height: scytheHeight,
visual: scytheAppearVisual,
currentX: spawnPosX,
currentY: spawnPosY,
animationPhase: 'appearing_static'
});
}
LK.setTimeout(function () {
if (laserWallInstance.isDead) {
return;
}
laserWallInstance.segments.forEach(function (segment) {
if (segment.isPlaceholder || segment.animationPhase !== 'appearing_static') {
return;
}
if (segment.visual && segment.visual.parent) {
segment.visual.parent.removeChild(segment.visual);
segment.visual.destroy();
}
var actionFrames = [];
var actionAssetNames = ['scythe_action_0', 'scythe_action_1', 'scythe_action_2', 'scythe_action_3', 'scythe_action_4', 'scythe_action_5'];
try {
actionAssetNames.forEach(function (name) {
actionFrames.push(LK.getAsset(name, {
anchorX: 0.5,
anchorY: 0.5,
clone: true
}));
});
} catch (e) {
console.error("Błąd ładowania klatek akcji kosy: ", e);
}
if (actionFrames.length === 0) {
console.error("Brak klatek dla actionScytheAnim segmentu", segment);
segment.visual = new Shape({
width: scytheWidth,
height: scytheHeight,
color: 0xFF0000,
shape: 'box'
});
segment.visual.anchor.set(0.5, 0.5);
segment.visual.x = segment.currentX;
segment.visual.y = segment.currentY;
game.addChild(segment.visual);
segment.animationPhase = 'error_action';
return;
}
var actionScytheAnim = new SpriteAnimation({
frames: actionFrames,
frameDuration: frameDurationAction,
loop: false,
x: segment.currentX,
y: segment.currentY,
anchorX: 0.5,
anchorY: 0.5
});
game.addChild(actionScytheAnim);
segment.visual = actionScytheAnim;
segment.animationPhase = 'action_once';
actionScytheAnim.play();
actionScytheAnim.onComplete = function () {
if (segment.isPlaceholder || segment.animationPhase !== 'action_once' || laserWallInstance && laserWallInstance.isDead) {
return;
}
if (segment.visual && segment.visual.parent) {
segment.visual.parent.removeChild(segment.visual);
segment.visual.destroy();
}
var loopFrames = [];
var loopAssetIndices = [2, 3, 4, 5];
var loopAssetNames = loopAssetIndices.map(function (idx) {
return idx < actionAssetNames.length ? actionAssetNames[idx] : null;
}).filter(function (name) {
return name !== null;
});
try {
loopAssetNames.forEach(function (name) {
loopFrames.push(LK.getAsset(name, {
anchorX: 0.5,
anchorY: 0.5,
clone: true
}));
});
} catch (e) {
console.error("Błąd ładowania klatek pętli kosy: ", e);
}
if (loopFrames.length === 0) {
console.error("Brak klatek dla loopingScytheAnim segmentu", segment);
segment.visual = new Shape({
width: scytheWidth,
height: scytheHeight,
color: 0xFF0000,
shape: 'box'
});
segment.visual.anchor.set(0.5, 0.5);
segment.visual.x = segment.currentX;
segment.visual.y = segment.currentY;
game.addChild(segment.visual);
segment.animationPhase = 'error_loop';
return;
}
var loopingScytheAnim = new SpriteAnimation({
frames: loopFrames,
frameDuration: frameDurationLoop,
loop: true,
x: segment.currentX,
y: segment.currentY,
anchorX: 0.5,
anchorY: 0.5
});
game.addChild(loopingScytheAnim);
segment.visual = loopingScytheAnim;
segment.animationPhase = 'looping_cut';
loopingScytheAnim.play();
};
});
}, warningDuration);
if (gameState.cursedCrystalActiveLaserWalls === undefined) {
gameState.cursedCrystalActiveLaserWalls = [];
}
gameState.cursedCrystalActiveLaserWalls.push(laserWallInstance);
}
self.currentAttackIndex = (self.currentAttackIndex + 1) % self.attackPattern.length;
self.attackCooldown = self.attackCooldowns[self.currentAttackName] || 180;
self.isCurrentlyPlayingAttackAnim = false;
};
self.playMinibossSkillAnim(executeAttackLogic);
};
self.teleport = function () {
if (self.isDead || self.isTeleporting) {
console.log("MinibossCC: Próba teleportacji, ale już martwy lub teleportuje. Dead: " + self.isDead + ", Teleporting: " + self.isTeleporting);
return;
}
if (!self.canTeleportWithClones) {
console.log("MinibossCC: Próba teleportacji z klonami, ale jest na cooldownie po klonach lub czeka na klony.");
return;
}
self.canTeleportWithClones = false;
self.isWaitingForClonesToDespawn = true;
console.log("MinibossCC: TELEPORT START. Pozycja: X=" + self.x.toFixed(0) + ", Y=" + self.y.toFixed(0) + ", Alpha: " + self.alpha);
self.isTeleporting = true;
self.attackCooldown = 120 + Math.floor(Math.random() * 60);
tween(self, {
alpha: 0
}, {
duration: 600,
easing: tween.easeInQuad,
onFinish: function onFinish() {
if (self.isDead) {
self.alpha = 1;
self.isTeleporting = false;
self.isWaitingForClonesToDespawn = false;
self.canTeleportWithClones = true;
console.log("MinibossCC: Teleport przerwany, boss martwy w trakcie fade-out.");
return;
}
var halfWidth = self.actualWidth / 2;
var halfHeight = self.actualHeight / 2;
var arenaMinX = 50 + halfWidth;
var arenaMaxX = 2048 - 50 - halfWidth;
var arenaMinY = 50 + halfHeight;
var arenaMaxY = 2732 - 50 - halfHeight;
var newX, newY, distanceToPlayer;
var attempts = 0;
var minDistanceToPlayer = 300;
do {
newX = arenaMinX + Math.random() * (arenaMaxX - arenaMinX);
newY = arenaMinY + Math.random() * (arenaMaxY - arenaMinY);
if (typeof player !== 'undefined' && player && !player.dead) {
distanceToPlayer = Math.sqrt(Math.pow(newX - player.x, 2) + Math.pow(newY - player.y, 2));
} else {
distanceToPlayer = minDistanceToPlayer + 1;
}
attempts++;
} while (distanceToPlayer < minDistanceToPlayer && attempts < 10);
if (attempts >= 10 && distanceToPlayer < minDistanceToPlayer) {
console.warn("MinibossCC: Nie udało się znaleźć pozycji wystarczająco daleko od gracza po 10 próbach.");
}
self.x = newX;
self.y = newY;
self.pickNewCreepDirection();
var clonesActuallySpawned = 0;
var minClonesToSpawn = 1;
var maxClonesToSpawn = 3; // Domyślnie 1-2 klony
// Użyj jednej flagi, np. gameState.minibossIsEnhanced, lub sprawdź jedną z istniejących
// Zakładam, że masz flagę np. gameState.minibossEnhancedProjectile lub stworzysz ogólną gameState.minibossIsEnhanced
if (gameState.minibossEnhancedProjectile) {
// Zmień ten warunek na swoją flagę ulepszenia bossa
console.log("MinibossCC (Ulepszony): Tworzy więcej klonów przy teleportacji!");
minClonesToSpawn = 3;
maxClonesToSpawn = 5;
}
var clonesToAttemptSpawn = minClonesToSpawn + Math.floor(Math.random() * (maxClonesToSpawn - minClonesToSpawn + 1));
console.log("MinibossCC będzie próbował stworzyć klonów:", clonesToAttemptSpawn);
for (var i = 0; i < clonesToAttemptSpawn; i++) {
if (self.activeClones.length < self.maxClones) {
// Nadal respektujemy self.maxClones
var cloneSpawnAttempts = 0;
var cloneX, cloneY, distToBoss, distToPlayerClone;
var minDistanceToBoss = 200;
var minDistanceToPlayerForClone = 150;
// Logika do...while do znajdowania pozycji klona POZOSTAJE BEZ ZMIAN
do {
cloneX = arenaMinX + Math.random() * (arenaMaxX - arenaMinX);
cloneY = arenaMinY + Math.random() * (arenaMaxY - arenaMinY);
distToBoss = Math.sqrt(Math.pow(cloneX - self.x, 2) + Math.pow(cloneY - self.y, 2));
if (typeof player !== 'undefined' && player && !player.dead) {
distToPlayerClone = Math.sqrt(Math.pow(cloneX - player.x, 2) + Math.pow(cloneY - player.y, 2));
} else {
distToPlayerClone = minDistanceToPlayerForClone + 1;
}
cloneSpawnAttempts++;
} while ((distToBoss < minDistanceToBoss || distToPlayerClone < minDistanceToPlayerForClone) && cloneSpawnAttempts < 10);
var clone = new MinibossCCClone({
x: cloneX,
y: cloneY,
owner: self
});
game.addChild(clone);
self.activeClones.push(clone);
clonesActuallySpawned++;
} else {
console.log("MinibossCC: Osiągnięto maksymalną liczbę klonów (" + self.maxClones + "), nie można stworzyć więcej.");
break;
}
}
if (clonesActuallySpawned === 0 && self.isWaitingForClonesToDespawn) {
console.log("MinibossCC: No clones were spawned, starting teleport cooldown immediately.");
self.startTeleportCooldownAfterClones();
}
self.teleportCooldownTimer = 0;
self.nextTeleportTime = self.teleportIntervalMin + Math.floor(Math.random() * (self.teleportIntervalMax - self.teleportIntervalMin + 1));
tween(self, {
alpha: 1
}, {
duration: 600,
easing: tween.easeOutQuad,
onFinish: function onFinishFadeIn() {
self.isTeleporting = false;
console.log("MinibossCC: TELEPORT FADE_IN_FINISH. Następna normalna teleportacja za: " + (self.nextTeleportTime / 60).toFixed(1) + "s");
}
});
}
});
};
self.takeDamage = function (amount) {
if (self.isDead) {
return;
}
self.hp -= amount;
gameState.cursedCrystalMinibossHP = self.hp;
LK.effects.flashObject(self.graphics || self, 0xFF0000, 200);
if (self.hp <= 0) {
self.hp = 0;
gameState.cursedCrystalMinibossHP = self.hp;
self.die();
}
if (ui && ui.updateMinibossHealthCC) {
ui.updateMinibossHealthCC(self.hp, self.maxHp, true);
}
};
self.die = function () {
if (self.isDead) {
return;
}
if (self.cloneTeleportTimeoutId) {
LK.clearTimeout(self.cloneTeleportTimeoutId);
self.cloneTeleportTimeoutId = null;
}
self.isWaitingForClonesToDespawn = false;
self.canTeleportWithClones = true;
if (self.activeClones && self.activeClones.length > 0) {
console.log("MinibossCC dying, clearing " + self.activeClones.length + " active clones.");
var clonesToKill = self.activeClones.slice();
clonesToKill.forEach(function (clone) {
if (clone && !clone.isDead) {
clone.die(true);
}
});
self.activeClones = [];
}
self.isDead = true;
console.log("MinibossCC has been defeated!");
if (gameState.cursedCrystalActiveProjectiles) {
gameState.cursedCrystalActiveProjectiles.forEach(function (proj) {});
gameState.cursedCrystalActiveProjectiles = [];
}
if (gameState.cursedCrystalActiveExplosions) {
gameState.cursedCrystalActiveExplosions = [];
}
if (gameState.cursedCrystalActiveLaserWalls) {
gameState.cursedCrystalActiveLaserWalls.forEach(function (wall) {});
gameState.cursedCrystalActiveLaserWalls = [];
}
if (typeof gameState.endCursedCrystalMode === 'function') {
gameState.endCursedCrystalMode(true);
}
if (self.parent) {
self.parent.removeChild(self);
}
if (self.destroy && !self.destroyed) {
self.destroy();
}
gameState.cursedCrystalMinibossObject = null;
gameState.isMinibossActiveCC = false;
};
self.startTeleportCooldownAfterClones = function () {
if (!self.isWaitingForClonesToDespawn && self.activeClones.length > 0) {
return;
}
if (!self.isWaitingForClonesToDespawn && self.activeClones.length === 0) {
return;
}
console.log("MinibossCC: All clones despawned. Starting " + self.teleportCloneCooldownDuration / 60 + "-second cooldown for clone teleport.");
self.isWaitingForClonesToDespawn = false;
if (self.cloneTeleportTimeoutId) {
LK.clearTimeout(self.cloneTeleportTimeoutId);
}
self.cloneTeleportTimeoutId = LK.setTimeout(function () {
self.cloneTeleportTimeoutId = null;
if (!self.isDead) {
self.canTeleportWithClones = true;
self.nextTeleportTime = self.teleportCooldownTimer;
console.log("MinibossCC: Clone teleport is OFF COOLDOWN. Boss can attempt teleport. nextTeleportTime now: " + self.nextTeleportTime);
}
}, self.teleportCloneCooldownDuration * (1000 / 60));
};
self.x = options.x || 2048 / 2;
self.y = options.y || 300;
self.pickNewCreepDirection();
return self;
});
// -------- KONIEC ZAKTUALIZOWANEJ KLASY MinibossCC --------
// NOWA KLASA DLA KLONA MINIBOSSA CC
var MinibossCCClone = Container.expand(function (options) {
var self = Container.call(this);
options = options || {};
self.owner = options.owner;
self.x = options.x || 2048 / 2;
self.y = options.y || 300;
self.isDead = false;
self.lifeTimer = 30 * 60;
self.attackCooldown = 4 * 60 + Math.floor(Math.random() * (2 * 60));
self.activeSkillAnimationInstance = null;
self.isCurrentlyPlayingAttackAnim = false;
self.creepSpeed = 0.9;
self.creepDirectionX = 0;
self.creepDirectionY = 0;
self.creepDirectionChangeIntervalMin = 2 * 60;
self.creepDirectionChangeIntervalMax = 5 * 60;
self.creepDirectionChangeTimer = 0;
// --- DEFINICJA METODY pickNewCreepDirection PRZED PIERWSZYM UŻYCIEM ---
self.pickNewCreepDirection = function () {
var angle = Math.random() * 2 * Math.PI;
self.creepDirectionX = Math.cos(angle);
self.creepDirectionY = Math.sin(angle);
self.creepDirectionChangeTimer = self.creepDirectionIntervalMin + Math.floor(Math.random() * (self.creepDirectionChangeIntervalMax - self.creepDirectionIntervalMin + 1));
};
// --- PIERWSZE WYWOŁANIE METODY ---
self.pickNewCreepDirection();
try {
self.graphics = self.attachAsset('miniboss_cc_asset', {
anchorX: 0.5,
anchorY: 0.5
});
} catch (e) {
console.warn("Nie udało się załadować 'miniboss_cc_asset' dla Klona, używam Shape.", e);
self.graphics = new Shape({
width: 120,
height: 120,
color: 0xFF8C00,
shape: 'box'
});
self.addChild(self.graphics);
}
self.width = self.graphics && typeof self.graphics.width !== 'undefined' ? self.graphics.width : 120;
self.height = self.graphics && typeof self.graphics.height !== 'undefined' ? self.graphics.height : 120;
self.createMinibossSkillAnim = function () {
var frames = [];
var frameBaseName = 'bossSkillAnim0';
var totalSkillFrames = 9;
for (var i = 0; i < totalSkillFrames; i++) {
var assetName = frameBaseName + (i === 0 ? '' : String(i));
try {
frames.push(LK.getAsset(assetName, {
anchorX: 0.5,
anchorY: 0.5,
clone: true
}));
} catch (e) {
console.error("Błąd ładowania klatki animacji Klona: " + assetName, e);
if (i > 0 && frames.length > 0) {
frames.push(frames[0]);
} else {
var placeholderFrame = new Shape({
width: 120,
height: 120,
color: 0xFF00FF,
shape: 'box'
});
placeholderFrame.anchor.set(0.5, 0.5);
frames.push(placeholderFrame);
}
}
}
for (var i = totalSkillFrames - 2; i >= 1; i--) {
var assetName = frameBaseName + (i === 0 ? '' : String(i));
try {
frames.push(LK.getAsset(assetName, {
anchorX: 0.5,
anchorY: 0.5,
clone: true
}));
} catch (e) {
console.error("Błąd ładowania klatki powrotnej animacji Klona: " + assetName, e);
if (frames.length > 0) {
frames.push(frames[0]);
}
}
}
var skillAnimationObject = new SpriteAnimation({
frames: frames,
frameDuration: 120,
loop: false,
anchorX: 0.5,
anchorY: 0.5,
x: 0,
y: 0
});
return skillAnimationObject;
};
self.playMinibossSkillAnim = function (onAnimationCompleteCallback) {
if (self.isDead) {
self.isCurrentlyPlayingAttackAnim = false;
if (typeof onAnimationCompleteCallback === 'function') {
onAnimationCompleteCallback();
}
return;
}
if (self.activeSkillAnimationInstance && self.activeSkillAnimationInstance.parent) {
self.activeSkillAnimationInstance.stop();
self.removeChild(self.activeSkillAnimationInstance);
self.activeSkillAnimationInstance.destroy();
self.activeSkillAnimationInstance = null;
}
if (self.graphics && !self.graphics.destroyed) {
self.graphics.visible = false;
}
var newAnimation = self.createMinibossSkillAnim();
self.activeSkillAnimationInstance = self.addChild(newAnimation);
self.activeSkillAnimationInstance.currentFrameIndex = 0;
self.activeSkillAnimationInstance.animationTimer = 0;
self.activeSkillAnimationInstance.framesArray = newAnimation.frames;
self.activeSkillAnimationInstance.singleFrameDuration = newAnimation.frameDuration || 120;
self.activeSkillAnimationInstance.playing = true;
self.activeSkillAnimationInstance.removeChildren();
if (self.activeSkillAnimationInstance.framesArray && self.activeSkillAnimationInstance.framesArray.length > 0) {
self.activeSkillAnimationInstance.addChild(self.activeSkillAnimationInstance.framesArray[0]);
} else {
self.isCurrentlyPlayingAttackAnim = false;
if (self.graphics && !self.graphics.destroyed) {
self.graphics.visible = true;
}
if (typeof onAnimationCompleteCallback === 'function') {
onAnimationCompleteCallback();
}
if (self.activeSkillAnimationInstance) {
if (self.activeSkillAnimationInstance.parent) {
self.removeChild(self.activeSkillAnimationInstance);
}
self.activeSkillAnimationInstance.destroy();
self.activeSkillAnimationInstance = null;
}
return;
}
self.activeSkillAnimationInstance.update = function () {
if (!this.playing || !this.framesArray || this.framesArray.length === 0) {
return;
}
this.animationTimer++;
if (this.animationTimer * (1000 / 60) >= this.singleFrameDuration) {
this.animationTimer = 0;
this.currentFrameIndex++;
if (this.currentFrameIndex >= this.framesArray.length) {
this.playing = false;
if (self.graphics && !self.graphics.destroyed) {
self.graphics.visible = true;
}
if (this.parent) {
this.parent.removeChild(this);
}
this.destroy();
if (self.activeSkillAnimationInstance === this) {
self.activeSkillAnimationInstance = null;
}
if (typeof onAnimationCompleteCallback === 'function') {
onAnimationCompleteCallback();
} else {
self.isCurrentlyPlayingAttackAnim = false;
}
} else {
this.removeChildren();
if (this.framesArray[this.currentFrameIndex]) {
this.addChild(this.framesArray[this.currentFrameIndex]);
}
}
}
};
};
self.takeDamage = function (amount) {
if (self.isDead) {
return;
}
LK.effects.flashObject(self.graphics || self, 0xFFFFFF, 150);
self.die();
};
self.die = function (isTimeout) {
if (self.isDead) {
return;
}
self.isDead = true;
if (!isTimeout) {
LK.effects.flashObject(self.graphics || self, 0xFFFFFF, 150);
}
if (self.owner && self.owner.activeClones) {
var index = self.owner.activeClones.indexOf(self);
if (index > -1) {
self.owner.activeClones.splice(index, 1);
if (self.owner.activeClones.length === 0 && self.owner.isWaitingForClonesToDespawn) {
self.owner.startTeleportCooldownAfterClones();
}
}
}
var deathTween = tween(self, {
alpha: 0
}, {
duration: 800,
easing: tween.easeOutQuad,
onFinish: function onFinish() {
if (self.parent) {
self.parent.removeChild(self);
}
if (self.destroy && !self.destroyed) {
self.destroy();
}
}
});
};
self.performSimpleAttack = function () {
if (self.isDead || !player || player.dead) {
self.isCurrentlyPlayingAttackAnim = false;
return;
}
var executeCloneAttackLogic = function executeCloneAttackLogic() {
if (self.isDead) {
self.isCurrentlyPlayingAttackAnim = false;
return;
}
var projectile = new MinibossExplosiveProjectile({
x: self.x,
y: self.y,
target: player,
speed: 4,
explosionRadius: 150
});
game.addChild(projectile);
if (gameState.cursedCrystalActiveProjectiles === undefined) {
gameState.cursedCrystalActiveProjectiles = [];
}
gameState.cursedCrystalActiveProjectiles.push(projectile);
self.attackCooldown = 8 * 60 + Math.floor(Math.random() * (2 * 60));
self.isCurrentlyPlayingAttackAnim = false;
};
self.playMinibossSkillAnim(executeCloneAttackLogic);
};
self.update = function () {
if (self.isDead || gameState.currentState !== "cursedCrystal") {
return;
}
self.lifeTimer--;
if (self.lifeTimer <= 0) {
self.die(true);
return;
}
if (self.activeSkillAnimationInstance) {} else {
self.creepDirectionChangeTimer--;
if (self.creepDirectionChangeTimer <= 0) {
self.pickNewCreepDirection();
}
var nextX = self.x + self.creepDirectionX * self.creepSpeed;
var nextY = self.y + self.creepDirectionY * self.creepSpeed;
var halfWidth = (self.width || 120) / 2;
var halfHeight = (self.height || 120) / 2;
var arenaMinX = 50 + halfWidth;
var arenaMaxX = 2048 - 50 - halfWidth;
var arenaMinY = 50 + halfHeight;
var arenaMaxY = 2732 - 50 - halfHeight;
if (nextX >= arenaMinX && nextX <= arenaMaxX) {
self.x = nextX;
} else {
self.creepDirectionX *= -1;
self.creepDirectionChangeTimer = self.creepDirectionIntervalMin;
}
if (nextY >= arenaMinY && nextY <= arenaMaxY) {
self.y = nextY;
} else {
self.creepDirectionY *= -1;
self.creepDirectionChangeTimer = self.creepDirectionIntervalMin;
}
}
if (self.attackCooldown > 0) {
self.attackCooldown--;
} else if (!self.isCurrentlyPlayingAttackAnim && !self.activeSkillAnimationInstance) {
self.isCurrentlyPlayingAttackAnim = true;
self.performSimpleAttack();
}
};
self.alpha = 0;
self.scaleX = 0.5;
self.scaleY = 0.5;
tween(self, {
alpha: 1,
scaleX: 1,
scaleY: 1
}, {
duration: 500,
easing: tween.easeOutQuad
});
return self;
});
// -------- KONIEC ZAKTUALIZOWANEJ KLASY MinibossCC --------
var MinibossExplosiveProjectile = Container.expand(function (options) {
var self = Container.call(this);
options = options || {};
self.x = options.x || 0;
self.y = options.y || 0;
self.speed = options.speed || 6;
self.target = options.target;
self.lifeTimer = 240;
self.isDead = false;
self.hasExploded = false;
self.explosionRadius = options.explosionRadius || 100;
self.explosionDamage = 1;
try {
self.graphics = self.attachAsset('projectile_spinning_asset', {
// UŻYJ NOWEGO ASSETU DLA POCISKU
anchorX: 0.5,
anchorY: 0.5,
width: 140,
height: 140
});
} catch (e) {
console.warn("Nie udało się załadować 'projectile_spinning_asset', używam Shape.", e);
self.graphics = new Shape({
width: 40,
height: 40,
color: 0xFF4500,
shape: 'ellipse'
});
self.addChild(self.graphics);
}
self.width = self.graphics.width;
self.height = self.graphics.height;
self.update = function () {
if (self.isDead || self.hasExploded || !self.target || self.target.dead) {
if (!self.hasExploded && !self.isDead) {
self.isDead = true;
}
return;
}
self.lifeTimer--;
var dx = self.target.x - self.x;
var dy = self.target.y - self.y;
var distanceToTarget = Math.sqrt(dx * dx + dy * dy);
if (distanceToTarget > 0) {
var moveX = dx / distanceToTarget * self.speed;
var moveY = dy / distanceToTarget * self.speed;
self.x += moveX;
self.y += moveY;
}
if (self.graphics) {
// Dodajemy rotację pocisku
self.graphics.rotation += 0.1; // Dostosuj prędkość obrotu
}
var playerRadius = (self.target.width || 150) / 2 * 0.7;
var projectileRadius = self.width / 2;
if (distanceToTarget < playerRadius + projectileRadius) {
self.explode();
return;
}
if (self.lifeTimer <= 0) {
self.explode();
return;
}
};
self.explode = function () {
if (self.hasExploded) {
return;
}
self.hasExploded = true;
self.isDead = true;
if (self.graphics && self.graphics.parent) {
self.graphics.parent.removeChild(self.graphics);
self.graphics.destroy();
self.graphics = null;
}
var explosionFramesAssets = [];
for (var i = 0; i < 7; i++) {
try {
explosionFramesAssets.push(LK.getAsset('explosion_frame_' + i, {
anchorX: 0.5,
anchorY: 0.5,
clone: true
}));
} catch (e) {
console.error("Błąd ładowania klatki eksplozji: explosion_frame_" + i, e);
var placeholderExplosionFrame = new Shape({
width: 50,
height: 50,
color: 0xFF8C00,
shape: 'ellipse'
});
placeholderExplosionFrame.anchor.set(0.5, 0.5);
explosionFramesAssets.push(placeholderExplosionFrame);
}
}
if (explosionFramesAssets.length < 7) {
// Potrzebujemy wszystkich 7 klatek do tej logiki
console.error("Nie załadowano wystarczającej liczby klatek eksplozji.");
if (gameState && gameState.cursedCrystalActiveExplosions) {
gameState.cursedCrystalActiveExplosions.push({
x: self.x,
y: self.y,
radius: self.explosionRadius,
damage: self.explosionDamage,
durationTimer: 30,
hitPlayerThisFrame: false
});
}
return;
}
var explosionDisplay = game.addChild(new Container());
explosionDisplay.x = self.x;
explosionDisplay.y = self.y;
var currentFrameGfx = null;
var INITIAL_SCALE = 0.2;
var MAX_SCALE = self.explosionRadius * 3.0 / (explosionFramesAssets[0] ? explosionFramesAssets[0].width : 50); // ZMIENIONA LINIA (1.5 -> 3.0)
var EXP_FRAME_DURATION_MS = 70;
var LOOP_FRAME_DURATION_MS = 90;
var SHRINK_FADE_DURATION_MS = 600;
var totalDurationMs = 2000;
var expansionPhaseDuration = 7 * EXP_FRAME_DURATION_MS; // 7 klatek * 70ms = 490ms
var loopPhaseDuration = totalDurationMs - expansionPhaseDuration - SHRINK_FADE_DURATION_MS;
if (loopPhaseDuration < 0) {
// Jeśli nie ma czasu na pętlę, skróć inne fazy lub ustaw min. czas pętli
loopPhaseDuration = Math.max(0, LOOP_FRAME_DURATION_MS * 4); // np. przynajmniej jedna pełna pętla 4 klatek
SHRINK_FADE_DURATION_MS = Math.max(200, totalDurationMs - expansionPhaseDuration - loopPhaseDuration);
}
var displayFrame = function displayFrame(frameAsset, scale, alpha) {
if (currentFrameGfx && currentFrameGfx.parent) {
explosionDisplay.removeChild(currentFrameGfx);
// Nie niszczymy frameAsset, bo to klon z puli LK.getAsset
}
if (frameAsset) {
currentFrameGfx = explosionDisplay.addChild(frameAsset);
currentFrameGfx.scale.set(scale);
currentFrameGfx.alpha = alpha === undefined ? 1 : alpha;
} else {
currentFrameGfx = null;
}
};
var currentExpansionFrame = 0;
function expandAnimation() {
if (currentExpansionFrame < 7) {
var progress = currentExpansionFrame / 6.0;
var scale = INITIAL_SCALE + (MAX_SCALE - INITIAL_SCALE) * progress;
displayFrame(explosionFramesAssets[currentExpansionFrame], Math.max(INITIAL_SCALE, scale));
currentExpansionFrame++;
LK.setTimeout(expandAnimation, EXP_FRAME_DURATION_MS);
} else {
startLoopingPhase();
}
}
var loopFramesIndices = [3, 4, 5, 6]; // Klatki 3, 4, 5, 6 do pętli
var currentLoopArrayIndex = 0;
var loopEndTime;
function startLoopingPhase() {
if (loopPhaseDuration <= 0) {
startShrinkingPhase();
return;
}
loopEndTime = Date.now() + loopPhaseDuration;
currentLoopArrayIndex = 0; // Zacznij od pierwszej klatki pętli (indeks 3 globalnie)
loopAnimation();
}
function loopAnimation() {
if (Date.now() < loopEndTime && explosionDisplay && !explosionDisplay.destroyed) {
displayFrame(explosionFramesAssets[loopFramesIndices[currentLoopArrayIndex % loopFramesIndices.length]], MAX_SCALE);
currentLoopArrayIndex++;
LK.setTimeout(loopAnimation, LOOP_FRAME_DURATION_MS);
} else {
startShrinkingPhase();
}
}
function startShrinkingPhase() {
if (!currentFrameGfx || !currentFrameGfx.parent || explosionDisplay && explosionDisplay.destroyed) {
if (explosionDisplay && !explosionDisplay.destroyed) {
explosionDisplay.destroy();
}
return;
}
// Aby zmniejszać ostatnio wyświetloną klatkę (lub konkretną np. klatkę 6 jako bazę)
// Dla pewności, że mamy co skalować, możemy ponownie wyświetlić ostatnią klatkę pętli (np. klatkę 6)
// jeśli currentFrameGfx mógł zostać usunięty lub jest nieoczekiwany.
// Jeśli currentFrameGfx jest już ostatnią klatką pętli, to dobrze.
// Jeśli chcemy zawsze zmniejszać np. klatkę nr 6:
// displayFrame(explosionFramesAssets[6], MAX_SCALE);
tween(currentFrameGfx, {
scaleX: 0.01,
scaleY: 0.01,
alpha: 0
}, {
duration: SHRINK_FADE_DURATION_MS,
easing: tween.easeInQuad,
onFinish: function onFinish() {
if (explosionDisplay && !explosionDisplay.destroyed) {
explosionDisplay.destroy();
}
}
});
}
expandAnimation(); // Rozpocznij pierwszą fazę
if (gameState && gameState.cursedCrystalActiveExplosions) {
gameState.cursedCrystalActiveExplosions.push({
x: self.x,
y: self.y,
radius: self.explosionRadius,
damage: self.explosionDamage,
durationTimer: Math.round(totalDurationMs / (1000 / 60)),
// Czas trwania logicznej eksplozji = ~2 sekundy
hitPlayerThisFrame: false
});
}
};
return self;
});
// Zamykająca klamra dla Container.expand klasy Boss
// --- POCZĄTEK BLOKU DO SKOPIOWANIA ---
var Player = Container.expand(function () {
var self = Container.call(this);
if (gameState.currentState === "cursedCrystal") {
console.log("Player Update - Cursed Crystal Tick, Rolling: " + this.rolling + ", Cooldown: " + this.rollCooldown);
} else if (gameState.currentState === "game") {
console.log("Player Update - Game Tick, Rolling: " + this.rolling + ", Cooldown: " + this.rollCooldown);
}
// --- 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.hasRolledThroughMinibossCCThisRoll = false;
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) {
var timestamp = Date.now();
var currentHealthBeforeDamage = this.health;
console.log("[Player.takeDamage] Called at: " + timestamp + " | Amount: " + amount + " | Invulnerable: " + this.invulnerable + " | Dead: " + this.dead + " | Health BEFORE: " + currentHealthBeforeDamage + " | Current State: " + gameState.currentState);
if (!this.invulnerable && !this.dead) {
if (gameState.currentState === "cursedCrystal") {
gameState.cursedCrystalPlayerHealth -= amount;
gameState.cursedCrystalPlayerHealth = Math.max(0, gameState.cursedCrystalPlayerHealth);
this.health = gameState.cursedCrystalPlayerHealth;
console.log(" [CursedCrystal DMG] New Health (gameState & this.health): " + this.health + " at " + timestamp);
if (ui && ui.updateHearts) {
ui.updateHearts(gameState.cursedCrystalPlayerHealth, gameState.cursedCrystalPlayerMaxHealth);
}
} else {
this.health -= amount;
this.health = Math.max(0, this.health);
console.log(" [DMG in " + gameState.currentState + "] New Health (this.health): " + this.health + " at " + timestamp);
}
if (this.health < currentHealthBeforeDamage) {
LK.effects.flashObject(this, 0xFF0000, 200);
}
if (this.health <= 0) {
console.log(" Player health is <= 0 at " + timestamp + ". Calling this.die().");
this.die();
return;
}
this.invulnerable = true;
console.log(" Player.invulnerable SET TO TRUE at " + timestamp + ". IMMEDIATELY CHECKING: player.invulnerable is " + this.invulnerable);
if (this.postHitInvulnerabilityTimer) {
LK.clearTimeout(this.postHitInvulnerabilityTimer);
this.postHitInvulnerabilityTimer = null;
}
var playerInstance = this;
this.postHitInvulnerabilityTimer = LK.setTimeout(function () {
var endInvulnerableTimestamp = Date.now();
if (!playerInstance || playerInstance.destroyed) {
console.log(" Player invulnerability timeout: player destroyed or null at " + endInvulnerableTimestamp);
return;
}
playerInstance.invulnerable = false;
console.log(" Player.invulnerable SET TO FALSE at " + endInvulnerableTimestamp + " (after 2000ms)");
var currentVisualSprite = null;
if (playerInstance.rolling && playerInstance.children.length > 0 && playerInstance.children[0] !== playerInstance.idleAnimationSprite) {} else if (playerInstance.idleAnimationSprite && playerInstance.idleAnimationSprite.visible && playerInstance.idleAnimationSprite.children.length > 0) {
currentVisualSprite = playerInstance.idleAnimationSprite.children[0];
}
if (currentVisualSprite && currentVisualSprite.alpha !== 1) {
currentVisualSprite.alpha = 1;
}
playerInstance.postHitInvulnerabilityTimer = null;
}, 2000);
} else {
console.log("[Player.takeDamage] SKIPPED at: " + timestamp + " (Invulnerable: " + this.invulnerable + ", Dead: " + this.dead + ")");
}
};
// --- Śmierć (z poprawkami dla storage i onFinish) ---
self.die = function () {
console.log("!!!! Player.die() called! State:", gameState.currentState, "Current Health:", self.health);
if (self.dead) {
console.log("Player.die() exited - already dead.");
return;
}
self.dead = true;
try {
self.clearRollTimeouts();
} catch (e) {
console.error("!!! Błąd podczas wywoływania self.clearRollTimeouts w Player.die:", e);
}
if (gameState.currentState === "cursedCrystal") {
console.log("Player died in Cursed Crystal mode. Calling gameState.endCursedCrystalMode(false) via tween.");
tween(self, {
alpha: 0,
scaleX: self.scaleX * 1.2,
scaleY: self.scaleY * 1.2
}, {
duration: 1000,
easing: tween.easeOut,
onFinish: function onFinish() {
console.log("Player.die tween (Cursed Crystal) onFinish.");
if (typeof gameState.endCursedCrystalMode === 'function') {
gameState.endCursedCrystalMode(false);
} else {
console.error("gameState.endCursedCrystalMode is not defined!");
if (typeof gameState.showGrillScreen === 'function') {
gameState.showGrillScreen();
}
}
}
});
return;
}
var currentDeaths = parseInt(storage.totalDeaths, 10) || 0;
storage.totalDeaths = currentDeaths + 1;
console.log("Updated totalDeaths to:", 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("Updated maxHearts to:", storage.maxHearts);
tween(self, {
alpha: 0,
scaleX: self.scaleX * 1.2,
scaleY: self.scaleY * 1.2
}, {
duration: 1000,
easing: tween.easeOut,
onFinish: function onFinish() {
console.log("Player.die tween (Non-CC) onFinish. gameState.currentState:", gameState.currentState);
if (gameState.currentState === "game") {
console.log("Player.die (game mode) onFinish - Calling gameState.gameOver(true).");
if (typeof boss !== 'undefined' && boss && typeof boss.clearAllAttacks === 'function') {
boss.clearAllAttacks("Player.die -> tutorial gameOver");
}
if (typeof gameState.gameOver === 'function') {
gameOverReasonIsDeath = true;
gameState.gameOver(true);
} else {
console.error("gameState.gameOver() is not defined for 'game' mode death!");
if (typeof gameState.showGrillScreen === 'function') {
gameState.showGrillScreen();
}
}
} else if (gameState.currentState === "rollMaster" || gameState.currentState === "rollMasterGameOver") {
console.log("Player.die (RollMaster or rollMasterGameOver state) onFinish. No further action needed from Player.die, endRollMasterMode handles the rest.");
} else if (gameState.currentState === "gameOver") {
console.log("Player.die onFinish while gameState.currentState is already 'gameOver'. No further action needed from Player.die.");
}
}
});
};
// Koniec self.die
// --- Aktualizacja (Update) ---
self.update = function () {
if (gameState.currentState === "cursedCrystal") {
//console.log("Player Update Tick - CC - Health: " + self.health + ", RollCD: " + self.rollCooldown + ", Rolling: " + self.rolling + ", Invuln: " + self.invulnerable + ", InvulnFrames: " + self.invulnerabilityFrames);
}
if (gameState.currentState !== "game" && gameState.currentState !== "rollMaster" && gameState.currentState !== "cursedCrystal") {
if (self.rolling) {
self.rolling = false;
self.invulnerabilityFrames = 0;
if (self.idleAnimationSprite && !self.idleAnimationSprite.destroyed) {
self.idleAnimationSprite.visible = true;
self.idleAnimationSprite.play();
if (self.children.length > 1) {
for (var i = self.children.length - 1; i >= 0; i--) {
if (self.children[i] !== self.idleAnimationSprite && self.children[i].destroy) {
self.children[i].destroy();
}
}
}
}
}
self.clearRollTimeouts();
return;
}
if (self.dead) {
return;
}
if (self.rollCooldown > 0) {
self.rollCooldown--;
}
var currentVisualSprite = null;
if (self.rolling) {
for (var i = 0; i < self.children.length; i++) {
if (self.children[i] !== self.idleAnimationSprite) {
currentVisualSprite = self.children[i];
break;
}
}
} 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 % 10 < 5 ? 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 pWidth = currentVisualSprite ? currentVisualSprite.width * Math.abs(currentVisualSprite.scaleX || 1) : self.width;
var pHeight = currentVisualSprite ? currentVisualSprite.height * (currentVisualSprite.scaleY || 1) : self.height;
var halfWidth = pWidth / 2;
var halfHeight = pHeight / 2;
var minXBoundary = halfWidth;
var maxXBoundary = 2048 - halfWidth;
var minYBoundary = halfHeight;
var maxYBoundary = 2732 - halfHeight;
nextX = Math.max(minXBoundary, Math.min(nextX, maxXBoundary));
nextY = Math.max(minYBoundary, Math.min(nextY, maxYBoundary));
if (gameState.currentState === "cursedCrystal" && typeof crystalCoreObject !== 'undefined' && crystalCoreObject && !crystalCoreObject.destroyed) {
var crystalCX = crystalCoreObject.x;
var crystalCY = crystalCoreObject.y;
var crystalRadius = crystalCoreObject.collisionRadius || 50;
var distToCrystalHorizontal = Math.abs(nextX - crystalCX);
var distToCrystalVertical = Math.abs(nextY - crystalCY);
if (distToCrystalHorizontal < halfWidth + crystalRadius && distToCrystalVertical < halfHeight + crystalRadius) {
if (self.x < crystalCX && nextX > self.x) {
nextX = crystalCX - crystalRadius - halfWidth - 1;
} else if (self.x > crystalCX && nextX < self.x) {
nextX = crystalCX + crystalRadius + halfWidth + 1;
}
if (self.y < crystalCY && nextY > self.y) {
nextY = crystalCY - crystalRadius - halfHeight - 1;
} else if (self.y > crystalCY && nextY < self.y) {
nextY = crystalCY + crystalRadius + halfHeight + 1;
}
}
}
self.x = nextX;
self.y = nextY;
if (gameState.currentState === "game" && 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 = pWidth / 2 * 0.8;
var hitboxMultiplier = 1.2;
var bossCollisionRadius = (boss.width || 180) * (boss.scaleX || 1) / 2 * hitboxMultiplier;
if (distance_b < playerCollisionRadius + bossCollisionRadius) {
boss.takeDamage(10);
self.hasRolledThroughBossThisRoll = true;
LK.effects.flashObject(boss, 0x00FF00, 200);
}
}
if (gameState.currentState === "cursedCrystal" && gameState.isMinibossActiveCC === true && gameState.cursedCrystalMinibossObject && !gameState.cursedCrystalMinibossObject.isDead && !self.hasRolledThroughMinibossCCThisRoll) {
var minibossCC = gameState.cursedCrystalMinibossObject;
var dx_mcc = self.x - minibossCC.x;
var dy_mcc = self.y - minibossCC.y;
var distance_mcc = Math.sqrt(dx_mcc * dx_mcc + dy_mcc * dy_mcc);
var playerRollRadius = pWidth / 2 * 0.8;
var minibossCCRadius = (minibossCC.width || 120) / 2 * 0.9;
if (distance_mcc < playerRollRadius + minibossCCRadius) {
minibossCC.takeDamage(10);
self.hasRolledThroughMinibossCCThisRoll = true;
console.log("Player rolled through Miniboss CC and dealt damage!");
}
}
} 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;
}
if (gameState.isInputActive && (gameState.currentState === "game" || gameState.currentState === "rollMaster" || gameState.currentState === "cursedCrystal") && 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);
var moveSpeed = 3;
if (distance_m > moveSpeed) {
var normalizedX = dx_m / distance_m;
var normalizedY = dy_m / distance_m;
var nextPlayerX = self.x + normalizedX * moveSpeed;
var nextPlayerY = self.y + normalizedY * moveSpeed;
var pWidthIdle = self.idleAnimationSprite.children[0] ? self.idleAnimationSprite.children[0].width * (self.idleAnimationSprite.scaleX || 1) : self.width;
var pHeightIdle = self.idleAnimationSprite.children[0] ? self.idleAnimationSprite.children[0].height * (self.idleAnimationSprite.scaleY || 1) : self.height;
var halfWidthIdle = pWidthIdle / 2;
var halfHeightIdle = pHeightIdle / 2;
var minXBoundaryIdle = halfWidthIdle;
var maxXBoundaryIdle = 2048 - halfWidthIdle;
var minYBoundaryIdle = halfHeightIdle;
var maxYBoundaryIdle = 2732 - halfHeightIdle;
nextPlayerX = Math.max(minXBoundaryIdle, Math.min(nextPlayerX, maxXBoundaryIdle));
nextPlayerY = Math.max(minYBoundaryIdle, Math.min(nextPlayerY, maxYBoundaryIdle));
if (gameState.currentState === "cursedCrystal" && typeof crystalCoreObject !== 'undefined' && crystalCoreObject && !crystalCoreObject.destroyed) {
var crystalCXIdle = crystalCoreObject.x;
var crystalCYIdle = crystalCoreObject.y;
var crystalRadiusIdle = crystalCoreObject.collisionRadius || 50;
var distToCrystalHorizontalIdle = Math.abs(nextPlayerX - crystalCXIdle);
var distToCrystalVerticalIdle = Math.abs(nextPlayerY - crystalCYIdle);
if (distToCrystalHorizontalIdle < halfWidthIdle + crystalRadiusIdle && distToCrystalVerticalIdle < halfHeightIdle + crystalRadiusIdle) {} else {
self.x = nextPlayerX;
self.y = nextPlayerY;
}
} else {
self.x = nextPlayerX;
self.y = nextPlayerY;
}
if (self.idleAnimationSprite && !self.idleAnimationSprite.destroyed) {
if (normalizedX > 0.1) {
self.idleAnimationSprite.scaleX = 1;
} else if (normalizedX < -0.1) {
self.idleAnimationSprite.scaleX = -1;
}
}
}
}
}
if (self.idleAnimationSprite && self.idleAnimationSprite.visible && !self.idleAnimationSprite.destroyed && typeof self.idleAnimationSprite.update === 'function') {
if (gameState.currentState === "cursedCrystal") {}
self.idleAnimationSprite.update();
}
}; // Ten nawias klamrowy zamyka funkcję self.update = function () { ... };
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 SoulEnemy = Container.expand(function (options) {
var self = Container.call(this);
options = options || {};
self.hp = options.hp || 1;
self.speed = options.speed || 2;
self.isDead = false;
self.targetX = options.targetX || 2048 / 2;
self.targetY = options.targetY || 2732 / 2;
var animFrames = [];
for (var i = 0; i < 6; i++) {
// 6 klatek animacji
try {
animFrames.push(LK.getAsset('soul_anim_' + i, {
anchorX: 0.5,
anchorY: 0.5,
clone: true
}));
} catch (e) {
console.error("Błąd ładowania klatki animacji duszy: soul_anim_" + i, e);
// Awaryjny placeholder, jeśli klatka się nie załaduje
var placeholderFrame = new Shape({
width: options.width || 30,
height: options.height || 30,
color: 0xFF00FF,
shape: 'ellipse'
});
placeholderFrame.anchor.set(0.5, 0.5);
animFrames.push(placeholderFrame);
}
}
if (animFrames.length > 0) {
self.animation = new SpriteAnimation({
frames: animFrames,
frameDuration: 120,
// Czas trwania klatki w ms - dostosuj
loop: true,
anchorX: 0.5,
anchorY: 0.5
});
self.addChild(self.animation);
self.animation.play();
} else {
// Jeśli nie udało się załadować żadnej klatki, stwórz awaryjny kształt
console.error("Nie udało się załadować żadnej klatki dla SoulEnemy. Tworzenie awaryjnego kształtu.");
self.fallbackGraphics = new Shape({
width: options.width || 30,
height: options.height || 30,
color: 0xADD8E6,
shape: 'ellipse'
});
self.fallbackGraphics.anchor.set(0.5, 0.5);
self.addChild(self.fallbackGraphics);
}
// Ustawienie self.width i self.height na podstawie pierwszej klatki animacji lub opcji
if (animFrames.length > 0 && animFrames[0] && typeof animFrames[0].width !== 'undefined') {
self.width = animFrames[0].width;
self.height = animFrames[0].height;
} else {
self.width = options.width || 30;
self.height = options.height || 30;
}
self.update = function () {
if (self.isDead || gameState.currentState !== "cursedCrystal") {
return;
}
var currentTargetX = self.targetX;
var currentTargetY = self.targetY;
if (typeof crystalCoreObject !== 'undefined' && crystalCoreObject && !crystalCoreObject.destroyed) {
currentTargetX = crystalCoreObject.x;
currentTargetY = crystalCoreObject.y;
}
var dx = currentTargetX - self.x;
var dy = currentTargetY - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (self.animation) {
// Obracanie animacji w kierunku ruchu
var angle = Math.atan2(dy, dx);
self.animation.rotation = angle;
// Jeśli chcesz, aby sprite "patrzył" w prawo, gdy kąt jest 0, możesz dodać:
// self.animation.rotation = angle + Math.PI / 2; // Jeśli sprite jest domyślnie skierowany w górę
} else if (self.fallbackGraphics) {
// Obracanie awaryjnej grafiki
var angle = Math.atan2(dy, dx);
self.fallbackGraphics.rotation = angle;
}
if (distance > self.speed) {
var moveX = dx / distance * self.speed;
var moveY = dy / distance * self.speed;
self.x += moveX;
self.y += moveY;
} else {
self.x = currentTargetX;
self.y = currentTargetY;
if (typeof crystalCoreObject !== 'undefined' && crystalCoreObject && !crystalCoreObject.destroyed && self.x === crystalCoreObject.x && self.y === crystalCoreObject.y) {
self.onHitCrystal();
} else if (!crystalCoreObject || crystalCoreObject.destroyed) {
self.isDead = true;
if (self.parent) {
self.parent.removeChild(self);
}
self.destroy();
}
}
// SpriteAnimation ma własną metodę update, która jest wywoływana automatycznie, jeśli jest dzieckiem
// Nie ma potrzeby wywoływania self.animation.update() ręcznie tutaj.
};
self.takeDamage = function (amount) {
if (self.isDead) {
return;
}
self.hp -= amount;
// Efekt flash na całym obiekcie SoulEnemy (który zawiera animację)
LK.effects.flashObject(self, 0xFFFFFF, 150);
if (self.hp <= 0) {
self.die();
}
};
self.die = function () {
if (self.isDead) {
return;
}
self.isDead = true;
gameState.cursedCrystalScore += 10;
if (ui && ui.updateScoreCC) {
ui.updateScoreCC(gameState.cursedCrystalScore);
}
if (self.parent) {
self.parent.removeChild(self);
}
self.destroy(); // To powinno zniszczyć również animację jako dziecko
};
self.onHitCrystal = function () {
if (self.isDead) {
return;
}
self.isDead = true; // Oznacz jako martwą, aby zatrzymać inne logiki (np. update)
gameState.cursedCrystalSoulsHitCrystal++;
gameState.cursedCrystalChargeLevel = Math.min(gameState.cursedCrystalTargetCharge, gameState.cursedCrystalChargeLevel + 3);
if (ui && ui.updateCrystalCharge) {
ui.updateCrystalCharge(gameState.cursedCrystalChargeLevel, gameState.cursedCrystalTargetCharge);
}
if (typeof gameState.updateCrystalVisual === 'function') {
gameState.updateCrystalVisual();
}
var FADE_OUT_DURATION = 300; // Czas trwania fade out w ms - dostosuj!
// Zatrzymaj animację SpriteAnimation, jeśli istnieje i gra
if (self.animation && typeof self.animation.stop === 'function') {
self.animation.stop();
}
tween(self, {
alpha: 0
}, {
duration: FADE_OUT_DURATION,
easing: tween.easeOutQuad,
// Możesz wybrać inną funkcję easing
onFinish: function onFinish() {
if (self.parent) {
self.parent.removeChild(self);
}
if (self.destroy && !self.destroyed) {
// Dodatkowe sprawdzenie !self.destroyed
self.destroy();
}
}
});
};
self.x = options.x || 0;
self.y = options.y || 0;
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;
// Dodaj tę linię do konstruktora, aby SpriteAnimation pamiętało własne alpha
// i mogło przekazywać je do klatek.
self.alpha = options.alpha !== undefined ? options.alpha : 1;
// 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);
}
// TUTAJ JEST KLUCZOWA ZMIANA W KONSTRUKTORZE:
// Ustaw alpha dla pierwszej klatki na podstawie alpha kontenera SpriteAnimation.
if (firstFrame) {
firstFrame.alpha = self.alpha;
}
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]);
}
self.currentFrame++;
if (self.currentFrame >= self.frames.length) {
if (self.loop) {
self.currentFrame = 0;
// TUTAJ JEST KLUCZOWA ZMIANA W UPDATE DLA PĘTLI:
var nextFrame = self.frames[self.currentFrame];
if (nextFrame) {
nextFrame.alpha = self.alpha; // Zastosuj alpha kontenera
}
self.addChild(nextFrame);
} else {
self.playing = false;
if (typeof self.onComplete === 'function') {
self.onComplete();
}
return;
}
} else {
// Dodajemy nową klatkę tylko wtedy, gdy animacja jeszcze trwa
// TUTAJ JEST KLUCZOWA ZMIANA W UPDATE DLA DALSZYCH KLATEK:
var nextFrame = self.frames[self.currentFrame];
if (nextFrame) {
nextFrame.alpha = self.alpha; // Zastosuj alpha kontenera
}
self.addChild(nextFrame);
}
}
};
// 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);
}
};
self.ccScoreText = new Text2("Score: 0", {
size: 50,
fill: 0xFFFF00,
// Żółty, dla odróżnienia
align: 'left'
});
self.ccScoreText.anchor.set(0, 0); // Lewy górny róg tego tekstu
self.addChild(self.ccScoreText);
// Rekord dla Cursed Crystal
self.ccHighScoreText = new Text2("Best: 0", {
size: 40,
fill: 0xFFFFFF,
// Biały
align: 'left'
});
self.ccHighScoreText.anchor.set(0, 0); // Lewy górny róg tego tekstu
self.addChild(self.ccHighScoreText);
// Pasek Naładowania Klejnotu
self.crystalChargeBarContainer = new Container();
self.addChild(self.crystalChargeBarContainer);
var baseAssetID = 'cursedenergybar'; // Nazwa Twojego assetu
var chargeBarAssetFullWidth; // Zmienna na pełną szerokość assetu
var chargeBarAssetHeight; // Zmienna na wysokość assetu
// Tło paska (ten sam asset, ale z większą przezroczystością)
try {
self.crystalChargeBarBg = self.attachAsset(baseAssetID, {
anchorX: 0.5,
anchorY: 0.5,
alpha: 0.3,
// Ustaw przezroczystość tła (np. 30%)
clone: true
});
chargeBarAssetFullWidth = self.crystalChargeBarBg.width; // Pobierz szerokość z assetu
chargeBarAssetHeight = self.crystalChargeBarBg.height; // Pobierz wysokość z assetu
self.crystalChargeBarContainer.addChild(self.crystalChargeBarBg);
} catch (e) {
console.error("Błąd ładowania assetu tła paska energii ('" + baseAssetID + "'): ", e);
chargeBarAssetFullWidth = 400; // Awaryjna szerokość
chargeBarAssetHeight = 25; // Awaryjna wysokość
self.crystalChargeBarBg = new Shape({
width: chargeBarAssetFullWidth,
height: chargeBarAssetHeight,
color: 0xCCCCCC,
alpha: 0.3
});
self.crystalChargeBarBg.anchor.set(0, 0.5);
self.crystalChargeBarContainer.addChild(self.crystalChargeBarBg);
}
// Wypełnienie paska (ten sam asset, w pełni widoczny)
try {
self.crystalChargeBarFill = self.attachAsset(baseAssetID, {
anchorX: 0.5,
anchorY: 0.5,
alpha: 1,
// Pełna widoczność
clone: true
});
self.crystalChargeBarFill.width = 0; // Zacznij od zerowej szerokości
// Jeśli Twoje tło i wypełnienie mają być idealnie na sobie, a oba mają anchorX:0, anchorY:0.5,
// to ich .x i .y względem kontenera powinny być takie same (np. 0,0).
// self.crystalChargeBarFill.x = 0;
// self.crystalChargeBarFill.y = 0;
self.crystalChargeBarContainer.addChild(self.crystalChargeBarFill);
} catch (e) {
console.error("Błąd ładowania assetu wypełnienia paska energii ('" + baseAssetID + "'): ", e);
self.crystalChargeBarFill = new Shape({
width: 0,
height: chargeBarAssetHeight ? chargeBarAssetHeight : 25,
color: 0x8A2BE2
});
self.crystalChargeBarFill.anchor.set(0, 0.5);
self.crystalChargeBarContainer.addChild(self.crystalChargeBarFill);
}
// --- DODANA NOWA FUNKCJA DO AKTUALIZACJI TEKSTU REKORDU ---
self.updateHighScoreDisplay = function (seconds) {
// 'seconds' to rekord czasu dla Roll Master
seconds = Math.max(0, seconds || 0);
var minutes = Math.floor(seconds / 60);
var remainingSeconds = seconds % 60;
var formattedSeconds = String(remainingSeconds).padStart(2, '0'); // Zawsze dwie cyfry dla sekund
var formattedTime = String(minutes).padStart(2, '0') + ':' + formattedSeconds;
if (this.highScoreText) {
// this.highScoreText to element UI dla rekordu Roll Master
// Wyświetlaj rekord Roll Master tylko, gdy jesteśmy w tym trybie lub na jego ekranie końca
if (gameState.currentState === "rollMaster" || gameState.currentState === "rollMasterGameOver") {
this.highScoreText.setText("Best Time: " + formattedTime);
// Pozycja i widoczność (alpha) tego elementu są zarządzane przez UI.prototype.positionElements
}
// W innych stanach gry nie modyfikujemy tekstu; positionElements powinno go ukryć.
}
};
self.updateScoreCC = function (currentScore) {
if (this.ccScoreText) {
// this.ccScoreText to element dla "Score: X" w Cursed Crystal
if (gameState.currentState === "cursedCrystal") {
this.ccScoreText.setText("Score: " + (currentScore || 0));
// Pozycja i widoczność (alpha) są zarządzane przez UI.prototype.positionElements("cursedCrystal")
}
}
};
self.updateHighScoreCC = function (highScore) {
// 'highScore' to rekord dla Cursed Crystal
if (this.ccHighScoreText) {
// this.ccHighScoreText to element dla "Best: Y" w Cursed Crystal
if (gameState.currentState === "cursedCrystal") {
this.ccHighScoreText.setText("Best: " + (highScore || 0));
// Pozycja i widoczność (alpha) są zarządzane przez UI.prototype.positionElements("cursedCrystal")
}
}
};
// Aktualizuje wizualizację paska naładowania Klejnotu
self.updateCrystalCharge = function (currentCharge, maxCharge) {
if (self.crystalChargeBarFill && self.crystalChargeBarBg) {
maxCharge = Math.max(1, maxCharge || 1); // Max musi być co najmniej 1
currentCharge = Math.max(0, Math.min(currentCharge, maxCharge));
var chargeBarBaseWidth = self.crystalChargeBarBg.width; // Szerokość tła paska
var fillWidth = currentCharge / maxCharge * chargeBarBaseWidth;
self.crystalChargeBarFill.width = fillWidth;
}
};
// Funkcja do pokazywania/ukrywania i aktualizacji paska HP Minibossa CC
// Wykorzysta istniejący self.bossHealthBar i self.bossHealthBarContainer
self.updateMinibossHealthCC = function (currentHP, maxHP, isActive) {
if (self.bossHealthBarContainer && self.bossHealthBar) {
if (isActive && currentHP > 0) {
self.bossHealthBarContainer.alpha = 1;
maxHP = Math.max(1, maxHP || 1);
currentHP = Math.max(0, Math.min(currentHP, maxHP));
var barWidth = 800; // Upewnij się, że to ta sama szerokość co w updateBossHealth
// lub pobierz z self.bossHealthBarBg.width jeśli masz tło
var currentBarWidth = currentHP / maxHP * barWidth;
self.bossHealthBar.width = currentBarWidth;
} else {
self.bossHealthBarContainer.alpha = 0;
}
}
};
// --- 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 (mogą być nadpisywane w case'ach)
if (self.deathsText) {
// Sprawdzenie, czy element istnieje
self.deathsText.x = 2048 - 150;
self.deathsText.y = 50;
}
if (self.heartContainer) {
self.heartContainer.y = 80;
}
if (self.bossHealthBarContainer) {
self.bossHealthBarContainer.x = 2048 / 2;
self.bossHealthBarContainer.y = 160;
}
// Resetuj widoczność wszystkich głównych elementów UI przed ustawieniem dla danego stanu
if (self.titleText) {
self.titleText.alpha = 0;
}
if (self.messageText) {
self.messageText.alpha = 0;
}
if (self.tutorialText) {
self.tutorialText.alpha = 0;
}
if (self.timerText) {
self.timerText.alpha = 0;
}
if (self.heartContainer) {
self.heartContainer.alpha = 0;
}
if (self.bossHealthBarContainer) {
self.bossHealthBarContainer.alpha = 0;
}
if (self.deathsText) {
self.deathsText.alpha = 0;
}
// Ten highScoreText jest specyficzny dla Roll Master, więc domyślnie go ukrywamy
// chyba że jesteśmy w stanie rollMaster lub rollMasterGameOver (obsłużone w case)
if (self.highScoreText) {
self.highScoreText.alpha = 0;
}
// NOWE ELEMENTY UI DLA CURSED CRYSTAL - też domyślnie ukryte
if (self.ccScoreText) {
self.ccScoreText.alpha = 0;
}
if (self.ccHighScoreText) {
self.ccHighScoreText.alpha = 0;
}
if (self.crystalChargeBarContainer) {
self.crystalChargeBarContainer.alpha = 0;
}
// Przywróć domyślną pozycję i styl timerText (może być nadpisane w case "rollMaster")
if (self.timerText) {
self.timerText.x = 350; // Domyślna pozycja X dla trybu "game"
self.timerText.y = 100; // Domyślna pozycja Y dla trybu "game"
self.timerText.anchor.set(0, 0);
self.timerText.style = {
size: 60,
fill: 0xFFFFFF
};
self.timerText.setText("00:00"); // Resetuj tekst na wszelki wypadek
}
// Logika dla highScoreText (Roll Master) - resetowanie tekstu poza trybem RM
if (self.highScoreText && state !== "rollMaster" && state !== "rollMasterGameOver") {
// self.highScoreText.setText("Best RM: 00:00"); // Możesz zresetować tekst lub zostawić ostatni znany
}
switch (state) {
case "title":
if (self.titleText) {
self.titleText.x = 2048 / 2;
self.titleText.y = 800;
self.titleText.alpha = 1;
}
if (self.messageText) {
self.messageText.x = 2048 / 2;
self.messageText.y = 1000;
// self.messageText.alpha jest zarządzane przez showMessage
}
if (self.tutorialText) {
self.tutorialText.x = 2048 / 2;
self.tutorialText.y = 1200;
// self.tutorialText.alpha jest zarządzane przez showTutorial
}
break;
case "game":
// Tryb walki z bossem (tutorial)
if (self.heartContainer) {
self.heartContainer.alpha = 1;
// Pozycja serc jest aktualizowana w updateHearts
}
if (self.timerText) {
// Timer dla standardowego bossa (jeśli jest)
self.timerText.alpha = 1;
// Ustaw x, y, anchor, style dla tego timera
self.timerText.x = 350; // Przykładowa pozycja z Twojego kodu
self.timerText.y = 100;
self.timerText.anchor.set(0, 0);
self.timerText.style = {
size: 60,
fill: 0xFFFFFF
};
}
if (self.deathsText) {
self.deathsText.alpha = 1;
}
if (self.bossHealthBarContainer && typeof boss !== 'undefined' && boss && !boss.dead && boss.health > 0) {
self.bossHealthBarContainer.alpha = 1;
} else if (self.bossHealthBarContainer) {
self.bossHealthBarContainer.alpha = 0;
}
if (self.tutorialText) {
// self.tutorialText.alpha jest zarządzane przez showTutorial/hideTutorial
self.tutorialText.x = 2048 / 2;
self.tutorialText.y = 200;
}
break;
case "grillMenu":
if (self.messageText) {
// Dla komunikatów w Grill Menu
self.messageText.x = 2048 / 2;
self.messageText.y = 500;
// alpha zarządzane przez showMessage
}
// Wszystkie inne elementy typowo rozgrywkowe powinny być ukryte przez reset na początku funkcji
break;
case "gameOver":
// Standardowy Game Over (po Boss+ lub tutorialu, jeśli tak zdecydujesz)
// Teksty i przyciski dla tego stanu są głównie zarządzane przez gameState.gameOver
if (self.titleText) {// Dla "DEFEAT!" lub "TIME'S UP!"
// alpha, text, x, y są ustawiane w gameState.gameOver
}
if (self.messageText) {// Dla dodatkowych wiadomości
// alpha, text, x, y są ustawiane w gameState.gameOver
}
if (self.deathsText) {
// Najpierw sprawdź, czy deathsText istnieje
// Warunek, kiedy pokazać licznik śmierci:
// Tylko jeśli gra się zakończyła (co wiemy, bo state === "gameOver"),
// przyczyną była śmierć gracza,
// ORAZ NIE był to tryb Boss+ (czyli isNewBossPlusMode jest false).
if (gameOverReasonIsDeath && !isNewBossPlusMode) {
self.deathsText.alpha = 1;
self.deathsText.x = 2048 - 150; // Twoja standardowa pozycja
self.deathsText.y = 50; // Twoja standardowa pozycja
} else {
self.deathsText.alpha = 0; // W innych przypadkach "gameOver" (np. Boss+) ukryj licznik śmierci
}
}
break;
// ... (case'y intro, fakeTutorial, realTutorial - zakładam, że są OK) ...
case "intro": //
case "fakeTutorial": //
case "realTutorial":
//
// Większość elementów UI jest zazwyczaj ukryta, zarządzane przez logikę tych stanów
break;
//
case "rollMaster":
// Pokaż elementy UI specyficzne dla aktywnego trybu Roll Master
if (self.timerText) {
// Timer/wynik dla Roll Master
self.timerText.alpha = 1;
self.timerText.x = 2048 / 2; // Środek-góra
self.timerText.y = 50;
self.timerText.anchor.set(0.5, 0);
self.timerText.style = {
size: 60,
fill: 0xFFFFFF
};
// Tekst jest aktualizowany w ui.updateTimerDisplay
}
if (self.highScoreText) {
// Rekord dla Roll Master (ten właściwy, nie ccHighScoreText)
self.highScoreText.alpha = 1;
self.highScoreText.x = 2048 - 50; // Prawy górny róg
self.highScoreText.y = 50;
self.highScoreText.anchor.set(1, 0);
// Tekst jest aktualizowany w ui.updateHighScoreDisplay
}
// Jawnie ukryj elementy UI z Cursed Crystal
if (self.ccScoreText) {
self.ccScoreText.alpha = 0;
}
if (self.ccHighScoreText) {
self.ccHighScoreText.alpha = 0;
} // <--- BARDZO WAŻNE
if (self.crystalChargeBarContainer) {
self.crystalChargeBarContainer.alpha = 0;
}
// Ukryj inne standardowe elementy gry
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;
} // Główny tytuł gry
break;
case "rollMasterGameOver":
// Jawnie ukryj elementy UI z Cursed Crystal
if (self.ccScoreText) {
self.ccScoreText.alpha = 0;
}
if (self.ccHighScoreText) {
self.ccHighScoreText.alpha = 0;
}
if (self.crystalChargeBarContainer) {
self.crystalChargeBarContainer.alpha = 0;
}
// Ukryj elementy aktywnej gry Roll Master (timer na żywo)
if (self.timerText) {
self.timerText.alpha = 0;
}
if (self.highScoreText) {
self.highScoreText.alpha = 0;
}
// Ukryj inne standardowe elementy gry
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;
case "cursedCrystal":
// TYLKO JEDNO WYSTĄPIENIE TEGO CASE
if (self.timerText) {
self.timerText.alpha = 0;
}
if (self.highScoreText) {
// To jest highScoreText dla Roll Master, więc ukrywamy
self.highScoreText.alpha = 0;
}
if (self.deathsText) {
// Licznik śmierci też ukrywamy w tym trybie
self.deathsText.alpha = 0;
}
if (self.tutorialText) {
// Tutorial też ukrywamy
self.tutorialText.alpha = 0;
}
// --- Elementy specyficzne dla Cursed Crystal ---
if (self.ccScoreText) {
self.ccScoreText.alpha = 1;
self.ccScoreText.x = 50;
self.ccScoreText.y = 20;
self.ccScoreText.anchor.set(0, 0);
}
if (self.ccHighScoreText) {
self.ccHighScoreText.alpha = 1;
self.ccHighScoreText.x = 2048 - 50;
self.ccHighScoreText.y = 20;
self.ccHighScoreText.anchor.set(1, 0);
}
// --- Serca Gracza ---
if (self.heartContainer) {
self.heartContainer.alpha = 1; // UPEWNIJ SIĘ, ŻE SERCA SĄ WIDOCZNE
var ccMaxHearts = typeof gameState !== 'undefined' && gameState.cursedCrystalPlayerMaxHealth ? gameState.cursedCrystalPlayerMaxHealth : 10;
var heartsWidth = ccMaxHearts * 50;
self.heartContainer.x = (2048 - heartsWidth) / 2 + 25;
self.heartContainer.y = 70; // Stała pozycja Y dla serc
}
// --- Pasek Ładowania Kryształu ---
if (self.crystalChargeBarContainer && self.crystalChargeBarBg) {
if (typeof gameState !== 'undefined' && gameState.isMinibossActiveCC) {
self.crystalChargeBarContainer.alpha = 0;
} else {
self.crystalChargeBarContainer.alpha = 1;
var chargeBarWidth = self.crystalChargeBarBg.width || 400;
self.crystalChargeBarContainer.anchorX = 0.5;
self.crystalChargeBarContainer.x = 2048 / 2;
self.crystalChargeBarContainer.y = (self.heartContainer ? self.heartContainer.y + (self.heartContainer.height || 40) : 70 + 40) + 50;
}
}
// --- Pasek HP Minibossa ---
if (self.bossHealthBarContainer) {
var heartsBottomY = self.heartContainer ? self.heartContainer.y + (self.heartContainer.height || 40) : 70 + 40;
// Poprzednio było: heartsBottomY + 20 (ok. 130), potem heartsBottomY + 70 (ok. 180)
// Aby przesunąć o kolejne 50px w dół od Y=180, potrzebujemy Y = 230
// Więc offset od heartsBottomY (ok. 110) musi być 120.
var desiredOffsetYBelowHearts = 120;
self.bossHealthBarContainer.y = heartsBottomY + desiredOffsetYBelowHearts;
// Widoczność paska HP bossa jest zarządzana przez updateMinibossHealthCC,
// więc tutaj tylko pozycjonujemy.
}
break;
case "cursedCrystalGameOver":
// Ukryj elementy rozgrywki CC (oprócz score i highscore, które mogą być częścią ekranu końca)
if (self.timerText) {
self.timerText.alpha = 0;
}
if (self.highScoreText) {
self.highScoreText.alpha = 0;
} // highScoreText dla RollMaster
if (self.crystalChargeBarContainer) {
self.crystalChargeBarContainer.alpha = 0;
}
if (self.heartContainer) {
self.heartContainer.alpha = 0;
} // Ukryj serca gracza na ekranie końca gry CC
if (self.bossHealthBarContainer) {
self.bossHealthBarContainer.alpha = 0;
}
if (self.deathsText) {
self.deathsText.alpha = 0;
}
if (self.tutorialText) {
self.tutorialText.alpha = 0;
}
break;
}
}; // 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
****/
// Jasnoniebieska elipsa dla Duszy
// Placeholder, zmień ID
// Dodana funkcja pomocnicza do pobierania czasu gry w milisekundach
// Dodana funkcja pomocnicza do pobierania czasu gry w milisekundach
// Globalny kontener na elementy scen intro/tutoriali i Grill Screena (do łatwego czyszczenia)
// Komentarze o assetach pominięte dla zwięzłości
// Ciemnofioletowe tło
// Fioletowy klejnot
// Pomarańczowy kwadrat dla Minibossa
function _typeof(o) {
"@babel/helpers - typeof";
return _typeof = "function" == typeof Symbol && "symbol" == typeof Symbol.iterator ? function (o) {
return typeof o;
} : function (o) {
return o && "function" == typeof Symbol && o.constructor === Symbol && o !== Symbol.prototype ? "symbol" : typeof o;
}, _typeof(o);
}
function _defineProperty(e, r, t) {
return (r = _toPropertyKey(r)) in e ? Object.defineProperty(e, r, {
value: t,
enumerable: !0,
configurable: !0,
writable: !0
}) : e[r] = t, e;
}
function _toPropertyKey(t) {
var i = _toPrimitive(t, "string");
return "symbol" == _typeof(i) ? i : i + "";
}
function _toPrimitive(t, r) {
if ("object" != _typeof(t) || !t) {
return t;
}
var e = t[Symbol.toPrimitive];
if (void 0 !== e) {
var i = e.call(t, r || "default");
if ("object" != _typeof(i)) {
return i;
}
throw new TypeError("@@toPrimitive must return a primitive value.");
}
return ("string" === r ? String : Number)(t);
}
if (typeof LK.getGameTime === 'undefined') {
LK.getGameTime = function () {
return Date.now(); // Zwraca aktualny czas w milisekundach
};
}
if (typeof LK.getGameTime === 'undefined') {
LK.getGameTime = function () {
return Date.now(); // Zwraca aktualny czas w milisekundach
};
}
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 = _defineProperty(_defineProperty(_defineProperty({
currentState: "title",
gameDuration: 120,
remainingTime: 0,
crystalExploding: false,
gameTimerInterval: null,
fakeTutorialTimerId: null,
bossSpeedIncreased: false,
currentIntroMusicInstance: null,
cursedCrystalMusicTracks: ['CursedCrystal', 'cursedcrystal2'],
currentRollSoulsInstance: null,
currentRollMasterMusicInstance: null,
currentCursedCrystalMusicInstance: null,
// Nowa właściwość
cursedCrystalPlayerHealth: 10,
// Startowe HP gracza w tym trybie
cursedCrystalPlayerMaxHealth: 10,
cursedCrystalChargeLevel: 0,
// Aktualny poziom naładowania Klejnotu (0-100%)
cursedCrystalTargetCharge: 100,
// Wartość naładowania do przywołania Minibossa
cursedCrystalSoulsHitCrystal: 0,
// Licznik dusz, które dotarły do kryształu (do skalowania Minibossa)
cursedCrystalScore: 0,
cursedCrystalHighScore: 0,
// Ładowane z localStorage
cursedCrystalEnemies: [],
// Tablica na Dusze i pociski Minibossa CC
cursedCrystalEnemySpawnTimer: 0,
cursedCrystalBaseSpawnInterval: 120,
// Początkowy interwał spawnu (np. 2 sekundy)
cursedCrystalCurrentSpawnInterval: 120,
cursedCrystalMinSpawnInterval: 30,
// Minimalny interwał spawnu (np. 0.5 sekundy)
cursedCrystalDifficultyTimer: 0,
cursedCrystalDifficultyInterval: 600,
// Co ile klatek (np. 10 sekund) zwiększać trudność
cursedCrystalEnemyBaseSpeed: 2,
cursedCrystalEnemyCurrentMaxSpeed: 4,
cursedCrystalTimeSurvived: 0,
// Czas przetrwania w klatkach lub sekundach
isMinibossActiveCC: false,
cursedCrystalMinibossObject: null,
cursedCrystalMinibossHP: 0,
cursedCrystalMinibossMaxHP: 200,
cursedCrystalActiveProjectiles: [],
// Dla pocisków Minibossa
cursedCrystalActiveExplosions: [],
cursedCrystalActiveLasers: [],
cursedCrystalActiveLaserWalls: [],
// Dla aktywnych obszarów eksplozji
// Bazowe HP Minibossa, będzie skalowane
cursedCrystalMinibossAttackTimer: 0,
cursedCrystalMinibossAttackInterval: 180,
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() {
// Zapewnienie, że LK.getGameTime jest zdefiniowane
if (typeof LK.getGameTime === 'undefined') {
LK.getGameTime = function () {
return Date.now(); // Zwraca aktualny czas w milisekundach
};
}
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);
this.showTitleScreen();
},
// --- gameState.showTitleScreen ---
showTitleScreen: function showTitleScreen() {
isNewBossPlusMode = false;
this.rollMasterTime = 0;
// Normalne działanie - bez flagi testowej
console.log("[showTitleScreen] State: Title Screen");
var needsIntroMusic = true;
// Zatrzymywanie muzyki z innych trybów
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();
}
}
// Logika muzyki intro
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.");
}
// Czyszczenie timerów
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(); // Usuwa elementy z currentSceneElements
// Ustawienie tła i cząsteczek
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);
// Reset gracza i bossa
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;
}
});
// Ustawienie UI
ui.positionElements("title");
ui.titleText.setText("Welcome Unchosen");
ui.showMessage("Tap to Start", 0); // Normalny tekst
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
};
// Usuwamy testowy przycisk i jego logikę
game.off('down');
game.on('down', function (x, y, obj) {
if (gameState.currentState === 'title') {
// Logika dla normalnego przejścia do intro
console.log("Title screen clicked - Proceeding to Intro.");
if (localParticleIntervalId) {
LK.clearInterval(localParticleIntervalId);
localParticleIntervalId = null;
}
if (localStartScreenParticles) {
localStartScreenParticles.forEach(function (p) {
if (p && p.destroy) {
p.destroy();
}
});
localStartScreenParticles = [];
}
// Muzyka intro powinna już grać lub zacząć grać, nie zatrzymujemy jej tutaj
gameState.showIntro(); // <<< KLUCZOWA ZMIANA - PRZYWRÓCENIE NORMALNEGO PRZEJŚCIA
}
});
},
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 (this.currentCursedCrystalMusicInstance && this.currentCursedCrystalMusicInstance.stop) {
console.log("[showTitleScreen] Zatrzymywanie currentCursedCrystalMusicInstance.");
if (typeof this.currentCursedCrystalMusicInstance.volume === 'number') {
this.currentCursedCrystalMusicInstance.volume = 0;
}
this.currentCursedCrystalMusicInstance.stop();
this.currentCursedCrystalMusicInstance = null;
} else {
// Opcjonalny fallback, jeśli chcesz (choć jeśli zawsze ustawiasz currentCursedCrystalMusicInstance, może nie być potrzebny)
var musicViaLK_CC = LK.music;
if (musicViaLK_CC && musicViaLK_CC.assetId === 'cursedcrystal' && musicViaLK_CC.playing && musicViaLK_CC.stop) {
console.log("[showTitleScreen] Fallback: Zatrzymywanie muzyki Cursed Crystal przez LK.music.");
if (typeof musicViaLK_CC.volume === 'number') {
musicViaLK_CC.volume = 0;
}
musicViaLK_CC.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();
}
}, 4000);
}, 4000);
}, delay);
}
showIntroText('From the authors of Dark Souls...', 3000, 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();
}
}, 1200);
});
});
});
});
});
},
// 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.currentCursedCrystalMusicInstance && this.currentCursedCrystalMusicInstance.stop) {
// DODANA LOGIKA
console.log("[StartGame] Zatrzymywanie currentCursedCrystalMusicInstance.");
if (typeof this.currentCursedCrystalMusicInstance.volume === 'number') {
this.currentCursedCrystalMusicInstance.volume = 0;
}
this.currentCursedCrystalMusicInstance.stop();
this.currentCursedCrystalMusicInstance = 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();
this.currentRollSoulsInstance = null; // Upewnij się, że zerujesz po zatrzymaniu, aby odtworzyć nową
}
console.log("[StartGame] Uruchamianie RollSouls.");
this.currentRollSoulsInstance = LK.playMusic('RollSouls', {
// Zakładam, że 'RollSouls' to muzyka dla tego trybu
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());
if (player) {
player.health = parseInt(storage.maxHearts, 10) || 5;
console.log("DEBUG: Inicjalizacja zdrowia gracza. player.health:", player.health, "storage.maxHearts:", storage.maxHearts);
}
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 = 600;
boss.health = boss.maxHealth;
this.gameDuration = 600;
boss.attackSpeedMultiplier = 0.6;
boss.speed = 7;
boss.ultimateAttackCooldownTimer = 0;
boss.nextUltimateAttackAvailableTime = boss.ultimateAttackMinInterval;
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");
if (ui && player) {
ui.updateHearts(player.health, parseInt(storage.maxHearts, 10) || 5);
} else if (ui) {
ui.updateHearts(0, parseInt(storage.maxHearts, 10) || 5);
}
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
};
// Przycisk "Cursed Crystal"
var cursedCrystalButton = new Container();
cursedCrystalButton.interactive = true;
cursedCrystalButton.cursor = "pointer";
cursedCrystalButton.x = 600; // Ta sama kolumna co inne główne przyciski
// Ustaw pozycję Y pod przyciskiem "New Boss+"
// Zakładamy, że newBossButton i newBossButtonBg istnieją i są zdefiniowane powyżej
var newBossButtonHeight = newBossButtonBg && newBossButtonBg.height ? newBossButtonBg.height : 200; // Domyślna wysokość, jeśli nieznana
var verticalSpacing = 700; // Odstęp między przyciskami
// --- POCZĄTEK POPRAWKI dla LK.getAssetMeta ---
var cursedCrystalButtonAssetForSizing;
var cursedCrystalButtonHeight = 250; // Domyślna wysokość assetu buttonCursedCrystal (zgodnie z definicją w LK.init.image)
try {
// Pobierz asset, aby spróbować odczytać jego rzeczywistą wysokość
cursedCrystalButtonAssetForSizing = LK.getAsset('buttonCursedCrystal', {});
if (cursedCrystalButtonAssetForSizing && typeof cursedCrystalButtonAssetForSizing.height === 'number') {
cursedCrystalButtonHeight = cursedCrystalButtonAssetForSizing.height;
}
} catch (e) {
// Jeśli wystąpi błąd przy pobieraniu assetu (np. jeszcze niezaładowany lub błąd ID),
// użyjemy domyślnej wysokości i wyświetlimy ostrzeżenie.
console.warn("Nie można pobrać assetu 'buttonCursedCrystal' do ustalenia wysokości, używam domyślnej: " + cursedCrystalButtonHeight, e);
}
// Oblicz pozycję Y przycisku Cursed Crystal
cursedCrystalButton.y = newBossButton.y + newBossButtonHeight / 2 + verticalSpacing + cursedCrystalButtonHeight / 2;
// --- KONIEC POPRAWKI dla LK.getAssetMeta ---
currentSceneElements.addChild(cursedCrystalButton);
var cursedCrystalButtonImage = LK.getAsset('buttonCursedCrystal', {
anchorX: 0.5,
anchorY: 0.5
});
cursedCrystalButton.addChild(cursedCrystalButtonImage);
cursedCrystalButton.down = function () {
if (gameState.currentState === "grillMenu") {
console.log("Przycisk Cursed Crystal kliknięty!");
gameState.startCursedCrystalMode();
}
};
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.currentCursedCrystalMusicInstance && this.currentCursedCrystalMusicInstance.stop) {
this.currentCursedCrystalMusicInstance.stop();
this.currentCursedCrystalMusicInstance = null;
} // <<< DODAJ TUTAJ
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(isDeathParam) {
// Krok 1: Zabezpieczenie parametru isDeathParam i przechwycenie aktualnego stanu isNewBossPlusMode
var localIsDeath = typeof isDeathParam === 'boolean' ? isDeathParam : true;
var calledDuringBossPlusMode = isNewBossPlusMode; // Przechwyć stan na początku wywołania!
console.log("gameState.gameOver CALLED. isDeathParam:", isDeathParam, "=> localIsDeath:", localIsDeath, ". Flag isNewBossPlusMode at call time (captured as calledDuringBossPlusMode):", calledDuringBossPlusMode, ". Current global isNewBossPlusMode:", isNewBossPlusMode // Dla porównania, jeśli coś by go zmieniło w międzyczasie
);
this.currentState = "gameOver"; // Ustaw stan gry
gameOverReasonIsDeath = localIsDeath; // Ustaw globalną flagę (jeśli nadal jej używasz)
// Krok 2: Zatrzymaj timery i inne procesy specyficzne dla trybu "game"
if (this.gameTimerInterval) {
LK.clearInterval(this.gameTimerInterval);
this.gameTimerInterval = null;
}
// if (this.rollMasterTimerInterval) { // To jest dla Roll Master, nie powinno być tutaj zatrzymywane
// LK.clearInterval(this.rollMasterTimerInterval);
// this.rollMasterTimerInterval = null;
// }
this.bossSpeedIncreased = false; // Reset flagi dla standardowego bossa
// Krok 3: Ukryj aktywne elementy gry (gracz, boss, ściany)
if (player && player.alpha !== 0) {
// Nie niszczymy gracza tutaj, jego animacja śmierci powinna się zakończyć,
// a obiekt player zostanie zniszczony przed pokazaniem przycisków lub restartem.
player.alpha = 0;
}
if (boss && boss.alpha !== 0) {
// Podobnie dla bossa, jego animacja śmierci (jeśli jest) powinna się zakończyć.
// Zostanie zniszczony później.
boss.alpha = 0;
}
walls.forEach(function (wall) {
if (wall) {
wall.alpha = 0;
}
});
// Krok 4: Wstępne ustawienie UI (czyszczenie tekstów)
if (ui) {
ui.positionElements("gameOver"); // Może ukryć niepotrzebne elementy, pokazać stałe dla gameOver
ui.titleText.setText("");
ui.titleText.alpha = 0;
ui.messageText.setText("");
ui.messageText.alpha = 0;
ui.tutorialText.setText("");
ui.tutorialText.alpha = 0; // Ukryj tutorial, jeśli był widoczny
}
// Krok 5: Wyświetl mema śmierci (jeśli localIsDeath) lub komunikat "Time's Up"
var memeDisplayDuration = 3000; // Czas wyświetlania mema/komunikatu przed przyciskami
var deathMemeVisual = null; // Zmienna do przechowywania obiektu mema
if (localIsDeath) {
var memeAssets = ['deathMeme1', 'deathMeme2', 'deathMeme3', 'deathMeme4', 'deathMeme5'];
var randomMemeAssetName = memeAssets[Math.floor(Math.random() * memeAssets.length)];
console.log("[GameOver] Wyświetlanie mema:", randomMemeAssetName);
try {
deathMemeVisual = LK.getAsset(randomMemeAssetName, {
anchorX: 0.5,
anchorY: 0.5,
alpha: 0,
x: 2048 / 2,
y: 2732 / 2 - 150,
// Trochę wyżej, żeby zrobić miejsce na przyciski
scaleX: 0.8,
scaleY: 0.8
});
currentSceneElements.addChild(deathMemeVisual); // Dodaj do kontenera, który będzie czyszczony
tween(deathMemeVisual, {
alpha: 1
}, {
duration: 500,
easing: tween.easeIn
});
} catch (e) {
console.error("[GameOver] Błąd ładowania assetu mema:", randomMemeAssetName, e);
if (ui) {
// Awaryjny tekst, jeśli mem się nie załaduje
ui.titleText.setText("DEFEAT!"); // Tłumaczenie
ui.titleText.style = {
size: 150,
fill: 0xFF0000,
align: 'center'
};
ui.titleText.alpha = 1;
}
}
} else if (calledDuringBossPlusMode && !localIsDeath) {
// Boss+ Time's Up
if (ui) {
ui.titleText.setText("TIME'S UP!"); // Tłumaczenie
ui.titleText.style = {
size: 150,
fill: 0xFFA500,
align: 'center',
stroke: 0x000000,
strokeThickness: 6
};
ui.titleText.alpha = 1;
// Można dodać dodatkowy komunikat poniżej tytułu
// ui.messageText.setText("The challenge ends.");
// ui.messageText.alpha = 1;
}
}
// Krok 6: Timeout, po którym wykonają się akcje końcowe (przyciski lub przejście)
LK.setTimeout(function () {
// Ten console.log był źródłem błędu, teraz używamy localIsDeath i calledDuringBossPlusMode
console.log("DEBUG: Timeout #1 (po memie) w gameState.gameOver. localIsDeath:", localIsDeath, "Captured BossPlus state (calledDuringBossPlusMode):", calledDuringBossPlusMode);
// Usuń mema, jeśli był wyświetlony
if (deathMemeVisual && !deathMemeVisual.destroyed) {
tween(deathMemeVisual, {
alpha: 0
}, {
duration: 300,
onFinish: function onFinish() {
if (deathMemeVisual && deathMemeVisual.parent) {
deathMemeVisual.parent.removeChild(deathMemeVisual);
}
if (deathMemeVisual && deathMemeVisual.destroy) {
deathMemeVisual.destroy();
}
deathMemeVisual = null;
}
});
}
// Wyczyść tytuł/wiadomość, która była z memem, aby przygotować miejsce na nowy tytuł z przyciskami
if (ui) {
ui.titleText.alpha = 0;
ui.messageText.alpha = 0;
}
// Ostateczne czyszczenie obiektów gry (gracz, boss, tło areny)
// clearScene() może być zbyt agresywne, jeśli currentSceneElements zawiera UI. Lepiej ręcznie.
if (player && player.destroy && !player.destroyed) {
player.destroy();
player = null;
}
if (boss && boss.destroy && !boss.destroyed) {
boss.destroy();
boss = null;
}
if (currentBackground) {
// Usuń tło areny, jeśli istnieje
if (currentBackground.parent) {
currentBackground.parent.removeChild(currentBackground);
}
if (currentBackground.destroy && !currentBackground.destroyed) {
currentBackground.destroy();
}
currentBackground = null;
}
game.setBackgroundColor(0x1a1a1a); // Ciemne tło dla ekranu z przyciskami
// Usuń poprzednie aktywne przyciski (np. z Cursed Crystal)
if (gameState.activeButtons && gameState.activeButtons.length > 0) {
gameState.activeButtons.forEach(function (btn) {
if (btn && btn.destroy && !btn.destroyed) {
// Dodatkowe sprawdzenie !btn.destroyed
if (btn.parent) {
btn.parent.removeChild(btn);
} // Usuń z rodzica przed zniszczeniem
btn.destroy();
}
});
}
gameState.activeButtons = []; // Wyczyść tablicę na nowe przyciski
// Logika przycisków lub restartu
if (calledDuringBossPlusMode) {
// Przypadek Boss+ (śmierć LUB koniec czasu) - wyświetl przyciski
console.log("gameOver: Scenariusz Boss+. Wyświetlanie tytułu i przycisków.");
var titleTextForBossPlus = localIsDeath ? "DEFEAT!" : "TIME'S UP!";
if (ui) {
ui.titleText.setText(titleTextForBossPlus);
ui.titleText.style = {
size: 120,
fill: localIsDeath ? 0xFF0000 : 0xFFA500,
align: 'center',
stroke: 0x000000,
strokeThickness: 6
};
ui.titleText.alpha = 1;
ui.titleText.x = 2048 / 2;
ui.titleText.y = 700; // Przykładowa pozycja Y dla tytułu
}
var buttonYStart = (ui && ui.titleText ? ui.titleText.y : 700) + 200;
var buttonSpacing = 220;
// Przycisk "Main Menu"
var menuButton = new Container();
menuButton.interactive = true;
menuButton.cursor = "pointer";
try {
var menuButtonAsset = LK.getAsset('mainmenu', {
anchorX: 0.5,
anchorY: 0.5
});
menuButton.addChild(menuButtonAsset);
} catch (e) {
console.error("Błąd ładowania assetu przycisku 'mainmenu':", e);
// Prosty fallback - tylko kształt, bez tekstu, bo zakładamy, że go nie potrzebujemy,
// skoro główny asset ma tekst. Jeśli chcesz, możesz dodać tu tekst awaryjny.
var menuButtonBgFallback = new Shape({
width: 400,
height: 100,
color: 0x555555,
shape: 'box'
});
menuButton.addChild(menuButtonBgFallback);
}
menuButton.x = 2048 / 2;
menuButton.y = buttonYStart;
menuButton.down = function () {
if (gameState.currentState !== "gameOver") {
return;
} // Dodatkowe zabezpieczenie
gameState.activeButtons.forEach(function (btn) {
if (btn && btn.destroy && !btn.destroyed) {
if (btn.parent) {
btn.parent.removeChild(btn);
}
btn.destroy();
}
});
gameState.activeButtons = [];
isNewBossPlusMode = false;
gameState.showGrillScreen();
};
currentSceneElements.addChild(menuButton); // Dodajemy do currentSceneElements
gameState.activeButtons.push(menuButton);
// Przycisk "Restart Boss+"
var restartButton = new Container();
restartButton.interactive = true;
restartButton.cursor = "pointer";
try {
var restartButtonAsset = LK.getAsset('restartgame', {
anchorX: 0.5,
anchorY: 0.5
});
restartButton.addChild(restartButtonAsset);
} catch (e) {
console.error("Błąd ładowania assetu przycisku 'restartgame':", e);
// Prosty fallback
var restartButtonBgFallback = new Shape({
width: 400,
height: 100,
color: 0x4477FF,
shape: 'box'
}); // Dostosuj wymiary i kolor
restartButton.addChild(restartButtonBgFallback);
}
restartButton.x = 2048 / 2;
restartButton.y = buttonYStart + buttonSpacing;
restartButton.down = function () {
if (gameState.currentState !== "gameOver") {
return;
}
gameState.activeButtons.forEach(function (btn) {
if (btn && btn.destroy && !btn.destroyed) {
if (btn.parent) {
btn.parent.removeChild(btn);
}
btn.destroy();
}
});
gameState.activeButtons = [];
isNewBossPlusMode = true;
gameState.startGame();
};
currentSceneElements.addChild(restartButton);
gameState.activeButtons.push(restartButton);
} else if (localIsDeath && !calledDuringBossPlusMode) {
// Śmierć w standardowym trybie (tutorial) - BEZ PRZYCISKÓW, od razu restart
console.log("gameOver: Standardowa śmierć (tutorial). Restartowanie poziomu.");
isNewBossPlusMode = false;
gameState.startGame();
} else {
// Inny, nieoczekiwany scenariusz (np. !localIsDeath i !calledDuringBossPlusMode)
// To jest fallback, np. jeśli jakimś cudem czas minął w trybie non-Boss+ (co nie powinno się zdarzyć)
console.warn("gameOver: Nieoczekiwany scenariusz (np. timeout w trybie non-Boss+). Przejście do Grill Menu.");
isNewBossPlusMode = false;
gameState.showGrillScreen();
}
}, memeDisplayDuration); // Czas na wyświetlenie mema/komunikatu przed pokazaniem przycisków/przejściem
},
// --- NOWA FUNKCJA: Zakończenie trybu Roll Master ---
// Wewnątrz obiektu gameState:
endRollMasterMode: function endRollMasterMode(finalTime) {
if (this.currentState === "rollMasterGameOver") {
return;
}
console.log("[EndRollMasterMode] Zakończono tryb Roll Master. Czas:", finalTime);
this.currentState = "rollMasterGameOver";
if (this.currentRollMasterMusicInstance && this.currentRollMasterMusicInstance.stop) {
this.currentRollMasterMusicInstance.stop();
this.currentRollMasterMusicInstance = null;
}
// ... (zatrzymanie timerów, czyszczenie ataków RM, logika rekordu - jak w poprzedniej odpowiedzi) ...
if (this.rollMasterTimerInterval) {
LK.clearInterval(this.rollMasterTimerInterval);
this.rollMasterTimerInterval = null;
}
if (this.randomRmattack1Timer) {
LK.clearTimeout(this.randomRmattack1Timer);
this.randomRmattack1Timer = null;
}
if (this.rollMasterAttacks && this.rollMasterAttacks.length > 0) {
this.rollMasterAttacks.forEach(function (atk) {
if (atk.visual && atk.visual.destroy && !atk.visual.destroyed) {
if (atk.visual.parent) {
atk.visual.parent.removeChild(atk.visual);
}
atk.visual.destroy();
}
});
this.rollMasterAttacks = [];
}
if (player && typeof player.clearRollTimeouts === 'function') {
player.clearRollTimeouts();
}
var oldHighScore = parseInt(storage.rollMasterHighScore, 10) || 0;
var newRecordRM = false;
if (finalTime > oldHighScore) {
storage.rollMasterHighScore = finalTime;
this.rollMasterHighScore = finalTime;
newRecordRM = true;
}
if (player && player.alpha !== 0) {
player.alpha = 0; // Ukryj gracza, ale nie niszcz od razu
}
var memeAndScoreDisplayDuration = 3000;
var deathMemeVisualRM = null;
var scoreTextVisualRM = null;
var memeAssets = ['deathMeme1', 'deathMeme2', 'deathMeme3', 'deathMeme4', 'deathMeme5'];
var randomMemeAssetName = memeAssets[Math.floor(Math.random() * memeAssets.length)];
try {
deathMemeVisualRM = LK.getAsset(randomMemeAssetName, {
anchorX: 0.5,
anchorY: 0.5,
alpha: 0,
x: 2048 / 2,
y: 2732 / 2 - 200,
scaleX: 0.8,
scaleY: 0.8
});
// Dodaj mema NAD istniejącym tłem (rollMasterBg), jeśli currentSceneElements jest nad nim
// lub bezpośrednio do 'game' na odpowiedniej warstwie.
// Jeśli currentSceneElements jest używane dla elementów pop-up, to jest dobre miejsce.
game.addChild(deathMemeVisualRM); // Lub currentSceneElements.addChild(deathMemeVisualRM);
tween(deathMemeVisualRM, {
alpha: 1
}, {
duration: 500,
easing: tween.easeIn
});
} catch (e) {
console.error("[EndRollMasterMode] Błąd ładowania mema:", e);
}
var timeMessage = "Your Time: " + String(Math.floor(finalTime / 60)).padStart(2, '0') + ':' + String(finalTime % 60).padStart(2, '0');
if (newRecordRM) {
timeMessage += "\n(NEW HIGH SCORE!)";
} else {
timeMessage += "\nBest: " + String(Math.floor(this.rollMasterHighScore / 60)).padStart(2, '0') + ':' + String(this.rollMasterHighScore % 60).padStart(2, '0');
}
scoreTextVisualRM = new Text2(timeMessage, {
size: 70,
fill: 0xFFFFFF,
align: 'center',
stroke: 0x000000,
strokeThickness: 4,
alpha: 0
});
scoreTextVisualRM.anchor.set(0.5, 0.5);
scoreTextVisualRM.x = 2048 / 2;
var memeHeightEstimate = deathMemeVisualRM && deathMemeVisualRM.height ? deathMemeVisualRM.height * (deathMemeVisualRM.scaleY || 1) : 560; // 700*0.8
scoreTextVisualRM.y = (deathMemeVisualRM ? deathMemeVisualRM.y + memeHeightEstimate / 2 : 2732 / 2 - 200 + 300) + 100;
game.addChild(scoreTextVisualRM); // Lub currentSceneElements.addChild(scoreTextVisualRM);
tween(scoreTextVisualRM, {
alpha: 1
}, {
duration: 500,
delay: 300,
easing: tween.easeIn
});
if (ui && ui.positionElements) {
ui.positionElements("rollMasterGameOver");
}
if (ui && ui.updateHighScoreDisplay) {
ui.updateHighScoreDisplay(this.rollMasterHighScore);
} // Pokaż rekord w UI
// Usuń stare aktywne przyciski (jeśli jakieś były)
if (gameState.activeButtons && gameState.activeButtons.length > 0) {
gameState.activeButtons.forEach(function (btn) {
if (btn && btn.destroy && !btn.destroyed) {
if (btn.parent) {
btn.parent.removeChild(btn);
}
btn.destroy();
}
});
}
gameState.activeButtons = [];
LK.setTimeout(function () {
console.log("DEBUG: Timeout #1 (po memie/wyniku) w endRollMasterMode. Pokazywanie przycisków.");
if (deathMemeVisualRM && !deathMemeVisualRM.destroyed) {
tween(deathMemeVisualRM, {
alpha: 0
}, {
duration: 300,
onFinish: function onFinish() {
if (deathMemeVisualRM.parent) {
deathMemeVisualRM.parent.removeChild(deathMemeVisualRM);
}
if (deathMemeVisualRM.destroy) {
deathMemeVisualRM.destroy();
}
deathMemeVisualRM = null;
}
});
}
if (scoreTextVisualRM && !scoreTextVisualRM.destroyed) {
tween(scoreTextVisualRM, {
alpha: 0
}, {
duration: 300,
onFinish: function onFinish() {
if (scoreTextVisualRM.parent) {
scoreTextVisualRM.parent.removeChild(scoreTextVisualRM);
}
if (scoreTextVisualRM.destroy) {
scoreTextVisualRM.destroy();
}
scoreTextVisualRM = null;
}
});
}
// Zniszcz obiekt gracza teraz, gdy już nie jest potrzebny
if (player && player.destroy && !player.destroyed) {
player.destroy();
player = null;
}
// TŁO: currentBackground (czyli rollMasterBg) POWINNO POZOSTAĆ. Nie niszczymy go.
// NIE wywołuj game.setBackgroundColor, aby nie nadpisać rollMasterBg.
// if (currentBackground) {
// // Jeśli currentBackground to było coś specyficznego dla mema, a nie rollMasterBg,
// // to tutaj trzeba by je usunąć i ewentualnie przywrócić rollMasterBg.
// // Ale zakładamy, że currentBackground to rollMasterBg od początku tego trybu.
// }
// game.setBackgroundColor(0x1a1a1a); // <--- USUWAMY TĘ LINIĘ (lub podobną)
var buttonYStart = 900;
var buttonSpacing = 220;
// Przycisk "Main Menu"
var menuButton = new Container(); /* ... reszta definicji bez zmian ... */
menuButton.interactive = true;
menuButton.cursor = "pointer";
var menuButtonBg = new Shape({
width: 400,
height: 100,
color: 0x555555,
shape: 'box'
});
try {
var menuButtonAsset = LK.getAsset('mainmenu', {
anchorX: 0.5,
anchorY: 0.5
});
menuButton.addChild(menuButtonAsset);
} catch (e) {
console.error("Błąd ładowania assetu przycisku 'mainmenu':", e);
var menuButtonBgFallback = new Shape({
width: 400,
height: 100,
color: 0x555555,
shape: 'box'
});
menuButton.addChild(menuButtonBgFallback);
// Możesz dodać awaryjny Text2, jeśli chcesz, gdyby asset się nie załadował
// var menuButtonTextFallback = new Text2("Main Menu", { size: 40, fill: 0xFFFFFF, anchor: { x: 0.5, y: 0.5 } });
// menuButton.addChild(menuButtonTextFallback);
}
menuButton.x = 2048 / 2;
menuButton.y = buttonYStart;
menuButton.down = function () {
if (gameState.currentState !== "rollMasterGameOver") {
return;
}
gameState.activeButtons.forEach(function (btn) {
if (btn && btn.destroy && !btn.destroyed) {
if (btn.parent) {
btn.parent.removeChild(btn);
}
btn.destroy();
}
});
gameState.activeButtons = [];
// WAŻNE: Zanim przejdziesz do GrillMenu, zniszcz currentBackground (rollMasterBg)
if (currentBackground && currentBackground.destroy && !currentBackground.destroyed) {
if (currentBackground.parent) {
currentBackground.parent.removeChild(currentBackground);
}
currentBackground.destroy();
currentBackground = null;
}
gameState.showGrillScreen();
};
game.addChild(menuButton); // Lub currentSceneElements.addChild(menuButton);
gameState.activeButtons.push(menuButton);
// Przycisk "Restart Mode"
var restartButton = new Container(); /* ... reszta definicji bez zmian ... */
try {
var restartButtonAsset = LK.getAsset('restartgame', {
anchorX: 0.5,
anchorY: 0.5
});
restartButton.addChild(restartButtonAsset);
} catch (e) {
console.error("Błąd ładowania assetu przycisku 'restartgame':", e);
var restartButtonBgFallback = new Shape({
width: 400,
height: 100,
color: 0x4477FF,
shape: 'box'
});
restartButton.addChild(restartButtonBgFallback);
// Możesz dodać awaryjny Text2
// var restartButtonTextFallback = new Text2("Restart Mode", { size: 40, fill: 0xFFFFFF, anchor: { x: 0.5, y: 0.5 } });
// restartButton.addChild(restartButtonTextFallback);
}
restartButton.x = 2048 / 2;
restartButton.y = buttonYStart + buttonSpacing;
restartButton.down = function () {
if (gameState.currentState !== "rollMasterGameOver") {
return;
}
gameState.activeButtons.forEach(function (btn) {
if (btn && btn.destroy && !btn.destroyed) {
if (btn.parent) {
btn.parent.removeChild(btn);
}
btn.destroy();
}
});
gameState.activeButtons = [];
// WAŻNE: Zanim zrestartujesz tryb, zniszcz currentBackground (rollMasterBg)
if (currentBackground && currentBackground.destroy && !currentBackground.destroyed) {
if (currentBackground.parent) {
currentBackground.parent.removeChild(currentBackground);
}
currentBackground.destroy();
currentBackground = null;
}
gameState.startRollMasterMode();
};
game.addChild(restartButton); // Lub currentSceneElements.addChild(restartButton);
gameState.activeButtons.push(restartButton);
}, memeAndScoreDisplayDuration);
},
startCursedCrystalMode: function startCursedCrystalMode() {
if (this.currentIntroMusicInstance && this.currentIntroMusicInstance.stop) {
this.currentIntroMusicInstance.stop();
this.currentIntroMusicInstance = null;
}
if (this.currentRollSoulsInstance && this.currentRollSoulsInstance.stop) {
this.currentRollSoulsInstance.stop();
this.currentRollSoulsInstance = null;
}
if (this.currentRollMasterMusicInstance && this.currentRollMasterMusicInstance.stop) {
this.currentRollMasterMusicInstance.stop();
this.currentRollMasterMusicInstance = null;
}
if (this.currentCursedCrystalMusicInstance && this.currentCursedCrystalMusicInstance.stop) {
this.currentCursedCrystalMusicInstance.stop();
this.currentCursedCrystalMusicInstance = null;
}
var randomIndex = Math.floor(Math.random() * this.cursedCrystalMusicTracks.length);
var selectedTrackId = this.cursedCrystalMusicTracks[randomIndex];
console.log(Date.now() + " [StartCursedCrystal] Próba odtworzenia: " + selectedTrackId);
this.currentCursedCrystalMusicInstance = LK.playMusic(selectedTrackId, {
loop: true,
volume: 0.7
});
// Logowanie wartości zwróconej przez LK.playMusic
console.log(Date.now() + " [StartCursedCrystal] LK.playMusic zwróciło dla '" + selectedTrackId + "':", this.currentCursedCrystalMusicInstance);
if (!this.currentCursedCrystalMusicInstance) {
console.error(Date.now() + " [StartCursedCrystal] Nie udało się odtworzyć muzyki (instancja jest falsy): " + selectedTrackId);
} else {
console.log(Date.now() + " [StartCursedCrystal] Instancja muzyki wydaje się być utworzona dla: " + selectedTrackId);
}
console.log("[StartCursedCrystal] Rozpoczynanie trybu Cursed Crystal...");
console.log("[StartCursedCrystal] Rozpoczynanie trybu Cursed Crystal...");
this.currentState = "cursedCrystal";
// 1. Zatrzymaj muzykę z innych trybów
if (this.currentIntroMusicInstance && this.currentIntroMusicInstance.stop) {
this.currentIntroMusicInstance.stop();
this.currentIntroMusicInstance = null;
}
if (this.currentRollSoulsInstance && this.currentRollSoulsInstance.stop) {
this.currentRollSoulsInstance.stop();
this.currentRollSoulsInstance = null;
}
if (this.currentRollMasterMusicInstance && this.currentRollMasterMusicInstance.stop) {
this.currentRollMasterMusicInstance.stop();
this.currentRollMasterMusicInstance = null;
}
// TODO: Odtwórz muzykę specyficzną dla Cursed Crystal, jeśli ją masz
// np. this.currentCursedCrystalMusic = LK.playMusic('cursedCrystalMusic_asset', { loop: true, volume: 0.7 });
// 2. Wyczyść scenę z elementów poprzedniego trybu
if (typeof clearScene === 'function') {
clearScene(); // Usuwa elementy z currentSceneElements
} else {
console.warn("Funkcja clearScene nie jest zdefiniowana globalnie!");
}
// Dokładne czyszczenie specyficznych elementów z innych trybów:
// Czyszczenie timerów i ataków z Roll Master
if (this.rollMasterTimerInterval) {
LK.clearInterval(this.rollMasterTimerInterval);
this.rollMasterTimerInterval = null;
}
if (this.randomRmattack1Timer) {
// Timer dla rmattack1 w Roll Master
LK.clearTimeout(this.randomRmattack1Timer);
this.randomRmattack1Timer = null;
}
if (this.rollMasterAttacks && this.rollMasterAttacks.length > 0) {
console.log("[StartCursedCrystal] Czyszczenie " + this.rollMasterAttacks.length + " ataków z Roll Master.");
this.rollMasterAttacks.forEach(function (atk) {
if (atk.visual && atk.visual.destroy && !atk.visual.destroyed) {
if (atk.visual.parent) {
// Dodatkowe zabezpieczenie
atk.visual.parent.removeChild(atk.visual);
}
atk.visual.destroy();
}
// Jeśli atak miał inne zasoby (np. własne timery), też powinny być tu czyszczone
});
this.rollMasterAttacks = [];
}
// Usuń specyficzne efekty z Grill Menu, jeśli istnieją
if (this.grillMenuEffects && this.grillMenuEffects.length > 0) {
console.log("[StartCursedCrystal] Czyszczenie " + this.grillMenuEffects.length + " efektów z Grill Menu.");
this.grillMenuEffects.forEach(function (effect) {
if (effect && effect.destroy && !effect.destroyed) {
if (effect.parent) {
// Dodatkowe zabezpieczenie
effect.parent.removeChild(effect);
}
effect.destroy();
}
});
this.grillMenuEffects = [];
}
// Usuń globalne obiekty gracza i bossa, jeśli istnieją
if (player && player.destroy && !player.destroyed) {
// Jeśli gracz ma jakieś specyficzne timery/interwały, które nie są czyszczone w jego .destroy() lub .clearRollTimeouts()
// to tutaj byłoby miejsce na ich wyczyszczenie przed zniszczeniem obiektu player.
// player.clearRollTimeouts(); // To powinno być wywoływane w Player.die() lub przed destroy()
player.destroy();
}
player = null;
if (boss && boss.destroy && !boss.destroyed) {
if (typeof boss.clearAllAttacks === 'function') {
boss.clearAllAttacks("startCursedCrystalMode"); // Upewnij się, że ataki bossa są czyszczone
}
// Podobnie, jeśli boss ma jakieś globalne timery nieczyszczone w .destroy()
boss.destroy();
}
boss = null;
// Usuń istniejące tło, jeśli jest
if (currentBackground && currentBackground.destroy) {
tween.stop(currentBackground); // Zatrzymaj animacje tła, jeśli były
currentBackground.destroy();
currentBackground = null;
}
// Usuń obiekt Klejnotu, jeśli istniał z poprzedniej sesji tego trybu
if (typeof crystalCoreObject !== 'undefined' && crystalCoreObject && crystalCoreObject.destroy && !crystalCoreObject.destroyed) {
crystalCoreObject.destroy();
}
crystalCoreObject = null;
// 3. Wywołaj konfigurację sceny dla Cursed Crystal
// Zamiast this.setupCursedCrystalScene_MinimalTest(); wywołaj normalną funkcję:
this.setupCursedCrystalScene();
// Aby użyć minimalnej sceny testowej, odkomentuj poniższe i zakomentuj powyższe:
// this.setupCursedCrystalScene_MinimalTest();
},
updateCrystalVisual: function updateCrystalVisual() {
if (!crystalCoreObject || crystalCoreObject.destroyed || this.isMinibossActiveCC || this.cursedCrystalChargeLevel >= this.cursedCrystalTargetCharge || this.crystalExploding) {
return;
}
var newAssetId = 'crystal_state_0';
var charge = this.cursedCrystalChargeLevel;
var targetCharge = this.cursedCrystalTargetCharge;
var thresholds = {
state1: 0.15 * targetCharge,
state2: 0.30 * targetCharge,
state3: 0.55 * targetCharge,
state4: 0.80 * targetCharge
};
if (charge >= thresholds.state4) {
newAssetId = 'crystal_state_4';
} else if (charge >= thresholds.state3) {
newAssetId = 'crystal_state_3';
} else if (charge >= thresholds.state2) {
newAssetId = 'crystal_state_2';
} else if (charge >= thresholds.state1) {
newAssetId = 'crystal_state_1';
}
if (crystalCoreObject.currentVisual && crystalCoreObject.currentVisual.assetId === newAssetId) {
return;
}
if (crystalCoreObject.currentVisual) {
crystalCoreObject.removeChild(crystalCoreObject.currentVisual);
if (crystalCoreObject.currentVisual.destroy && !crystalCoreObject.currentVisual.destroyed) {
crystalCoreObject.currentVisual.destroy();
}
crystalCoreObject.currentVisual = null;
}
try {
var newVisual = LK.getAsset(newAssetId, {
anchorX: 0.5,
anchorY: 0.5
});
crystalCoreObject.addChild(newVisual);
crystalCoreObject.currentVisual = newVisual;
crystalCoreObject.currentVisual.assetId = newAssetId;
console.log("Wizualizacja kryształu zmieniona na: " + newAssetId + " (naładowanie: " + charge + ")");
} catch (e) {
console.error("Błąd ładowania assetu kryształu: " + newAssetId, e);
if (newAssetId !== 'crystal_state_0' && (!crystalCoreObject.currentVisual || crystalCoreObject.currentVisual.assetId !== 'crystal_state_0')) {
try {
var baseVisual = LK.getAsset('crystal_state_0', {
anchorX: 0.5,
anchorY: 0.5
});
crystalCoreObject.addChild(baseVisual);
crystalCoreObject.currentVisual = baseVisual;
crystalCoreObject.currentVisual.assetId = 'crystal_state_0';
} catch (e2) {
console.error("Błąd ładowania awaryjnego assetu 'crystal_state_0'", e2);
}
}
}
},
playCrystalExplosionAnimation: function playCrystalExplosionAnimation() {
var crystalOriginalX = 2048 / 2; // Domyślna pozycja X na środek ekranu
var crystalOriginalY = 2732 / 2; // Domyślna pozycja Y na środek ekranu
if (crystalCoreObject && !crystalCoreObject.destroyed) {
// Jeśli crystalCoreObject istnieje, użyj jego aktualnej pozycji
crystalOriginalX = crystalCoreObject.x;
crystalOriginalY = crystalCoreObject.y;
console.log("Odtwarzanie animacji eksplozji kryształu w pozycji: X=" + crystalOriginalX + ", Y=" + crystalOriginalY);
// Zatrzymaj istniejące tweeny na kontenerze kryształu (lewitacja, pulsowanie)
if (crystalCoreObject.levitationTween && crystalCoreObject.levitationTween.stop) {
crystalCoreObject.levitationTween.stop();
crystalCoreObject.levitationTween = null; // Wyczyść referencję
}
if (crystalCoreObject.scaleTween && crystalCoreObject.scaleTween.stop) {
crystalCoreObject.scaleTween.stop();
crystalCoreObject.scaleTween = null; // Wyczyść referencję
}
// Można dodać reset pozycji/skali, jeśli tweeny mogły je zmienić w sposób niepożądany dla animacji
crystalCoreObject.scale.set(1); // Przywróć domyślną skalę kontenera
// crystalCoreObject.y = crystalCoreObject.originalY; // Jeśli chcesz przywrócić oryginalną Y przed animacją
// Usuń obecną statyczną grafikę kryształu (dziecko kontenera crystalCoreObject)
if (crystalCoreObject.currentVisual) {
crystalCoreObject.removeChild(crystalCoreObject.currentVisual);
if (crystalCoreObject.currentVisual.destroy && !crystalCoreObject.currentVisual.destroyed) {
crystalCoreObject.currentVisual.destroy();
}
crystalCoreObject.currentVisual = null;
}
var explosionFramesAssets = [];
for (var i = 0; i < 4; i++) {
// Załóżmy 4 klatki animacji
try {
// Upewnij się, że anchorX i anchorY są ustawione, jeśli Twoje klatki tego wymagają
// dla poprawnego pozycjonowania wewnątrz SpriteAnimation
explosionFramesAssets.push(LK.getAsset('crystal_explosion_f' + i, {
anchorX: 0.5,
anchorY: 0.5,
clone: true
}));
} catch (e) {
console.error("Błąd ładowania klatki eksplozji: crystal_explosion_f" + i, e);
// Awaryjny placeholder jeśli klatki brakuje, aby animacja nadal miała odpowiednią długość
var placeholderFrame = new Shape({
width: 150,
height: 150,
color: 0xFF8C00,
shape: 'ellipse'
});
placeholderFrame.anchor.set(0.5, 0.5); // Ustaw anchor dla Shape
explosionFramesAssets.push(placeholderFrame);
}
}
var explosionAnim = new SpriteAnimation({
frames: explosionFramesAssets,
frameDuration: 150,
// Czas trwania klatki w ms (np. 150ms * 4 klatki = 0.6s) - dostosuj
loop: false,
anchorX: 0.5,
// Anchor samej animacji SpriteAnimation, jeśli ma być inaczej pozycjonowana w crystalCoreObject
anchorY: 0.5
// x i y będą 0,0 względem kontenera crystalCoreObject, jeśli animacja ma być na jego środku
});
crystalCoreObject.addChild(explosionAnim); // Dodaj animację eksplozji do kontenera kryształu
explosionAnim.play();
// TODO: Dodaj tutaj swój dźwięk eksplozji, jeśli masz
// np. LK.getSound('twoj_dzwiek_eksplozji_krysztalu').play();
} else {
// Ten blok jest awaryjny, jeśli crystalCoreObject nie istnieje, gdy funkcja jest wołana
console.warn("Próba eksplozji, ale crystalCoreObject nie istnieje lub został zniszczony przed rozpoczęciem animacji. Bezpośrednie spawnowanie Minibossa w domyślnej pozycji.");
this.spawnCursedCrystalMiniboss(crystalOriginalX, crystalOriginalY);
this.crystalExploding = false; // Zresetuj flagę
return; // Zakończ funkcję, jeśli nie ma kryształu do animowania
}
var selfGameState = this; // Zachowaj referencję 'this' (gameState) dla callbacku onComplete
explosionAnim.onComplete = function () {
console.log("Animacja eksplozji kryształu zakończona.");
// Zniszcz kontener crystalCoreObject po zakończeniu animacji
if (crystalCoreObject && !crystalCoreObject.destroyed) {
if (crystalCoreObject.parent) {
crystalCoreObject.parent.removeChild(crystalCoreObject);
}
crystalCoreObject.destroy();
}
crystalCoreObject = null; // Wyczyść globalną/dostępną referencję
// Wywołaj logikę spawnowania Minibossa, przekazując zapisaną pozycję
selfGameState.spawnCursedCrystalMiniboss(crystalOriginalX, crystalOriginalY);
selfGameState.crystalExploding = false; // Zresetuj flagę po zakończeniu całego procesu
};
},
spawnCursedCrystalMiniboss: function spawnCursedCrystalMiniboss(spawnX, spawnY) {
// Argumenty to spawnX i spawnY
console.log("MINIBOSS SPAWNING SEQUENCE INITIATED! Pozycja: X=" + spawnX + ", Y=" + spawnY);
var self = this;
self.isMinibossActiveCC = true;
if (ui && ui.crystalChargeBarContainer) {
ui.crystalChargeBarContainer.alpha = 0;
}
var timeSurvivedInMinutes = Math.floor(self.cursedCrystalTimeSurvived / (60 * 60));
var baseMinibossHp = 200;
var hpPerMinute = 100;
var scaledMaxHp = baseMinibossHp + timeSurvivedInMinutes * hpPerMinute;
scaledMaxHp = Math.max(baseMinibossHp, scaledMaxHp);
self.cursedCrystalMinibossMaxHP = scaledMaxHp;
self.cursedCrystalMinibossHP = scaledMaxHp;
var twoMinutesInFrames = 1 * 60 * 60; // 2 minuty * 60 sekund * 60 klatek/sekundę
if (self.cursedCrystalTimeSurvived >= twoMinutesInFrames) {
console.log("GRACZ PRZETRWAŁ PONAD 2 MINUTY! Miniboss otrzymuje ulepszone ataki!");
self.minibossEnhancedProjectile = true;
self.minibossEnhancedLaserWall = true;
} else {
self.minibossEnhancedProjectile = false;
self.minibossEnhancedLaserWall = false;
}
self.cursedCrystalEnemies.forEach(function (soul) {
if (soul && !soul.isDead) {
if (soul.parent) {
soul.parent.removeChild(soul);
}
if (soul.destroy && !soul.destroyed) {
soul.destroy();
}
}
});
self.cursedCrystalEnemies = [];
self.cursedCrystalChargeLevel = 0;
if (ui && ui.updateCrystalCharge) {
ui.updateCrystalCharge(self.cursedCrystalChargeLevel, self.cursedCrystalTargetCharge);
}
LK.setTimeout(function () {
if (self.currentState !== "cursedCrystal" || !self.isMinibossActiveCC) {
console.log("Spawn Minibossa anulowany - zmiana stanu gry lub isMinibossActiveCC jest false.");
return;
}
console.log("FAZA SPAWNU MINIBOSSA PO OPÓŹNIENIU");
var minibossOptions = {
maxHp: scaledMaxHp,
x: spawnX,
// ***** POPRAWKA: Używamy argumentu funkcji spawnX *****
y: spawnY // ***** POPRAWKA: Używamy argumentu funkcji spawnY *****
};
self.cursedCrystalMinibossObject = game.addChild(new MinibossCC(minibossOptions));
if (self.cursedCrystalMinibossObject) {
self.cursedCrystalMinibossObject.alpha = 0;
tween(self.cursedCrystalMinibossObject, {
alpha: 1
}, {
duration: 1500,
easing: tween.easeInQuad,
onFinish: function onFinish() {
console.log("Miniboss w pełni widoczny.");
}
});
if (ui && ui.updateMinibossHealthCC) {
ui.updateMinibossHealthCC(self.cursedCrystalMinibossHP, self.cursedCrystalMinibossMaxHP, true);
}
if (ui && ui.showMessage) {
ui.showMessage("Demon Pepe is here", 3000);
}
} else {
console.error("Nie udało się stworzyć obiektu MinibossCC!");
}
}, 3000);
},
setupCursedCrystalScene: function setupCursedCrystalScene() {
console.log("[SetupCursedCrystal] Konfiguracja sceny Cursed Crystal...");
// 1. Ustaw tło dla areny
try {
// Upewnij się, że stary currentBackground jest niszczony, jeśli istnieje
if (typeof currentBackground !== 'undefined' && currentBackground && currentBackground.destroy && !currentBackground.destroyed) {
currentBackground.destroy();
}
currentBackground = LK.getAsset('cursedCrystalArenaBg_asset', {
anchorX: 0,
// Zgodnie z Twoim kodem
anchorY: 0,
// Zgodnie z Twoim kodem
x: 0,
// Zgodnie z Twoim kodem
y: 0 // Zgodnie z Twoim kodem
});
game.addChildAt(currentBackground, 0);
} catch (e) {
console.warn("Nie znaleziono assetu tła Cursed Crystal, używam domyślnego koloru.", e);
game.setBackgroundColor(0x100510);
if (typeof currentBackground !== 'undefined' && currentBackground && currentBackground.destroy && !currentBackground.destroyed) {
currentBackground.destroy();
}
currentBackground = null;
}
// 2. Stwórz obiekt Klejnotu Dusz (CrystalCore) jako KONTENER
if (typeof crystalCoreObject !== 'undefined' && crystalCoreObject) {
if (crystalCoreObject.levitationTween && crystalCoreObject.levitationTween.stop) {
crystalCoreObject.levitationTween.stop();
}
if (crystalCoreObject.scaleTween && crystalCoreObject.scaleTween.stop) {
crystalCoreObject.scaleTween.stop();
}
if (crystalCoreObject.destroy && !crystalCoreObject.destroyed) {
crystalCoreObject.destroy();
}
}
crystalCoreObject = new Container(); // *** NOWOŚĆ: crystalCoreObject jest teraz kontenerem ***
crystalCoreObject.x = 2048 / 2;
crystalCoreObject.y = 2732 / 2;
crystalCoreObject.collisionRadius = 60;
game.addChild(crystalCoreObject);
// Dodaj początkową wizualizację kryształu (0% naładowania)
try {
var initialCrystalVisual = LK.getAsset('crystal_state_0', {
anchorX: 0.5,
anchorY: 0.5
});
crystalCoreObject.addChild(initialCrystalVisual);
crystalCoreObject.currentVisual = initialCrystalVisual; // Referencja do aktualnej grafiki
crystalCoreObject.currentVisual.assetId = 'crystal_state_0'; // Do sprawdzania, co jest wyświetlane
} catch (e) {
console.error("KRYTYCZNY BŁĄD: Nie można załadować 'crystal_state_0'!", e);
// Awaryjny kształt, jeśli assetu nie ma
var fallbackVisual = new Shape({
width: 100,
height: 100,
color: 0x8A2BE2,
shape: 'ellipse'
});
fallbackVisual.anchor.set(0.5);
crystalCoreObject.addChild(fallbackVisual);
crystalCoreObject.currentVisual = fallbackVisual;
crystalCoreObject.currentVisual.assetId = 'fallback_crystal';
}
// Animacje lewitacji i pulsowania dla kontenera crystalCoreObject
if (crystalCoreObject && !crystalCoreObject.destroyed) {
crystalCoreObject.originalY = crystalCoreObject.y;
var levitationHeight = 30;
var oneWayDuration = 1250;
var levitateCrystalUp = function levitateCrystalUp(targetObject) {
if (!targetObject || targetObject.destroyed) {
return;
}
if (targetObject.levitationTween && targetObject.levitationTween.stop) {
targetObject.levitationTween.stop();
}
targetObject.y = targetObject.originalY;
targetObject.levitationTween = tween(targetObject, {
y: targetObject.originalY - levitationHeight
}, {
duration: oneWayDuration,
easing: tween.easeInOut,
onFinish: function onFinish() {
if (targetObject && !targetObject.destroyed) {
levitateCrystalDown(targetObject);
}
}
});
};
var levitateCrystalDown = function levitateCrystalDown(targetObject) {
if (!targetObject || targetObject.destroyed) {
return;
}
if (targetObject.levitationTween && targetObject.levitationTween.stop) {
targetObject.levitationTween.stop();
}
targetObject.levitationTween = tween(targetObject, {
y: targetObject.originalY
}, {
duration: oneWayDuration,
easing: tween.easeInOut,
onFinish: function onFinish() {
if (targetObject && !targetObject.destroyed) {
levitateCrystalUp(targetObject);
}
}
});
};
levitateCrystalUp(crystalCoreObject);
if (crystalCoreObject.scaleTween && crystalCoreObject.scaleTween.stop) {
crystalCoreObject.scaleTween.stop();
}
crystalCoreObject.baseScaleX = crystalCoreObject.scaleX || 1; // Kontener zwykle ma scale 1
crystalCoreObject.baseScaleY = crystalCoreObject.scaleY || 1;
crystalCoreObject.scaleTween = tween(crystalCoreObject, {
scaleX: crystalCoreObject.baseScaleX * 1.05,
scaleY: crystalCoreObject.baseScaleY * 1.05
}, {
duration: oneWayDuration * 2 * 0.75,
easing: tween.easeInOut,
yoyo: true,
repeat: Infinity
});
}
// 3. Stwórz nowy obiekt gracza (bez zmian w tej sekcji)
if (typeof player !== 'undefined' && player && player.destroy && !player.destroyed) {
player.destroy();
}
player = game.addChild(new Player());
player.health = this.cursedCrystalPlayerMaxHealth;
player.x = 2048 / 2;
player.y = (crystalCoreObject && crystalCoreObject.y ? crystalCoreObject.y : 2732 / 2) + (crystalCoreObject && crystalCoreObject.currentVisual && crystalCoreObject.currentVisual.height ? crystalCoreObject.currentVisual.height / 2 : 50) + player.height / 2 + 50; // Pozycjonowanie względem środka/dolnej krawędzi kryształu
player.dead = false;
player.rolling = false;
player.invulnerable = false;
player.rollCooldown = 0;
if (player.clearRollTimeouts) {
player.clearRollTimeouts();
}
this.currentInputPos.x = player.x;
this.currentInputPos.y = player.y;
this.isInputActive = false;
this.cursedCrystalPlayerHealth = this.cursedCrystalPlayerMaxHealth; // Zakładam, że this.cursedCrystalPlayerMaxHealth jest zdefiniowane (np. w gameState lub przekazane)
this.cursedCrystalChargeLevel = 0;
this.cursedCrystalSoulsHitCrystal = 0;
this.cursedCrystalScore = 0;
this.cursedCrystalHighScore = parseInt(storage.cursedCrystalHighScore, 10) || 0;
this.cursedCrystalEnemies = [];
this.cursedCrystalEnemySpawnTimer = 0;
this.cursedCrystalSoulsPerGroupMin = 1;
this.cursedCrystalSoulsPerGroupMax = 5;
this.cursedCrystalGroupSpawnSpread = 60;
this.cursedCrystalBaseSpawnInterval = 90; // Interwał między grupami (np. 3 sekundy)
this.cursedCrystalCurrentSpawnInterval = this.cursedCrystalBaseSpawnInterval;
this.cursedCrystalDifficultyTimer = 0;
this.cursedCrystalEnemyBaseSpeed = 0.1; // Ustawiamy niższą bazową prędkość
this.cursedCrystalEnemyCurrentMaxSpeed = this.cursedCrystalEnemyBaseSpeed; // Aktualna prędkość startuje od bazowej
this.cursedCrystalTimeSurvived = 0;
this.isMinibossActiveCC = false;
this.crystalExploding = false;
if (this.cursedCrystalMinibossObject && this.cursedCrystalMinibossObject.destroy && !this.cursedCrystalMinibossObject.destroyed) {
this.cursedCrystalMinibossObject.destroy();
}
this.cursedCrystalMinibossObject = null;
this.cursedCrystalMinibossHP = 0;
this.cursedCrystalActiveProjectiles = [];
this.cursedCrystalActiveExplosions = [];
this.cursedCrystalActiveLaserWalls = [];
// 5. Konfiguracja UI (bez zmian w tej sekcji)
if (ui) {
ui.positionElements("cursedCrystal");
if (ui.updateScoreCC) {
ui.updateScoreCC(this.cursedCrystalScore);
}
if (ui.updateHighScoreCC) {
ui.updateHighScoreCC(this.cursedCrystalHighScore);
}
if (ui.updateCrystalCharge) {
ui.updateCrystalCharge(this.cursedCrystalChargeLevel, this.cursedCrystalTargetCharge);
}
if (ui.updateHearts) {
ui.updateHearts(this.cursedCrystalPlayerHealth, this.cursedCrystalPlayerMaxHealth);
}
if (ui.updateMinibossHealthCC) {
ui.updateMinibossHealthCC(0, this.cursedCrystalMinibossMaxHP, false);
}
if (ui.bossHealthBarContainer) {
ui.bossHealthBarContainer.alpha = 0;
}
if (ui.timerText) {
ui.timerText.alpha = 0;
}
if (ui.highScoreText) {
ui.highScoreText.alpha = 0;
}
if (ui.deathsText) {
ui.deathsText.alpha = 0;
}
}
// 6. Wyświetl startowy komunikat (bez zmian)
if (ui && ui.showMessage) {
ui.showMessage("Protect the Soul Gem!", 3000);
}
this.gameActive = true;
console.log("[SetupCursedCrystal] Scena Cursed Crystal skonfigurowana. Gracz HP: " + player.health);
}
}, "setupCursedCrystalScene", function setupCursedCrystalScene() {
// <<< NOWY KOD setupCursedCrystalScene ZACZYNA SIĘ TUTAJ >>>
console.log("[SetupCursedCrystal] Konfiguracja sceny Cursed Crystal...");
// 1. Ustaw tło dla areny
try {
if (typeof currentBackground !== 'undefined' && currentBackground && currentBackground.destroy && !currentBackground.destroyed) {
currentBackground.destroy();
}
currentBackground = LK.getAsset('cursedCrystalArenaBg_asset', {
// Podmień na swój asset tła
anchorX: 0,
anchorY: 0,
x: 0,
y: -100
});
game.addChildAt(currentBackground, 0);
} catch (e) {
console.warn("Nie znaleziono assetu tła Cursed Crystal, używam domyślnego koloru.", e);
game.setBackgroundColor(0x100510);
if (typeof currentBackground !== 'undefined' && currentBackground && currentBackground.destroy && !currentBackground.destroyed) {
currentBackground.destroy();
}
currentBackground = null;
}
// 2. Stwórz obiekt Klejnotu Dusz (CrystalCore) jako KONTENER
if (typeof crystalCoreObject !== 'undefined' && crystalCoreObject) {
if (crystalCoreObject.levitationTween && crystalCoreObject.levitationTween.stop) {
crystalCoreObject.levitationTween.stop();
}
if (crystalCoreObject.scaleTween && crystalCoreObject.scaleTween.stop) {
crystalCoreObject.scaleTween.stop();
}
if (crystalCoreObject.destroy && !crystalCoreObject.destroyed) {
crystalCoreObject.destroy();
}
}
crystalCoreObject = new Container(); // *** NOWOŚĆ: crystalCoreObject jest teraz kontenerem ***
crystalCoreObject.x = 2048 / 2;
crystalCoreObject.y = 2732 / 2;
game.addChild(crystalCoreObject);
try {
var initialCrystalVisual = LK.getAsset('crystal_state_0', {
anchorX: 0.5,
anchorY: 0.5
});
crystalCoreObject.addChild(initialCrystalVisual);
crystalCoreObject.currentVisual = initialCrystalVisual;
crystalCoreObject.currentVisual.assetId = 'crystal_state_0';
} catch (e) {
console.error("KRYTYCZNY BŁĄD: Nie można załadować 'crystal_state_0'!", e);
var fallbackVisual = new Shape({
width: 100,
height: 100,
color: 0x8A2BE2,
shape: 'ellipse'
});
fallbackVisual.anchor.set(0.5);
crystalCoreObject.addChild(fallbackVisual);
crystalCoreObject.currentVisual = fallbackVisual;
crystalCoreObject.currentVisual.assetId = 'fallback_crystal';
}
if (crystalCoreObject && !crystalCoreObject.destroyed) {
crystalCoreObject.originalY = crystalCoreObject.y;
var levitationHeight = 30;
var oneWayDuration = 1250;
var levitateCrystalUp = function levitateCrystalUp(targetObject) {
if (!targetObject || targetObject.destroyed) {
return;
}
if (targetObject.levitationTween && targetObject.levitationTween.stop) {
targetObject.levitationTween.stop();
}
targetObject.y = targetObject.originalY;
targetObject.levitationTween = tween(targetObject, {
y: targetObject.originalY - levitationHeight
}, {
duration: oneWayDuration,
easing: tween.easeInOut,
onFinish: function onFinish() {
if (targetObject && !targetObject.destroyed) {
levitateCrystalDown(targetObject);
}
}
});
};
var levitateCrystalDown = function levitateCrystalDown(targetObject) {
if (!targetObject || targetObject.destroyed) {
return;
}
if (targetObject.levitationTween && targetObject.levitationTween.stop) {
targetObject.levitationTween.stop();
}
targetObject.levitationTween = tween(targetObject, {
y: targetObject.originalY
}, {
duration: oneWayDuration,
easing: tween.easeInOut,
onFinish: function onFinish() {
if (targetObject && !targetObject.destroyed) {
levitateCrystalUp(targetObject);
}
}
});
};
levitateCrystalUp(crystalCoreObject);
if (crystalCoreObject.scaleTween && crystalCoreObject.scaleTween.stop) {
crystalCoreObject.scaleTween.stop();
}
crystalCoreObject.baseScaleX = crystalCoreObject.scaleX || 1;
crystalCoreObject.baseScaleY = crystalCoreObject.scaleY || 1;
crystalCoreObject.scaleTween = tween(crystalCoreObject, {
scaleX: crystalCoreObject.baseScaleX * 1.05,
scaleY: crystalCoreObject.baseScaleY * 1.05
}, {
duration: oneWayDuration * 2 * 0.75,
easing: tween.easeInOut,
yoyo: true,
repeat: Infinity
});
}
if (typeof player !== 'undefined' && player && player.destroy && !player.destroyed) {
player.destroy();
}
player = game.addChild(new Player());
player.health = this.cursedCrystalPlayerMaxHealth; // Użyj this
player.x = 2048 / 2;
player.y = (crystalCoreObject && crystalCoreObject.y ? crystalCoreObject.y : 2732 / 2) + (crystalCoreObject && crystalCoreObject.currentVisual && crystalCoreObject.currentVisual.height ? crystalCoreObject.currentVisual.height / 2 : 50) + player.height / 2 + 50;
player.dead = false;
player.rolling = false;
player.invulnerable = false;
player.rollCooldown = 0;
if (player.clearRollTimeouts) {
player.clearRollTimeouts();
}
this.currentInputPos.x = player.x; // Użyj this
this.currentInputPos.y = player.y; // Użyj this
this.isInputActive = false; // Użyj this
this.cursedCrystalPlayerHealth = this.cursedCrystalPlayerMaxHealth; // Użyj this
this.cursedCrystalChargeLevel = 0; // Użyj this
this.cursedCrystalSoulsHitCrystal = 0; // Użyj this
this.cursedCrystalScore = 0; // Użyj this
this.cursedCrystalHighScore = parseInt(storage.cursedCrystalHighScore, 10) || 0; // Użyj this
this.cursedCrystalEnemies = []; // Użyj this
this.cursedCrystalEnemySpawnTimer = 0; // Użyj this
this.cursedCrystalCurrentSpawnInterval = this.cursedCrystalBaseSpawnInterval; // Użyj this
this.cursedCrystalDifficultyTimer = 0; // Użyj this
this.cursedCrystalEnemyCurrentMaxSpeed = this.cursedCrystalEnemyBaseSpeed; // Użyj this
this.cursedCrystalTimeSurvived = 0; // Użyj this
this.isMinibossActiveCC = false; // Użyj this
this.crystalExploding = false; // *** NOWA FLAGA, użyj this ***
if (this.cursedCrystalMinibossObject && this.cursedCrystalMinibossObject.destroy && !this.cursedCrystalMinibossObject.destroyed) {
// Użyj this
this.cursedCrystalMinibossObject.destroy();
}
this.cursedCrystalMinibossObject = null; // Użyj this
this.cursedCrystalMinibossHP = 0; // Użyj this
this.cursedCrystalActiveProjectiles = []; // Użyj this
this.cursedCrystalActiveExplosions = []; // Użyj this
this.cursedCrystalActiveLaserWalls = []; // Użyj this
if (ui) {
ui.positionElements("cursedCrystal");
if (ui.updateScoreCC) {
ui.updateScoreCC(this.cursedCrystalScore);
}
if (ui.updateHighScoreCC) {
ui.updateHighScoreCC(this.cursedCrystalHighScore);
}
if (ui.updateCrystalCharge) {
ui.updateCrystalCharge(this.cursedCrystalChargeLevel, this.cursedCrystalTargetCharge);
}
if (ui.updateHearts) {
ui.updateHearts(this.cursedCrystalPlayerHealth, this.cursedCrystalPlayerMaxHealth);
}
if (ui.updateMinibossHealthCC) {
ui.updateMinibossHealthCC(0, this.cursedCrystalMinibossMaxHP, false);
}
if (ui.bossHealthBarContainer) {
ui.bossHealthBarContainer.alpha = 0;
}
if (ui.timerText) {
ui.timerText.alpha = 0;
}
if (ui.highScoreText) {
ui.highScoreText.alpha = 0;
}
if (ui.deathsText) {
ui.deathsText.alpha = 0;
}
}
if (ui && ui.showMessage) {
ui.showMessage("Catch them all!", 3000);
}
console.log("[SetupCursedCrystal] Scena Cursed Crystal skonfigurowana. Gracz HP: " + player.health);
// <<< NOWY KOD setupCursedCrystalScene KOŃCZY SIĘ TUTAJ >>>
}), "endCursedCrystalMode", function endCursedCrystalMode(isVictory) {
console.log("DEBUG: gameState.endCursedCrystalMode called. Victory: " + isVictory);
this.currentState = "cursedCrystalGameOver";
// --- CZYSZCZENIE AKTYWNYCH ELEMENTÓW TRYBU CC ---
// 1. Dusze (SoulEnemy)
if (this.cursedCrystalEnemies && this.cursedCrystalEnemies.length > 0) {
console.log("DEBUG: Clearing " + this.cursedCrystalEnemies.length + " remaining SoulEnemies.");
this.cursedCrystalEnemies.forEach(function (enemy) {
if (enemy && enemy.destroy && !enemy.destroyed) {
if (enemy.parent) {
enemy.parent.removeChild(enemy);
}
enemy.destroy();
}
});
}
this.cursedCrystalEnemies = [];
// 2. Pociski Minibossa CC (jeśli gracz zginął, a boss jeszcze strzelał)
if (this.cursedCrystalActiveProjectiles && this.cursedCrystalActiveProjectiles.length > 0) {
console.log("DEBUG: Clearing " + this.cursedCrystalActiveProjectiles.length + " remaining projectiles.");
this.cursedCrystalActiveProjectiles.forEach(function (proj) {
if (proj && proj.destroy && !proj.destroyed) {
if (proj.graphics && proj.graphics.parent) {
proj.graphics.parent.removeChild(proj.graphics);
}
if (proj.graphics && proj.graphics.destroy) {
proj.graphics.destroy();
}
proj.destroy();
}
});
}
this.cursedCrystalActiveProjectiles = [];
// 3. Eksplozje Minibossa CC
if (this.cursedCrystalActiveExplosions && this.cursedCrystalActiveExplosions.length > 0) {
console.log("DEBUG: Clearing " + this.cursedCrystalActiveExplosions.length + " active explosions.");
// Wizualizacje eksplozji są zarządzane przez tweeny i powinny same zniknąć,
// ale czyścimy tablicę logiczną.
}
this.cursedCrystalActiveExplosions = [];
// 4. Ściany Laserowe Minibossa CC
if (this.cursedCrystalActiveLaserWalls && this.cursedCrystalActiveLaserWalls.length > 0) {
console.log("DEBUG: Clearing " + this.cursedCrystalActiveLaserWalls.length + " laser walls.");
this.cursedCrystalActiveLaserWalls.forEach(function (wall) {
if (wall && wall.segments) {
wall.segments.forEach(function (seg) {
if (seg.visual && seg.visual.destroy && !seg.visual.destroyed) {
if (seg.visual.parent) {
seg.visual.parent.removeChild(seg.visual);
}
seg.visual.destroy();
}
});
}
if (wall && wall.warningVisuals && wall.warningVisuals.length > 0) {
wall.warningVisuals.forEach(function (warnVis) {
if (warnVis && warnVis.destroy && !warnVis.destroyed) {
if (warnVis.parent) {
warnVis.parent.removeChild(warnVis);
}
warnVis.destroy();
}
});
}
});
}
this.cursedCrystalActiveLaserWalls = [];
// 5. Sam Miniboss CC (jeśli gracz zginął, a Miniboss nie został pokonany)
// Metoda MinibossCC.die() czyści po sobie, gdy jest pokonany.
// Jeśli gracz umiera, obiekt Minibossa może nadal istnieć.
if (!isVictory && this.cursedCrystalMinibossObject && !this.cursedCrystalMinibossObject.isDead) {
console.log("DEBUG: Player died, clearing active MinibossCC.");
if (this.cursedCrystalMinibossObject.parent) {
this.cursedCrystalMinibossObject.parent.removeChild(this.cursedCrystalMinibossObject);
}
if (this.cursedCrystalMinibossObject.destroy) {
this.cursedCrystalMinibossObject.destroy();
}
}
this.cursedCrystalMinibossObject = null;
this.isMinibossActiveCC = false;
// 6. Obiekt Klejnotu (jeśli był i nie został zniszczony przy spawnie minibossa)
if (typeof crystalCoreObject !== 'undefined' && crystalCoreObject && crystalCoreObject.destroy && !crystalCoreObject.destroyed) {
if (crystalCoreObject.parent) {
crystalCoreObject.parent.removeChild(crystalCoreObject);
}
crystalCoreObject.destroy();
}
crystalCoreObject = null;
// 7. Zatrzymaj muzykę Cursed Crystal, jeśli jest
// if (this.currentCursedCrystalMusic && this.currentCursedCrystalMusic.stop) {
// this.currentCursedCrystalMusic.stop();
// this.currentCursedCrystalMusic = null;
// }
// Uruchom muzykę menu/grilla (jeśli jest taka potrzeba, np. showGrillScreen to zrobi)
// --- LOGIKA WYNIKU I REKORDU ---
var finalScore = this.cursedCrystalScore;
if (isVictory) {
var minutesSurvived = Math.floor(this.cursedCrystalTimeSurvived / (60 * 60)); // Zakładając 60 FPS
var bonusPoints = 1000 + minutesSurvived * 100;
finalScore += bonusPoints;
console.log("DEBUG: Victory! Base score: " + this.cursedCrystalScore + ", Time survived (frames): " + this.cursedCrystalTimeSurvived + " (" + minutesSurvived + " min), Bonus: " + bonusPoints + ", Final score: " + finalScore);
}
this.cursedCrystalScore = finalScore; // Zaktualizuj wynik w gameState
var highScore = parseInt(storage.cursedCrystalHighScore, 10) || 0;
var newRecord = false;
if (finalScore > highScore) {
storage.cursedCrystalHighScore = finalScore;
highScore = finalScore;
newRecord = true;
console.log("DEBUG: New High Score for Cursed Crystal: " + highScore);
}
if (ui) {
ui.titleText.setText(isVictory ? "VICTORY!" : "DEFEAT!"); // Przetłumaczone
ui.titleText.style = {
size: 120,
fill: isVictory ? 0x00FF00 : 0xFF0000,
align: 'center',
stroke: 0x000000,
strokeThickness: 6
};
var message = "Your Score: " + finalScore + "\n"; // Przetłumaczone
message += "Best Score: " + highScore; // Przetłumaczone
if (newRecord && isVictory) {
message += "\n(NEW HIGH SCORE!)"; // Przetłumaczone
} else if (newRecord && !isVictory) {
message += "\n(New High Score... Pepe sad)"; // Przetłumaczone (z lekką adaptacją)
}
ui.messageText.setText(message);
ui.messageText.style = {
size: 70,
fill: 0xFFFFFF,
align: 'center',
stroke: 0x000000,
strokeThickness: 4
};
// Usunięcie starych przycisków, jeśli istnieją (bez zmian)
if (this.activeButtons && this.activeButtons.length > 0) {
this.activeButtons.forEach(function (btn) {
if (btn && btn.destroy) {
btn.destroy();
}
});
}
this.activeButtons = [];
// Upewnij się, że teksty tytułu i wiadomości są od razu widoczne
ui.titleText.alpha = 1;
ui.messageText.alpha = 1;
// Ustaw pozycje dla titleText i messageText (przeniesione z ui.positionElements dla jasności)
// Zakładam, że te pozycje są poprawne dla ekranu końca gry
ui.titleText.x = 2048 / 2;
ui.titleText.y = 600;
ui.titleText.anchor.set(0.5);
ui.messageText.x = 2048 / 2;
ui.messageText.y = 800;
ui.messageText.anchor.set(0.5, 0.5);
// Opóźnienie tworzenia i wyświetlania przycisków
LK.setTimeout(function () {
// Sprawdź, czy nadal jesteśmy w tym samym stanie gry, aby uniknąć dodawania przycisków, jeśli stan się zmienił
if (gameState.currentState !== "cursedCrystalGameOver") {
return;
}
// Definiujemy oczekiwane wysokości na początku, aby były dostępne dla obu przycisków
var mainMenuButtonExpectedHeight = 100;
var restartButtonExpectedHeight = 100;
var spacingBetweenButtons = 60;
var menuButton = new Container();
menuButton.interactive = true;
menuButton.cursor = "pointer";
var menuButtonVisualHeight = mainMenuButtonExpectedHeight; // Użyjemy tej zmiennej
try {
var menuButtonAsset = LK.getAsset('mainmenu', {
anchorX: 0.5,
anchorY: 0.5
});
menuButton.addChild(menuButtonAsset);
if (menuButtonAsset && typeof menuButtonAsset.height === 'number') {
menuButtonVisualHeight = menuButtonAsset.height; // Użyj rzeczywistej wysokości assetu, jeśli dostępna
}
} catch (e) {
console.error("Błąd ładowania assetu przycisku 'mainmenu':", e);
var menuButtonBgFallback = new Shape({
width: 400,
height: mainMenuButtonExpectedHeight,
color: 0x555555,
shape: 'box'
});
menuButton.addChild(menuButtonBgFallback);
}
menuButton.x = 2048 / 2;
// Poprawiona linia dla menuButton.y:
menuButton.y = (ui.messageText.y || 800) + (ui.messageText.height || 70) / 2 + menuButtonVisualHeight / 2 + 80;
menuButton.down = function () {
if (gameState.currentState === "cursedCrystalGameOver") {
gameState.activeButtons.forEach(function (btn) {
if (btn && btn.destroy && !btn.destroyed) {
if (btn.parent) {
btn.parent.removeChild(btn);
}
btn.destroy();
}
});
gameState.activeButtons = [];
gameState.showGrillScreen();
}
};
currentSceneElements.addChild(menuButton);
gameState.activeButtons.push(menuButton);
// --- Przycisk "Restart Mode" ---
var restartButton = new Container();
restartButton.interactive = true;
restartButton.cursor = "pointer";
var restartButtonVisualHeight = restartButtonExpectedHeight; // Użyjemy tej zmiennej
try {
var restartButtonAsset = LK.getAsset('restartgame', {
anchorX: 0.5,
anchorY: 0.5
});
restartButton.addChild(restartButtonAsset);
if (restartButtonAsset && typeof restartButtonAsset.height === 'number') {
restartButtonVisualHeight = restartButtonAsset.height; // Użyj rzeczywistej wysokości assetu
}
} catch (e) {
console.error("Błąd ładowania assetu przycisku 'restartgame':", e);
var restartButtonBgFallback = new Shape({
width: 400,
height: restartButtonExpectedHeight,
color: 0x555555,
shape: 'box'
});
restartButton.addChild(restartButtonBgFallback);
}
restartButton.x = 2048 / 2;
// Poprawiona linia dla restartButton.y, z większym odstępem:
restartButton.y = menuButton.y + menuButtonVisualHeight / 2 + restartButtonVisualHeight / 2 + spacingBetweenButtons;
restartButton.down = function () {
if (gameState.currentState === "cursedCrystalGameOver") {
gameState.activeButtons.forEach(function (btn) {
if (btn && btn.destroy && !btn.destroyed) {
if (btn.parent) {
btn.parent.removeChild(btn);
}
btn.destroy();
}
});
gameState.activeButtons = [];
gameState.startCursedCrystalMode();
}
};
currentSceneElements.addChild(restartButton);
gameState.activeButtons.push(restartButton);
// ui.positionElements("cursedCrystalGameOver");
}, 3000); // Opóźnienie 3000 ms (3 sekundy)
}
}), "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" || this.currentState === "cursedCrystal") && 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", "cursedCrystal"];
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", "cursedCrystal"];
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", "cursedCrystal"];
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
});
}
function spawnCursedSoul() {
console.log("--- spawnCursedSoul (COMPACT GROUP) CALLED ---");
if (gameState.currentState !== "cursedCrystal" || typeof gameState.gameActive === 'boolean' && !gameState.gameActive) {
console.log("spawnCursedSoul (COMPACT GROUP): Exiting - wrong state or game not active. State:", gameState.currentState, "GameActive:", gameState.gameActive);
return;
}
if (typeof crystalCoreObject === 'undefined' || !crystalCoreObject || crystalCoreObject.destroyed) {
console.log("spawnCursedSoul (COMPACT GROUP): Exiting - crystalCoreObject missing or destroyed.");
return;
}
var minSouls = gameState.cursedCrystalSoulsPerGroupMin || 1; // Domyślnie 4, jeśli niezdefiniowane
var maxSouls = gameState.cursedCrystalSoulsPerGroupMax || 5; // Domyślnie 5
if (maxSouls < minSouls) {
maxSouls = minSouls;
}
var numSoulsInGroup = minSouls + Math.floor(Math.random() * (maxSouls - minSouls + 1));
console.log("numSoulsInGroup to spawn:", numSoulsInGroup);
if (numSoulsInGroup <= 0) {
console.warn("spawnCursedSoul (COMPACT GROUP): numSoulsInGroup is 0 or less.");
return;
}
var soulSpeed = gameState.cursedCrystalEnemyCurrentMaxSpeed;
console.log("Soul speed for group:", soulSpeed); // Powinno być niskie na początku
var soulHP = 1;
var soulAssetWidth = 30;
var soulAssetHeight = 30;
var gameWidth = 2048;
var gameHeight = 2732;
var edge = Math.floor(Math.random() * 4);
var groupBaseSpawnX, groupBaseSpawnY;
// Zmniejszamy rozrzut dla bardziej zwartej grupy
var groupSpawnTightnessFactor = 400; // Mniejsza wartość = ciaśniejsza grupa wzdłuż krawędzi
var depthSpreadFactor = gameState.cursedCrystalGroupSpawnSpread / 3 || 140; // Mniejszy rozrzut w głąb
switch (edge) {
case 0:
// Góra
groupBaseSpawnX = Math.random() * (gameWidth - 400) + 200; // Szerszy zakres, ale nadal unika rogów
groupBaseSpawnY = -soulAssetHeight;
break;
case 1:
// Prawo
groupBaseSpawnX = gameWidth + soulAssetWidth;
groupBaseSpawnY = Math.random() * (gameHeight - 400) + 200;
break;
case 2:
// Dół
groupBaseSpawnX = Math.random() * (gameWidth - 400) + 200;
groupBaseSpawnY = gameHeight + soulAssetHeight;
break;
default:
// Lewo (case 3)
groupBaseSpawnX = -soulAssetWidth;
groupBaseSpawnY = Math.random() * (gameHeight - 400) + 200;
break;
}
console.log("Spawning group from edge:", edge, "BaseX:", groupBaseSpawnX.toFixed(0), "BaseY:", groupBaseSpawnY.toFixed(0));
for (var i = 0; i < numSoulsInGroup; i++) {
var offsetX = 0;
var offsetY = 0;
// Ustawienie dusz blisko siebie, z lekkim losowym przesunięciem
if (edge === 0 || edge === 2) {
// Spawn z góry lub dołu - rozrzut poziomy
offsetX = (Math.random() - 0.5) * groupSpawnTightnessFactor;
offsetY = (Math.random() - 0.5) * depthSpreadFactor; // Mniejszy rozrzut w głąb
} else {
// Spawn z lewej lub prawej - rozrzut pionowy
offsetY = (Math.random() - 0.5) * groupSpawnTightnessFactor;
offsetX = (Math.random() - 0.5) * depthSpreadFactor; // Mniejszy rozrzut w głąb
}
var finalSpawnX = groupBaseSpawnX + offsetX;
var finalSpawnY = groupBaseSpawnY + offsetY;
// console.log("Creating soul " + (i + 1) + " at X:", finalSpawnX.toFixed(0), "Y:", finalSpawnY.toFixed(0));
var newSoul = new SoulEnemy({
x: finalSpawnX,
y: finalSpawnY,
hp: soulHP,
speed: soulSpeed,
// Wszystkie duszki w grupie mają tę samą (niską na początku) prędkość
targetX: crystalCoreObject.x,
targetY: crystalCoreObject.y
});
game.addChild(newSoul);
gameState.cursedCrystalEnemies.push(newSoul);
}
console.log("Finished spawning group of " + numSoulsInGroup + " souls.");
}
// --- Główna pętla aktualizacji gry ---
game.update = function () {
if (gameState.currentState === "cursedCrystal") {
// console.log("Game Update Tick - CURSED CRYSTAL");
}
if (ui) {
if (gameState.currentState !== "cursedCrystal" && gameState.currentState !== "cursedCrystalGameOver") {
ui.updateDeathsCounter();
}
if (boss && (gameState.currentState === "game" || gameState.currentState === "gameOver" && !gameOverReasonIsDeath && isNewBossPlusMode)) {
var maxHpBoss = boss.maxHealth > 0 ? boss.maxHealth : 1;
ui.updateBossHealth(boss.health, maxHpBoss);
} else if (gameState.currentState !== "cursedCrystal" && gameState.currentState !== "cursedCrystalGameOver") {
ui.updateBossHealth(0, 1);
}
if (player && gameState.currentState !== "cursedCrystal" && gameState.currentState !== "cursedCrystalGameOver") {
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" && gameState.currentState !== "rollMaster" && gameState.currentState !== "cursedCrystal" && gameState.currentState !== "cursedCrystalGameOver") {
ui.updateHearts(parseInt(storage.maxHearts, 10) || 5, parseInt(storage.maxHearts, 10) || 5);
}
}
if (coffinMemeImage && !coffinMemeImage.destroyed) {
if (gameState.currentState === "game" && player) {
if (!player.dead) {
var currentHp = player.health;
var maxHp = parseInt(storage.maxHearts, 10) || 5;
var visibilityFactor = 1 - Math.max(0, 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 > 0.1 ? visibilityFactor * 0.8 : 0;
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 {
coffinMemeImage.y = 2932;
coffinMemeImage.alpha = 1.0;
}
} else if (gameState.currentState !== "gameOver") {
if (coffinMemeImage.alpha > 0) {
coffinMemeImage.alpha = Math.max(0, coffinMemeImage.alpha - 0.05);
}
}
}
if (gameState.currentState === "game") {
if (player) {
player.update();
}
if (boss) {
boss.update();
}
} else if (gameState.currentState === "rollMaster") {
if (player) {
player.update();
}
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;
}
if (gameState.unlockedAttacks.includes('rmattack2') && gameState.explosionSpawnInterval > 0) {
gameState.explosionSpawnTimer++;
if (gameState.explosionSpawnTimer >= gameState.explosionSpawnInterval) {
gameState.explosionSpawnTimer = 0;
launchRmattack2();
}
}
if (gameState.unlockedAttacks.includes('rmattack3') && gameState.laserSpawnInterval > 0) {
gameState.laserSpawnTimer++;
if (gameState.laserSpawnTimer >= gameState.laserSpawnInterval) {
gameState.laserSpawnTimer = 0;
launchRmattack3();
}
}
if (gameState.unlockedAttacks.includes('rmattack4') && gameState.spreaderSpawnInterval > 0) {
gameState.spreaderSpawnTimer++;
if (gameState.spreaderSpawnTimer >= gameState.spreaderSpawnInterval) {
gameState.spreaderSpawnTimer = 0;
launchRmattack4();
}
}
for (var i = gameState.rollMasterAttacks.length - 1; i >= 0; i--) {
var atk = gameState.rollMasterAttacks[i];
var shouldRemove = false;
if (!atk || !atk.visual || atk.visual.destroyed) {
gameState.rollMasterAttacks.splice(i, 1);
continue;
}
if (atk.visual.update && typeof atk.visual.update === 'function') {
atk.visual.update();
}
switch (atk.type) {
case 'projectile':
atk.visual.x += atk.vx;
atk.visual.y += atk.vy;
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_p = (player.width || 150) / 2 * 0.8;
var projectileRadius = atk.radius || 45;
if (dist_p < playerRadius_p + projectileRadius) {
player.takeDamage(1);
shouldRemove = true;
}
}
var screenMargin = 200;
if (atk.visual.x < -screenMargin || atk.visual.x > 2048 + screenMargin || atk.visual.y < -screenMargin || atk.visual.y > 2732 + screenMargin) {
shouldRemove = true;
}
break;
case '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 || 150) / 2 * 0.8;
if (dist_e < playerRadius_e + atk.radius) {
player.takeDamage(1);
atk.damageDealt = true;
}
}
break;
case 'laser':
if (player && !player.dead && !player.rolling && !player.invulnerable) {
var laserHalfWidth = (atk.width || 100) / 2;
var laserHalfHeight = (atk.height || 100) / 2;
var playerHalfWidth_l = (player.width || 150) / 2 * 0.8;
var playerHalfHeight_l = (player.height || 150) / 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_l;
var playerRight = player.x + playerHalfWidth_l;
var playerTop = player.y - playerHalfHeight_l;
var playerBottom = player.y + playerHalfHeight_l;
if (laserLeft < playerRight && laserRight > playerLeft && laserTop < playerBottom && laserBottom > playerTop) {
player.takeDamage(1);
}
}
if (atk.lifeTime && atk.lifeTime > 0) {
atk.lifeTime--;
}
break;
case 'spreader_parent':
atk.timer--;
if (atk.timer <= 0) {
var originX = atk.visual.x;
var originY = atk.visual.y;
var projectileSpeed_s = 5 + gameState.rollMasterDifficulty * 0.5;
var projectileRadius_s = 45;
var numProjectiles_s = 12;
for (var j = 0; j < numProjectiles_s; j++) {
var angle_s = Math.PI * 2 / numProjectiles_s * j;
var vx_s = Math.cos(angle_s) * projectileSpeed_s;
var vy_s = Math.sin(angle_s) * projectileSpeed_s;
var projectileFrames_s = [];
for (var k_s = 0; k_s <= 6; k_s++) {
projectileFrames_s.push(LK.getAsset('rmattack1_' + k_s, {
anchorX: 0.5,
anchorY: 0.5
}));
}
var projectileVisual_s = new SpriteAnimation({
frames: projectileFrames_s,
frameDuration: 100,
loop: true,
x: originX,
y: originY,
anchorX: 0.5,
anchorY: 0.5,
scaleX: 1.2,
scaleY: 1.2
});
projectileVisual_s.rotation = Math.atan2(vy_s, vx_s);
var projectileObject_s = game.addChild(projectileVisual_s);
gameState.rollMasterAttacks.push({
type: 'projectile',
visual: projectileObject_s,
vx: vx_s,
vy: vy_s,
radius: projectileRadius_s
});
}
shouldRemove = true;
}
break;
default:
console.warn("Nieznany typ ataku w RollMaster: ", atk.type, atk);
break;
}
if (shouldRemove) {
if (atk.visual && atk.visual.destroy && !atk.visual.destroyed) {
if (atk.visual.parent) {
atk.visual.parent.removeChild(atk.visual);
}
atk.visual.destroy();
}
gameState.rollMasterAttacks.splice(i, 1);
}
}
if (player && player.dead && gameState.currentState === "rollMaster") {
if (typeof gameState.endRollMasterMode === 'function') {
gameState.endRollMasterMode(gameState.rollMasterTime);
} else {
console.error("Cannot call endRollMasterMode, function undefined");
}
}
} else if (gameState.currentState === "cursedCrystal") {
if (player) {
player.update();
}
if (!gameState.isMinibossActiveCC) {
gameState.cursedCrystalTimeSurvived++;
gameState.cursedCrystalEnemySpawnTimer++;
if (gameState.cursedCrystalEnemySpawnTimer >= gameState.cursedCrystalCurrentSpawnInterval) {
gameState.cursedCrystalEnemySpawnTimer = 0;
spawnCursedSoul();
}
gameState.cursedCrystalDifficultyTimer++;
if (gameState.cursedCrystalDifficultyTimer >= gameState.cursedCrystalDifficultyInterval) {
gameState.cursedCrystalDifficultyTimer = 0;
var spawnIntervalDecrease = 5;
gameState.cursedCrystalCurrentSpawnInterval = Math.max(gameState.cursedCrystalMinSpawnInterval, gameState.cursedCrystalCurrentSpawnInterval - spawnIntervalDecrease);
var speedIncrease = 0.25;
gameState.cursedCrystalEnemyCurrentMaxSpeed += speedIncrease;
}
}
for (var i = gameState.cursedCrystalEnemies.length - 1; i >= 0; i--) {
var enemy = gameState.cursedCrystalEnemies[i];
if (!enemy) {
gameState.cursedCrystalEnemies.splice(i, 1);
continue;
}
if (enemy.isDead) {
gameState.cursedCrystalEnemies.splice(i, 1);
continue;
}
enemy.update();
if (enemy.isDead) {
gameState.cursedCrystalEnemies.splice(i, 1);
continue;
}
if (player && player.rolling) {
var dx_soul = player.x - enemy.x;
var dy_soul = player.y - enemy.y;
var distance_soul = Math.sqrt(dx_soul * dx_soul + dy_soul * dy_soul);
var playerRollRadius_soul = (player.width || 150) / 2 * 0.7;
var soulRadius_coll = (enemy.width || 30) / 2 * 0.8;
if (distance_soul < playerRollRadius_soul + soulRadius_coll) {
enemy.takeDamage(1);
}
}
}
if (gameState.currentState === "cursedCrystal" && !gameState.isMinibossActiveCC && gameState.cursedCrystalChargeLevel >= gameState.cursedCrystalTargetCharge && !gameState.crystalExploding) {
gameState.crystalExploding = true; // Ustaw flagę, że proces eksplozji/spawnu się rozpoczął
// Zdefiniuj współrzędne spawnu minibossa na podstawie crystalCoreObject lub domyślnych
var tempCrystalSpawnX, tempCrystalSpawnY;
if (typeof crystalCoreObject !== 'undefined' && crystalCoreObject && !crystalCoreObject.destroyed) {
tempCrystalSpawnX = crystalCoreObject.x;
tempCrystalSpawnY = crystalCoreObject.y;
} else {
tempCrystalSpawnX = 2048 / 2; // Domyślna pozycja X na środek ekranu
tempCrystalSpawnY = 2732 / 2; // Domyślna pozycja Y na środek ekranu
console.warn("crystalCoreObject nie istnieje podczas próby spawnu minibossa, używam domyślnych współrzędnych.");
}
if (typeof gameState.playCrystalExplosionAnimation === 'function') {
// Ta funkcja powinna teraz poprawnie używać wewnętrznych crystalOriginalX/Y
// i przekazywać je do spawnCursedCrystalMiniboss
gameState.playCrystalExplosionAnimation();
} else {
// Awaryjne spawnowanie, jeśli playCrystalExplosionAnimation nie istnieje
console.error("gameState.playCrystalExplosionAnimation is not defined! Spawning miniboss directly.");
// Przekaż zdefiniowane wyżej współrzędne
gameState.spawnCursedCrystalMiniboss(tempCrystalSpawnX, tempCrystalSpawnY);
gameState.crystalExploding = false; // Resetuj flagę, jeśli spawn był bezpośredni i synchroniczny
}
// UWAGA: Poniższy blok kodu, który oryginalnie tworzył minibossOptions i spawnował minibossa
// w tym miejscu, został usunięty. Logika ta jest teraz w całości obsługiwana przez
// gameState.playCrystalExplosionAnimation() -> gameState.spawnCursedCrystalMiniboss()
// lub przez awaryjne, bezpośrednie wywołanie gameState.spawnCursedCrystalMiniboss(tempCrystalSpawnX, tempCrystalSpawnY) powyżej.
// To eliminuje redundancję i błąd ReferenceError.
} // Koniec if'a spawującego Minibossa
// Aktualizacja Minibossa CC, jeśli jest aktywny
if (gameState.isMinibossActiveCC && gameState.cursedCrystalMinibossObject && !gameState.cursedCrystalMinibossObject.isDead) {
gameState.cursedCrystalMinibossObject.update();
}
if (gameState.cursedCrystalMinibossObject && gameState.cursedCrystalMinibossObject.activeClones) {
for (var cIdx = gameState.cursedCrystalMinibossObject.activeClones.length - 1; cIdx >= 0; cIdx--) {
var cloneInstance = gameState.cursedCrystalMinibossObject.activeClones[cIdx];
if (cloneInstance.isDead) {
// Już obsłużone w cloneInstance.die(), ale dla pewności
gameState.cursedCrystalMinibossObject.activeClones.splice(cIdx, 1);
continue;
}
cloneInstance.update();
// Kolizja gracza (roll) z klonem
if (player && player.rolling && !cloneInstance.isDead) {
var dx_clone_roll = player.x - cloneInstance.x;
var dy_clone_roll = player.y - cloneInstance.y;
var distance_clone_roll = Math.sqrt(dx_clone_roll * dx_clone_roll + dy_clone_roll * dy_clone_roll);
var playerRollRadius_clone = (player.width || 150) / 2 * 0.7;
var cloneRadius_coll = (cloneInstance.width || 120) / 2 * 0.9;
if (distance_clone_roll < playerRollRadius_clone + cloneRadius_coll) {
cloneInstance.takeDamage(1); // Klon ginie po jednym trafieniu rollem
}
}
}
}
// --- Aktualizacja Pocisków Minibossa ---
if (typeof gameState.cursedCrystalActiveProjectiles === 'undefined' || !gameState.cursedCrystalActiveProjectiles) {
gameState.cursedCrystalActiveProjectiles = [];
}
for (var k = gameState.cursedCrystalActiveProjectiles.length - 1; k >= 0; k--) {
var projectile = gameState.cursedCrystalActiveProjectiles[k];
if (!projectile || projectile.isDead) {
if (projectile && projectile.graphics && projectile.graphics.parent) {
projectile.graphics.parent.removeChild(projectile.graphics);
if (projectile.graphics.destroy) {
projectile.graphics.destroy();
}
}
gameState.cursedCrystalActiveProjectiles.splice(k, 1);
continue;
}
projectile.update();
if (projectile.isDead) {
if (projectile.graphics && projectile.graphics.parent) {
projectile.graphics.parent.removeChild(projectile.graphics);
if (projectile.graphics.destroy) {
projectile.graphics.destroy();
}
}
gameState.cursedCrystalActiveProjectiles.splice(k, 1);
}
}
// --- Sprawdzanie Obrażeń od Eksplozji Minibossa ---
if (typeof gameState.cursedCrystalActiveExplosions === 'undefined' || !gameState.cursedCrystalActiveExplosions) {
gameState.cursedCrystalActiveExplosions = [];
}
for (var expIdx = gameState.cursedCrystalActiveExplosions.length - 1; expIdx >= 0; expIdx--) {
var explosion = gameState.cursedCrystalActiveExplosions[expIdx];
if (!explosion) {
gameState.cursedCrystalActiveExplosions.splice(expIdx, 1);
continue;
}
explosion.durationTimer--;
if (player && !player.dead && !player.invulnerable && !explosion.hitPlayerThisFrame) {
var distToExplosion = Math.sqrt(Math.pow(player.x - explosion.x, 2) + Math.pow(player.y - explosion.y, 2));
if (distToExplosion < (player.width || 150) / 2 * 0.7 + explosion.radius) {
player.takeDamage(explosion.damage);
explosion.hitPlayerThisFrame = true;
}
}
if (explosion.durationTimer <= 0) {
gameState.cursedCrystalActiveExplosions.splice(expIdx, 1);
}
}
// --- LOGIKA DLA AKTYWNYCH ŚCIAN LASEROWYCH MINIBOSSA ---
if (typeof gameState.cursedCrystalActiveLaserWalls === 'undefined' || !gameState.cursedCrystalActiveLaserWalls) {
gameState.cursedCrystalActiveLaserWalls = [];
}
if (gameState.cursedCrystalActiveLaserWalls && gameState.cursedCrystalActiveLaserWalls.length > 0) {
for (var wallIdx = gameState.cursedCrystalActiveLaserWalls.length - 1; wallIdx >= 0; wallIdx--) {
var wall = gameState.cursedCrystalActiveLaserWalls[wallIdx];
if (!wall || wall.isDead) {
if (wall && wall.segments) {
wall.segments.forEach(function (seg) {
if (seg.isPlaceholder) {
return;
}
if (seg.visual && seg.visual.parent) {
seg.visual.parent.removeChild(seg.visual);
if (seg.visual.destroy && !seg.visual.destroyed) {
seg.visual.destroy();
}
}
});
}
gameState.cursedCrystalActiveLaserWalls.splice(wallIdx, 1);
continue;
}
if (gameState.cursedCrystalMinibossObject && !gameState.cursedCrystalMinibossObject.isDead) {
wall.pivotX = gameState.cursedCrystalMinibossObject.x;
wall.pivotY = gameState.cursedCrystalMinibossObject.y;
}
if (wall.warningTimer > 0) {
wall.warningTimer -= 1000 / 60;
wall.segments.forEach(function (segment) {
if (segment.isPlaceholder) {
return;
}
var initialAngleForStaticDisplay = 0;
var rotatedOffsetX = segment.initialOffsetX * Math.cos(initialAngleForStaticDisplay) - segment.initialOffsetY * Math.sin(initialAngleForStaticDisplay);
var rotatedOffsetY = segment.initialOffsetX * Math.sin(initialAngleForStaticDisplay) + segment.initialOffsetY * Math.cos(initialAngleForStaticDisplay);
segment.currentX = wall.pivotX + rotatedOffsetX;
segment.currentY = wall.pivotY + rotatedOffsetY;
if (segment.visual && !segment.visual.destroyed) {
segment.visual.x = segment.currentX;
segment.visual.y = segment.currentY;
segment.visual.rotation = initialAngleForStaticDisplay;
}
});
} else {
if (wall.activeTimer > 0) {
wall.activeTimer -= 1000 / 60;
wall.currentAngle += wall.rotationSpeed;
wall.segments.forEach(function (segment) {
if (segment.isPlaceholder) {
return;
}
var rotatedOffsetX = segment.initialOffsetX * Math.cos(wall.currentAngle) - segment.initialOffsetY * Math.sin(wall.currentAngle);
var rotatedOffsetY = segment.initialOffsetX * Math.sin(wall.currentAngle) + segment.initialOffsetY * Math.cos(wall.currentAngle);
segment.currentX = wall.pivotX + rotatedOffsetX;
segment.currentY = wall.pivotY + rotatedOffsetY;
if (segment.visual && !segment.visual.destroyed) {
segment.visual.x = segment.currentX;
segment.visual.y = segment.currentY;
var baseRotation = wall.currentAngle;
if (segment.animationPhase === 'action_once' || segment.animationPhase === 'looping_cut') {
if (segment.currentIndividualSpinAngle === undefined) {
segment.currentIndividualSpinAngle = 0;
}
var RAPID_SPIN_SPEED = 0.4;
segment.currentIndividualSpinAngle += RAPID_SPIN_SPEED;
segment.visual.rotation = baseRotation + segment.currentIndividualSpinAngle;
} else {
segment.visual.rotation = baseRotation;
}
if (player && !player.dead && !player.invulnerable) {
var playerHalfWidth_lw = (player.width || 150) / 2 * 0.7;
var playerHalfHeight_lw = (player.height || 150) / 2 * 0.7;
var segmentHalfWidth_lw = (segment.width || 120) / 2;
var segmentHalfHeight_lw = (segment.height || 120) / 2;
if (Math.abs(player.x - segment.currentX) < playerHalfWidth_lw + segmentHalfWidth_lw && Math.abs(player.y - segment.currentY) < playerHalfHeight_lw + segmentHalfHeight_lw) {
player.takeDamage(1);
}
}
}
});
if (wall.activeTimer <= 0) {
wall.isDead = true;
}
} else {
wall.isDead = true;
}
}
}
}
if (player && player.dead) {/* Obsługiwane przez player.die() */}
}
};
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!");
}