Code edit (1 edits merged)
Please save this source code
Code edit (1 edits merged)
Please save this source code
Code edit (1 edits merged)
Please save this source code
Code edit (1 edits merged)
Please save this source code
Code edit (1 edits merged)
Please save this source code
Code edit (1 edits merged)
Please save this source code
Code edit (1 edits merged)
Please save this source code
Code edit (1 edits merged)
Please save this source code
Code edit (7 edits merged)
Please save this source code
User prompt
Please fix the bug: 'Cannot set properties of undefined (setting 'update')' in or related to this line: 'game.game.update = function () {' Line Number: 4529
Code edit (1 edits merged)
Please save this source code
Code edit (1 edits merged)
Please save this source code
Code edit (1 edits merged)
Please save this source code
Code edit (1 edits merged)
Please save this source code
Code edit (1 edits merged)
Please save this source code
Code edit (1 edits merged)
Please save this source code
Code edit (14 edits merged)
Please save this source code
User prompt
Please fix the bug: 'LLK is not defined' in or related to this line: 'var currentSceneElements = new Container();' Line Number: 1904
User prompt
Please fix the bug: 'LLK is not defined' in or related to this line: 'LLK.init.image('fireball0', {' Line Number: 1904
Code edit (1 edits merged)
Please save this source code
Code edit (1 edits merged)
Please save this source code
Code edit (1 edits merged)
Please save this source code
Code edit (1 edits merged)
Please save this source code
Code edit (21 edits merged)
Please save this source code
User prompt
add new assets linearattack1 to linearattack5
/****
* 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 (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);
}
if (typeof gameState !== 'undefined' && gameState.currentState !== "game" && gameState.currentState !== "rollMaster" && gameState.currentState !== "cursedCrystal") {
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();
}
};
// 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;
});
// -------- POCZĄTEK ZAKTUALIZOWANEJ KLASY MinibossCC --------
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 || 1.5;
self.isDead = false;
self.attackPattern = ['explosiveProjectile', 'laserWall'];
self.currentAttackIndex = 0;
self.attackCooldowns = {
'explosiveProjectile': 8 * 60,
'laserWall': 5 * 60
};
self.attackCooldown = 120;
self.currentAttackName = '';
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;
}
self.x = options.x || 2048 / 2;
self.y = options.y || 300;
self.update = function () {
if (self.isDead || gameState.currentState !== "cursedCrystal") {
return;
}
if (self.attackCooldown > 0) {
self.attackCooldown--;
} else if (player && !player.dead) {
self.performAttack();
self.attackCooldown = self.attackCooldowns[self.currentAttackName] || 180;
}
};
self.performAttack = function () {
if (!player || player.dead) {
return;
}
self.currentAttackName = self.attackPattern[self.currentAttackIndex];
console.log("MinibossCC performs attack: " + self.currentAttackName);
if (self.currentAttackName === 'explosiveProjectile') {
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);
} else if (self.currentAttackName === 'laserWall') {
var numLaserSegments = 5;
var laserSegmentWidth = 30;
var laserSegmentHeight = 120;
var spacingBetweenSegments = 10;
var totalWallEffectiveHeight = numLaserSegments * laserSegmentHeight + (numLaserSegments - 1) * spacingBetweenSegments;
var wallPivotX = self.x; // Punkt obrotu ściany
var wallPivotY = self.y; // Punkt obrotu ściany
var warningDuration = 1500;
var activeDuration = 2000; // 2 sekundy obracania się
// Pełen obrót (2*PI radianów) w ciągu activeDuration (np. 120 klatek)
// rotationSpeed = (2 * Math.PI) / (activeDuration / (1000/60)); // Radianów na klatkę
var rotationSpeed = 2 * Math.PI / 120; // ~3 stopnie na klatkę dla pełnego obrotu w 2s @ 60fps
var laserWallInstance = {
pivotX: wallPivotX,
pivotY: wallPivotY,
segments: [],
currentAngle: 0,
// Początkowy kąt
rotationSpeed: rotationSpeed,
activeTimer: activeDuration,
// w ms
isDead: false,
warningVisuals: [] // Do przechowania wizualizacji ostrzeżeń
};
// Faza Ostrzeżenia
var initialSegmentOffsetY = -(totalWallEffectiveHeight / 2) + laserSegmentHeight / 2;
for (var i = 0; i < numLaserSegments; i++) {
var segmentOffsetY = initialSegmentOffsetY + i * (laserSegmentHeight + spacingBetweenSegments);
// Pozycja ostrzeżenia jest na razie statyczna względem Minibossa
var warningPosX = wallPivotX; // Ostrzeżenie w linii X Minibossa
var warningPosY = wallPivotY + segmentOffsetY; // Rozłożone pionowo
var warningFrames = [];
for (var wf = 0; wf < 5; wf++) {
try {
warningFrames.push(LK.getAsset('rmattack3_warning_' + wf, {
anchorX: 0.5,
anchorY: 0.5
}));
} catch (e) {
warningFrames.push(new Shape({
width: laserSegmentWidth,
height: laserSegmentHeight,
color: 0xFF0000,
shape: 'box'
}));
}
}
var warningAnim = new SpriteAnimation({
frames: warningFrames,
frameDuration: 100,
loop: true,
x: warningPosX,
y: warningPosY,
anchorX: 0.5,
anchorY: 0.5,
alpha: 0.7
});
game.addChild(warningAnim);
laserWallInstance.warningVisuals.push(warningAnim);
warningAnim.play();
// Zapisz offsetY dla późniejszego obliczenia pozycji obracanych segmentów
// offsetX jest 0, bo na razie ściana jest idealnie pionowa i wycentrowana na X bossa
laserWallInstance.segments.push({
initialOffsetX: 0,
// Względem pivotX
initialOffsetY: segmentOffsetY,
// Względem pivotY
width: laserSegmentWidth,
height: laserSegmentHeight,
visual: null,
// Grafika lasera zostanie stworzona później
currentX: warningPosX,
// Na razie to samo co warning
currentY: warningPosY // Na razie to samo co warning
});
}
LK.setTimeout(function () {
// Po zakończeniu ostrzeżenia
// Usuń ostrzeżenia
laserWallInstance.warningVisuals.forEach(function (warnVis) {
if (warnVis && warnVis.parent) {
warnVis.parent.removeChild(warnVis);
warnVis.destroy();
}
});
laserWallInstance.warningVisuals = [];
// Stwórz aktywne segmenty laserowe
laserWallInstance.segments.forEach(function (segment) {
var laserStrikeFrames = [];
var useStrikeAnimation = false;
try {
for (var sf = 0; sf < 5; sf++) {
laserStrikeFrames.push(LK.getAsset('rmattack3_strike_' + sf, {
anchorX: 0.5,
anchorY: 0.5,
width: segment.width,
height: segment.height
}));
}
useStrikeAnimation = true;
} catch (e) {/* Użyj Shape */}
if (useStrikeAnimation) {
segment.visual = new SpriteAnimation({
frames: laserStrikeFrames,
frameDuration: 40,
loop: true,
x: segment.currentX,
y: segment.currentY,
anchorX: 0.5,
anchorY: 0.5
});
segment.visual.play();
} else {
segment.visual = new Shape({
width: segment.width,
height: segment.height,
color: 0xFF0000,
shape: 'box',
x: segment.currentX,
y: segment.currentY,
anchorX: 0.5,
anchorY: 0.5 // Shape też może mieć anchor, jeśli jest zdefiniowany w Twojej klasie Shape
});
}
game.addChild(segment.visual);
});
if (gameState.cursedCrystalActiveLaserWalls === undefined) {
gameState.cursedCrystalActiveLaserWalls = [];
}
gameState.cursedCrystalActiveLaserWalls.push(laserWallInstance);
}, warningDuration);
}
self.currentAttackIndex = (self.currentAttackIndex + 1) % self.attackPattern.length;
};
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;
}
self.isDead = true;
console.log("MinibossCC has been defeated!");
if (gameState.cursedCrystalActiveProjectiles) {
gameState.cursedCrystalActiveProjectiles.forEach(function (proj) {
if (proj.graphics && proj.graphics.parent) {
proj.graphics.parent.removeChild(proj.graphics);
}
if (proj.destroy) {
proj.destroy();
}
proj.isDead = true;
});
gameState.cursedCrystalActiveProjectiles = [];
}
if (gameState.cursedCrystalActiveExplosions) {
gameState.cursedCrystalActiveExplosions = [];
}
if (gameState.cursedCrystalActiveLaserWalls) {
// Czyszczenie ścian laserów przy śmierci bossa
gameState.cursedCrystalActiveLaserWalls.forEach(function (wall) {
wall.segments.forEach(function (seg) {
if (seg.visual && seg.visual.parent) {
seg.visual.parent.removeChild(seg.visual);
}
if (seg.visual && seg.visual.destroy) {
seg.visual.destroy();
}
});
wall.isDead = true;
});
gameState.cursedCrystalActiveLaserWalls = [];
}
if (typeof gameState.endCursedCrystalMode === 'function') {
gameState.endCursedCrystalMode(true);
}
if (self.parent) {
self.parent.removeChild(self);
}
self.destroy();
gameState.cursedCrystalMinibossObject = null;
gameState.isMinibossActiveCC = false;
};
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 || 7; // Prędkość pocisku (w pikselach na klatkę)
self.target = options.target; // Referencja do obiektu gracza
self.lifeTimer = 120; // 2 sekundy przy 60fps
self.isDead = false; // Czy pocisk powinien zostać usunięty z listy aktywnych
self.hasExploded = false; // Czy pocisk już eksplodował
self.explosionRadius = options.explosionRadius || 100; // Promień eksplozji (dla średnicy 200x200)
// Możemy to zwiększyć do ~500 dla 1/6 mapy
self.explosionDamage = 1; // Obrażenia eksplozji
// Grafika pocisku (placeholder - dostosujesz asset)
try {
self.graphics = self.attachAsset('placeholder_projectile_asset', {
// Użyj własnego assetu
anchorX: 0.5,
anchorY: 0.5,
width: 40,
// Jak wspomniałeś
height: 40
});
} catch (e) {
console.warn("Nie udało się załadować 'placeholder_projectile_asset' dla MinibossExplosiveProjectile, używam Shape.", e);
self.graphics = new Shape({
width: 40,
height: 40,
color: 0xFF4500,
// Pomarańczowo-czerwony
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) {
// Jeśli cel zniknął, a pocisk nie wybuchł, oznacz do usunięcia
self.isDead = true;
}
return;
}
self.lifeTimer--;
// Logika naprowadzania
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;
}
// Kolizja z graczem
var playerRadius = (self.target.width || 150) / 2 * 0.7; // Użyj promienia gracza
var projectileRadius = self.width / 2;
if (distanceToTarget < playerRadius + projectileRadius) {
// Nie zadajemy obrażeń bezpośrednio od pocisku, tylko od eksplozji
self.explode();
return; // Zatrzymaj dalsze przetwarzanie tego pocisku
}
// Sprawdzenie timera życia
if (self.lifeTimer <= 0) {
self.explode();
return;
}
// (Opcjonalnie) Sprawdzenie granic ekranu, jeśli chcesz, by wybuchał na krawędziach
// if (self.x < 0 || self.x > 2048 || self.y < 0 || self.y > 2732) {
// self.explode();
// return;
// }
};
self.explode = function () {
if (self.hasExploded) {
return;
}
self.hasExploded = true;
self.isDead = true; // Oznacz do usunięcia z listy aktywnych pocisków
if (self.graphics && self.graphics.parent) {
self.graphics.parent.removeChild(self.graphics);
self.graphics.destroy();
}
// Stwórz efekt wizualny eksplozji (placeholder)
var explosionVisual = new Shape({
// Użyjesz tu własnej animacji/assetu
width: self.explosionRadius * 2,
// Średnica
height: self.explosionRadius * 2,
color: 0xFF8C00,
// Ciemnopomarańczowy
shape: 'ellipse'
});
explosionVisual.x = self.x;
explosionVisual.y = self.y;
explosionVisual.alpha = 0.7;
game.addChild(explosionVisual); // Dodaj do głównej sceny
tween(explosionVisual, {
scaleX: 1.2,
scaleY: 1.2,
alpha: 0
}, {
duration: 500,
// Czas trwania wizualizacji eksplozji
easing: tween.easeOutQuad,
onFinish: function onFinish() {
if (explosionVisual.parent) {
explosionVisual.parent.removeChild(explosionVisual);
}
explosionVisual.destroy();
}
});
// Dodaj obiekt logiczny eksplozji do śledzenia obrażeń
if (gameState && gameState.cursedCrystalActiveExplosions) {
gameState.cursedCrystalActiveExplosions.push({
x: self.x,
y: self.y,
radius: self.explosionRadius,
damage: self.explosionDamage,
durationTimer: 30,
// Klatki, przez które eksplozja zadaje obrażenia (np. 0.5s)
hitPlayerThisFrame: false // Flaga, aby zadać obrażenia tylko raz
});
}
// console.log("Pocisk eksplodował w: X=" + self.x.toFixed(0) + ", Y=" + self.y.toFixed(0));
};
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: 300,
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) {
if (!self.invulnerable && !self.dead) {
var previousHealth = self.health;
// Logika specyficzna dla trybu Cursed Crystal
if (gameState.currentState === "cursedCrystal") {
gameState.cursedCrystalPlayerHealth -= amount;
gameState.cursedCrystalPlayerHealth = Math.max(0, gameState.cursedCrystalPlayerHealth); // Zapobiegaj ujemnemu zdrowiu
self.health = gameState.cursedCrystalPlayerHealth; // Synchronizuj self.health z aktualnym stanem gry
console.log("Cursed Crystal: Player took " + amount + " damage. New Health (gameState & self.health): " + self.health);
if (ui && ui.updateHearts) {
// Aktualizuj UI serc od razu
ui.updateHearts(gameState.cursedCrystalPlayerHealth, gameState.cursedCrystalPlayerMaxHealth);
}
} else {
// Logika dla innych trybów (np. "game", "rollMaster")
self.health -= amount;
self.health = Math.max(0, self.health); // Zapobiegaj ujemnemu zdrowiu
console.log(gameState.currentState + ": Player took " + amount + " damage. New Health (self.health): " + self.health);
// W innych trybach UI serc jest aktualizowane w game.update
}
if (self.health < previousHealth) {
// Tylko jeśli faktycznie stracił HP
LK.effects.flashObject(self, 0xFF0000, 200);
}
if (self.health <= 0) {
self.die(); // self.die() obsłuży specyfikę trybu
return;
}
self.invulnerable = true;
if (self.postHitInvulnerabilityTimer) {
LK.clearTimeout(self.postHitInvulnerabilityTimer);
self.postHitInvulnerabilityTimer = null;
}
self.postHitInvulnerabilityTimer = LK.setTimeout(function () {
if (!self || self.destroyed) {
return;
}
self.invulnerable = false;
var currentVisualSprite = null;
if (self.rolling && self.children.length > 0 && self.children[0] !== self.idleAnimationSprite) {
// Sprite rolla
} else if (self.idleAnimationSprite && self.idleAnimationSprite.visible && self.idleAnimationSprite.children.length > 0) {
currentVisualSprite = self.idleAnimationSprite.children[0];
}
if (currentVisualSprite && currentVisualSprite.alpha !== 1) {
currentVisualSprite.alpha = 1;
}
self.postHitInvulnerabilityTimer = null;
}, 2000);
}
};
// --- Ś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);
}
// Logika specyficzna dla trybu Cursed Crystal
if (gameState.currentState === "cursedCrystal") {
console.log("Player died in Cursed Crystal mode. Calling gameState.endCursedCrystalMode(false).");
// Animacja śmierci może być tutaj, a potem wywołanie endCursedCrystalMode w onFinish
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 (self && self.destroy && !self.destroyed) {
self.destroy();
player = null;
}
if (typeof gameState.endCursedCrystalMode === 'function') {
gameState.endCursedCrystalMode(false); // Przekaż 'false' dla porażki
} else {
console.error("gameState.endCursedCrystalMode is not defined!");
gameState.showGrillScreen(); // Awaryjny powrót
}
}
});
return; // Zakończ tutaj dla Cursed Crystal
}
// Logika dla pozostałych trybów ("game", "rollMaster")
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);
// Animacja śmierci dla innych trybów
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. State:", gameState.currentState);
if (self && self.destroy && !self.destroyed) {
self.destroy();
player = null;
}
if (gameState.currentState === "game") {
// Tylko dla standardowego trybu gry z bossem
console.log("Player.die (game mode) onFinish - Calling gameState.gameOver(true).");
if (typeof boss !== 'undefined' && boss && typeof boss.clearAllAttacks === 'function') {
boss.clearAllAttacks("Player.die -> gameOver");
}
if (typeof gameState.gameOver === 'function') {
gameOverReasonIsDeath = true; // Ustaw globalną flagę
gameState.gameOver(true);
} else {
console.error("gameState.gameOver() is not defined for 'game' mode death!");
gameState.showGrillScreen(); // Awaryjny powrót
}
} else if (gameState.currentState === "rollMaster") {
// W Roll Master śmierć jest obsługiwana przez game.update, które wywołuje endRollMasterMode
console.log("Player.die (RollMaster) onFinish. Transition handled by endRollMasterMode (called from game.update).");
} else {
console.warn("Player.die onFinish in unhandled state: " + gameState.currentState + ". Defaulting to Grill Screen.");
gameState.showGrillScreen();
}
}
});
}; // Koniec self.die
// --- Aktualizacja (Update) ---
self.update = function () {
// Log tylko dla Cursed Crystal dla celów debugowania spowolnienia/cooldownu
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();
// Usuń ewentualne resztki animacji rolla
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) {
// Zakładamy, że sprite rolla nie jest 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;
}
self.invulnerable = true;
} else {
if (currentVisualSprite) {
currentVisualSprite.alpha = 1;
}
self.invulnerable = false;
}
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 * (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; // Lewa krawędź ekranu
var maxXBoundary = 2048 - halfWidth; // Prawa krawędź ekranu
var minYBoundary = halfHeight; // Górna krawędź ekranu
var maxYBoundary = 2732 - halfHeight; // Dolna krawędź ekranu
// Podstawowe granice ekranu
nextX = Math.max(minXBoundary, Math.min(nextX, maxXBoundary));
nextY = Math.max(minYBoundary, Math.min(nextY, maxYBoundary));
// Specjalna logika kolizji z Klejnotem dla Cursed Crystal podczas rolla
if (gameState.currentState === "cursedCrystal" && typeof crystalCoreObject !== 'undefined' && crystalCoreObject && !crystalCoreObject.destroyed) {
var crystalCX = crystalCoreObject.x;
var crystalCY = crystalCoreObject.y;
var crystalRadius = crystalCoreObject.width / 2; // Zakładamy, że klejnot jest okrągły/eliptyczny
var distToCrystalHorizontal = Math.abs(nextX - crystalCX);
var distToCrystalVertical = Math.abs(nextY - crystalCY);
// Prosta kolizja AABB dla Klejnotu (player nie może wejść w bounding box klejnotu)
if (distToCrystalHorizontal < halfWidth + crystalRadius && distToCrystalVertical < halfHeight + crystalRadius) {
// Kolizja - zatrzymaj ruch w kierunku klejnotu
// To jest uproszczenie, można by zaimplementować odbicie
if (self.x < crystalCX && nextX > self.x) {
nextX = crystalCX - crystalRadius - halfWidth - 1;
} // Z lewej
else if (self.x > crystalCX && nextX < self.x) {
nextX = crystalCX + crystalRadius + halfWidth + 1;
} // Z prawej
if (self.y < crystalCY && nextY > self.y) {
nextY = crystalCY - crystalRadius - halfHeight - 1;
} // Z góry
else if (self.y > crystalCY && nextY < self.y) {
nextY = crystalCY + crystalRadius + halfHeight + 1;
} // Z dołu
}
}
self.x = nextX;
self.y = nextY;
// Kolizja rolla z bossem (tylko w trybie "game")
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; // Użyj aktualnej szerokości sprite'a
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);
// pWidth jest już obliczone wyżej w tym bloku if(self.rolling)
var playerRollRadius = pWidth / 2 * 0.8; // Efektywny promień gracza podczas rolla
var minibossCCRadius = (minibossCC.width || 120) / 2 * 0.9; // Efektywny promień Minibossa CC (asset ma 120x120, mnożnik 0.9 dla lekkiego zmniejszenia hitboxa)
if (distance_mcc < playerRollRadius + minibossCCRadius) {
minibossCC.takeDamage(10); // Zadaj 10 obrażeń
self.hasRolledThroughMinibossCCThisRoll = true; // Ustaw flagę, aby nie zadawać obrażeń wielokrotnie w tym samym rollu
// Możesz dodać efekt wizualny trafienia, np. flashObject dla Minibossa
// LK.effects.flashObject(minibossCC.graphics || minibossCC, 0x00FF00, 150);
console.log("Player rolled through Miniboss CC and dealt damage!");
}
}
} else if (self.invulnerable && !self.rolling) {
// Niewrażliwość po otrzymaniu ciosu
if (currentVisualSprite) {
currentVisualSprite.alpha = Math.floor(Date.now() / 100) % 4 > 1 ? 0.3 : 1;
}
} else {
// Normalny stan (nie rolling, nie invulnerable po ciosie)
if (currentVisualSprite && currentVisualSprite.alpha !== 1) {
currentVisualSprite.alpha = 1;
}
// Ruch gracza przez przytrzymanie (chodzenie)
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; // Prędkość chodzenia
if (distance_m > moveSpeed) {
// Ruszaj się tylko jeśli kursor jest dalej niż jeden krok
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;
// Podstawowe granice ekranu dla chodzenia
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));
// Kolizja chodzenia z Klejnotem dla Cursed Crystal
if (gameState.currentState === "cursedCrystal" && typeof crystalCoreObject !== 'undefined' && crystalCoreObject && !crystalCoreObject.destroyed) {
var crystalCXIdle = crystalCoreObject.x;
var crystalCYIdle = crystalCoreObject.y;
var crystalRadiusIdle = crystalCoreObject.width / 2;
var distToCrystalHorizontalIdle = Math.abs(nextPlayerX - crystalCXIdle);
var distToCrystalVerticalIdle = Math.abs(nextPlayerY - crystalCYIdle);
if (distToCrystalHorizontalIdle < halfWidthIdle + crystalRadiusIdle && distToCrystalVerticalIdle < halfHeightIdle + crystalRadiusIdle) {
// Kolizja - nie ruszaj się
} else {
self.x = nextPlayerX;
self.y = nextPlayerY;
}
} else {
self.x = nextPlayerX;
self.y = nextPlayerY;
}
// Obracanie postaci (idle sprite)
if (self.idleAnimationSprite && !self.idleAnimationSprite.destroyed) {
if (normalizedX > 0.1) {
self.idleAnimationSprite.scaleX = 1;
} else if (normalizedX < -0.1) {
self.idleAnimationSprite.scaleX = -1;
}
}
}
}
}
// Aktualizacja animacji idle (powinna być zawsze wywoływana, jeśli widoczna)
if (self.idleAnimationSprite && self.idleAnimationSprite.visible && !self.idleAnimationSprite.destroyed && typeof self.idleAnimationSprite.update === 'function') {
// Log tylko dla Cursed Crystal, aby nie zaśmiecać w innych trybach
if (gameState.currentState === "cursedCrystal") {
// console.log("Idle Anim Update - CC"); // Można odkomentować do bardzo szczegółowego logowania
}
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 || {};
// --- Właściwości Duszy ---
self.hp = options.hp || 1; // Domyślne HP, może być przekazane przy tworzeniu
self.speed = options.speed || 2; // Domyślna prędkość, może być przekazana
self.isDead = false;
self.targetX = options.targetX || 2048 / 2; // Domyślnie celuje w środek ekranu (Klejnot)
self.targetY = options.targetY || 2732 / 2;
// Grafika Duszy
try {
self.graphics = self.attachAsset('soul_asset', {
anchorX: 0.5,
anchorY: 0.5
// Szerokość i wysokość zostaną pobrane z assetu
// tint: options.color || 0xADD8E6 // Można nadpisać tint jeśli potrzeba
});
} catch (e) {
console.warn("Nie udało się załadować 'soul_asset', używam awaryjnego Shape dla SoulEnemy:", e);
self.graphics = new Shape({
// Klasa Shape z Twojego kodu
width: options.width || 30,
height: options.height || 30,
color: options.color || 0xADD8E6,
// Jasnoniebieski
shape: 'ellipse'
});
self.addChild(self.graphics); // Shape trzeba dodać jako dziecko
}
// Ustawienie self.width i self.height na podstawie grafiki dla kolizji
// Czekamy aż asset się załaduje, jeśli to LK.getAsset, ale attachAsset powinno od razu przypisać
// Jeśli 'soul_asset' to LK.init.shape, to .width i .height powinny być dostępne od razu
if (self.graphics && typeof self.graphics.width !== 'undefined') {
self.width = self.graphics.width;
self.height = self.graphics.height;
} else {
// Fallback, jeśli grafika nie ma od razu wymiarów (np. przy błędzie i Shape)
self.width = options.width || 30;
self.height = options.height || 30;
}
// --- Metoda aktualizacji (ruch) ---
self.update = function () {
if (self.isDead || gameState.currentState !== "cursedCrystal") {
return;
}
// Celuj w crystalCoreObject jeśli istnieje, inaczej w zapamiętany targetX, targetY
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 (distance > self.speed) {
// Jeśli dystans jest większy niż prędkość, aby uniknąć przeskakiwania
var moveX = dx / distance * self.speed;
var moveY = dy / distance * self.speed;
self.x += moveX;
self.y += moveY;
} else {
// Dusza dotarła do celu (lub bardzo blisko)
self.x = currentTargetX;
self.y = currentTargetY;
// Sprawdź, czy celem faktycznie był crystalCoreObject przed wywołaniem onHitCrystal
if (typeof crystalCoreObject !== 'undefined' && crystalCoreObject && !crystalCoreObject.destroyed && self.x === crystalCoreObject.x && self.y === crystalCoreObject.y) {
self.onHitCrystal();
} else if (!crystalCoreObject || crystalCoreObject.destroyed) {
// Klejnotu nie ma, dusza dotarła do zapasowego punktu, może powinna się zniszczyć?
console.log("SoulEnemy reached target point, but crystalCoreObject is missing/destroyed.");
self.isDead = true; // Oznacz jako martwą
if (self.parent) {
self.parent.removeChild(self);
}
self.destroy();
}
}
};
// --- Metoda otrzymywania obrażeń ---
self.takeDamage = function (amount) {
if (self.isDead) {
return;
}
self.hp -= amount;
LK.effects.flashObject(self.graphics || self, 0xFFFFFF, 150); // Krótki błysk na grafice
if (self.hp <= 0) {
self.die();
}
};
// --- Metoda śmierci Duszy (np. z ręki gracza) ---
self.die = function () {
if (self.isDead) {
return;
}
self.isDead = true;
// TODO: Można dodać efekt wizualny/dźwiękowy śmierci Duszy
// Zwiększ wynik gracza w trybie Cursed Crystal
gameState.cursedCrystalScore += 10; // Przykładowa wartość punktów za Duszę
if (ui && ui.updateScoreCC) {
// Sprawdź czy UI i metoda istnieją
ui.updateScoreCC(gameState.cursedCrystalScore);
}
// Usuń obiekt z gry (logika usuwania z gameState.cursedCrystalEnemies będzie w pętli gry)
if (self.parent) {
self.parent.removeChild(self);
}
self.destroy(); // Zniszcz obiekt, aby zwolnić zasoby
};
// --- Metoda wywoływana po dotarciu do Klejnotu ---
self.onHitCrystal = function () {
if (self.isDead) {
// Jeśli już umarła (np. od rolla w tej samej klatce), nie rób nic więcej
return;
}
self.isDead = true; // Oznacz jako martwą, żeby nie procesować dalej
gameState.cursedCrystalSoulsHitCrystal++;
// Zwiększ naładowanie o stałą wartość lub zależną od typu duszy w przyszłości
gameState.cursedCrystalChargeLevel = Math.min(gameState.cursedCrystalTargetCharge, gameState.cursedCrystalChargeLevel + 5);
// Aktualizuj UI paska naładowania
if (ui && ui.updateCrystalCharge) {
ui.updateCrystalCharge(gameState.cursedCrystalChargeLevel, gameState.cursedCrystalTargetCharge);
}
// TODO: Dźwięk uderzenia w Klejnot
// LK.getSound('soul_hit_crystal_sound').play();
// Usuń obiekt z gry
if (self.parent) {
self.parent.removeChild(self);
}
self.destroy();
};
// Inicjalizacja pozycji (może być nadpisana przez opcje przy tworzeniu)
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(); // Kontener na pasek
self.addChild(self.crystalChargeBarContainer);
var chargeBarWidth = 400;
var chargeBarHeight = 25;
// Tło paska naładowania
self.crystalChargeBarBg = new Shape({
// Używamy naszej klasy Shape
width: chargeBarWidth,
height: chargeBarHeight,
color: 0x333333 // Ciemnoszary
});
self.crystalChargeBarBg.anchor.set(0, 0.5); // Anchor na lewo, środek w pionie
self.crystalChargeBarContainer.addChild(self.crystalChargeBarBg);
// Wypełnienie paska naładowania
self.crystalChargeBarFill = new Shape({
width: 0,
// Zacznij od 0
height: chargeBarHeight - 4,
// Nieco mniejszy, aby stworzyć ramkę
color: 0x8A2BE2 // Kolor fioletowy (jak klejnot)
});
self.crystalChargeBarFill.anchor.set(0, 0.5);
self.crystalChargeBarFill.x = 2; // Mały offset, aby był wewnątrz tła
self.crystalChargeBarFill.y = 0; // y jest względem kontenera paska
self.crystalChargeBarContainer.addChild(self.crystalChargeBarFill);
// --- DODANA NOWA FUNKCJA DO AKTUALIZACJI TEKSTU REKORDU ---
self.updateHighScoreDisplay = function (seconds) {
seconds = Math.max(0, seconds || 0);
var minutes = Math.floor(seconds / 60);
var remainingSeconds = seconds % 60;
var formattedSeconds = remainingSeconds < 10 ? '0' + remainingSeconds : remainingSeconds;
var formattedTime = String(minutes).padStart(2, '0') + ':' + formattedSeconds;
if (self.highScoreText) {
self.highScoreText.setText("CC Score: " + (gameState.cursedCrystalScore || 0)); // Pokaż wynik CC
self.highScoreText.x = 2048 / 2; // Przykładowe wyśrodkowanie
self.highScoreText.y = 50; // Na górze
self.highScoreText.anchor.set(0.5, 0);
self.highScoreText.alpha = 1;
}
};
self.updateScoreCC = function (currentScore) {
if (self.ccScoreText) {
self.ccScoreText.setText("Score: " + (currentScore || 0));
}
};
// Aktualizuje wyświetlanie rekordu w Cursed Crystal
self.updateHighScoreCC = function (highScore) {
if (self.ccHighScoreText) {
self.ccHighScoreText.setText("Best: " + (highScore || 0));
}
};
// 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;
// self.titleText.setText("Welcome Unchosen"); // Ustawiane w gameState.showTitleScreen
}
if (self.messageText) {
self.messageText.x = 2048 / 2;
self.messageText.y = 1000;
// self.messageText.alpha = 1; // Ustawiane przez showMessage
}
if (self.tutorialText) {
self.tutorialText.x = 2048 / 2;
self.tutorialText.y = 1200;
// self.tutorialText.alpha = 1; // Ustawiane przez showTutorial
}
break;
case "game":
// Tryb walki z bossem
if (self.messageText) {
self.messageText.x = 2048 / 2;
self.messageText.y = 1500;
// self.messageText.alpha = 0; // Ustawiane przez showMessage
}
if (self.tutorialText) {
self.tutorialText.x = 2048 / 2;
self.tutorialText.y = 200;
// self.tutorialText.alpha = 1; // Ustawiane przez showTutorial/hideTutorial
}
if (self.heartContainer) {
self.heartContainer.alpha = 1;
// Pozycja serc może zależeć od maxHearts, więc może być aktualizowana w updateHearts lub tutaj
// np. self.heartContainer.x = (2048 - ( (parseInt(storage.maxHearts, 10) || 5) * 50)) / 2 + 25;
}
if (self.timerText) {
// Standardowy timer gry
self.timerText.alpha = 1;
}
if (self.deathsText) {
self.deathsText.alpha = 1;
}
// Widoczność paska HP bossa jest zarządzana przez updateBossHealth
if (self.bossHealthBarContainer && boss && !boss.dead && boss.health > 0) {
self.bossHealthBarContainer.alpha = 1;
} else if (self.bossHealthBarContainer) {
self.bossHealthBarContainer.alpha = 0;
}
break;
case "grillMenu":
if (self.messageText) {
self.messageText.x = 2048 / 2;
self.messageText.y = 500;
// self.messageText.alpha = 0; // Ustawiane przez showMessage
}
// Ukryj elementy gry jak serca, timer, pasek bossa itp.
if (self.heartContainer) {
self.heartContainer.alpha = 0;
}
if (self.timerText) {
self.timerText.alpha = 0;
}
if (self.bossHealthBarContainer) {
self.bossHealthBarContainer.alpha = 0;
}
if (self.deathsText) {
self.deathsText.alpha = 0;
}
break;
case "gameOver":
// Standardowy Game Over (po walce z bossem lub Boss+)
if (self.titleText) {
self.titleText.x = 2048 / 2;
self.titleText.y = 800;
// self.titleText.alpha = 1; // Ustawiane w gameState.gameOver
}
if (self.messageText) {
self.messageText.x = 2048 / 2;
self.messageText.y = 1000;
// self.messageText.alpha = 0; // Ustawiane w gameState.gameOver przez showMessage
}
if (self.tutorialText) {
self.tutorialText.x = 2048 / 2;
self.tutorialText.y = 1200;
}
if (self.deathsText) {
// Pokaż licznik śmierci
self.deathsText.alpha = 1;
}
// Widoczność paska HP bossa zarządzana przez updateBossHealth (np. jeśli pokonano Boss+)
// if (self.bossHealthBarContainer && !gameOverReasonIsDeath && isNewBossPlusMode) {
// self.bossHealthBarContainer.alpha = 1; // Pokaż pełny pasek jeśli przetrwano Boss+ (logika w updateBossHealth)
// }
break;
case "intro":
case "fakeTutorial":
case "realTutorial":
// Większość elementów UI jest zazwyczaj ukryta, zarządzane przez logikę tych stanów
break;
case "rollMaster":
case "rollMasterGameOver":
// Dodajemy też gameOver dla spójności UI rekordu
if (self.timerText) {
self.timerText.x = 2048 / 2;
self.timerText.y = 50;
self.timerText.anchor.set(0.5, 0);
self.timerText.style = {
size: 60,
fill: 0xFFFFFF
};
self.timerText.alpha = 1;
// Tekst timera ("Time: XX:XX") jest ustawiany w ui.updateTimerDisplay
}
if (self.highScoreText) {
// Ten jest dedykowany dla Roll Master
self.highScoreText.alpha = 1;
self.highScoreText.x = 2048 - 50;
self.highScoreText.y = 50;
self.highScoreText.anchor.set(1, 0);
// Tekst rekordu ("Best: XX:XX") jest ustawiany w ui.updateHighScoreDisplay (który powinien być wywoływany z gameState.rollMasterHighScore)
}
// Ukryj inne niepotrzebne elementy
if (self.heartContainer) {
self.heartContainer.alpha = 0;
}
if (self.bossHealthBarContainer) {
self.bossHealthBarContainer.alpha = 0;
}
if (self.deathsText) {
self.deathsText.alpha = 0;
}
if (self.tutorialText) {
self.tutorialText.alpha = 0;
}
if (self.titleText) {
self.titleText.alpha = 0;
}
break;
case "cursedCrystal":
// 1. Score i High Score na samej górze
if (self.ccScoreText) {
self.ccScoreText.alpha = 1;
self.ccScoreText.x = 50;
self.ccScoreText.y = 20; // Y=20 dla wyniku
}
if (self.ccHighScoreText) {
self.ccHighScoreText.alpha = 1;
self.ccHighScoreText.x = 2048 - 50;
self.ccHighScoreText.y = 20; // Y=20 dla rekordu
self.ccHighScoreText.anchor.set(1, 0);
}
// 2. Serca gracza poniżej (np. Y=70)
if (self.heartContainer) {
self.heartContainer.alpha = 1;
var ccMaxHearts = gameState.cursedCrystalPlayerMaxHealth || 10;
self.heartContainer.x = (2048 - ccMaxHearts * 50) / 2 + 25; // Wyśrodkowanie serc
self.heartContainer.y = 70; // <<< Zmieniono Y dla serc
}
// 3. Pasek Naładowania Klejnotu poniżej serc (np. Y=130)
if (self.crystalChargeBarContainer) {
self.crystalChargeBarContainer.alpha = 1;
var chargeBarWidth = self.crystalChargeBarBg.width || 400; // Pobierz szerokość tła paska
self.crystalChargeBarContainer.x = (2048 - chargeBarWidth) / 2; // Wyśrodkuj
self.crystalChargeBarContainer.y = 130; // <<< Zmieniono Y dla paska naładowania
}
// 4. Pasek HP Minibossa poniżej paska naładowania (np. Y=180 lub Y=200)
// Ten pasek będzie widoczny tylko, gdy Miniboss jest aktywny (zarządzane przez updateMinibossHealthCC)
if (self.bossHealthBarContainer) {
// Ustawiamy pozycję, ale alpha jest kontrolowane przez updateMinibossHealthCC
self.bossHealthBarContainer.y = 180; // <<< Zmieniono Y dla paska HP Minibossa
// self.bossHealthBarContainer.alpha = 0; // Domyślnie ukryty, updateMinibossHealthCC go pokaże
}
// Ukryj inne niepotrzebne elementy (te linie są OK)
if (self.timerText) {
self.timerText.alpha = 0;
}
if (self.highScoreText) {
self.highScoreText.alpha = 0;
} // Ten od Roll Master
if (self.deathsText) {
self.deathsText.alpha = 0;
}
if (self.tutorialText) {
self.tutorialText.alpha = 0;
}
if (self.titleText) {
self.titleText.alpha = 0;
}
break;
case "cursedCrystalGameOver":
if (self.titleText) {
// Tekst i styl są ustawiane w gameState.endCursedCrystalMode
self.titleText.alpha = 1;
self.titleText.x = 2048 / 2;
self.titleText.y = 600; // Wyżej niż poprzednio
self.titleText.anchor.set(0.5);
}
if (self.messageText) {
// Tekst i styl są ustawiane w gameState.endCursedCrystalMode
self.messageText.alpha = 1;
self.messageText.x = 2048 / 2;
self.messageText.y = 800; // Pod tytułem
self.messageText.anchor.set(0.5, 0.5); // Wyśrodkowanie tekstu wieloliniowego
}
// Pozycjonowanie przycisków (zakładając, że są w gameState.activeButtons)
if (gameState.activeButtons && gameState.activeButtons.length > 0) {
var buttonYStart = 1100;
var buttonSpacing = 150;
gameState.activeButtons.forEach(function (button, index) {
if (button) {
button.x = 2048 / 2;
button.y = buttonYStart + index * buttonSpacing;
// Zakładamy, że przyciski mają anchor 0.5, 0.5 dla ich własnych BG i Text
}
});
}
// Ukryj elementy niepotrzebne na ekranie końca gry CC
if (self.heartContainer) {
self.heartContainer.alpha = 0;
}
if (self.timerText) {
self.timerText.alpha = 0;
} // Standardowy timer gry
if (self.highScoreText) {
self.highScoreText.alpha = 0;
} // Ten od Roll Master
if (self.bossHealthBarContainer) {
self.bossHealthBarContainer.alpha = 0;
}
if (self.deathsText) {
self.deathsText.alpha = 0;
}
if (self.tutorialText) {
self.tutorialText.alpha = 0;
}
// Ukryj UI specyficzne dla Cursed Crystal (w grze)
if (self.ccScoreText) {
self.ccScoreText.alpha = 0;
}
if (self.ccHighScoreText) {
self.ccHighScoreText.alpha = 0;
} // Ten od wyniku na żywo
if (self.crystalChargeBarContainer) {
self.crystalChargeBarContainer.alpha = 0;
}
// Upewnij się, że tło gry (jeśli jakieś jest) jest odpowiednie lub wyczyszczone
// game.setBackgroundColor(0x222222); // Np. ciemne tło
break;
case "cursedCrystalGameOver":
if (self.titleText) {
// Tekst ustawiany w gameState.endCursedCrystalMode
self.titleText.alpha = 1;
self.titleText.x = 2048 / 2;
self.titleText.y = 600;
}
if (self.messageText) {
// Tekst (wynik/rekord) ustawiany w gameState.endCursedCrystalMode
self.messageText.alpha = 1;
self.messageText.x = 2048 / 2;
self.messageText.y = 800;
}
// Ukryj elementy niepotrzebne na ekranie końca gry CC
if (self.heartContainer) {
self.heartContainer.alpha = 0;
}
if (self.timerText) {
self.timerText.alpha = 0;
}
if (self.highScoreText) {
self.highScoreText.alpha = 0;
}
if (self.bossHealthBarContainer) {
self.bossHealthBarContainer.alpha = 0;
}
if (self.deathsText) {
self.deathsText.alpha = 0;
}
if (self.tutorialText) {
self.tutorialText.alpha = 0;
}
// Ukryj UI specyficzne dla Cursed Crystal (w grze)
if (self.ccScoreText) {
self.ccScoreText.alpha = 0;
}
if (self.ccHighScoreText) {
self.ccHighScoreText.alpha = 0;
}
if (self.crystalChargeBarContainer) {
self.crystalChargeBarContainer.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
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 = {
currentState: "title",
gameDuration: 120,
remainingTime: 0,
gameTimerInterval: null,
fakeTutorialTimerId: null,
bossSpeedIncreased: false,
currentIntroMusicInstance: null,
currentRollSoulsInstance: null,
currentRollMasterMusicInstance: 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: 15,
// 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;
console.log("[showTitleScreen] State: Title Screen");
var needsIntroMusic = true;
if (this.currentRollSoulsInstance && this.currentRollSoulsInstance.stop) {
console.log("[showTitleScreen] Zatrzymywanie currentRollSoulsInstance.");
if (typeof this.currentRollSoulsInstance.volume === 'number') {
this.currentRollSoulsInstance.volume = 0;
}
this.currentRollSoulsInstance.stop();
this.currentRollSoulsInstance = null;
} else {
var musicViaLK_RS = LK.music;
if (musicViaLK_RS && musicViaLK_RS.assetId === 'RollSouls' && musicViaLK_RS.playing && musicViaLK_RS.stop) {
console.log("[showTitleScreen] Fallback: Zatrzymywanie RollSouls przez LK.music.");
if (typeof musicViaLK_RS.volume === 'number') {
musicViaLK_RS.volume = 0;
}
musicViaLK_RS.stop();
}
}
if (this.currentRollMasterMusicInstance && this.currentRollMasterMusicInstance.stop) {
console.log("[showTitleScreen] Zatrzymywanie currentRollMasterMusicInstance.");
if (typeof this.currentRollMasterMusicInstance.volume === 'number') {
this.currentRollMasterMusicInstance.volume = 0;
}
this.currentRollMasterMusicInstance.stop();
this.currentRollMasterMusicInstance = null;
} else {
var musicViaLK_RM = LK.music;
if (musicViaLK_RM && musicViaLK_RM.assetId === 'rollmaster' && musicViaLK_RM.playing && musicViaLK_RM.stop) {
console.log("[showTitleScreen] Fallback: Zatrzymywanie 'rollmaster' przez LK.music.");
if (typeof musicViaLK_RM.volume === 'number') {
musicViaLK_RM.volume = 0;
}
musicViaLK_RM.stop();
}
}
if (this.currentIntroMusicInstance && this.currentIntroMusicInstance.playing) {
console.log("[showTitleScreen] currentIntroMusicInstance już gra.");
needsIntroMusic = false;
} else {
var lkMusicCheck = LK.music;
if (lkMusicCheck && lkMusicCheck.assetId === 'introMusic' && lkMusicCheck.playing) {
console.log("[showTitleScreen] introMusic już gra (wg LK.music). Zapisuję instancję.");
this.currentIntroMusicInstance = lkMusicCheck;
needsIntroMusic = false;
}
}
if (needsIntroMusic) {
console.log("[showTitleScreen] Uruchamianie introMusic...");
this.currentIntroMusicInstance = LK.playMusic('introMusic', {
loop: true,
volume: 0.7,
fade: {
start: 0,
end: 0.7,
duration: 1000
}
});
if (!this.currentIntroMusicInstance) {
console.error("[showTitleScreen] LK.playMusic('introMusic') nie zwróciło instancji!");
}
} else {
console.log("[showTitleScreen] introMusic nie jest ponownie uruchamiana.");
}
if (this.fakeTutorialTimerId) {
LK.clearTimeout(this.fakeTutorialTimerId);
this.fakeTutorialTimerId = null;
}
if (this.gameTimerInterval) {
LK.clearInterval(this.gameTimerInterval);
this.gameTimerInterval = null;
}
if (this.rollMasterTimerInterval) {
LK.clearInterval(this.rollMasterTimerInterval);
this.rollMasterTimerInterval = null;
}
clearScene();
if (currentBackground) {
tween.stop(currentBackground);
currentBackground.destroy();
currentBackground = null;
}
this.currentState = "title";
game.setBackgroundColor(0x1a1a1a);
currentBackground = LK.getAsset('titleBg', {
anchorX: 0,
anchorY: 0,
x: 0,
y: 0
});
game.addChildAt(currentBackground, 0);
if (typeof particleIntervalId !== 'undefined' && particleIntervalId) {
LK.clearInterval(particleIntervalId);
particleIntervalId = null;
}
if (typeof startScreenParticles !== 'undefined') {
startScreenParticles.forEach(function (p) {
if (p && p.destroy) {
p.destroy();
}
});
startScreenParticles = [];
}
var localStartScreenParticles = [];
function localSpawnBackgroundParticle() {/* ... logika cząsteczek ... */}
var localParticleIntervalId = LK.setInterval(localSpawnBackgroundParticle, 400);
// Aby uniknąć konfliktów z globalnymi zmiennymi, jeśli są, można przekazać je do gameState lub zarządzać nimi lokalnie.
// Dla uproszczenia, zakładam, że particleIntervalId i startScreenParticles są zarządzane w sposób, który nie koliduje.
if (player && player.destroy) {
player.destroy();
}
player = null;
if (boss && boss.destroy) {
boss.destroy();
}
boss = null;
walls.forEach(function (wall) {
if (wall) {
wall.alpha = 1;
}
});
ui.positionElements("title");
ui.titleText.setText("Welcome Unchosen");
ui.showMessage("Tap to Start", 0);
ui.showTutorial("Swipe to Roll - Death is Progress");
ui.updateHearts(storage.maxHearts, storage.maxHearts);
ui.updateDeathsCounter();
this.touchStart = {
x: 0,
y: 0
};
this.touchEnd = {
x: 0,
y: 0
};
game.off('down');
game.on('down', function () {
if (gameState.currentState === 'title') {
if (typeof localParticleIntervalId !== 'undefined' && localParticleIntervalId) {
LK.clearInterval(localParticleIntervalId);
}
if (localStartScreenParticles) {
localStartScreenParticles.forEach(function (p) {
if (p && p.destroy) {
p.destroy();
}
});
}
gameState.showIntro();
}
});
},
showIntro: function showIntro() {
var skipElement = null;
var skipTimerId = null;
var self = this; // self tutaj to gameState
if (this.fakeTutorialTimerId) {
LK.clearTimeout(this.fakeTutorialTimerId);
this.fakeTutorialTimerId = null;
}
if (typeof particleIntervalId !== 'undefined') {
LK.clearInterval(particleIntervalId);
particleIntervalId = null;
}
if (typeof startScreenParticles !== 'undefined') {
startScreenParticles.forEach(function (p) {
if (p && p.destroy) {
p.destroy();
}
});
startScreenParticles = [];
}
clearScene();
if (currentBackground) {
tween.stop(currentBackground);
currentBackground.destroy();
currentBackground = null;
}
this.currentState = "intro";
game.setBackgroundColor(0x111111);
currentBackground = LK.getAsset('introBg', {
anchorX: 0.5,
anchorY: 0.4,
x: 1000,
y: 1000,
scaleX: 1,
scaleY: 1
});
game.addChildAt(currentBackground, 0);
tween(currentBackground, {
scaleX: 1.5,
scaleY: 1.5
}, {
duration: 38000,
easing: tween.linear,
repeat: Infinity,
yoyo: true
});
if (player && player.destroy) {
player.destroy();
}
player = null;
if (boss && boss.destroy) {
boss.destroy();
}
boss = null;
walls.forEach(function (wall) {
if (wall) {
wall.alpha = 0;
}
});
ui.positionElements("intro");
skipTimerId = LK.setTimeout(function () {
if (self.currentState !== "intro") {
skipTimerId = null;
return;
}
skipElement = new Text2("Skip intro", {
size: 70,
fill: 0xFFFFFF
});
skipElement.x = 2048 - 50;
skipElement.y = 2732 - 50;
skipElement.anchor.set(1, 1);
skipElement.interactive = true;
skipElement.cursor = "pointer";
currentSceneElements.addChild(skipElement);
skipElement.down = function () {
if (self.currentState !== "intro") {
return;
}
console.log("[Skip Intro] Pominięto intro przez tekst!");
if (skipTimerId) {
LK.clearTimeout(skipTimerId);
skipTimerId = null;
}
if (currentBackground) {
tween.stop(currentBackground);
}
clearScene();
if (self.currentIntroMusicInstance && self.currentIntroMusicInstance.stop) {
console.log("[Skip Intro] Zatrzymywanie currentIntroMusicInstance.");
if (typeof self.currentIntroMusicInstance.volume === 'number') {
self.currentIntroMusicInstance.volume = 0;
}
self.currentIntroMusicInstance.stop();
self.currentIntroMusicInstance = null;
} else {
console.log("[Skip Intro] Brak currentIntroMusicInstance lub metody stop. Fallback na LK.music.");
var musicViaLK_I = LK.music;
if (musicViaLK_I && musicViaLK_I.assetId === 'introMusic' && musicViaLK_I.stop) {
if (typeof musicViaLK_I.volume === 'number') {
musicViaLK_I.volume = 0;
}
musicViaLK_I.stop();
}
}
if (self.currentRollMasterMusicInstance && self.currentRollMasterMusicInstance.stop) {
console.log("[Skip Intro] Zatrzymywanie currentRollMasterMusicInstance.");
if (typeof self.currentRollMasterMusicInstance.volume === 'number') {
self.currentRollMasterMusicInstance.volume = 0;
}
self.currentRollMasterMusicInstance.stop();
self.currentRollMasterMusicInstance = null;
}
console.log("[Skip Intro] Odtwarzanie RollSouls...");
self.currentRollSoulsInstance = LK.playMusic('RollSouls', {
loop: true,
volume: 0.7,
fade: {
start: 0,
end: 0.7,
duration: 1000
}
});
if (!self.currentRollSoulsInstance) {
console.error("[Skip Intro] LK.playMusic('RollSouls') nie zwróciło instancji!");
}
console.log("[Skip Intro] Przechodzenie do showRealTutorial...");
self.showRealTutorial();
};
skipTimerId = null;
}, 3000);
function showIntroText(text, delay, onComplete) {
var introText = new Text2(text, {
size: 90,
fill: 0xFFFFFF,
align: 'center',
wordWrap: true,
wordWrapWidth: 1800
});
introText.anchor.set(0.5, 0.5);
introText.x = 2048 / 2;
introText.y = 2232 / 2 - 400;
introText.alpha = 0;
currentSceneElements.addChild(introText);
LK.setTimeout(function () {
if (self.currentState !== "intro") {
return;
}
tween(introText, {
alpha: 1
}, {
duration: 2000,
easing: tween.easeInOut
});
LK.setTimeout(function () {
if (self.currentState !== "intro") {
return;
}
tween(introText, {
alpha: 0
}, {
duration: 2000,
easing: tween.easeInOut
});
LK.setTimeout(function () {
if (onComplete) {
onComplete();
}
}, 3000);
}, 3000);
}, delay);
}
showIntroText('From the authors of Dark Souls...', 2000, function () {
if (self.currentState !== "intro") {
return;
}
showIntroText('I have no information. I don’t know them.', 0, function () {
if (self.currentState !== "intro") {
return;
}
showIntroText('Silas GameStudio...', 0, function () {
if (self.currentState !== "intro") {
return;
}
showIntroText('Still looking for funding.', 0, function () {
if (self.currentState !== "intro") {
return;
}
showIntroText('Oh, and it doesn’t even exist.', 0, function () {
if (self.currentState !== "intro") {
return;
}
LK.setTimeout(function () {
if (self.currentState !== "intro") {
return;
}
if (skipTimerId) {
LK.clearTimeout(skipTimerId);
skipTimerId = null;
}
if (skipElement && skipElement.parent) {
currentSceneElements.removeChild(skipElement);
if (skipElement.destroy) {
skipElement.destroy();
}
skipElement = null;
}
if (typeof gameState.showFakeTutorial === 'function') {
gameState.showFakeTutorial();
} else {
console.error("Error: gameState.showFakeTutorial is not defined");
gameState.showTitleScreen();
}
}, 1000);
});
});
});
});
});
},
// Koniec funkcji showIntro
// --- KONIEC SEKWENCJI INTRO ---
// <--- Koniec funkcji showIntro
showFakeTutorial: function showFakeTutorial() {
clearScene();
if (this.fakeTutorialTimerId) {
LK.clearTimeout(this.fakeTutorialTimerId);
this.fakeTutorialTimerId = null;
}
if (this.currentIntroMusicInstance) {
console.log("[FakeTutorial] Próba wyciszenia i zatrzymania currentIntroMusicInstance.");
var instanceToFadeAndStop = this.currentIntroMusicInstance;
LK.tween(instanceToFadeAndStop, {
volume: 0
}, {
duration: 3000,
onFinish: function onFinish() {
if (instanceToFadeAndStop.stop) {
instanceToFadeAndStop.stop();
console.log("[FakeTutorial] currentIntroMusicInstance zatrzymane po tweenie.");
if (gameState.currentIntroMusicInstance === instanceToFadeAndStop) {
gameState.currentIntroMusicInstance = null;
}
}
}
});
} else {
console.log("[FakeTutorial] Nie znaleziono currentIntroMusicInstance. Sprawdzanie LK.music...");
var musicViaLK_I = LK.music;
if (musicViaLK_I && musicViaLK_I.assetId === 'introMusic') {
LK.tween(musicViaLK_I, {
volume: 0
}, {
duration: 3000,
onFinish: function onFinish() {
if (musicViaLK_I.stop) {
musicViaLK_I.stop();
}
}
});
}
}
if (this.currentRollMasterMusicInstance && this.currentRollMasterMusicInstance.stop) {
console.log("[FakeTutorial] Zatrzymywanie currentRollMasterMusicInstance.");
if (typeof this.currentRollMasterMusicInstance.volume === 'number') {
this.currentRollMasterMusicInstance.volume = 0;
}
this.currentRollMasterMusicInstance.stop();
this.currentRollMasterMusicInstance = null;
}
console.log("[FakeTutorial] Odtwarzanie RollSouls...");
this.currentRollSoulsInstance = LK.playMusic('RollSouls', {
loop: true,
volume: 0.7,
fade: {
start: 0,
end: 0.7,
duration: 3000
}
});
if (!this.currentRollSoulsInstance) {
console.error("[FakeTutorial] LK.playMusic('RollSouls') nie zwróciło instancji!");
}
this.currentState = "fakeTutorial";
ui.positionElements("fakeTutorial");
var instructionText = new Text2('Press LPM to block.. or don’t press.', {
size: 100,
fill: 0xFFFFFF,
align: 'center',
wordWrap: true,
wordWrapWidth: 1800,
dropShadow: true,
dropShadowColor: 0x000000,
dropShadowDistance: 3,
dropShadowBlur: 4,
dropShadowAngle: Math.PI / 4
});
instructionText.anchor.set(0.5, 0.5);
instructionText.x = 2048 / 2;
instructionText.y = 2732 / 2;
currentSceneElements.addChild(instructionText);
this.fakeTutorialTimerId = LK.setTimeout(function () {
try {
if (instructionText && instructionText.destroy) {
instructionText.destroy();
}
} catch (e) {}
gameState.fakeTutorialTimerId = null;
if (gameState && typeof gameState.showRealTutorial === 'function') {
gameState.showRealTutorial();
} else {
console.error("Error: gameState.showRealTutorial is not a function in fakeTutorial timeout!");
gameState.showTitleScreen();
}
}, 6000);
},
// *** NOWA FUNKCJA: Prawdziwy Tutorial (oddzielona od startGame) ***
showRealTutorial: function showRealTutorial() {
if (this.fakeTutorialTimerId) {
LK.clearTimeout(this.fakeTutorialTimerId);
}
this.fakeTutorialTimerId = null; // Wyczyść timer fake tutorialu
clearScene(); // Wyczyść elementy fake tutorialu/fake game over
// Zatrzymaj animację tła intro i usuń je
if (currentBackground) {
tween.stop(currentBackground);
currentBackground.destroy();
currentBackground = null;
}
this.currentState = "realTutorial";
// Ustaw tło tutoriala
if (currentBackground && currentBackground.destroy) {
currentBackground.destroy();
}
currentBackground = LK.getAsset('realtutorialbg', {});
game.addChildAt(currentBackground, 0);
// Pokaż ściany areny
walls.forEach(function (wall) {
if (wall) {
wall.alpha = 1;
}
});
ui.positionElements("realTutorial"); // Ustaw UI (głównie ukrywa elementy gry)
// --- NIE MA tutorialTitle ani tutorialDesc (tekst jest na grafice) ---
// Przycisk "Let's Roll!" do rozpoczęcia gry
var startButton = new Container();
startButton.interactive = true;
startButton.x = 2048 / 2;
startButton.y = 1250; // Zmniejszona pozycja Y, był 1500
currentSceneElements.addChild(startButton);
// Powiększone tło przycisku
var startButtonBg = LK.getAsset('button_bg', {
anchorX: 0.5,
anchorY: 0.5
});
startButtonBg.scale.set(1.3); // Skala tła przycisku zwiększona
startButton.addChild(startButtonBg);
// Powiększony i wyraźniejszy tekst przycisku
var startButtonText = new Text2("Let's Roll!", {
size: 80,
fill: 0xFFFFFF,
stroke: 0x000000,
strokeThickness: 6
});
startButtonText.anchor.set(0.5, 0.5);
startButton.addChild(startButtonText);
// Funkcja kliknięcia przycisku
startButton.down = function () {
gameState.startGame(); // Rozpocznij grę
};
},
// Obsługa inputu w stanie fakeTutorial (fałszywa śmierć)
handleFakeTutorialInput: function handleFakeTutorialInput() {
// Wyczyść timer, który miał prowadzić do prawdziwego tutorialu
if (this.fakeTutorialTimerId) {
LK.clearTimeout(this.fakeTutorialTimerId);
this.fakeTutorialTimerId = null; // Zresetuj ID timera
}
clearScene(); // Wyczyść ekran fałszywego tutorialu
// Ustaw stan tymczasowo (można by dodać dedykowany stan 'fakeGameOver')
this.currentState = "fakeGameOver"; // Zmieniono z "gameOver"
// Zatrzymaj animację tła intro, ale nie usuwaj go jeszcze
if (currentBackground) {
tween.stop(currentBackground);
}
ui.positionElements("gameOver"); // Użyj pozycjonowania gameOver dla tekstów "YOU DIED"
// Wyświetl "YOU DIED" na czerwono
var diedText = new Text2("YOU DIED", {
size: 150,
fill: 0xFF0000
}); // Czerwony kolor
diedText.anchor.set(0.5, 0.5);
diedText.x = 2048 / 2;
diedText.y = 600;
currentSceneElements.addChild(diedText);
// Wyświetl wyjaśnienie
var explanationText = new Text2("Did you check the title of the game?", {
size: 70,
fill: 0xFFFFFF,
align: 'center',
wordWrap: true,
wordWrapWidth: 1800,
dropShadow: true,
dropShadowColor: 0x000000,
// cień czarny
dropShadowDistance: 3,
// odległość cienia
dropShadowBlur: 4,
// lekkie rozmycie cienia
dropShadowAngle: Math.PI / 4 // kąt padania cienia (45 stopni)
});
explanationText.anchor.set(0.5, 0.5);
explanationText.x = 2048 / 2;
explanationText.y = 800;
currentSceneElements.addChild(explanationText);
// Dodaj przycisk "How to play again"
var howToPlayButtonContainer = new Container();
howToPlayButtonContainer.interactive = true;
howToPlayButtonContainer.x = 2048 / 2;
howToPlayButtonContainer.y = 1300; // Pozycja przycisku
currentSceneElements.addChild(howToPlayButtonContainer);
var buttonBg = LK.getAsset('button_bg', {
anchorX: 0.5,
anchorY: 0.5
});
howToPlayButtonContainer.addChild(buttonBg);
var buttonText = new Text2('How to play', {
size: 50,
fill: 0xFFFFFF
}); // Zmieniono tekst i rozmiar
buttonText.anchor.set(0.5, 0.5);
howToPlayButtonContainer.addChild(buttonText);
// Akcja przycisku: Przejdź do prawdziwego tutorialu
howToPlayButtonContainer.down = function () {
// *** POPRAWKA: Sprawdzenie przed wywołaniem ***
if (gameState && typeof gameState.showRealTutorial === 'function') {
gameState.showRealTutorial(); // Przejdź do prawdziwego tutorialu
} else {
console.error("Error: gameState.showRealTutorial is not a function in fake input handler!");
gameState.showTitleScreen(); // Awaryjny powrót do tytułu
}
};
},
// Przejście do stanu gry (walka z bossem) - wywoływane z Prawdziwego Tutorialu
startGame: function startGame() {
var _this = this;
console.log("[StartGame] Rozpoczynanie standardowej gry lub NewBoss+.");
if (this.currentIntroMusicInstance && this.currentIntroMusicInstance.stop) {
console.log("[StartGame] Zatrzymywanie currentIntroMusicInstance.");
if (typeof this.currentIntroMusicInstance.volume === 'number') {
this.currentIntroMusicInstance.volume = 0;
}
this.currentIntroMusicInstance.stop();
this.currentIntroMusicInstance = null;
}
if (this.currentRollMasterMusicInstance && this.currentRollMasterMusicInstance.stop) {
console.log("[StartGame] Zatrzymywanie currentRollMasterMusicInstance.");
if (typeof this.currentRollMasterMusicInstance.volume === 'number') {
this.currentRollMasterMusicInstance.volume = 0;
}
this.currentRollMasterMusicInstance.stop();
this.currentRollMasterMusicInstance = null;
}
if (this.currentRollSoulsInstance && this.currentRollSoulsInstance.stop) {
console.log("[StartGame] Zatrzymywanie poprzedniej currentRollSoulsInstance.");
if (typeof this.currentRollSoulsInstance.volume === 'number') {
this.currentRollSoulsInstance.volume = 0;
}
this.currentRollSoulsInstance.stop();
}
console.log("[StartGame] Uruchamianie RollSouls.");
this.currentRollSoulsInstance = LK.playMusic('RollSouls', {
loop: true,
volume: 0.7
});
if (!this.currentRollSoulsInstance) {
console.error("[StartGame] LK.playMusic('RollSouls') nie zwróciło instancji!");
}
if (this.fakeTutorialTimerId) {
LK.clearTimeout(this.fakeTutorialTimerId);
this.fakeTutorialTimerId = null;
}
if (this.gameTimerInterval) {
LK.clearInterval(this.gameTimerInterval);
this.gameTimerInterval = null;
}
clearScene();
if (currentBackground) {
tween.stop(currentBackground);
currentBackground.destroy();
currentBackground = null;
}
this.currentState = "game";
if (gameState.grillMenuEffects && gameState.grillMenuEffects.length > 0) {
gameState.grillMenuEffects.forEach(function (effect) {
if (effect && effect.parent) {
tween.stop(effect);
effect.parent.removeChild(effect);
if (effect.destroy && !effect.destroyed) {
effect.destroy();
}
} else if (effect && effect.destroy && !effect.destroyed) {
console.warn("Sparkle/Star object found without parent but has destroy method:", effect);
effect.destroy();
} else {
console.warn("Sparkle/Star object found without parent or destroy method:", effect);
}
});
gameState.grillMenuEffects = [];
}
game.setBackgroundColor(0x111111);
var arenaBg = LK.getAsset('arena', {
anchorX: 0.5,
anchorY: 0.5,
x: 2048 / 2,
y: 2732 / 2
});
game.addChildAt(arenaBg, 0);
if (player && player.destroy) {
player.destroy();
}
player = game.addChild(new Player());
// --- POCZĄTEK ZMIAN ---
if (player) {
player.health = parseInt(storage.maxHearts, 10) || 5;
console.log("DEBUG: Inicjalizacja zdrowia gracza. player.health:", player.health, "storage.maxHearts:", storage.maxHearts);
}
// --- KONIEC ZMIAN ---
player.rolling = false;
player.invulnerable = false;
player.invulnerabilityFrames = 0;
player.rollCooldown = 0;
if (player && typeof player.clearRollTimeouts === 'function') {
player.clearRollTimeouts();
}
player.hasRolledThroughBossThisRoll = false;
player.x = 2048 / 2;
player.y = 2732 / 2 + 400;
player.alpha = 1;
player.dead = false;
if (boss && boss.destroy) {
boss.destroy();
}
boss = game.addChild(new Boss());
boss.x = 2048 / 2;
boss.y = 2732 / 2 - 400;
boss.alpha = 1;
boss.attackCooldown = 90;
boss.attacks = [];
boss.attackSpeedMultiplier = 1;
boss.repositioning = false;
boss.dead = false;
boss.phase = 1;
boss.tint = 0xFFFFFF;
boss.scale.set(1, 1);
if (isNewBossPlusMode) {
boss.maxHealth = 700;
boss.health = boss.maxHealth;
this.gameDuration = 600;
boss.attackSpeedMultiplier = 0.6;
boss.speed = 7;
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");
// --- POCZĄTEK ZMIAN ---
if (ui && player) {
ui.updateHearts(player.health, parseInt(storage.maxHearts, 10) || 5);
} else if (ui) {
ui.updateHearts(0, parseInt(storage.maxHearts, 10) || 5);
}
// --- KONIEC ZMIAN ---
ui.showTutorial("Swipe to Roll!");
ui.updateBossHealth(boss.health, boss.maxHealth);
if (coffinMemeImage && !coffinMemeImage.destroyed) {
coffinMemeImage.destroy();
coffinMemeImage = null;
}
try {
var assetMeta = null;
try {
assetMeta = LK.getAssetMeta('coffinDanceMeme');
} catch (metaError) {
console.warn("LK.getAssetMeta nie działa lub brak assetu 'coffinDanceMeme'. Używam domyślnej wysokości 200.");
}
var memeHeight = assetMeta ? assetMeta.height : 200;
coffinMemeImage = LK.getAsset('coffinDanceMeme', {
anchorX: 0.5,
anchorY: 1.0,
x: 2048 / 2,
y: 2732 + memeHeight,
alpha: 1
});
var uiIndex = game.getChildIndex(ui);
if (uiIndex > -1) {
game.addChildAt(coffinMemeImage, uiIndex);
} else {
game.addChild(coffinMemeImage);
}
console.log("Coffin dance meme załadowany i schowany, y start:", coffinMemeImage.y, "Height:", memeHeight);
} catch (e) {
console.error("Nie udało się stworzyć/załadować assetu coffinDanceMeme:", e);
coffinMemeImage = null;
}
this.remainingTime = this.gameDuration;
ui.updateTimerDisplay(this.remainingTime);
if (this.gameTimerInterval) {
LK.clearInterval(this.gameTimerInterval);
}
this.bossSpeedIncreased = false;
this.gameTimerInterval = LK.setInterval(function () {
if (_this.currentState === "game") {
_this.remainingTime--;
ui.updateTimerDisplay(_this.remainingTime);
if (!isNewBossPlusMode) {
var accelerationThreshold = _this.gameDuration - 60;
if (_this.remainingTime <= accelerationThreshold && !_this.bossSpeedIncreased) {
_this.bossSpeedIncreased = true;
if (boss && !boss.dead) {
boss.attackSpeedMultiplier *= 0.7;
boss.speed += 1;
ui.showMessage("Boss attacks faster!", 2000);
}
}
}
if (_this.remainingTime <= 0) {
LK.clearInterval(_this.gameTimerInterval);
_this.gameTimerInterval = null;
if (isNewBossPlusMode) {
gameOverReasonIsDeath = false;
gameState.gameOver(false);
} else {
gameState.showGrillScreen();
}
}
} else {
if (_this.gameTimerInterval) {
LK.clearInterval(_this.gameTimerInterval);
}
_this.gameTimerInterval = null;
}
}, 1000);
LK.setTimeout(function () {
if (_this.currentState === "game") {
ui.hideTutorial();
}
}, 3000);
},
// Koniec funkcji startGame
// victory() - nieużywane, logika w timerze i boss.die()
showGrillScreen: function showGrillScreen() {
this.rollMasterTime = 0; // <-- DODAJ TĘ LINIĘ
isNewBossPlusMode = false;
console.log("State: Grill Menu (isNewBossPlusMode reset to false)");
// Wyczyść timery (pozostaw ten fragment)
if (this.gameTimerInterval) {
LK.clearInterval(this.gameTimerInterval);
}
this.gameTimerInterval = null;
if (this.fakeTutorialTimerId) {
LK.clearTimeout(this.fakeTutorialTimerId);
}
this.fakeTutorialTimerId = null;
this.bossSpeedIncreased = false; // Reset flagi
// --- DODANY KOD: Tablica do przechowywania efektów z menu grilla ---
gameState.grillMenuEffects = []; // Tworzymy nową tablicę przy wejściu do menu
// --- KONIEC DODANEGO KODU ---
// --- Rozpoczęcie agresywnego czyszczenia sceny ---
// Stwórz listę obiektów (kontenerów, UI, ścian), które powinny pozostać w 'game'.
// Upewnij się, że 'ui', 'currentSceneElements' i wszystkie obiekty w tablicy 'walls' są tutaj.
var childrenToKeep = [ui, currentSceneElements].concat(walls);
// Przejdź przez wszystkie dzieci głównego obiektu 'game' od końca do początku
for (var i = game.children.length - 1; i >= 0; i--) {
var child = game.children[i];
// Sprawdź, czy obecne dziecko NIE jest na liście obiektów do zachowania
if (childrenToKeep.indexOf(child) === -1) {
// console.log("Usuwam obiekt:", child); // Możesz tymczasowo odkomentować do debugowania
// Usuń i zniszcz obiekt
if (child && child.destroy) {
child.destroy(); // Użyj destroy jeśli dostępne (zalecane w LK)
} else if (child && child.parent) {
child.parent.removeChild(child); // Fallback - usuń z rodzica
}
}
}
// Po agresywnym czyszczeniu, upewnij się, że globalne zmienne gracza, bossa i tła są null
// Te obiekty powinny zostać usunięte przez powyższą pętlę, ale warto zresetować zmienne.
currentBackground = null;
player = null;
boss = null;
// --- Koniec agresywnego czyszczenia sceny ---
this.currentState = "grillMenu";
game.setBackgroundColor(0x333333); // Tło grilla
// Teraz dodaj nowe tło dla ekranu Grilla (ten fragment zostaje taki, jak go poprawiliśmy ostatnio - JEDNO dodanie grillMenu)
currentBackground = LK.getAsset('grillMenu', {
anchorX: 0.5,
anchorY: 0.5,
x: 2048 / 2,
y: 2732 / 2
});
// Dodaj nowe tło na spód
if (currentBackground) {
// Sprawdź czy asset został poprawnie załadowany
game.addChildAt(currentBackground, 0);
}
var confirmRestButton = null;
// ✨ DODAJEMY 5 SPARKLINGÓW ✨
// Sparkle 1
var sparkle1 = game.addChild(LK.getAsset('sparkle', {
anchorX: 0.5,
anchorY: 0.5,
x: 2048 / 2 - 460,
y: 2732 / 2 + 690
}));
gameState.grillMenuEffects.push(sparkle1); // <--- DODANA LINIJA
// Sparkle 2
var sparkle2 = game.addChild(LK.getAsset('sparkle', {
anchorX: 0.5,
anchorY: 0.5,
x: 2048 / 2 - 520,
y: 2732 / 2 + 580
}));
gameState.grillMenuEffects.push(sparkle2); // <--- DODANA LINIJA
// Sparkle 3
var sparkle3 = game.addChild(LK.getAsset('sparkle', {
anchorX: 0.5,
anchorY: 0.5,
x: 2048 / 2 - 660,
y: 2732 / 2 + 550
}));
gameState.grillMenuEffects.push(sparkle3); // <--- DODANA LINIJA
// Sparkle 4
var sparkle4 = game.addChild(LK.getAsset('sparkle', {
anchorX: 0.5,
anchorY: 0.5,
x: 2048 / 2 - 500,
y: 2732 / 2 + 680
}));
gameState.grillMenuEffects.push(sparkle4); // <--- DODANA LINIJA
// Sparkle 5
var sparkle5 = game.addChild(LK.getAsset('sparkle', {
anchorX: 0.5,
anchorY: 0.5,
x: 2048 / 2 - 620,
y: 2732 / 2 + 720
}));
gameState.grillMenuEffects.push(sparkle5); // <--- DODANA LINIJA
// Funkcja animacji sparkle
function animateSparkle(s) {
// Sprawdzenie stanu (pozostaje bez zmian)
if (gameState.currentState !== "grillMenu" || !s || s.destroyed) {
return;
}
// ZAPISZ pozycję Y na początku TEGO cyklu animacji
var initialYThisCycle = s.y;
s.alpha = 0; // Rozpocznij od przezroczystości 0
// Pozycję Y ustawimy za chwilę, nie ma potrzeby resetowania jej tutaj od razu
// Animacja pojawienia się i ruchu w górę
tween(s, {
alpha: 1,
y: initialYThisCycle - 40 // Przesuń w górę WZGLĘDEM ZAPISANEJ pozycji
}, {
duration: 800,
easing: tween.easeOut,
onFinish: function onFinish() {
// Po zakończeniu ruchu w górę
if (gameState.currentState !== "grillMenu" || !s || s.destroyed) {
return;
}
// Animacja zanikania (na tej wyższej pozycji)
tween(s, {
alpha: 0
}, {
duration: 300,
easing: tween.easeIn,
onFinish: function onFinish() {
// Po zakończeniu zanikania
if (gameState.currentState !== "grillMenu" || !s || s.destroyed) {
return;
}
// *** KLUCZOWA ZMIANA: Resetuj pozycję Y PRZED następnym cyklem ***
s.y = initialYThisCycle; // <-- WRÓĆ do pozycji Y z początku tego cyklu
// Zaplanuj kolejny cykl animacji
LK.setTimeout(function () {
animateSparkle(s); // Wywołaj kolejny cykl (teraz zacznie z poprawnej pozycji Y)
}, Math.random() * 500 + 300);
}
});
}
});
}
// Start animacji sparklingów
// Upewnij się, że animacje startują z pewnym opóźnieniem po dodaniu do tablicy
LK.setTimeout(function () {
if (gameState.currentState === "grillMenu") {
animateSparkle(sparkle1);
}
}, Math.random() * 500);
LK.setTimeout(function () {
if (gameState.currentState === "grillMenu") {
animateSparkle(sparkle2);
}
}, Math.random() * 500 + 500);
LK.setTimeout(function () {
if (gameState.currentState === "grillMenu") {
animateSparkle(sparkle3);
}
}, Math.random() * 500 + 1000);
LK.setTimeout(function () {
if (gameState.currentState === "grillMenu") {
animateSparkle(sparkle4);
}
}, Math.random() * 500 + 1500);
LK.setTimeout(function () {
if (gameState.currentState === "grillMenu") {
animateSparkle(sparkle5);
}
}, Math.random() * 500 + 2000);
// 🌟 DODAJEMY 3 GWIAZDKI NA NIEBIE 🌟
// Star 1
var star1 = game.addChild(LK.getAsset('star', {
anchorX: 0.5,
anchorY: 0.5,
x: 2048 / 2 - 800,
y: 2732 / 2 - 1000
}));
gameState.grillMenuEffects.push(star1); // <--- DODANA LINIJA
// Star 2
var star2 = game.addChild(LK.getAsset('star', {
anchorX: 0.5,
anchorY: 0.5,
x: 2048 / 2 - 300,
y: 2732 / 2 - 1150
}));
gameState.grillMenuEffects.push(star2); // <--- DODANA LINIJA
// Star 3
var star3 = game.addChild(LK.getAsset('star', {
anchorX: 0.5,
anchorY: 0.5,
x: 2048 / 2 - 200,
y: 2732 / 2 - 900
}));
gameState.grillMenuEffects.push(star3); // <--- DODANA LINIJA
// Funkcja animacji gwiazdek
function animateStar(star) {
// --- DODANE SPRAWDZENIE STANU ---
if (gameState.currentState !== "grillMenu" || !star || star.destroyed) {
return;
}
// --- KONIEC DODANEGO SPRAWDZENIA ---
star.alpha = 0;
tween(star, {
alpha: 1
}, {
duration: 2000,
easing: tween.easeIn,
onFinish: function onFinish() {
// --- DODANE SPRAWDZENIE STANU W CALLBACKU ---
if (gameState.currentState !== "grillMenu" || !star || star.destroyed) {
return;
}
// --- KONIEC DODANEGO SPRAWDZENIA ---
tween(star, {
alpha: 0
}, {
duration: 2000,
easing: tween.easeOut,
onFinish: function onFinish() {
// --- DODANE SPRAWDZENIE STANU W WEWNĘTRZNYM CALLBACKU ---
if (gameState.currentState !== "grillMenu" || !star || star.destroyed) {
return;
}
// --- KONIEC DODANEGO SPRAWDZENIA ---
// Po całym cyklu czekamy losowo 2-5 sekund przed kolejnym pojawieniem się
LK.setTimeout(function () {
animateStar(star);
}, Math.random() * 3000 + 2000);
}
});
}
});
}
// Start animacji gwiazdek
// Upewnij się, że animacje startują z pewnym opóźnieniem po dodaniu do tablicy
LK.setTimeout(function () {
if (gameState.currentState === "grillMenu") {
animateStar(star1);
}
}, Math.random() * 1000);
LK.setTimeout(function () {
if (gameState.currentState === "grillMenu") {
animateStar(star2);
}
}, Math.random() * 1000 + 500);
LK.setTimeout(function () {
if (gameState.currentState === "grillMenu") {
animateStar(star3);
}
}, Math.random() * 1000 + 1000);
// --- KONIEC gwiazdek ---
// --- KONIEC sparklingów ---
// Ukryj ściany (jeśli są widoczne w menu)
walls.forEach(function (wall) {
if (wall) {
wall.alpha = 0;
}
});
// Ustaw UI dla Grill Menu
ui.positionElements("grillMenu");
ui.updateBossHealth(0, 1); // Ukryj pasek HP bossa
ui.updateHearts(0, storage.maxHearts); // Ukryj serca gracza
// --- Przyciski na ekranie Grilla ---
var buttonYStart = 1000;
var buttonYOffset = 150;
// Przycisk "Rest"
var restButton = new Container();
restButton.interactive = true;
restButton.cursor = "pointer";
restButton.x = 600;
restButton.y = 650;
currentSceneElements.addChild(restButton);
var restButtonBg = LK.getAsset('buttonRest', {
anchorX: 0.5,
anchorY: 0.5
});
restButton.addChild(restButtonBg);
restButton.down = function () {
if (!confirmRestButton || confirmRestButton && confirmRestButton.destroyed) {
confirmRestButton = new Container();
confirmRestButton.interactive = true;
confirmRestButton.cursor = "pointer";
// Zamieniamy tekst i tło na konkretny asset
var confirmButtonGraphic = LK.getAsset('confirmRestButton', {
anchorX: 0.5,
anchorY: 0.5
});
confirmRestButton.addChild(confirmButtonGraphic);
var restButtonActualWidth = restButtonBg.width || 500;
var confirmButtonActualWidth = confirmButtonGraphic.width || 550;
var paddingBetweenButtons = 30;
confirmRestButton.x = restButton.x + restButtonActualWidth / 2 + confirmButtonActualWidth / 2 + paddingBetweenButtons;
confirmRestButton.y = restButton.y;
currentSceneElements.addChild(confirmRestButton);
confirmRestButton.down = function () {
var farewellGraphic = null;
try {
farewellGraphic = LK.getAsset('grillMenuFarewellGraphic', {
anchorX: 0.5,
anchorY: 0.5,
x: 2048 / 2,
y: 2732 / 2,
alpha: 1 // od razu widoczna
});
currentSceneElements.addChild(farewellGraphic);
LK.setTimeout(function () {
if (gameState.currentState === "grillMenu" && farewellGraphic && !farewellGraphic.destroyed) {
tween(farewellGraphic, {
alpha: 0
}, {
duration: 500,
onFinish: function onFinish() {
if (farewellGraphic && !farewellGraphic.destroyed) {
currentSceneElements.removeChild(farewellGraphic);
farewellGraphic.destroy();
}
if (confirmRestButton && !confirmRestButton.destroyed) {
currentSceneElements.removeChild(confirmRestButton);
confirmRestButton.destroy();
confirmRestButton = null;
}
}
});
} else if (farewellGraphic && !farewellGraphic.destroyed) {
currentSceneElements.removeChild(farewellGraphic);
farewellGraphic.destroy();
}
}, 6000);
} catch (e) {
console.error("Błąd grillMenuFarewellGraphic:", e);
farewellGraphic = new Shape({
width: 2048,
height: 2732,
color: 0x101030
});
farewellGraphic.x = 2048 / 2;
farewellGraphic.y = 2732 / 2;
farewellGraphic.alpha = 0;
currentSceneElements.addChild(farewellGraphic);
tween(farewellGraphic, {
alpha: 0.8
}, {
duration: 500
});
LK.setTimeout(function () {
if (farewellGraphic && !farewellGraphic.destroyed) {
tween(farewellGraphic, {
alpha: 0
}, {
duration: 500,
onFinish: function onFinish() {
if (farewellGraphic && !farewellGraphic.destroyed) {
currentSceneElements.removeChild(farewellGraphic);
farewellGraphic.destroy();
}
if (confirmRestButton && !confirmRestButton.destroyed) {
currentSceneElements.removeChild(confirmRestButton);
confirmRestButton.destroy();
confirmRestButton = null;
}
}
});
}
}, 6000);
if (ui && ui.showMessage) {
ui.showMessage("Błąd: Brak assetu grafiki. Placeholder.", 3000);
}
}
};
} else {
console.log("Przycisk 'Potwierdź' już istnieje.");
}
};
// Przycisk "Upgrade Roll" (TEN I KOLEJNE PRZYCISKI POZOSTAJĄ BEZ ZMIAN)
var upgradeButton = new Container();
upgradeButton.interactive = true;
upgradeButton.x = 600;
upgradeButton.y = 850;
currentSceneElements.addChild(upgradeButton);
var upgradeButtonBg = LK.getAsset('buttonUpgrade', {
anchorX: 0.5,
anchorY: 0.5
});
upgradeButton.addChild(upgradeButtonBg);
upgradeButton.down = function () {
if (currentBackground) {
tween(currentBackground, {
alpha: 0
}, {
duration: 600,
easing: tween.easeIn,
onFinish: function onFinish() {
currentBackground.destroy();
currentBackground = LK.getAsset('upgradebg', {
anchorX: 0.5,
anchorY: 0.5,
x: 2048 / 2,
y: 2732 / 2,
alpha: 0
});
game.addChildAt(currentBackground, 0);
tween(currentBackground, {
alpha: 1
}, {
duration: 1200,
easing: tween.easeOut
});
}
});
}
};
// Przycisk "New Boss+"
var newBossButton = new Container();
newBossButton.interactive = true;
newBossButton.cursor = "pointer";
newBossButton.x = 600;
newBossButton.y = 1050; // jeszcze niżej
currentSceneElements.addChild(newBossButton); // Dodaj do kontenera sceny
var newBossButtonBg = LK.getAsset('buttonBoss', {
anchorX: 0.5,
anchorY: 0.5
});
newBossButton.addChild(newBossButtonBg);
newBossButton.down = function () {
isNewBossPlusMode = true;
// Poprzedni kod usuwający sparkle tutaj jest już niepotrzebny,
// ponieważ obsługa usuwania jest teraz w startGame z wykorzystaniem gameState.grillMenuEffects
gameState.startGame();
};
// Przycisk "Roll Master"
var rollMasterButton = new Container(); // Tworzymy kontener na przycisk
rollMasterButton.interactive = true; // Ustawiamy interaktywność
rollMasterButton.cursor = "pointer"; // Zmieniamy kursor po najechaniu
rollMasterButton.x = 600; // Ta sama pozycja X co inne przyciski
rollMasterButton.y = 2500; // Pozycja Y pod przyciskiem "New Boss+"
currentSceneElements.addChild(rollMasterButton); // Dodajemy kontener do sceny
// --- NOWY KOD TŁA ---
// Pobieramy asset obrazka dla przycisku Roll Master
var rollMasterButtonImage = LK.getAsset('buttonRollMaster', {
anchorX: 0.5,
// Ustawiamy punkt zaczepienia na środek obrazka
anchorY: 0.5
});
rollMasterButton.addChild(rollMasterButtonImage); // Dodajemy obrazek do kontenera przycisku
// --- KONIEC NOWEGO KODU TŁA ---
// Usunęliśmy kod tworzący i dodający obiekt Text2
// Akcja przycisku (kliknięcie) pozostaje bez zmian
rollMasterButton.down = function () {
gameState.startRollMasterMode(); // Uruchom tryb Roll Master
};
// 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 = 30; // 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.currentRollSoulsInstance && this.currentRollSoulsInstance.stop) {
console.log("[SetupRollMaster] Zatrzymywanie currentRollSoulsInstance.");
if (typeof this.currentRollSoulsInstance.volume === 'number') {
this.currentRollSoulsInstance.volume = 0;
}
this.currentRollSoulsInstance.stop();
this.currentRollSoulsInstance = null;
}
// Zatrzymaj poprzednią instancję rollmaster music, jeśli jakimś cudem istnieje
if (this.currentRollMasterMusicInstance && this.currentRollMasterMusicInstance.stop) {
console.log("[SetupRollMaster] Zatrzymywanie poprzedniej currentRollMasterMusicInstance.");
if (typeof this.currentRollMasterMusicInstance.volume === 'number') {
this.currentRollMasterMusicInstance.volume = 0;
}
this.currentRollMasterMusicInstance.stop();
// this.currentRollMasterMusicInstance = null; // Zerujemy, bo zaraz przypiszemy nową
}
console.log("[SetupRollMaster] Odtwarzanie muzyki 'rollmaster'.");
this.currentRollMasterMusicInstance = LK.playMusic('rollmaster', {
loop: true,
volume: 0.7,
fade: {
start: 0,
end: 0.7,
duration: 1000
}
});
if (!this.currentRollMasterMusicInstance) {
console.error("[SetupRollMaster] LK.playMusic('rollmaster') nie zwróciło instancji!");
}
// --- KONIEC SEKCJI ZARZĄDZANIA MUZYKĄ ---
try {
currentBackground = LK.getAsset('rollMasterBg', {
anchorX: 0.5,
anchorY: 0.5,
x: 2048 / 2,
y: 2732 / 2
});
game.addChildAt(currentBackground, 0);
} catch (e) {
console.warn("Nie znaleziono assetu 'rollMasterBg', używam koloru tła.");
game.setBackgroundColor(0x222233);
}
if (typeof Player !== 'undefined') {
player = game.addChild(new Player());
player.x = 2048 / 2;
player.y = 2732 / 2 + 400;
player.health = 1;
player.rolling = false;
player.invulnerable = false;
player.invulnerabilityFrames = 0;
player.rollCooldown = 0;
if (player && typeof player.clearRollTimeouts === 'function') {
player.clearRollTimeouts();
}
player.hasRolledThroughBossThisRoll = false;
player.alpha = 1;
player.dead = false;
} else {
console.error("Klasa Player nie jest zdefiniowana!");
this.showGrillScreen();
return;
}
this.rollMasterTime = 0;
this.rollMasterDifficulty = 1;
this.rollMasterAttacks = [];
this.attackUnlockOrder = ['rmattack1', 'rmattack2', 'rmattack3', 'rmattack4'];
this.unlockedAttacks = ['rmattack1'];
this.nextUnlockTime = 15;
console.log("Ataki startowe:", this.unlockedAttacks, "Następne odblokowanie w:", this.nextUnlockTime + "s");
this.attackSpawnTimer = 0;
this.attackSpawnInterval = 120;
this.explosionSpawnTimer = 0;
this.explosionSpawnInterval = 240;
this.laserSpawnTimer = 0;
this.laserSpawnInterval = 300;
this.laserWarningTime = 1500;
this.spreaderSpawnTimer = 0;
this.spreaderSpawnInterval = 600;
this.spreaderSplitTime = 180;
if (this.randomRmattack1Timer) {
LK.clearTimeout(this.randomRmattack1Timer);
this.randomRmattack1Timer = null;
}
this.rollMasterHighScore = storage.rollMasterHighScore || 0;
if (ui && ui.updateHighScoreDisplay) {
ui.updateHighScoreDisplay(this.rollMasterHighScore);
} else {
console.error("Nie można zaktualizować wyświetlania rekordu - brak ui.updateHighScoreDisplay");
}
if (ui && ui.timerText) {
ui.updateTimerDisplay(this.rollMasterTime);
} else {
console.error("Nie można znaleźć ui.timerText do skonfigurowania!");
}
if (ui && ui.updateHearts) {
ui.updateHearts(1, 1);
}
if (ui && ui.updateBossHealth) {
ui.updateBossHealth(0, 1);
}
if (ui && ui.positionElements) {
ui.positionElements("rollMaster");
} else {
console.error("Nie można ustawić elementów UI dla Roll Master - brak ui.positionElements");
}
if (this.rollMasterTimerInterval) {
LK.clearInterval(this.rollMasterTimerInterval);
}
this.rollMasterTimerInterval = LK.setInterval(function () {
if (_this2.currentState !== "rollMaster") {
LK.clearInterval(_this2.rollMasterTimerInterval);
_this2.rollMasterTimerInterval = null;
return;
}
_this2.rollMasterTime++;
if (ui && ui.updateTimerDisplay) {
ui.updateTimerDisplay(_this2.rollMasterTime);
}
if (_this2.rollMasterTime > 0 && _this2.rollMasterTime % 20 === 0) {
var newAttackJustUnlocked = false;
var newAttackFriendlyName = "";
if (_this2.unlockedAttacks.length < _this2.attackUnlockOrder.length) {
var nextAttackIndex = _this2.unlockedAttacks.length;
var attackToUnlockTechnicalName = _this2.attackUnlockOrder[nextAttackIndex];
_this2.unlockedAttacks.push(attackToUnlockTechnicalName);
newAttackJustUnlocked = true;
newAttackFriendlyName = attackToUnlockTechnicalName.replace('rmattack1', 'Fart of the Forgotten Pyromancer').replace('rmattack2', 'Blood Ball Z').replace('rmattack3', 'Holy Wifi Beam').replace('rmattack4', 'Flame Skull.exe');
console.log("Odblokowano nowy atak:", attackToUnlockTechnicalName, "w", _this2.rollMasterTime + "s");
}
_this2.increaseRollMasterDifficulty();
if (ui && ui.showMessage) {
var messageToShow = "";
var currentDifficulty = _this2.rollMasterDifficulty;
if (newAttackJustUnlocked) {
messageToShow = "New Attack: " + newAttackFriendlyName + "! (Difficulty: " + currentDifficulty + ")";
} else {
switch (currentDifficulty) {
case 2:
case 3:
case 4:
messageToShow = "Even the pause button is afraid.";
break;
case 5:
messageToShow = "Alright, that’s probably enough difficulty... (Poziom: " + currentDifficulty + ")";
break;
case 6:
messageToShow = "Wait, are you still playing? I ran out of messages. (Poziom: " + currentDifficulty + ")";
break;
case 7:
messageToShow = "Okay seriously, stop. I didn’t test this far. (Poziom: " + currentDifficulty + ")";
break;
default:
messageToShow = "I stopped writing messages after level 7. You're on your own. Level: " + currentDifficulty;
break;
}
}
ui.showMessage(messageToShow, 2500);
}
}
}, 1000);
if (ui && ui.showMessage && this.unlockedAttacks.length > 0) {
var firstAttackName = this.unlockedAttacks[0];
var friendlyFirstName = firstAttackName.replace('rmattack1', 'Fart of the Forgotten Pyromancer').replace('rmattack2', 'Blood Ball Z').replace('rmattack3', 'Holy Wifi Beam').replace('rmattack4', 'Flame Skull.exe');
LK.setTimeout(function () {
if (gameState.currentState === "rollMaster") {
ui.showMessage("First Attack: " + friendlyFirstName, 3000);
}
}, 1000);
}
console.log("Scena Roll Master skonfigurowana i muzyka 'rollmaster' powinna grać.");
},
// <-- WAŻNE: Przecinek tutaj
// --- NOWA FUNKCJA: Zwiększanie trudności w Roll Master ---
increaseRollMasterDifficulty: function increaseRollMasterDifficulty() {
this.rollMasterDifficulty++;
console.log("Trudność Roll Master zwiększona do:", this.rollMasterDifficulty);
var decreaseAmountProjectile = 10;
var minIntervalProjectile = 30;
this.attackSpawnInterval = Math.max(minIntervalProjectile, this.attackSpawnInterval - decreaseAmountProjectile);
console.log("Nowy interwał projectile:", this.attackSpawnInterval);
var decreaseAmountExplosion = 15;
var minIntervalExplosion = 60;
this.explosionSpawnInterval = Math.max(minIntervalExplosion, this.explosionSpawnInterval - decreaseAmountExplosion);
console.log("Nowy interwał explosion:", this.explosionSpawnInterval);
// --- NOWA LOGIKA DLA LASERA ---
var decreaseAmountLaser = 20; // Jak szybko ma skracać się interwał lasera
var minIntervalLaser = 120; // Minimalny interwał lasera (np. 2 sekundy)
this.laserSpawnInterval = Math.max(minIntervalLaser, this.laserSpawnInterval - decreaseAmountLaser);
console.log("Nowy interwał laser:", this.laserSpawnInterval);
// --- NOWA LOGIKA DLA SPREADERA ---
var decreaseAmountSpreader = 30; // Jak szybko ma skracać się interwał spreadera
var minIntervalSpreader = 180; // Minimalny interwał spreadera (np. 3 sekundy)
this.spreaderSpawnInterval = Math.max(minIntervalSpreader, this.spreaderSpawnInterval - decreaseAmountSpreader);
console.log("Nowy interwał spreader:", this.spreaderSpawnInterval);
},
// Przejście do stanu Game Over
// isDeath: true (gracz zginął), false (czas minął w Boss+)
gameOver: function gameOver(isDeath) {
console.log("DEBUG: gameState.gameOver(", isDeath, ") called! State:", this.currentState, "Boss dead:", boss ? boss.dead : 'N/A', "Player dead:", player ? player.dead : 'N/A');
console.log("[GameOver] Stan gry Game Over. Przyczyna śmierć:", isDeath);
this.currentState = "gameOver";
gameOverReasonIsDeath = isDeath;
// Zatrzymaj timery i inne procesy gry
if (this.gameTimerInterval) {
LK.clearInterval(this.gameTimerInterval);
this.gameTimerInterval = null;
}
if (this.rollMasterTimerInterval) {
LK.clearInterval(this.rollMasterTimerInterval);
this.rollMasterTimerInterval = null;
}
this.bossSpeedIncreased = false;
// Zatrzymaj animację tła, jeśli istnieje
if (currentBackground) {
tween.stop(currentBackground);
}
game.setBackgroundColor(0x000000);
// Ukryj gracza i bossa (przez alpha)
if (player && player.alpha !== 0) {
player.alpha = 0;
}
if (boss && boss.alpha !== 0) {
boss.alpha = 0;
}
walls.forEach(function (wall) {
if (wall) {
wall.alpha = 0;
}
});
// Ustaw elementy UI dla stanu gameOver
ui.positionElements("gameOver");
ui.titleText.alpha = 0;
ui.messageText.alpha = 0;
ui.showTutorial("");
// --- POCZĄTEK ZMIAN: Animacje memów ---
// Rozpocznij DŁUGI FADE-OUT dla coffinDanceMeme
if (coffinMemeImage && !coffinMemeImage.destroyed) {
console.log("DEBUG: Rozpoczynanie długiego fade-out (5s) dla coffinDanceMeme w gameOver.");
// Upewnij się, że jest widoczny i na dole ekranu przed animacją
coffinMemeImage.alpha = 1.0;
coffinMemeImage.y = 2932;
tween.stop(coffinMemeImage); // Zatrzymaj poprzednie tweeny
tween(coffinMemeImage, {
alpha: 0
}, {
duration: 2500,
// Czas zanikania = czas trwania ekranu gameOver
easing: tween.easeIn,
onFinish: function onFinish() {
// Zniszcz po zakończeniu animacji
if (coffinMemeImage && !coffinMemeImage.destroyed) {
console.log("DEBUG: CoffinDanceMeme fade-out zakończony, niszczenie.");
if (coffinMemeImage.parent) {
coffinMemeImage.parent.removeChild(coffinMemeImage);
}
coffinMemeImage.destroy();
coffinMemeImage = null; // Zresetuj zmienną globalną
}
}
});
} else {
console.log("DEBUG: coffinDanceMeme nie istnieje lub zniszczony w gameOver - nie można animować fade-out.");
}
// Wyświetl losowy mem śmierci z FADE-IN
var memeAssets = ['deathMeme1', 'deathMeme2', 'deathMeme3', 'deathMeme4', 'deathMeme5'];
var randomMemeAssetName = memeAssets[Math.floor(Math.random() * memeAssets.length)];
console.log("[GameOver] Wybrano mema:", randomMemeAssetName);
try {
var memeImage = LK.getAsset(randomMemeAssetName, {
anchorX: 0.5,
anchorY: 0.5,
alpha: 0 // Start invisible
});
memeImage.x = 2048 / 2;
memeImage.y = 2732 / 2 - 200;
memeImage.scale.set(0.8);
currentSceneElements.addChild(memeImage); // Dodaj do currentSceneElements
// Animacja Fade-In (0.5 sekundy)
tween(memeImage, {
alpha: 1
}, {
duration: 500,
easing: tween.easeIn
});
} catch (e) {
console.error("[GameOver] Błąd podczas ładowania assetu mema:", randomMemeAssetName, e);
ui.titleText.setText("YOU DIED (anyway)");
ui.titleText.alpha = 1;
}
// --- KONIEC ZMIAN: Animacje memów ---
// Wyświetl dodatkowy tekst
var extraTextMessage = "";
if (!isDeath && isNewBossPlusMode) {
extraTextMessage = "You just lost 10 minutes of your life. For what?";
}
if (extraTextMessage) {
var extraText = new Text2(extraTextMessage, {
size: 60,
fill: 0xFFFFFF,
align: 'center',
stroke: 0x000000,
strokeThickness: 4
});
extraText.anchor.set(0.5, 0.5);
extraText.x = 2048 / 2;
extraText.y = 2732 / 2 + 250;
currentSceneElements.addChild(extraText);
}
// Timeout przed przejściem do następnego stanu
LK.setTimeout(function () {
console.log("DEBUG: Wykonuje się timeout w gameState.gameOver. isDeath:", isDeath, "isNewBossPlusMode:", isNewBossPlusMode);
clearScene(); // Czyści losowy mem śmierci i extraText z currentSceneElements
// Niszczenie coffinDanceMeme jest teraz w onFinish jego tweentu
// Zniszcz gracza, bossa, tło...
if (player && player.destroy && !player.destroyed) {
player.destroy();
}
player = null;
if (boss && boss.destroy && !boss.destroyed) {
boss.destroy();
}
boss = null;
if (currentBackground) {
currentBackground.destroy();
}
currentBackground = null;
// Resetuj UI
ui.titleText.setText("");
ui.titleText.alpha = 0;
ui.messageText.setText("");
ui.messageText.alpha = 0;
ui.tutorialText.setText("");
ui.tutorialText.alpha = 0;
ui.updateDeathsCounter();
ui.updateBossHealth(0, 1);
ui.updateHearts(0, parseInt(storage.maxHearts, 10) || 5);
// Logika przejścia do następnego stanu
if (isDeath && !isNewBossPlusMode) {
console.log("gameOver timeout: Standard death -> Restarting standard game...");
isNewBossPlusMode = false;
if (typeof gameState.startGame === 'function') {
gameState.startGame();
} else {
console.error("Nie można wywołać startGame po standardowej śmierci!");
}
} else if (isDeath && isNewBossPlusMode) {
console.log("gameOver timeout: Boss+ death -> Going to Grill Menu...");
isNewBossPlusMode = false;
if (typeof gameState.showGrillScreen === 'function') {
gameState.showGrillScreen();
} else {
console.error("Nie można wywołać showGrillScreen po śmierci w Boss+!");
}
} else if (!isDeath && isNewBossPlusMode) {
console.log("gameOver timeout: Boss+ timeout -> Going to Grill Menu...");
isNewBossPlusMode = false;
if (typeof gameState.showGrillScreen === 'function') {
gameState.showGrillScreen();
} else {
console.error("Nie można wywołać showGrillScreen po timeout w Boss+!");
}
} else {
console.log("gameOver timeout: Fallback case -> Going to Grill Menu...");
isNewBossPlusMode = false;
if (typeof gameState.showGrillScreen === 'function') {
gameState.showGrillScreen();
} else {
console.error("Nie można wywołać showGrillScreen w fallbacku gameOver!");
}
}
}, 5000);
},
// --- NOWA FUNKCJA: Zakończenie trybu Roll Master ---
endRollMasterMode: function endRollMasterMode(finalTime) {
var _this3 = this;
if (this.currentState === "rollMasterGameOver") {
console.log("DEBUG: Próba ponownego wejścia do endRollMasterMode. Zatrzymano.");
return;
}
console.log("[EndRollMaster] Zakończono tryb Roll Master. Czas:", finalTime);
this.currentState = "rollMasterGameOver";
// Zatrzymaj muzykę i timery etc.
if (this.currentRollMasterMusicInstance && this.currentRollMasterMusicInstance.playing) {/* ... log ... */}
if (this.rollMasterTimerInterval) {
LK.clearInterval(this.rollMasterTimerInterval);
this.rollMasterTimerInterval = null;
}
if (this.randomRmattack1Timer) {
LK.clearTimeout(this.randomRmattack1Timer);
this.randomRmattack1Timer = null;
}
if (this.rollMasterAttacks && this.rollMasterAttacks.length > 0) {/* ... czyszczenie ataków ... */}
if (player && player.clearRollTimeouts) {
player.clearRollTimeouts();
}
// Sprawdź rekord
var oldHighScore = this.rollMasterHighScore || 0;
if (finalTime > oldHighScore) {/* ... zapisz rekord ... */}
game.setBackgroundColor(0x000000); // Czarne tło
if (player && player.alpha !== 0) {
player.alpha = 0;
} // Ukryj gracza
// Wyświetl losowy mem śmierci z DŁUŻSZYM FADE-IN
var memeAssets = ['deathMeme1', 'deathMeme2', 'deathMeme3', 'deathMeme4', 'deathMeme5'];
var randomMemeAssetName = memeAssets[Math.floor(Math.random() * memeAssets.length)];
try {
var memeImage = LK.getAsset(randomMemeAssetName, {
anchorX: 0.5,
anchorY: 0.5,
alpha: 0
});
memeImage.x = 2048 / 2;
memeImage.y = 2732 / 2 - 200;
memeImage.scale.set(0.8);
currentSceneElements.addChild(memeImage);
// Wydłużony fade-in (1.5 sekundy)
tween(memeImage, {
alpha: 1
}, {
duration: 1500,
easing: tween.easeIn
});
} catch (e) {
console.error("[EndRollMaster] Błąd ładowania mema:", e);
}
// Wyświetl tekst z czasem
var timeMessage = "Your Time: " + String(Math.floor(finalTime / 60)).padStart(2, '0') + ':' + String(finalTime % 60).padStart(2, '0');
var timeText = new Text2(timeMessage, {
size: 60,
fill: 0xFFFFFF,
align: 'center',
stroke: 0x000000,
strokeThickness: 4
});
timeText.anchor.set(0.5, 0.5);
timeText.x = 2048 / 2;
timeText.y = 2732 / 2 + 250;
currentSceneElements.addChild(timeText);
// Timeout przed przejściem do grillMenu
LK.setTimeout(function () {
console.log("DEBUG: Wykonuje się timeout w gameState.gameOver. isDeath:", isDeath, "isNewBossPlusMode:", isNewBossPlusMode);
clearScene(); // Czyści losowy mem śmierci i extraText z currentSceneElements
// Niszczenie coffinDanceMeme jest teraz w onFinish jego tweentu
// Zniszcz gracza, bossa, tło...
if (player && player.destroy && !player.destroyed) {
player.destroy();
}
player = null;
if (boss && boss.destroy && !boss.destroyed) {
boss.destroy();
}
boss = null;
if (currentBackground) {
currentBackground.destroy();
}
currentBackground = null;
// Resetuj UI
ui.titleText.setText("");
ui.titleText.alpha = 0;
ui.messageText.setText("");
ui.messageText.alpha = 0;
ui.tutorialText.setText("");
ui.tutorialText.alpha = 0;
ui.updateDeathsCounter();
ui.updateBossHealth(0, 1);
ui.updateHearts(0, parseInt(storage.maxHearts, 10) || 5);
// Logika przejścia do następnego stanu
if (isDeath && !isNewBossPlusMode) {
console.log("gameOver timeout: Standard death -> Restarting standard game...");
isNewBossPlusMode = false;
if (typeof gameState.startGame === 'function') {
gameState.startGame();
} else {
console.error("Nie można wywołać startGame po standardowej śmierci!");
}
} else if (isDeath && isNewBossPlusMode) {
console.log("gameOver timeout: Boss+ death -> Going to Grill Menu...");
isNewBossPlusMode = false;
if (typeof gameState.showGrillScreen === 'function') {
gameState.showGrillScreen();
} else {
console.error("Nie można wywołać showGrillScreen po śmierci w Boss+!");
}
} else if (!isDeath && isNewBossPlusMode) {
console.log("gameOver timeout: Boss+ timeout -> Going to Grill Menu...");
isNewBossPlusMode = false;
if (typeof gameState.showGrillScreen === 'function') {
gameState.showGrillScreen();
} else {
console.error("Nie można wywołać showGrillScreen po timeout w Boss+!");
}
} else {
console.log("gameOver timeout: Fallback case -> Going to Grill Menu...");
isNewBossPlusMode = false;
if (typeof gameState.showGrillScreen === 'function') {
gameState.showGrillScreen();
} else {
console.error("Nie można wywołać showGrillScreen w fallbacku gameOver!");
}
}
}, 5000);
},
startCursedCrystalMode: function startCursedCrystalMode() {
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();
},
setupCursedCrystalScene: function setupCursedCrystalScene() {
console.log("[SetupCursedCrystal] Konfiguracja sceny Cursed Crystal...");
// 1. Ustaw tło dla areny
try {
currentBackground = LK.getAsset('cursedCrystalArenaBg_asset', {
anchorX: 0,
anchorY: 0,
x: 0,
y: 0
});
game.addChildAt(currentBackground, 0);
} catch (e) {
console.warn("Nie znaleziono assetu 'cursedCrystalArenaBg_asset', używam domyślnego koloru tła gry.");
game.setBackgroundColor(0x100510);
}
// 2. Stwórz i spozycjonuj obiekt Klejnotu Dusz (CrystalCore)
try {
crystalCoreObject = LK.getAsset('crystalCore_asset', {
anchorX: 0.5,
anchorY: 0.5
});
crystalCoreObject.x = 2048 / 2;
crystalCoreObject.y = 2732 / 2;
game.addChild(crystalCoreObject);
} catch (e) {
console.error("KRYTYCZNY BŁĄD: Nie można stworzyć crystalCore_asset!", e);
crystalCoreObject = new Shape({
width: 100,
height: 100,
color: 0xFF00FF,
shape: 'box'
});
crystalCoreObject.x = 2048 / 2;
crystalCoreObject.y = 2732 / 2;
game.addChild(crystalCoreObject);
if (ui && ui.showMessage) {
ui.showMessage("CRITICAL ERROR: Crystal asset missing!", 0);
}
}
// 3. Stwórz nowy obiekt gracza
if (player && player.destroy && !player.destroyed) {
player.destroy(); // Upewnij się, że stary gracz jest zniszczony
}
player = game.addChild(new Player());
console.log("CC Scene: Player created, health from constructor: " + player.health); // Powinno być 5 (z konstruktora Playera)
player.health = this.cursedCrystalPlayerMaxHealth; // Ustaw zdrowie na max dla tego trybu
console.log("CC Scene: Player health explicitly SET to cursedCrystalPlayerMaxHealth: " + player.health + " (Max defined in gameState: " + this.cursedCrystalPlayerMaxHealth + ")");
player.x = 2048 / 2;
player.y = crystalCoreObject.y + crystalCoreObject.height / 2 + player.height / 2 + 50;
player.dead = false;
player.rolling = false;
player.invulnerable = false;
// player.rollCooldown = 0; // W normalnej scenie nie resetujemy cooldownu do 0, chyba że tak zdecydujemy
// Ustawienie w konstruktorze Playera na 0 jest tylko dla testu
if (player.clearRollTimeouts) {
// Upewnij się, że ta metoda istnieje
player.clearRollTimeouts();
}
// FIX NA RUCH (jeśli potrzebny - currentInputPos i isInputActive)
this.currentInputPos.x = player.x;
this.currentInputPos.y = player.y;
this.isInputActive = false;
// 4. Zresetuj zmienne gameState specyficzne dla "Cursed Crystal"
this.cursedCrystalPlayerHealth = this.cursedCrystalPlayerMaxHealth; // To jest kluczowe dla UI serc
console.log("CC Scene: gameState.cursedCrystalPlayerHealth INITED to: " + this.cursedCrystalPlayerHealth);
this.cursedCrystalChargeLevel = 0;
this.cursedCrystalSoulsHitCrystal = 0;
this.cursedCrystalScore = 0;
this.cursedCrystalHighScore = parseInt(storage.cursedCrystalHighScore, 10) || 0;
this.cursedCrystalEnemies = []; // Wyczyść tablicę wrogów dla nowej gry
this.cursedCrystalEnemySpawnTimer = 0;
this.cursedCrystalCurrentSpawnInterval = this.cursedCrystalBaseSpawnInterval;
this.cursedCrystalDifficultyTimer = 0;
this.cursedCrystalEnemyCurrentMaxSpeed = this.cursedCrystalEnemyBaseSpeed; // Powinno być raczej cursedCrystalEnemyBaseSpeed lub minSpeed
this.cursedCrystalTimeSurvived = 0;
this.isMinibossActiveCC = false;
if (this.cursedCrystalMinibossObject && this.cursedCrystalMinibossObject.destroy && !this.cursedCrystalMinibossObject.destroyed) {
this.cursedCrystalMinibossObject.destroy();
}
this.cursedCrystalMinibossObject = null;
this.cursedCrystalMinibossHP = 0;
// 5. Konfiguracja UI
if (ui) {
ui.positionElements("cursedCrystal");
console.log("CC Scene: Calling ui.updateHearts with playerHealth: " + this.cursedCrystalPlayerHealth + ", maxHealth: " + this.cursedCrystalPlayerMaxHealth);
ui.updateHearts(this.cursedCrystalPlayerHealth, this.cursedCrystalPlayerMaxHealth);
// Ukryj standardowy pasek HP bossa i timer (chyba że Cursed Crystal ma własny timer)
if (ui.bossHealthBarContainer) {
ui.bossHealthBarContainer.alpha = 0;
} // Lub ui.updateBossHealth(0,1);
if (ui.timerText) {
ui.timerText.alpha = 0;
} // Ukryj standardowy timer
// TODO: Tutaj wywołaj funkcje UI do aktualizacji paska naładowania klejnotu, wyniku CC etc.
// np. ui.updateCrystalCharge(this.cursedCrystalChargeLevel, this.cursedCrystalTargetCharge);
// np. ui.updateScoreCC(this.cursedCrystalScore, this.cursedCrystalHighScore);
}
// 6. Wyświetl startowy komunikat
if (ui && ui.showMessage) {
ui.showMessage("Protect the Soul Gem!", 3000);
}
console.log("[SetupCursedCrystal] Scena Cursed Crystal skonfigurowana. Klejnot na: " + crystalCoreObject.x + "," + crystalCoreObject.y + ". Gracz HP: " + player.health);
},
setupCursedCrystalScene_MinimalTest: function setupCursedCrystalScene_MinimalTest() {
// To jest początek tej nowej funkcji
console.log("[SetupCursedCrystal_MinimalTest] Konfiguracja minimalnej sceny testowej...");
// 1. Ustaw proste tło (bez assetu, tylko kolor)
game.setBackgroundColor(0x100510); // Kolor z Twojego placeholder assetu
if (currentBackground && currentBackground.destroy && !currentBackground.destroyed) {
currentBackground.destroy();
}
currentBackground = null;
// Usuń obiekt Klejnotu, jeśli istniał
if (typeof crystalCoreObject !== 'undefined' && crystalCoreObject && crystalCoreObject.destroy && !crystalCoreObject.destroyed) {
crystalCoreObject.destroy();
}
crystalCoreObject = null;
// 2. Stwórz nowy obiekt gracza (bez Klejnotu, na razie)
if (player && player.destroy && !player.destroyed) {
player.destroy();
}
player = game.addChild(new Player()); // Player() ustawi rollCooldown na 0 (zgodnie ze zmianą w konstruktorze Player dla testu)
console.log("MinimalTest: Player created, health from constructor: " + player.health);
player.health = this.cursedCrystalPlayerMaxHealth; // Użyj zmiennej z gameState
console.log("MinimalTest: Player health SET to cursedCrystalPlayerMaxHealth: " + player.health);
player.x = 2048 / 2;
player.y = 2732 / 2 + 100;
player.dead = false;
player.rolling = false;
// player.rollCooldown = 0; // Już ustawione w Player() dla tego testu
player.invulnerable = false;
if (player.clearRollTimeouts) {
player.clearRollTimeouts();
}
// FIX NA RUCH
this.currentInputPos.x = player.x;
this.currentInputPos.y = player.y;
this.isInputActive = false;
// 3. Resetuj tylko kluczowe zmienne gameState potrzebne do działania gracza
this.cursedCrystalPlayerHealth = this.cursedCrystalPlayerMaxHealth;
console.log("MinimalTest: gameState.cursedCrystalPlayerHealth INITED to: " + this.cursedCrystalPlayerHealth);
this.cursedCrystalChargeLevel = 0;
this.cursedCrystalScore = 0;
this.cursedCrystalEnemies = [];
// 4. Minimalna konfiguracja UI (tylko serca)
if (ui) {
ui.positionElements("cursedCrystal");
console.log("MinimalTest: Calling ui.updateHearts with playerHealth: " + this.cursedCrystalPlayerHealth + ", maxHealth: " + this.cursedCrystalPlayerMaxHealth);
ui.updateHearts(this.cursedCrystalPlayerHealth, this.cursedCrystalPlayerMaxHealth);
if (ui.timerText) {
ui.timerText.alpha = 0;
}
if (ui.highScoreText) {
ui.highScoreText.alpha = 0;
}
if (ui.bossHealthBarContainer) {
ui.bossHealthBarContainer.alpha = 0;
}
if (ui.deathsText) {
ui.deathsText.alpha = 0;
}
}
console.log("[SetupCursedCrystal_MinimalTest] Minimalna scena skonfigurowana. Gracz HP: " + player.health);
if (ui && ui.showMessage) {
ui.showMessage("Cursed Crystal - MINIMAL TEST (RollCD=0)", 3000);
}
},
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);
}
// --- WYŚWIETLANIE UI ---
if (ui) {
ui.titleText.setText(isVictory ? "ZWYCIĘSTWO!" : "PORAŻKA!");
ui.titleText.style = {
size: 120,
fill: isVictory ? 0x00FF00 : 0xFF0000,
align: 'center',
stroke: 0x000000,
strokeThickness: 6
};
var message = "Twój wynik: " + finalScore + "\n";
message += "Najlepszy wynik: " + highScore;
if (newRecord && isVictory) {
message += "\n(NOWY REKORD!)";
} else if (newRecord && !isVictory) {
message += "\n(Nowy rekord... mimo wszystko!)";
}
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ą
if (this.activeButtons && this.activeButtons.length > 0) {
this.activeButtons.forEach(function (btn) {
if (btn && btn.destroy) {
btn.destroy();
}
});
}
this.activeButtons = [];
// Przycisk "Powrót do Menu"
var menuButton = new Container();
menuButton.interactive = true;
menuButton.cursor = "pointer";
var menuButtonBg = new Shape({
width: 400,
height: 100,
color: 0x555555,
shape: 'box'
}); // Placeholder
var menuButtonText = new Text2("Menu Główne", {
size: 40,
fill: 0xFFFFFF
});
menuButtonText.anchor.set(0.5);
menuButton.addChild(menuButtonBg);
menuButton.addChild(menuButtonText);
// Pozycja będzie ustawiona w ui.positionElements("cursedCrystalGameOver")
menuButton.down = function () {
if (gameState.currentState === "cursedCrystalGameOver") {
gameState.activeButtons.forEach(function (btn) {
if (btn && btn.destroy) {
btn.destroy();
}
});
gameState.activeButtons = [];
gameState.showGrillScreen();
}
};
currentSceneElements.addChild(menuButton); // Dodajemy do currentSceneElements
this.activeButtons.push(menuButton);
// Przycisk "Restartuj Tryb"
var restartButton = new Container();
restartButton.interactive = true;
restartButton.cursor = "pointer";
var restartButtonBg = new Shape({
width: 400,
height: 100,
color: 0x555555,
shape: 'box'
}); // Placeholder
var restartButtonText = new Text2("Restartuj Tryb", {
size: 40,
fill: 0xFFFFFF
});
restartButtonText.anchor.set(0.5);
restartButton.addChild(restartButtonBg);
restartButton.addChild(restartButtonText);
// Pozycja będzie ustawiona w ui.positionElements("cursedCrystalGameOver")
restartButton.down = function () {
if (gameState.currentState === "cursedCrystalGameOver") {
gameState.activeButtons.forEach(function (btn) {
if (btn && btn.destroy) {
btn.destroy();
}
});
gameState.activeButtons = [];
gameState.startCursedCrystalMode();
}
};
currentSceneElements.addChild(restartButton); // Dodajemy do currentSceneElements
this.activeButtons.push(restartButton);
// Upewnij się, że wszystkie elementy UI są widoczne
ui.titleText.alpha = 1;
ui.messageText.alpha = 1;
ui.positionElements("cursedCrystalGameOver"); // Pozycjonowanie po utworzeniu przycisków
}
},
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() {
// Sprawdzenia, czy możemy spawnSouldusha
if (gameState.currentState !== "cursedCrystal") {
return; // Nie spawnuj, jeśli nie jesteśmy w odpowiednim trybie
}
if (typeof crystalCoreObject === 'undefined' || !crystalCoreObject || crystalCoreObject.destroyed) {
// console.warn("Nie można spawnować Duszy: crystalCoreObject nie istnieje lub jest zniszczony.");
return; // Nie spawnuj, jeśli nie ma celu
}
var soulSpeed = gameState.cursedCrystalEnemyCurrentMaxSpeed || gameState.cursedCrystalEnemyBaseSpeed || 2;
var soulHP = 1; // Domyślne HP dla Duszy, można to później uzależnić od poziomu trudności
// Wymiary Duszy - powinny odpowiadać assetowi 'soul_asset'
// Jeśli 'soul_asset' to LK.init.shape, jego wymiary są tam zdefiniowane.
// Można je pobrać dynamicznie, ale dla uproszczenia na razie użyjemy stałych.
var soulWidth = 30;
var soulHeight = 30;
var spawnX, spawnY;
var edge = Math.floor(Math.random() * 4); // 0: góra, 1: prawo, 2: dół, 3: lewo
var gameWidth = 2048; // Szerokość ekranu gry
var gameHeight = 2732; // Wysokość ekranu gry
switch (edge) {
case 0:
// Górna krawędź
spawnX = Math.random() * gameWidth;
spawnY = -soulHeight / 2; // Pojawia się tuż nad ekranem
break;
case 1:
// Prawa krawędź
spawnX = gameWidth + soulWidth / 2; // Tuż za prawą krawędzią
spawnY = Math.random() * gameHeight;
break;
case 2:
// Dolna krawędź
spawnX = Math.random() * gameWidth;
spawnY = gameHeight + soulHeight / 2; // Tuż pod dolną krawędzią
break;
case 3: // Lewa krawędź
default:
spawnX = -soulWidth / 2; // Tuż za lewą krawędzią
spawnY = Math.random() * gameHeight;
break;
}
var newSoul = new SoulEnemy({
x: spawnX,
y: spawnY,
hp: soulHP,
speed: soulSpeed,
targetX: crystalCoreObject.x,
// Cel to Klejnot
targetY: crystalCoreObject.y
// width, height, color będą z 'soul_asset' lub domyślnych w SoulEnemy
});
game.addChild(newSoul); // Dodaj Duszę do głównego kontenera gry (dla renderowania)
gameState.cursedCrystalEnemies.push(newSoul); // Dodaj do tablicy śledzącej Dusze
// console.log("Spawned Soul at: X=" + spawnX.toFixed(0) + ", Y=" + spawnY.toFixed(0) + " | Speed: " + soulSpeed);
}
// --- 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 && player.invulnerable) {
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.isMinibossActiveCC && gameState.cursedCrystalChargeLevel >= gameState.cursedCrystalTargetCharge) {
gameState.isMinibossActiveCC = true;
console.log("MINIBOSS SPAWNING! Charge: " + gameState.cursedCrystalChargeLevel + "/" + gameState.cursedCrystalTargetCharge);
var crystalPosX = 2048 / 2;
var crystalPosY = 2732 / 2;
if (typeof crystalCoreObject !== 'undefined' && crystalCoreObject && !crystalCoreObject.destroyed) {
crystalPosX = crystalCoreObject.x;
crystalPosY = crystalCoreObject.y;
if (crystalCoreObject.parent) {
crystalCoreObject.parent.removeChild(crystalCoreObject);
}
crystalCoreObject.destroy();
crystalCoreObject = null;
}
var timeSurvivedInMinutes = Math.floor(gameState.cursedCrystalTimeSurvived / 3600);
var baseMinibossHp = 100;
var hpPerMinute = 100;
var scaledMaxHp = baseMinibossHp + timeSurvivedInMinutes * hpPerMinute;
scaledMaxHp = Math.max(baseMinibossHp, scaledMaxHp);
gameState.cursedCrystalMinibossMaxHP = scaledMaxHp;
gameState.cursedCrystalMinibossHP = scaledMaxHp;
var minibossOptions = {
maxHp: scaledMaxHp,
x: crystalPosX,
y: crystalPosY
};
gameState.cursedCrystalEnemies.forEach(function (soul) {
if (soul && !soul.isDead) {
if (soul.parent) {
soul.parent.removeChild(soul);
}
if (soul.destroy) {
soul.destroy();
}
}
});
gameState.cursedCrystalEnemies = [];
gameState.cursedCrystalMinibossObject = game.addChild(new MinibossCC(minibossOptions));
if (ui && ui.updateMinibossHealthCC) {
ui.updateMinibossHealthCC(gameState.cursedCrystalMinibossHP, gameState.cursedCrystalMinibossMaxHP, true);
}
if (ui && ui.showMessage) {
ui.showMessage("The Guardian Wakes!", 3000);
}
}
if (gameState.isMinibossActiveCC && gameState.cursedCrystalMinibossObject && !gameState.cursedCrystalMinibossObject.isDead) {
gameState.cursedCrystalMinibossObject.update();
}
// --- 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 = [];
}
for (var wallIdx = gameState.cursedCrystalActiveLaserWalls.length - 1; wallIdx >= 0; wallIdx--) {
var wall = gameState.cursedCrystalActiveLaserWalls[wallIdx];
if (!wall || wall.isDead) {
// Upewnij się, że wszystkie wizualizacje segmentów są usunięte, jeśli ściana jest martwa
if (wall && wall.segments) {
wall.segments.forEach(function (seg) {
if (seg.visual && seg.visual.parent) {
seg.visual.parent.removeChild(seg.visual);
if (seg.visual.destroy) {
seg.visual.destroy();
}
}
});
}
gameState.cursedCrystalActiveLaserWalls.splice(wallIdx, 1);
continue;
}
// Sprawdź, czy wizualizacje segmentów już istnieją (tj. czy faza ostrzeżenia minęła)
if (wall.segments.length > 0 && wall.segments[0].visual) {
wall.activeTimer -= 1000 / 60; // Zmniejsz timer aktywności (w ms)
if (wall.activeTimer > 0) {
wall.currentAngle += wall.rotationSpeed; // Obracaj ścianę
wall.segments.forEach(function (segment) {
// Oblicz nową pozycję segmentu na podstawie obrotu ściany
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.x = segment.currentX;
segment.visual.y = segment.currentY;
segment.visual.rotation = wall.currentAngle; // Obróć również grafikę segmentu
if (typeof segment.visual.update === 'function') {
// Dla SpriteAnimation
segment.visual.update();
}
// Kolizja z graczem dla tego segmentu
if (player && !player.dead && !player.invulnerable) {
// Prosta kolizja AABB (można ulepszyć dla obróconych prostokątów)
var playerHalfWidth_lw = (player.width || 150) / 2 * 0.7;
var playerHalfHeight_lw = (player.height || 150) / 2 * 0.7;
var segmentHalfWidth_lw = segment.width / 2;
var segmentHalfHeight_lw = segment.height / 2;
// Używamy aktualnej pozycji segmentu (segment.currentX, segment.currentY)
if (Math.abs(player.x - segment.currentX) < playerHalfWidth_lw + segmentHalfWidth_lw && Math.abs(player.y - segment.currentY) < playerHalfHeight_lw + segmentHalfHeight_lw) {
player.takeDamage(1);
// Można dodać flagę trafienia dla tego segmentu/ściany, aby uniknąć wielokrotnych trafień w tej samej klatce
}
}
}
});
} else {
// Czas aktywności ściany minął
wall.isDead = true; // Oznacz do usunięcia (grafiki są usuwane w pętli powyżej przy isDead)
}
}
// Jeśli segment.visual jeszcze nie istnieje, to znaczy, że jesteśmy w fazie ostrzeżenia,
// która jest zarządzana przez setTimeout w MinibossCC.performAttack.
// Tutaj nie ma potrzeby dodatkowej logiki dla samej fazy ostrzeżenia.
}
if (player && player.dead) {/* Obsługiwane przez player.die() */}
}
};
// -------- KONIEC CAŁEJ, PEŁNEJ, ZAKTUALIZOWANEJ FUNKCJI game.update -------- // Koniec game.update
// --- Rozpoczęcie gry ---
// Zakładamy, że obiekt gameState jest zdefiniowany gdzieś wcześniej w kodzie
if (typeof gameState !== 'undefined' && typeof gameState.init === 'function') {
gameState.init();
} else {
console.error("Obiekt gameState lub gameState.init nie jest zdefiniowany przed wywołaniem init!");
} ===================================================================
--- original.js
+++ change.js
@@ -947,8 +947,407 @@
self.attackCooldown = 120; // Początkowy cooldown przed pierwszym atakiem
self.dead = false;
return self;
});
+// -------- POCZĄTEK ZAKTUALIZOWANEJ KLASY MinibossCC --------
+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 || 1.5;
+ self.isDead = false;
+ self.attackPattern = ['explosiveProjectile', 'laserWall'];
+ self.currentAttackIndex = 0;
+ self.attackCooldowns = {
+ 'explosiveProjectile': 8 * 60,
+ 'laserWall': 5 * 60
+ };
+ self.attackCooldown = 120;
+ self.currentAttackName = '';
+ 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;
+ }
+ self.x = options.x || 2048 / 2;
+ self.y = options.y || 300;
+ self.update = function () {
+ if (self.isDead || gameState.currentState !== "cursedCrystal") {
+ return;
+ }
+ if (self.attackCooldown > 0) {
+ self.attackCooldown--;
+ } else if (player && !player.dead) {
+ self.performAttack();
+ self.attackCooldown = self.attackCooldowns[self.currentAttackName] || 180;
+ }
+ };
+ self.performAttack = function () {
+ if (!player || player.dead) {
+ return;
+ }
+ self.currentAttackName = self.attackPattern[self.currentAttackIndex];
+ console.log("MinibossCC performs attack: " + self.currentAttackName);
+ if (self.currentAttackName === 'explosiveProjectile') {
+ 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);
+ } else if (self.currentAttackName === 'laserWall') {
+ var numLaserSegments = 5;
+ var laserSegmentWidth = 30;
+ var laserSegmentHeight = 120;
+ var spacingBetweenSegments = 10;
+ var totalWallEffectiveHeight = numLaserSegments * laserSegmentHeight + (numLaserSegments - 1) * spacingBetweenSegments;
+ var wallPivotX = self.x; // Punkt obrotu ściany
+ var wallPivotY = self.y; // Punkt obrotu ściany
+ var warningDuration = 1500;
+ var activeDuration = 2000; // 2 sekundy obracania się
+ // Pełen obrót (2*PI radianów) w ciągu activeDuration (np. 120 klatek)
+ // rotationSpeed = (2 * Math.PI) / (activeDuration / (1000/60)); // Radianów na klatkę
+ var rotationSpeed = 2 * Math.PI / 120; // ~3 stopnie na klatkę dla pełnego obrotu w 2s @ 60fps
+ var laserWallInstance = {
+ pivotX: wallPivotX,
+ pivotY: wallPivotY,
+ segments: [],
+ currentAngle: 0,
+ // Początkowy kąt
+ rotationSpeed: rotationSpeed,
+ activeTimer: activeDuration,
+ // w ms
+ isDead: false,
+ warningVisuals: [] // Do przechowania wizualizacji ostrzeżeń
+ };
+ // Faza Ostrzeżenia
+ var initialSegmentOffsetY = -(totalWallEffectiveHeight / 2) + laserSegmentHeight / 2;
+ for (var i = 0; i < numLaserSegments; i++) {
+ var segmentOffsetY = initialSegmentOffsetY + i * (laserSegmentHeight + spacingBetweenSegments);
+ // Pozycja ostrzeżenia jest na razie statyczna względem Minibossa
+ var warningPosX = wallPivotX; // Ostrzeżenie w linii X Minibossa
+ var warningPosY = wallPivotY + segmentOffsetY; // Rozłożone pionowo
+ var warningFrames = [];
+ for (var wf = 0; wf < 5; wf++) {
+ try {
+ warningFrames.push(LK.getAsset('rmattack3_warning_' + wf, {
+ anchorX: 0.5,
+ anchorY: 0.5
+ }));
+ } catch (e) {
+ warningFrames.push(new Shape({
+ width: laserSegmentWidth,
+ height: laserSegmentHeight,
+ color: 0xFF0000,
+ shape: 'box'
+ }));
+ }
+ }
+ var warningAnim = new SpriteAnimation({
+ frames: warningFrames,
+ frameDuration: 100,
+ loop: true,
+ x: warningPosX,
+ y: warningPosY,
+ anchorX: 0.5,
+ anchorY: 0.5,
+ alpha: 0.7
+ });
+ game.addChild(warningAnim);
+ laserWallInstance.warningVisuals.push(warningAnim);
+ warningAnim.play();
+ // Zapisz offsetY dla późniejszego obliczenia pozycji obracanych segmentów
+ // offsetX jest 0, bo na razie ściana jest idealnie pionowa i wycentrowana na X bossa
+ laserWallInstance.segments.push({
+ initialOffsetX: 0,
+ // Względem pivotX
+ initialOffsetY: segmentOffsetY,
+ // Względem pivotY
+ width: laserSegmentWidth,
+ height: laserSegmentHeight,
+ visual: null,
+ // Grafika lasera zostanie stworzona później
+ currentX: warningPosX,
+ // Na razie to samo co warning
+ currentY: warningPosY // Na razie to samo co warning
+ });
+ }
+ LK.setTimeout(function () {
+ // Po zakończeniu ostrzeżenia
+ // Usuń ostrzeżenia
+ laserWallInstance.warningVisuals.forEach(function (warnVis) {
+ if (warnVis && warnVis.parent) {
+ warnVis.parent.removeChild(warnVis);
+ warnVis.destroy();
+ }
+ });
+ laserWallInstance.warningVisuals = [];
+ // Stwórz aktywne segmenty laserowe
+ laserWallInstance.segments.forEach(function (segment) {
+ var laserStrikeFrames = [];
+ var useStrikeAnimation = false;
+ try {
+ for (var sf = 0; sf < 5; sf++) {
+ laserStrikeFrames.push(LK.getAsset('rmattack3_strike_' + sf, {
+ anchorX: 0.5,
+ anchorY: 0.5,
+ width: segment.width,
+ height: segment.height
+ }));
+ }
+ useStrikeAnimation = true;
+ } catch (e) {/* Użyj Shape */}
+ if (useStrikeAnimation) {
+ segment.visual = new SpriteAnimation({
+ frames: laserStrikeFrames,
+ frameDuration: 40,
+ loop: true,
+ x: segment.currentX,
+ y: segment.currentY,
+ anchorX: 0.5,
+ anchorY: 0.5
+ });
+ segment.visual.play();
+ } else {
+ segment.visual = new Shape({
+ width: segment.width,
+ height: segment.height,
+ color: 0xFF0000,
+ shape: 'box',
+ x: segment.currentX,
+ y: segment.currentY,
+ anchorX: 0.5,
+ anchorY: 0.5 // Shape też może mieć anchor, jeśli jest zdefiniowany w Twojej klasie Shape
+ });
+ }
+ game.addChild(segment.visual);
+ });
+ if (gameState.cursedCrystalActiveLaserWalls === undefined) {
+ gameState.cursedCrystalActiveLaserWalls = [];
+ }
+ gameState.cursedCrystalActiveLaserWalls.push(laserWallInstance);
+ }, warningDuration);
+ }
+ self.currentAttackIndex = (self.currentAttackIndex + 1) % self.attackPattern.length;
+ };
+ 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;
+ }
+ self.isDead = true;
+ console.log("MinibossCC has been defeated!");
+ if (gameState.cursedCrystalActiveProjectiles) {
+ gameState.cursedCrystalActiveProjectiles.forEach(function (proj) {
+ if (proj.graphics && proj.graphics.parent) {
+ proj.graphics.parent.removeChild(proj.graphics);
+ }
+ if (proj.destroy) {
+ proj.destroy();
+ }
+ proj.isDead = true;
+ });
+ gameState.cursedCrystalActiveProjectiles = [];
+ }
+ if (gameState.cursedCrystalActiveExplosions) {
+ gameState.cursedCrystalActiveExplosions = [];
+ }
+ if (gameState.cursedCrystalActiveLaserWalls) {
+ // Czyszczenie ścian laserów przy śmierci bossa
+ gameState.cursedCrystalActiveLaserWalls.forEach(function (wall) {
+ wall.segments.forEach(function (seg) {
+ if (seg.visual && seg.visual.parent) {
+ seg.visual.parent.removeChild(seg.visual);
+ }
+ if (seg.visual && seg.visual.destroy) {
+ seg.visual.destroy();
+ }
+ });
+ wall.isDead = true;
+ });
+ gameState.cursedCrystalActiveLaserWalls = [];
+ }
+ if (typeof gameState.endCursedCrystalMode === 'function') {
+ gameState.endCursedCrystalMode(true);
+ }
+ if (self.parent) {
+ self.parent.removeChild(self);
+ }
+ self.destroy();
+ gameState.cursedCrystalMinibossObject = null;
+ gameState.isMinibossActiveCC = false;
+ };
+ 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 || 7; // Prędkość pocisku (w pikselach na klatkę)
+ self.target = options.target; // Referencja do obiektu gracza
+ self.lifeTimer = 120; // 2 sekundy przy 60fps
+ self.isDead = false; // Czy pocisk powinien zostać usunięty z listy aktywnych
+ self.hasExploded = false; // Czy pocisk już eksplodował
+ self.explosionRadius = options.explosionRadius || 100; // Promień eksplozji (dla średnicy 200x200)
+ // Możemy to zwiększyć do ~500 dla 1/6 mapy
+ self.explosionDamage = 1; // Obrażenia eksplozji
+ // Grafika pocisku (placeholder - dostosujesz asset)
+ try {
+ self.graphics = self.attachAsset('placeholder_projectile_asset', {
+ // Użyj własnego assetu
+ anchorX: 0.5,
+ anchorY: 0.5,
+ width: 40,
+ // Jak wspomniałeś
+ height: 40
+ });
+ } catch (e) {
+ console.warn("Nie udało się załadować 'placeholder_projectile_asset' dla MinibossExplosiveProjectile, używam Shape.", e);
+ self.graphics = new Shape({
+ width: 40,
+ height: 40,
+ color: 0xFF4500,
+ // Pomarańczowo-czerwony
+ 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) {
+ // Jeśli cel zniknął, a pocisk nie wybuchł, oznacz do usunięcia
+ self.isDead = true;
+ }
+ return;
+ }
+ self.lifeTimer--;
+ // Logika naprowadzania
+ 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;
+ }
+ // Kolizja z graczem
+ var playerRadius = (self.target.width || 150) / 2 * 0.7; // Użyj promienia gracza
+ var projectileRadius = self.width / 2;
+ if (distanceToTarget < playerRadius + projectileRadius) {
+ // Nie zadajemy obrażeń bezpośrednio od pocisku, tylko od eksplozji
+ self.explode();
+ return; // Zatrzymaj dalsze przetwarzanie tego pocisku
+ }
+ // Sprawdzenie timera życia
+ if (self.lifeTimer <= 0) {
+ self.explode();
+ return;
+ }
+ // (Opcjonalnie) Sprawdzenie granic ekranu, jeśli chcesz, by wybuchał na krawędziach
+ // if (self.x < 0 || self.x > 2048 || self.y < 0 || self.y > 2732) {
+ // self.explode();
+ // return;
+ // }
+ };
+ self.explode = function () {
+ if (self.hasExploded) {
+ return;
+ }
+ self.hasExploded = true;
+ self.isDead = true; // Oznacz do usunięcia z listy aktywnych pocisków
+ if (self.graphics && self.graphics.parent) {
+ self.graphics.parent.removeChild(self.graphics);
+ self.graphics.destroy();
+ }
+ // Stwórz efekt wizualny eksplozji (placeholder)
+ var explosionVisual = new Shape({
+ // Użyjesz tu własnej animacji/assetu
+ width: self.explosionRadius * 2,
+ // Średnica
+ height: self.explosionRadius * 2,
+ color: 0xFF8C00,
+ // Ciemnopomarańczowy
+ shape: 'ellipse'
+ });
+ explosionVisual.x = self.x;
+ explosionVisual.y = self.y;
+ explosionVisual.alpha = 0.7;
+ game.addChild(explosionVisual); // Dodaj do głównej sceny
+ tween(explosionVisual, {
+ scaleX: 1.2,
+ scaleY: 1.2,
+ alpha: 0
+ }, {
+ duration: 500,
+ // Czas trwania wizualizacji eksplozji
+ easing: tween.easeOutQuad,
+ onFinish: function onFinish() {
+ if (explosionVisual.parent) {
+ explosionVisual.parent.removeChild(explosionVisual);
+ }
+ explosionVisual.destroy();
+ }
+ });
+ // Dodaj obiekt logiczny eksplozji do śledzenia obrażeń
+ if (gameState && gameState.cursedCrystalActiveExplosions) {
+ gameState.cursedCrystalActiveExplosions.push({
+ x: self.x,
+ y: self.y,
+ radius: self.explosionRadius,
+ damage: self.explosionDamage,
+ durationTimer: 30,
+ // Klatki, przez które eksplozja zadaje obrażenia (np. 0.5s)
+ hitPlayerThisFrame: false // Flaga, aby zadać obrażenia tylko raz
+ });
+ }
+ // console.log("Pocisk eksplodował w: X=" + self.x.toFixed(0) + ", Y=" + self.y.toFixed(0));
+ };
+ 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);
@@ -1021,8 +1420,9 @@
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);
@@ -1386,9 +1786,24 @@
self.hasRolledThroughBossThisRoll = true;
LK.effects.flashObject(boss, 0x00FF00, 200);
}
}
- // TODO: W Cursed Crystal, kolizja rolla z Duszami lub Minibossem CC będzie tutaj
+ 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);
+ // pWidth jest już obliczone wyżej w tym bloku if(self.rolling)
+ var playerRollRadius = pWidth / 2 * 0.8; // Efektywny promień gracza podczas rolla
+ var minibossCCRadius = (minibossCC.width || 120) / 2 * 0.9; // Efektywny promień Minibossa CC (asset ma 120x120, mnożnik 0.9 dla lekkiego zmniejszenia hitboxa)
+ if (distance_mcc < playerRollRadius + minibossCCRadius) {
+ minibossCC.takeDamage(10); // Zadaj 10 obrażeń
+ self.hasRolledThroughMinibossCCThisRoll = true; // Ustaw flagę, aby nie zadawać obrażeń wielokrotnie w tym samym rollu
+ // Możesz dodać efekt wizualny trafienia, np. flashObject dla Minibossa
+ // LK.effects.flashObject(minibossCC.graphics || minibossCC, 0x00FF00, 150);
+ console.log("Player rolled through Miniboss CC and dealt damage!");
+ }
+ }
} else if (self.invulnerable && !self.rolling) {
// Niewrażliwość po otrzymaniu ciosu
if (currentVisualSprite) {
currentVisualSprite.alpha = Math.floor(Date.now() / 100) % 4 > 1 ? 0.3 : 1;
@@ -2272,8 +2687,67 @@
}
break;
case "cursedCrystalGameOver":
if (self.titleText) {
+ // Tekst i styl są ustawiane w gameState.endCursedCrystalMode
+ self.titleText.alpha = 1;
+ self.titleText.x = 2048 / 2;
+ self.titleText.y = 600; // Wyżej niż poprzednio
+ self.titleText.anchor.set(0.5);
+ }
+ if (self.messageText) {
+ // Tekst i styl są ustawiane w gameState.endCursedCrystalMode
+ self.messageText.alpha = 1;
+ self.messageText.x = 2048 / 2;
+ self.messageText.y = 800; // Pod tytułem
+ self.messageText.anchor.set(0.5, 0.5); // Wyśrodkowanie tekstu wieloliniowego
+ }
+ // Pozycjonowanie przycisków (zakładając, że są w gameState.activeButtons)
+ if (gameState.activeButtons && gameState.activeButtons.length > 0) {
+ var buttonYStart = 1100;
+ var buttonSpacing = 150;
+ gameState.activeButtons.forEach(function (button, index) {
+ if (button) {
+ button.x = 2048 / 2;
+ button.y = buttonYStart + index * buttonSpacing;
+ // Zakładamy, że przyciski mają anchor 0.5, 0.5 dla ich własnych BG i Text
+ }
+ });
+ }
+ // Ukryj elementy niepotrzebne na ekranie końca gry CC
+ if (self.heartContainer) {
+ self.heartContainer.alpha = 0;
+ }
+ if (self.timerText) {
+ self.timerText.alpha = 0;
+ } // Standardowy timer gry
+ if (self.highScoreText) {
+ self.highScoreText.alpha = 0;
+ } // Ten od Roll Master
+ if (self.bossHealthBarContainer) {
+ self.bossHealthBarContainer.alpha = 0;
+ }
+ if (self.deathsText) {
+ self.deathsText.alpha = 0;
+ }
+ if (self.tutorialText) {
+ self.tutorialText.alpha = 0;
+ }
+ // Ukryj UI specyficzne dla Cursed Crystal (w grze)
+ if (self.ccScoreText) {
+ self.ccScoreText.alpha = 0;
+ }
+ if (self.ccHighScoreText) {
+ self.ccHighScoreText.alpha = 0;
+ } // Ten od wyniku na żywo
+ if (self.crystalChargeBarContainer) {
+ self.crystalChargeBarContainer.alpha = 0;
+ }
+ // Upewnij się, że tło gry (jeśli jakieś jest) jest odpowiednie lub wyczyszczone
+ // game.setBackgroundColor(0x222222); // Np. ciemne tło
+ break;
+ case "cursedCrystalGameOver":
+ if (self.titleText) {
// Tekst ustawiany w gameState.endCursedCrystalMode
self.titleText.alpha = 1;
self.titleText.x = 2048 / 2;
self.titleText.y = 600;
@@ -2337,8 +2811,9 @@
// 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
if (typeof LK.getGameTime === 'undefined') {
LK.getGameTime = function () {
return Date.now(); // Zwraca aktualny czas w milisekundach
};
@@ -2394,9 +2869,9 @@
// Startowe HP gracza w tym trybie
cursedCrystalPlayerMaxHealth: 10,
cursedCrystalChargeLevel: 0,
// Aktualny poziom naładowania Klejnotu (0-100%)
- cursedCrystalTargetCharge: 100,
+ cursedCrystalTargetCharge: 15,
// Wartość naładowania do przywołania Minibossa
cursedCrystalSoulsHitCrystal: 0,
// Licznik dusz, które dotarły do kryształu (do skalowania Minibossa)
cursedCrystalScore: 0,
@@ -2420,8 +2895,14 @@
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,
@@ -4416,20 +4897,68 @@
timeText.y = 2732 / 2 + 250;
currentSceneElements.addChild(timeText);
// Timeout przed przejściem do grillMenu
LK.setTimeout(function () {
- console.log("DEBUG: Wykonuje się timeout w gameState.endRollMasterMode.");
- clearScene(); // Czyści losowy mem śmierci i timeText
- // Zniszcz gracza, jeśli istnieje
+ console.log("DEBUG: Wykonuje się timeout w gameState.gameOver. isDeath:", isDeath, "isNewBossPlusMode:", isNewBossPlusMode);
+ clearScene(); // Czyści losowy mem śmierci i extraText z currentSceneElements
+ // Niszczenie coffinDanceMeme jest teraz w onFinish jego tweentu
+ // Zniszcz gracza, bossa, tło...
if (player && player.destroy && !player.destroyed) {
player.destroy();
}
player = null;
- // Przejdź do grillMenu
- if (_this3 && typeof _this3.showGrillScreen === 'function') {
- console.log("[EndRollMaster] Przechodzenie do showGrillScreen.");
- _this3.showGrillScreen();
- } else {/* ... obsługa błędu ... */}
+ if (boss && boss.destroy && !boss.destroyed) {
+ boss.destroy();
+ }
+ boss = null;
+ if (currentBackground) {
+ currentBackground.destroy();
+ }
+ currentBackground = null;
+ // Resetuj UI
+ ui.titleText.setText("");
+ ui.titleText.alpha = 0;
+ ui.messageText.setText("");
+ ui.messageText.alpha = 0;
+ ui.tutorialText.setText("");
+ ui.tutorialText.alpha = 0;
+ ui.updateDeathsCounter();
+ ui.updateBossHealth(0, 1);
+ ui.updateHearts(0, parseInt(storage.maxHearts, 10) || 5);
+ // Logika przejścia do następnego stanu
+ if (isDeath && !isNewBossPlusMode) {
+ console.log("gameOver timeout: Standard death -> Restarting standard game...");
+ isNewBossPlusMode = false;
+ if (typeof gameState.startGame === 'function') {
+ gameState.startGame();
+ } else {
+ console.error("Nie można wywołać startGame po standardowej śmierci!");
+ }
+ } else if (isDeath && isNewBossPlusMode) {
+ console.log("gameOver timeout: Boss+ death -> Going to Grill Menu...");
+ isNewBossPlusMode = false;
+ if (typeof gameState.showGrillScreen === 'function') {
+ gameState.showGrillScreen();
+ } else {
+ console.error("Nie można wywołać showGrillScreen po śmierci w Boss+!");
+ }
+ } else if (!isDeath && isNewBossPlusMode) {
+ console.log("gameOver timeout: Boss+ timeout -> Going to Grill Menu...");
+ isNewBossPlusMode = false;
+ if (typeof gameState.showGrillScreen === 'function') {
+ gameState.showGrillScreen();
+ } else {
+ console.error("Nie można wywołać showGrillScreen po timeout w Boss+!");
+ }
+ } else {
+ console.log("gameOver timeout: Fallback case -> Going to Grill Menu...");
+ isNewBossPlusMode = false;
+ if (typeof gameState.showGrillScreen === 'function') {
+ gameState.showGrillScreen();
+ } else {
+ console.error("Nie można wywołać showGrillScreen w fallbacku gameOver!");
+ }
+ }
}, 5000);
},
startCursedCrystalMode: function startCursedCrystalMode() {
console.log("[StartCursedCrystal] Rozpoczynanie trybu Cursed Crystal...");
@@ -4693,8 +5222,222 @@
if (ui && ui.showMessage) {
ui.showMessage("Cursed Crystal - MINIMAL TEST (RollCD=0)", 3000);
}
},
+ 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);
+ }
+ // --- WYŚWIETLANIE UI ---
+ if (ui) {
+ ui.titleText.setText(isVictory ? "ZWYCIĘSTWO!" : "PORAŻKA!");
+ ui.titleText.style = {
+ size: 120,
+ fill: isVictory ? 0x00FF00 : 0xFF0000,
+ align: 'center',
+ stroke: 0x000000,
+ strokeThickness: 6
+ };
+ var message = "Twój wynik: " + finalScore + "\n";
+ message += "Najlepszy wynik: " + highScore;
+ if (newRecord && isVictory) {
+ message += "\n(NOWY REKORD!)";
+ } else if (newRecord && !isVictory) {
+ message += "\n(Nowy rekord... mimo wszystko!)";
+ }
+ 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ą
+ if (this.activeButtons && this.activeButtons.length > 0) {
+ this.activeButtons.forEach(function (btn) {
+ if (btn && btn.destroy) {
+ btn.destroy();
+ }
+ });
+ }
+ this.activeButtons = [];
+ // Przycisk "Powrót do Menu"
+ var menuButton = new Container();
+ menuButton.interactive = true;
+ menuButton.cursor = "pointer";
+ var menuButtonBg = new Shape({
+ width: 400,
+ height: 100,
+ color: 0x555555,
+ shape: 'box'
+ }); // Placeholder
+ var menuButtonText = new Text2("Menu Główne", {
+ size: 40,
+ fill: 0xFFFFFF
+ });
+ menuButtonText.anchor.set(0.5);
+ menuButton.addChild(menuButtonBg);
+ menuButton.addChild(menuButtonText);
+ // Pozycja będzie ustawiona w ui.positionElements("cursedCrystalGameOver")
+ menuButton.down = function () {
+ if (gameState.currentState === "cursedCrystalGameOver") {
+ gameState.activeButtons.forEach(function (btn) {
+ if (btn && btn.destroy) {
+ btn.destroy();
+ }
+ });
+ gameState.activeButtons = [];
+ gameState.showGrillScreen();
+ }
+ };
+ currentSceneElements.addChild(menuButton); // Dodajemy do currentSceneElements
+ this.activeButtons.push(menuButton);
+ // Przycisk "Restartuj Tryb"
+ var restartButton = new Container();
+ restartButton.interactive = true;
+ restartButton.cursor = "pointer";
+ var restartButtonBg = new Shape({
+ width: 400,
+ height: 100,
+ color: 0x555555,
+ shape: 'box'
+ }); // Placeholder
+ var restartButtonText = new Text2("Restartuj Tryb", {
+ size: 40,
+ fill: 0xFFFFFF
+ });
+ restartButtonText.anchor.set(0.5);
+ restartButton.addChild(restartButtonBg);
+ restartButton.addChild(restartButtonText);
+ // Pozycja będzie ustawiona w ui.positionElements("cursedCrystalGameOver")
+ restartButton.down = function () {
+ if (gameState.currentState === "cursedCrystalGameOver") {
+ gameState.activeButtons.forEach(function (btn) {
+ if (btn && btn.destroy) {
+ btn.destroy();
+ }
+ });
+ gameState.activeButtons = [];
+ gameState.startCursedCrystalMode();
+ }
+ };
+ currentSceneElements.addChild(restartButton); // Dodajemy do currentSceneElements
+ this.activeButtons.push(restartButton);
+ // Upewnij się, że wszystkie elementy UI są widoczne
+ ui.titleText.alpha = 1;
+ ui.messageText.alpha = 1;
+ ui.positionElements("cursedCrystalGameOver"); // Pozycjonowanie po utworzeniu przycisków
+ }
+ },
processTouchGesture: function processTouchGesture() {
// ... (kod obsługi fake tutorial) ...
var dx = this.touchEnd.x - this.touchStart.x;
var dy = this.touchEnd.y - this.touchStart.y;
@@ -5234,48 +5977,42 @@
}
// --- Główna pętla aktualizacji gry ---
game.update = function () {
if (gameState.currentState === "cursedCrystal") {
- console.log("Game Update Tick - CURSED CRYSTAL"); // Log specyficzny dla CC
+ // console.log("Game Update Tick - CURSED CRYSTAL");
}
- // Aktualizacja UI (wspólna dla wielu stanów)
if (ui) {
if (gameState.currentState !== "cursedCrystal" && gameState.currentState !== "cursedCrystalGameOver") {
- ui.updateDeathsCounter(); // Licznik śmierci niepotrzebny w CC
+ 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") {
- // Ukryj pasek bossa, jeśli nie jest w grze/gameover boss+ lub w trybie CC
ui.updateBossHealth(0, 1);
}
- // Aktualizacja serc gracza dla trybów innych niż CC (dla CC jest w Player.takeDamage i setup)
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") {
- // Jeśli nie ma gracza i nie jesteśmy w aktywnych trybach walki ani CC, pokaż domyślne serca (np. dla menu)
- ui.updateHearts(parseInt(storage.maxHearts, 10) || 5, parseInt(storage.maxHearts, 10) || 5); // Lub (0, max) jeśli chcesz puste
+ ui.updateHearts(parseInt(storage.maxHearts, 10) || 5, parseInt(storage.maxHearts, 10) || 5);
}
}
- // Logika mema z trumną (tylko dla trybu "game")
if (coffinMemeImage && !coffinMemeImage.destroyed) {
if (gameState.currentState === "game" && player) {
- // Upewnij się, że player istnieje
if (!player.dead) {
var currentHp = player.health;
var maxHp = parseInt(storage.maxHearts, 10) || 5;
- var visibilityFactor = 1 - Math.max(0, currentHp) / maxHp; // Upewnij się, że currentHp nie jest ujemne
+ 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; // Mniejsza alfa, bardziej subtelne
+ 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);
@@ -5285,20 +6022,17 @@
if (Math.abs(currentAlpha - targetAlpha) > 0.01) {
coffinMemeImage.alpha = currentAlpha < targetAlpha ? Math.min(targetAlpha, currentAlpha + alphaChangeSpeed) : Math.max(targetAlpha, currentAlpha - alphaChangeSpeed);
}
} else {
- // Gracz jest martwy w trybie "game"
coffinMemeImage.y = 2932;
coffinMemeImage.alpha = 1.0;
}
} else if (gameState.currentState !== "gameOver") {
- // Ukryj mema jeśli nie jesteśmy w grze i nie na gameOver
if (coffinMemeImage.alpha > 0) {
coffinMemeImage.alpha = Math.max(0, coffinMemeImage.alpha - 0.05);
}
}
}
- // Główna logika stanów gry
if (gameState.currentState === "game") {
if (player) {
player.update();
}
@@ -5308,18 +6042,17 @@
} else if (gameState.currentState === "rollMaster") {
if (player) {
player.update();
}
- // --- Logika spawnowania i aktualizacji ataków RollMaster (pozostaje bez zmian, skopiowana z Twojego kodu) ---
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; // Zwiększono minimalne opóźnienie
+ var nextDelay = 800 + Math.random() * 1500;
gameState.randomRmattack1Timer = LK.setTimeout(_spawnRandomRmattack, nextDelay);
};
_spawnRandomRmattack();
}
@@ -5365,9 +6098,9 @@
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 / 2 * 0.8;
+ 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;
@@ -5388,9 +6121,9 @@
if (!atk.damageDealt && player && !player.dead && !player.rolling && !player.invulnerable) {
var dx_e = player.x - atk.visual.x;
var dy_e = player.y - atk.visual.y;
var dist_e = Math.sqrt(dx_e * dx_e + dy_e * dy_e);
- var playerRadius_e = player.width / 2 * 0.8;
+ var playerRadius_e = (player.width || 150) / 2 * 0.8;
if (dist_e < playerRadius_e + atk.radius) {
player.takeDamage(1);
atk.damageDealt = true;
}
@@ -5399,18 +6132,18 @@
case 'laser':
if (player && !player.dead && !player.rolling && !player.invulnerable) {
var laserHalfWidth = (atk.width || 100) / 2;
var laserHalfHeight = (atk.height || 100) / 2;
- var playerHalfWidth = player.width / 2 * 0.8;
- var playerHalfHeight = player.height / 2 * 0.8;
+ 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;
- var playerRight = player.x + playerHalfWidth;
- var playerTop = player.y - playerHalfHeight;
- var playerBottom = player.y + playerHalfHeight;
+ 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);
}
}
@@ -5422,24 +6155,24 @@
atk.timer--;
if (atk.timer <= 0) {
var originX = atk.visual.x;
var originY = atk.visual.y;
- var projectileSpeed = 5 + gameState.rollMasterDifficulty * 0.5;
- var projectileRadius = 45;
- var numProjectiles = 12;
- for (var j = 0; j < numProjectiles; j++) {
- var angle = Math.PI * 2 / numProjectiles * j;
- var vx = Math.cos(angle) * projectileSpeed;
- var vy = Math.sin(angle) * projectileSpeed;
- var projectileFrames = [];
- for (var k = 0; k <= 6; k++) {
- projectileFrames.push(LK.getAsset('rmattack1_' + k, {
+ 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 = new SpriteAnimation({
- frames: projectileFrames,
+ var projectileVisual_s = new SpriteAnimation({
+ frames: projectileFrames_s,
frameDuration: 100,
loop: true,
x: originX,
y: originY,
@@ -5447,128 +6180,253 @@
anchorY: 0.5,
scaleX: 1.2,
scaleY: 1.2
});
- projectileVisual.rotation = Math.atan2(vy, vx);
- var projectileObject = game.addChild(projectileVisual);
+ projectileVisual_s.rotation = Math.atan2(vy_s, vx_s);
+ var projectileObject_s = game.addChild(projectileVisual_s);
gameState.rollMasterAttacks.push({
type: 'projectile',
- visual: projectileObject,
- vx: vx,
- vy: vy,
- radius: projectileRadius
+ visual: projectileObject_s,
+ vx: vx_s,
+ vy: vy_s,
+ radius: projectileRadius_s
});
}
shouldRemove = true;
}
break;
- // Typy 'line' i 'circle' są z trybu bossa, nie powinny tu być, ale zostawiam na wszelki wypadek
- case 'line':
- case 'circle':
- atk.lifeTime--;
- if (player && !player.dead && !player.rolling && !player.invulnerable) {
- var dx_lc = player.x - atk.visual.x;
- var dy_lc = player.y - atk.visual.y;
- var distance_lc = Math.sqrt(dx_lc * dx_lc + dy_lc * dy_lc);
- var playerRadius_lc = player.width / 2 * 0.8;
- var attackRadius_lc = atk.radius;
- if (distance_lc < playerRadius_lc + attackRadius_lc) {
- player.takeDamage(1);
- shouldRemove = true;
- }
- }
- if (atk.lifeTime <= 0) {
- 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);
- } // Dodatkowe zabezpieczenie
+ }
atk.visual.destroy();
}
gameState.rollMasterAttacks.splice(i, 1);
}
}
if (player && player.dead && gameState.currentState === "rollMaster") {
- // Sprawdzaj stan przed wywołaniem
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(); // Aktualizacja gracza
+ player.update();
}
- // --- Logika Spawnowania Dusz ---
- gameState.cursedCrystalEnemySpawnTimer++; // Zwiększ timer spawnu
- if (gameState.cursedCrystalEnemySpawnTimer >= gameState.cursedCrystalCurrentSpawnInterval) {
- gameState.cursedCrystalEnemySpawnTimer = 0; // Zresetuj timer
- spawnCursedSoul(); // Wywołaj funkcję spawnowania nowej Duszy
+ 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;
+ }
}
- // --- Logika Aktualizacji Dusz (ruch, kolizje) ---
- // TO ZOSTANIE DODANE W NASTĘPNYM KROKU
- // for (var i = gameState.cursedCrystalEnemies.length - 1; i >= 0; i--) {
- // var enemy = gameState.cursedCrystalEnemies[i];
- // if (enemy && !enemy.isDead) { // Upewnij się, że wróg istnieje i nie jest już martwy
- // enemy.update(); // Metoda update z SoulEnemy (ruch w kierunku klejnotu)
- // // Kolizja Duszy z Klejnotem jest obsługiwana w enemy.update() -> enemy.onHitCrystal()
- // // TODO: Kolizja "rolla" gracza z Duszami
- // // if (player && player.rolling && !player.invulnerable && /* warunek kolizji */) {
- // // enemy.takeDamage(1); // Przykładowe obrażenia
- // // // Możliwe, że gracz powinien zadać obrażenia tylko jednej Duszy na roll,
- // // // lub że Dusza powinna mieć cooldown na otrzymywanie obrażeń.
- // // }
- // }
- // // Usuwanie martwych Dusz (które zginęły od gracza lub uderzyły w klejnot i same się zniszczyły)
- // // Metody SoulEnemy.die() i SoulEnemy.onHitCrystal() powinny usuwać obiekt z `game`
- // // i oznaczać isDead. Tutaj tylko usuwamy z tablicy gameState.cursedCrystalEnemies.
- // if (enemy && enemy.isDead) {
- // gameState.cursedCrystalEnemies.splice(i, 1);
- // }
- // }
- // --- Logika Zwiększania Trudności ---
- // TO ZOSTANIE DODANE PÓŹNIEJ
- // gameState.cursedCrystalDifficultyTimer++;
- // if (gameState.cursedCrystalDifficultyTimer >= gameState.cursedCrystalDifficultyInterval) {
- // gameState.cursedCrystalDifficultyTimer = 0;
- // // increaseCursedCrystalDifficulty(); // Funkcja do zaimplementowania
- // }
- // --- Logika Minibossa (jeśli aktywny) ---
- // TO ZOSTANIE DODANE PÓŹNIEJ
- // if (gameState.isMinibossActiveCC && gameState.cursedCrystalMinibossObject) {
- // gameState.cursedCrystalMinibossObject.update();
- // // Kolizje Minibossa z graczem, rollem gracza
- // }
- // Sprawdzenie warunków zakończenia gry
- if (player && player.dead) {
- // Funkcja player.die() dla trybu "cursedCrystal" powinna już wywołać
- // gameState.endCursedCrystalMode(false), więc nie ma potrzeby robić tego tutaj ponownie.
- // console.log("Cursed Crystal: Player is dead, endCursedCrystalMode should have been called.");
+ 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 && player.invulnerable) {
+ 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);
+ }
+ }
}
- // TODO: Sprawdzenie, czy Miniboss został pokonany (jeśli jest aktywny) -> gameState.endCursedCrystalMode(true)
- // Aktualizacja elementów UI specyficznych dla Cursed Crystal, które tego wymagają w pętli gry.
- // Wiele aktualizacji UI (wynik, pasek naładowania) dzieje się teraz w metodach SoulEnemy.
- // Pasek HP Minibossa będzie wymagał aktualizacji tutaj, gdy miniboss zostanie dodany.
- // if (ui) {
- // if (gameState.isMinibossActiveCC && gameState.cursedCrystalMinibossObject) {
- // ui.updateMinibossHealthCC(gameState.cursedCrystalMinibossHP, gameState.cursedCrystalMinibossMaxHP, true);
- // } else if (ui.bossHealthBarContainer && ui.bossHealthBarContainer.alpha !== 0) {
- // // Ukryj pasek, jeśli nie ma minibossa, a jest widoczny
- // ui.updateMinibossHealthCC(0, 1, false);
- // }
- // }
+ if (!gameState.isMinibossActiveCC && gameState.cursedCrystalChargeLevel >= gameState.cursedCrystalTargetCharge) {
+ gameState.isMinibossActiveCC = true;
+ console.log("MINIBOSS SPAWNING! Charge: " + gameState.cursedCrystalChargeLevel + "/" + gameState.cursedCrystalTargetCharge);
+ var crystalPosX = 2048 / 2;
+ var crystalPosY = 2732 / 2;
+ if (typeof crystalCoreObject !== 'undefined' && crystalCoreObject && !crystalCoreObject.destroyed) {
+ crystalPosX = crystalCoreObject.x;
+ crystalPosY = crystalCoreObject.y;
+ if (crystalCoreObject.parent) {
+ crystalCoreObject.parent.removeChild(crystalCoreObject);
+ }
+ crystalCoreObject.destroy();
+ crystalCoreObject = null;
+ }
+ var timeSurvivedInMinutes = Math.floor(gameState.cursedCrystalTimeSurvived / 3600);
+ var baseMinibossHp = 100;
+ var hpPerMinute = 100;
+ var scaledMaxHp = baseMinibossHp + timeSurvivedInMinutes * hpPerMinute;
+ scaledMaxHp = Math.max(baseMinibossHp, scaledMaxHp);
+ gameState.cursedCrystalMinibossMaxHP = scaledMaxHp;
+ gameState.cursedCrystalMinibossHP = scaledMaxHp;
+ var minibossOptions = {
+ maxHp: scaledMaxHp,
+ x: crystalPosX,
+ y: crystalPosY
+ };
+ gameState.cursedCrystalEnemies.forEach(function (soul) {
+ if (soul && !soul.isDead) {
+ if (soul.parent) {
+ soul.parent.removeChild(soul);
+ }
+ if (soul.destroy) {
+ soul.destroy();
+ }
+ }
+ });
+ gameState.cursedCrystalEnemies = [];
+ gameState.cursedCrystalMinibossObject = game.addChild(new MinibossCC(minibossOptions));
+ if (ui && ui.updateMinibossHealthCC) {
+ ui.updateMinibossHealthCC(gameState.cursedCrystalMinibossHP, gameState.cursedCrystalMinibossMaxHP, true);
+ }
+ if (ui && ui.showMessage) {
+ ui.showMessage("The Guardian Wakes!", 3000);
+ }
+ }
+ if (gameState.isMinibossActiveCC && gameState.cursedCrystalMinibossObject && !gameState.cursedCrystalMinibossObject.isDead) {
+ gameState.cursedCrystalMinibossObject.update();
+ }
+ // --- 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 = [];
+ }
+ for (var wallIdx = gameState.cursedCrystalActiveLaserWalls.length - 1; wallIdx >= 0; wallIdx--) {
+ var wall = gameState.cursedCrystalActiveLaserWalls[wallIdx];
+ if (!wall || wall.isDead) {
+ // Upewnij się, że wszystkie wizualizacje segmentów są usunięte, jeśli ściana jest martwa
+ if (wall && wall.segments) {
+ wall.segments.forEach(function (seg) {
+ if (seg.visual && seg.visual.parent) {
+ seg.visual.parent.removeChild(seg.visual);
+ if (seg.visual.destroy) {
+ seg.visual.destroy();
+ }
+ }
+ });
+ }
+ gameState.cursedCrystalActiveLaserWalls.splice(wallIdx, 1);
+ continue;
+ }
+ // Sprawdź, czy wizualizacje segmentów już istnieją (tj. czy faza ostrzeżenia minęła)
+ if (wall.segments.length > 0 && wall.segments[0].visual) {
+ wall.activeTimer -= 1000 / 60; // Zmniejsz timer aktywności (w ms)
+ if (wall.activeTimer > 0) {
+ wall.currentAngle += wall.rotationSpeed; // Obracaj ścianę
+ wall.segments.forEach(function (segment) {
+ // Oblicz nową pozycję segmentu na podstawie obrotu ściany
+ 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.x = segment.currentX;
+ segment.visual.y = segment.currentY;
+ segment.visual.rotation = wall.currentAngle; // Obróć również grafikę segmentu
+ if (typeof segment.visual.update === 'function') {
+ // Dla SpriteAnimation
+ segment.visual.update();
+ }
+ // Kolizja z graczem dla tego segmentu
+ if (player && !player.dead && !player.invulnerable) {
+ // Prosta kolizja AABB (można ulepszyć dla obróconych prostokątów)
+ var playerHalfWidth_lw = (player.width || 150) / 2 * 0.7;
+ var playerHalfHeight_lw = (player.height || 150) / 2 * 0.7;
+ var segmentHalfWidth_lw = segment.width / 2;
+ var segmentHalfHeight_lw = segment.height / 2;
+ // Używamy aktualnej pozycji segmentu (segment.currentX, segment.currentY)
+ if (Math.abs(player.x - segment.currentX) < playerHalfWidth_lw + segmentHalfWidth_lw && Math.abs(player.y - segment.currentY) < playerHalfHeight_lw + segmentHalfHeight_lw) {
+ player.takeDamage(1);
+ // Można dodać flagę trafienia dla tego segmentu/ściany, aby uniknąć wielokrotnych trafień w tej samej klatce
+ }
+ }
+ }
+ });
+ } else {
+ // Czas aktywności ściany minął
+ wall.isDead = true; // Oznacz do usunięcia (grafiki są usuwane w pętli powyżej przy isDead)
+ }
+ }
+ // Jeśli segment.visual jeszcze nie istnieje, to znaczy, że jesteśmy w fazie ostrzeżenia,
+ // która jest zarządzana przez setTimeout w MinibossCC.performAttack.
+ // Tutaj nie ma potrzeby dodatkowej logiki dla samej fazy ostrzeżenia.
+ }
+ if (player && player.dead) {/* Obsługiwane przez player.die() */}
}
- // Inne stany (title, gameOver, grillMenu etc.) nie mają zazwyczaj aktywnej logiki w game.update,
- // ale mogą mieć np. animacje UI, które są obsługiwane przez LK lub tween.
-}; // Koniec game.update
+};
+// -------- KONIEC CAŁEJ, PEŁNEJ, ZAKTUALIZOWANEJ FUNKCJI game.update -------- // Koniec game.update
// --- Rozpoczęcie gry ---
// Zakładamy, że obiekt gameState jest zdefiniowany gdzieś wcześniej w kodzie
if (typeof gameState !== 'undefined' && typeof gameState.init === 'function') {
gameState.init();