Code edit (1 edits merged)
Please save this source code
Code edit (8 edits merged)
Please save this source code
User prompt
asset mainmenu its a shape not image
User prompt
add new asset mainmenu
Code edit (3 edits merged)
Please save this source code
User prompt
add new asseet cursedenergybar
Code edit (1 edits merged)
Please save this source code
Code edit (1 edits merged)
Please save this source code
Code edit (8 edits merged)
Please save this source code
User prompt
add new assets scythe_action_0 scythe_action_1 scythe_action_2 scythe_action_3 scythe_action_4 scythe_action_5
User prompt
add new assets scythe_action_0 scythe_action_1 scythe_action_2 scythe_action_3 scythe_action_4 scythe_action_5
User prompt
add new asset scythe_appear_1
Code edit (1 edits merged)
Please save this source code
Code edit (17 edits merged)
Please save this source code
User prompt
add new asset explosion_frame_0 to explosion_frame_6
User prompt
add new asset projectile_spinning_asset
Code edit (1 edits merged)
Please save this source code
Code edit (1 edits merged)
Please save this source code
User prompt
add new asset bossSkillAnim06 to bossSkillAnim08 width: 300, height: 300
Code edit (18 edits merged)
Please save this source code
User prompt
add new assets bossSkillAnim0 to bossSkillAnim05 width: 300, height: 300
Code edit (1 edits merged)
Please save this source code
User prompt
add new asset crystal_state_0, crystal_state_1, crystal_state_2, crystal_state_3, crystal_state_4, crystal_explosion_f0, crystal_explosion_f1, crystal_explosion_f2, crystal_explosion_f3
Code edit (1 edits merged)
Please save this source code
Code edit (1 edits merged)
Please save this source code
/**** * Plugins ****/ var tween = LK.import("@upit/tween.v1"); var storage = LK.import("@upit/storage.v1"); /**** * Classes ****/ // Klasy z stworzylo zloto.txt (bardziej zaawansowana walka z bossem) // Klasy z stworzylo zloto.txt (bardziej zaawansowana walka z bossem) var Boss = Container.expand(function () { var self = Container.call(this); 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; self.pickNewCreepDirection(); tween(self, { tint: 0xFF3300 }, { duration: 1000, easing: tween.easeInOut }); } } if (self.health <= 0) { console.log("DEBUG: Boss health <= 0, calling self.die()."); self.die(); } }; // NOWA METODA DO CZYSZCZENIA ATAKÓW self.clearAllAttacks = function () { console.log("DEBUG: Boss.clearAllAttacks CALLED. Clearing " + self.attacks.length + " attacks."); var attacksToClear = self.attacks.slice(); // Iteruj po kopii attacksToClear.forEach(function (attack) { if (attack.visual && !attack.visual.destroyed) { if (attack.visual.parent) { attack.visual.parent.removeChild(attack.visual); // Usuń z rodzica (np. 'game') } attack.visual.destroy(); } }); self.attacks = []; // Wyczyść tablicę ataków bossa console.log("DEBUG: Boss.clearAllAttacks FINISHED. Attacks remaining: " + self.attacks.length); }; self.die = function () { var currentGameState = typeof gameState !== 'undefined' ? gameState.currentState : "gameState undefined"; console.log("DEBUG: Boss.die() called. Current state:", currentGameState, "Is boss already dead?", self.dead, "Is NewBossPlusMode?", isNewBossPlusMode); if (self.dead && currentGameState !== "game") { console.log("DEBUG: Boss.die() - Already dead and not in 'game' state. Attempting cleanup again just in case."); self.clearAllAttacks("Die - Already dead, not in game state"); return; } if (self.dead) { console.log("DEBUG: Boss.die() - Already dead. Exiting."); return; } self.dead = true; console.log("DEBUG: Boss.die() - Boss marked as dead. Clearing attacks."); self.clearAllAttacks("Die - Normal death sequence"); // Dźwięk zwycięstwa tylko w trybie standardowym if (typeof isNewBossPlusMode !== 'undefined' && !isNewBossPlusMode) { LK.getSound('victory').play(); } if (typeof gameState !== 'undefined' && gameState.currentState === "game") { console.log("DEBUG: Boss.die - In 'game' state."); // ZMIANA TUTAJ: Boss+ będzie miał animację śmierci i przejdzie do Grill Menu // Niezależnie od tego, czy to Boss+ czy standardowy, wykonaj animację śmierci console.log("DEBUG: Boss.die - Starting death animation tween for boss (standard or Boss+)."); tween(self, { alpha: 0, scaleX: self.scaleX * 1.2, scaleY: self.scaleY * 1.2 }, { duration: 2000, easing: tween.easeOut, onFinish: function onFinish() { console.log("DEBUG: Boss.die tween onFinish reached."); // Zwiększ licznik pokonanych bossów (możesz chcieć osobny licznik dla Boss+) storage.bossesDefeated = (parseInt(storage.bossesDefeated, 10) || 0) + 1; console.log("DEBUG: Boss.die onFinish - Bosses defeated:", storage.bossesDefeated); if (typeof gameState !== 'undefined' && typeof gameState.showGrillScreen === 'function') { // Dla Boss+ możesz przekazać specjalną flagę, jeśli chcesz inny komunikat na Grill Menu var wasBossPlusDefeated = typeof isNewBossPlusMode !== 'undefined' && isNewBossPlusMode; gameState.showGrillScreen(false, wasBossPlusDefeated); // (survivedBossPlusChallenge = false, defeatedBossPlus = wasBossPlusDefeated) } } }); } else { console.log("DEBUG: Boss.die - Not in 'game' state (e.g., during restart), skipping animations. Attacks should be cleared."); } }; self.update = function () { if (typeof gameState !== 'undefined' && gameState.currentState !== "game" && gameState.currentState !== "rollMaster") { if (self.rolling) { self.rolling = false; } return; } if (self.dead) { return; } self.ultimateAttackCooldownTimer++; if (self.circleAttackActiveCooldown < self.circleAttackCooldownDuration) { self.circleAttackActiveCooldown++; } if (self.attackCooldown > 30 && typeof player !== 'undefined' && player && !player.dead && !self.isCharging) { var dx = player.x - self.x; var dy = player.y - self.y; var distance = Math.sqrt(dx * dx + dy * dy); var moveSpeed = self.speed || 2; if (distance > 150) { var moveX = dx / distance * moveSpeed; var moveY = dy / distance * moveSpeed; var nextX = self.x + moveX; var nextY = self.y + moveY; var halfWidth = (self.width || 100) * (self.scaleX || 1) / 2; var halfHeight = (self.height || 100) * (self.scaleY || 1) / 2; var minX = 100 + halfWidth; var maxX = 2048 - 100 - halfWidth; var minY = 300 + halfHeight; var maxY = 2732 - 100 - halfHeight; self.x = Math.max(minX, Math.min(nextX, maxX)); self.y = Math.max(minY, Math.min(nextY, maxY)); } } for (var i = self.attacks.length - 1; i >= 0; i--) { var attack = self.attacks[i]; var shouldRemove = false; if (attack.type !== 'line_controller' && (!attack || !attack.visual || attack.visual.destroyed)) { self.attacks.splice(i, 1); continue; } if (attack.visual && typeof attack.visual.update === 'function') { attack.visual.update(); } if (attack.type === 'circle_orbiting') { if (!attack.detached) { attack.baseAngle += attack.angularSpeed; var currentOrbAngle = attack.baseAngle + attack.angleOffset; attack.x = attack.centerX + Math.cos(currentOrbAngle) * attack.radius; attack.y = attack.centerY + Math.sin(currentOrbAngle) * attack.radius; if (attack.visual) { attack.visual.x = attack.x; attack.visual.y = attack.y; } attack.detachCounter--; if (attack.detachCounter <= 0) { attack.detached = true; var tangentialAngle = currentOrbAngle + Math.PI / 2; var launchSpeed = 6; attack.vx = Math.cos(tangentialAngle) * launchSpeed; attack.vy = Math.sin(tangentialAngle) * launchSpeed; } } else { attack.x += attack.vx; attack.y += attack.vy; if (attack.visual) { attack.visual.x = attack.x; attack.visual.y = attack.y; } attack.lifeTime--; if (attack.x < -200 || attack.x > 2248 || attack.y < -200 || attack.y > 2932) { shouldRemove = true; } if (attack.lifeTime <= 0) { shouldRemove = true; } } } else if (attack.type === 'ultimate_orb') { attack.x += attack.vx; attack.y += attack.vy; if (attack.visual) { attack.visual.x = attack.x; attack.visual.y = attack.y; } attack.lifeTime--; if (attack.x < -200 || attack.x > 2048 + 200 || attack.y < -200 || attack.y > 2732 + 200) { shouldRemove = true; } if (attack.lifeTime <= 0) { shouldRemove = true; } } else if (attack.type === 'line_controller') { attack.x += attack.directionX * attack.speed; attack.y += attack.directionY * attack.speed; var currentTime = LK.getGameTime(); if (attack.currentSegmentIndex < attack.totalSegmentsToSpawn && currentTime - attack.lastSpawnTime >= attack.segmentDelay) { self.createAttack(attack.x, attack.y, attack.segmentLifeTime, 'line'); attack.currentSegmentIndex++; attack.lastSpawnTime = currentTime; } var controllerLifeTimeAfterSpawning = attack.segmentLifeTime; if (attack.currentSegmentIndex >= attack.totalSegmentsToSpawn && LK.getGameTime() - attack.lastSpawnTime > controllerLifeTimeAfterSpawning) { shouldRemove = true; } } else if (attack.attackObjectType && attack.type !== 'line_controller') { attack.lifeTime--; if (attack.lifeTime <= 0) { shouldRemove = true; } } if (!shouldRemove && attack.isActive && typeof player !== 'undefined' && player && !player.dead && !player.invulnerable) { if (attack.type !== 'line_controller') { var dx_p = player.x - attack.x; var dy_p = player.y - attack.y; var distance_p = Math.sqrt(dx_p * dx_p + dy_p * dy_p); var playerRadius_p = player.width / 2 * 0.8; var colRadius = attack.collisionRadius || attack.radius || 60; if (distance_p < playerRadius_p + colRadius) { player.takeDamage(1); shouldRemove = true; } } } if (shouldRemove) { if (attack.visual && !attack.visual.destroyed) { if (attack.visual.parent) { attack.visual.parent.removeChild(attack.visual); } attack.visual.destroy(); } self.attacks.splice(i, 1); } } if (self.attackCooldown > 0) { self.attackCooldown--; } if (self.attackCooldown <= 0 && !self.isCharging) { self.startAttackPattern(); } }; // Koniec self.update dla Bossa // Pełne implementacje funkcji lineAttack, chargeAttack, ultimateAttack, startAttackPattern // powinny być tutaj wklejone z Twojej poprzedniej, kompletnej wersji. // Poniżej skrócone wersje dla kompletności struktury. self.lineAttack = function () { LK.getSound('bossAttack').play(); if (typeof player === 'undefined' || !player) { return; } var startX = self.x; var startY = self.y; var targetX = player.x; var targetY = player.y; var dx = targetX - startX; var dy = targetY - startY; var distance = Math.sqrt(dx * dx + dy * dy); var normalizedDx = 0; var normalizedDy = 0; if (distance > 0) { normalizedDx = dx / distance; normalizedDy = dy / distance; } var wallSpeed = typeof isNewBossPlusMode !== 'undefined' && isNewBossPlusMode ? 3 : 2; var numberOfSegments = typeof isNewBossPlusMode !== 'undefined' && isNewBossPlusMode ? 10 : 8; var segmentSpawnDuration = typeof isNewBossPlusMode !== 'undefined' && isNewBossPlusMode ? 2500 : 3000; var segmentLifeTime = typeof isNewBossPlusMode !== 'undefined' && isNewBossPlusMode ? 2000 : 2500; var delayPerSegment = segmentSpawnDuration / numberOfSegments; var lineAttackController = { type: 'line_controller', currentSegmentIndex: 0, lastSpawnTime: LK.getGameTime(), totalSegmentsToSpawn: numberOfSegments, segmentDelay: delayPerSegment, segmentLifeTime: segmentLifeTime, directionX: normalizedDx, directionY: normalizedDy, speed: wallSpeed, x: startX, y: startY, targetX: targetX, targetY: targetY }; self.attacks.push(lineAttackController); }; self.chargeAttack = function () { LK.getSound('bossAttack').play(); if (typeof player === 'undefined' || !player) { return; } if (self.bossAttackAnim) { self.bossAttackAnim.stop(); if (self.bossAttackAnim.parent) { self.bossAttackAnim.parent.removeChild(self.bossAttackAnim); } self.bossAttackAnim.destroy(); self.bossAttackAnim = null; } if (!self.bossGraphics || self.bossGraphics && self.bossGraphics.destroyed) { if (self.bossGraphics && self.bossGraphics.parent) { self.bossGraphics.parent.removeChild(self.bossGraphics); } self.bossGraphics = self.addChild(LK.getAsset('bossIdle', { anchorX: 0.5, anchorY: 0.5, x: 0, y: 0 })); self.bossGraphics.scaleX = 2; self.bossGraphics.scaleY = 2; } self.isCharging = true; var dx = player.x - self.x; var dy = player.y - self.y; var distance = Math.sqrt(dx * dx + dy * dy); if (distance > 0) { dx /= distance; dy /= distance; } var chargeDistance = typeof isNewBossPlusMode !== 'undefined' && isNewBossPlusMode ? 700 : 500; var chargeDuration = typeof isNewBossPlusMode !== 'undefined' && isNewBossPlusMode ? 600 : 800; tween(self, { x: self.x + dx * chargeDistance, y: self.y + dy * chargeDistance }, { duration: chargeDuration * (self.attackSpeedMultiplier || 1), easing: tween.easeIn, onFinish: function onFinish() { self.isCharging = false; } }); }; self.ultimateAttack = function () { if (self.dead || typeof gameState !== 'undefined' && gameState.currentState !== "game" || typeof isNewBossPlusMode !== 'undefined' && !isNewBossPlusMode) { return; } if (self.bossAttackAnim) { self.bossAttackAnim.stop(); if (self.bossAttackAnim.parent) { self.bossAttackAnim.parent.removeChild(self.bossAttackAnim); } self.bossAttackAnim.destroy(); self.bossAttackAnim = null; } if (!self.bossGraphics || self.bossGraphics && self.bossGraphics.destroyed) { if (self.bossGraphics && self.bossGraphics.parent) { self.bossGraphics.parent.removeChild(self.bossGraphics); } self.bossGraphics = self.addChild(LK.getAsset('bossIdle', { anchorX: 0.5, anchorY: 0.5, x: 0, y: 0 })); self.bossGraphics.scaleX = 2; self.bossGraphics.scaleY = 2; } var screenWidth = 2048; var screenHeight = 2732; var iconWidth = 700; var iconHeight = 700; var startX = -iconWidth; var middleX = screenWidth / 2; var endX = screenWidth + iconWidth; var targetY = 250; var hoverAmplitudeY = 20; var hoverDurationY = 1000; var rotationAmplitude = 0.05; var rotationDuration = 1500; var moveInDuration = 400; var hoverDurationTotal = 2000; var moveOutDuration = 400; var attackIcon = game.addChild(LK.getAsset('ultimateBossAttack_icon', { anchorX: 0.5, anchorY: 0.5, x: startX, y: targetY, alpha: 0, scaleX: 0.8, scaleY: 0.8, rotation: 0 })); tween(attackIcon, { alpha: 1, scaleX: 1, scaleY: 1, x: middleX }, { duration: moveInDuration, easing: tween.easeOutQuad, onFinish: function onFinish() { if (attackIcon.destroyed) { return; } var hoverTweenY = tween(attackIcon, { y: attackIcon.y + hoverAmplitudeY }, { duration: hoverDurationY / 2, easing: tween.easeInOut, yoyo: true, repeat: Infinity }); var rotationTween = tween(attackIcon, { rotation: attackIcon.rotation + rotationAmplitude }, { duration: rotationDuration / 2, easing: tween.easeInOut, yoyo: true, repeat: Infinity }); LK.setTimeout(function () { if (attackIcon.destroyed) { if (hoverTweenY) { hoverTweenY.stop(); } if (rotationTween) { rotationTween.stop(); } return; } if (hoverTweenY) { hoverTweenY.stop(); } if (rotationTween) { rotationTween.stop(); } tween(attackIcon, { y: targetY, rotation: 0 }, { duration: 100, onFinish: function onFinish() { tween(attackIcon, { alpha: 0, x: endX }, { duration: moveOutDuration, easing: tween.easeInQuad, onFinish: function onFinish() { if (attackIcon && !attackIcon.destroyed) { if (attackIcon.parent) { attackIcon.parent.removeChild(attackIcon); } attackIcon.destroy(); } } }); } }); }, hoverDurationTotal); } }); var originalOrbWidth = 300; var originalOrbHeight = 300; var baseOrbRadius = Math.max(originalOrbWidth, originalOrbHeight) / 2; var growAndFadeInDuration = 6000; var finalScale = 2.6; var orbSpeed = 8; var travelDurationMs = 6000; function createAndLaunchOrb(spawnX, spawnDelay) { LK.setTimeout(function () { if (self.dead || typeof gameState !== 'undefined' && gameState.currentState !== "game") { return; } var orbFrames = []; for (var i = 0; i <= 7; i++) { orbFrames.push(LK.getAsset('ultimatebossattack_orb_' + i, { anchorX: 0.5, anchorY: 0.5, clone: true })); } var orbAnim = new SpriteAnimation({ frames: orbFrames, frameDuration: 70, loop: true, x: spawnX, y: 300, anchorX: 0.5, anchorY: 0.5, scaleX: 0.1, scaleY: 0.1, alpha: 0 }); game.addChild(orbAnim); tween(orbAnim, { scaleX: finalScale, scaleY: finalScale, alpha: 1 }, { duration: growAndFadeInDuration, easing: tween.easeOutQuad, onFinish: function onFinish() { if (orbAnim.destroyed) { return; } var targetXPlayer = typeof player !== 'undefined' && player ? player.x : screenWidth / 2; var targetYPlayer = typeof player !== 'undefined' && player ? player.y : screenHeight / 2; var actualOrbRadius = baseOrbRadius * finalScale; var dxOrb = targetXPlayer - orbAnim.x; var dyOrb = targetYPlayer - orbAnim.y; var distOrb = Math.sqrt(dxOrb * dxOrb + dyOrb * dyOrb); var vxOrb = distOrb > 0 ? dxOrb / distOrb * orbSpeed : 0; var vyOrb = distOrb > 0 ? dyOrb / distOrb * orbSpeed : 0; self.attacks.push({ type: 'ultimate_orb', visual: orbAnim, vx: vxOrb, vy: vyOrb, radius: actualOrbRadius, lifeTime: travelDurationMs / (1000 / 60), x: orbAnim.x, y: orbAnim.y, isActive: true }); } }); }, spawnDelay); } var spawnDelayBetweenOrbs = 1000; createAndLaunchOrb(screenWidth / 2, 0); createAndLaunchOrb(screenWidth / 2 - 600, spawnDelayBetweenOrbs); createAndLaunchOrb(screenWidth / 2 + 600, spawnDelayBetweenOrbs * 2); }; self.startAttackPattern = function () { self.attackCooldown = typeof isNewBossPlusMode !== 'undefined' && isNewBossPlusMode ? 150 : 190; var attackType; var availableAttacks = []; if (self.circleAttackActiveCooldown >= self.circleAttackCooldownDuration) { availableAttacks.push(0); } availableAttacks.push(1); availableAttacks.push(2); var isUltimateAttackReady = self.ultimateAttackCooldownTimer >= self.nextUltimateAttackAvailableTime; if (typeof isNewBossPlusMode !== 'undefined' && isNewBossPlusMode && isUltimateAttackReady) { availableAttacks.push(3); } if (availableAttacks.length === 0) { return; } attackType = availableAttacks[Math.floor(Math.random() * availableAttacks.length)]; self.playBossAttackAnim(attackType); if (attackType === 0) { self.circleAttack(); self.circleAttackActiveCooldown = 0; } else if (attackType === 1) { self.lineAttack(); } else if (attackType === 2) { self.chargeAttack(); } else if (attackType === 3 && typeof isNewBossPlusMode !== 'undefined' && isNewBossPlusMode) { self.ultimateAttack(); self.ultimateAttackCooldownTimer = 0; self.nextUltimateAttackAvailableTime = self.ultimateAttackMinInterval + Math.floor(Math.random() * (self.ultimateAttackMaxInterval - self.ultimateAttackMinInterval + 1)); self.attackCooldown = 6 * 60; } }; // Inicjalizacja zmiennych bossa self.nextUltimateAttackAvailableTime = self.ultimateAttackMinInterval + Math.floor(Math.random() * (self.ultimateAttackMaxInterval - self.ultimateAttackMinInterval + 1)); self.health = typeof isNewBossPlusMode !== 'undefined' && isNewBossPlusMode ? 300 : 150; self.maxHealth = self.health; self.speed = typeof isNewBossPlusMode !== 'undefined' && isNewBossPlusMode ? 3 : 2; self.attackSpeedMultiplier = 1; self.phase = 1; self.attacks = []; self.attackCooldown = 120; // Początkowy cooldown przed pierwszym atakiem self.dead = false; return self; }); var MinibossCC = Container.expand(function (options) { var self = Container.call(this); options = options || {}; self.maxHp = options.maxHp || 200; self.hp = self.maxHp; self.speed = options.speed || 0; self.isDead = false; self.attackPattern = ['explosiveProjectile', 'laserWall']; self.currentAttackIndex = 0; self.attackCooldowns = { 'explosiveProjectile': 8 * 60, 'laserWall': 5 * 60 }; self.attackCooldown = 120; self.currentAttackName = ''; self.isCurrentlyPlayingAttackAnim = false; self.activeSkillAnimationInstance = null; self.isTeleporting = false; self.teleportCooldownTimer = 0; self.teleportIntervalMin = 9 * 60; self.teleportIntervalMax = 16 * 60; self.nextTeleportTime = self.teleportIntervalMin + Math.floor(Math.random() * (self.teleportIntervalMax - self.teleportIntervalMin + 1)); self.activeClones = []; self.maxClones = 2; self.canTeleportWithClones = true; self.teleportCloneCooldownDuration = 10 * 60; self.isWaitingForClonesToDespawn = false; self.cloneTeleportTimeoutId = null; self.creepSpeed = 0.9; self.creepDirectionX = 0; self.creepDirectionY = 0; self.creepDirectionChangeIntervalMin = 3 * 60; self.creepDirectionChangeIntervalMax = 7 * 60; self.creepDirectionChangeTimer = 0; self.actualWidth = 120; self.actualHeight = 120; try { self.graphics = self.attachAsset('miniboss_cc_asset', { anchorX: 0.5, anchorY: 0.5 }); } catch (e) { console.warn("Nie udało się załadować 'miniboss_cc_asset', używam awaryjnego Shape:", e); self.graphics = new Shape({ width: 120, height: 120, color: 0xFF8C00, shape: 'box' }); self.addChild(self.graphics); } if (self.graphics && typeof self.graphics.width !== 'undefined') { self.width = self.graphics.width * (self.graphics.scaleX || 1); self.height = self.graphics.height * (self.graphics.scaleY || 1); } else { self.width = 120; self.height = 120; } if (self.graphics && typeof self.graphics.width === 'number' && typeof self.graphics.height === 'number') { self.actualWidth = self.graphics.width * (self.graphics.scaleX || 1); self.actualHeight = self.graphics.height * (self.graphics.scaleY || 1); } else { self.actualWidth = self.width; self.actualHeight = self.height; } self.pickNewCreepDirection = function () { var angle = Math.random() * 2 * Math.PI; self.creepDirectionX = Math.cos(angle); self.creepDirectionY = Math.sin(angle); self.creepDirectionChangeTimer = self.creepDirectionIntervalMin + Math.floor(Math.random() * (self.creepDirectionChangeIntervalMax - self.creepDirectionIntervalMin + 1)); }; self.createMinibossSkillAnim = function () { var frames = []; var frameBaseName = 'bossSkillAnim0'; var totalSkillFrames = 9; for (var i = 0; i < totalSkillFrames; i++) { var assetName = frameBaseName + (i === 0 ? '' : String(i)); try { frames.push(LK.getAsset(assetName, { anchorX: 0.5, anchorY: 0.5, clone: true })); } catch (e) { console.error("Błąd ładowania klatki animacji Klona: " + assetName, e); if (i > 0 && frames.length > 0) { frames.push(frames[0]); } else { var placeholderFrame = new Shape({ width: 120, height: 120, color: 0xFF00FF, shape: 'box' }); placeholderFrame.anchor.set(0.5, 0.5); frames.push(placeholderFrame); } } } for (var i = totalSkillFrames - 2; i >= 1; i--) { var assetName = frameBaseName + (i === 0 ? '' : String(i)); try { frames.push(LK.getAsset(assetName, { anchorX: 0.5, anchorY: 0.5, clone: true })); } catch (e) { console.error("Błąd ładowania klatki powrotnej animacji Klona: " + assetName, e); if (frames.length > 0) { frames.push(frames[0]); } } } var skillAnimationObject = new SpriteAnimation({ frames: frames, frameDuration: 120, loop: false, anchorX: 0.5, anchorY: 0.5, x: 0, y: 0 }); return skillAnimationObject; }; self.playMinibossSkillAnim = function (onAnimationCompleteCallback) { if (self.isDead || self.isTeleporting) { self.isCurrentlyPlayingAttackAnim = false; if (typeof onAnimationCompleteCallback === 'function') { onAnimationCompleteCallback(); } return; } if (self.activeSkillAnimationInstance && self.activeSkillAnimationInstance.parent) { self.activeSkillAnimationInstance.stop(); self.removeChild(self.activeSkillAnimationInstance); self.activeSkillAnimationInstance.destroy(); self.activeSkillAnimationInstance = null; } if (self.graphics && !self.graphics.destroyed) { self.graphics.visible = false; } var newAnimation = self.createMinibossSkillAnim(); self.activeSkillAnimationInstance = self.addChild(newAnimation); self.activeSkillAnimationInstance.currentFrameIndex = 0; self.activeSkillAnimationInstance.animationTimer = 0; self.activeSkillAnimationInstance.framesArray = newAnimation.frames; self.activeSkillAnimationInstance.singleFrameDuration = newAnimation.frameDuration || 120; self.activeSkillAnimationInstance.playing = true; self.activeSkillAnimationInstance.removeChildren(); if (self.activeSkillAnimationInstance.framesArray && self.activeSkillAnimationInstance.framesArray.length > 0) { self.activeSkillAnimationInstance.addChild(self.activeSkillAnimationInstance.framesArray[0]); } else { self.isCurrentlyPlayingAttackAnim = false; if (self.graphics && !self.graphics.destroyed) { self.graphics.visible = true; } if (typeof onAnimationCompleteCallback === 'function') { onAnimationCompleteCallback(); } if (self.activeSkillAnimationInstance) { if (self.activeSkillAnimationInstance.parent) { self.removeChild(self.activeSkillAnimationInstance); } self.activeSkillAnimationInstance.destroy(); self.activeSkillAnimationInstance = null; } return; } self.activeSkillAnimationInstance.update = function () { if (!this.playing || !this.framesArray || this.framesArray.length === 0) { return; } this.animationTimer++; if (this.animationTimer * (1000 / 60) >= this.singleFrameDuration) { this.animationTimer = 0; this.currentFrameIndex++; if (this.currentFrameIndex >= this.framesArray.length) { this.playing = false; if (self.graphics && !self.graphics.destroyed) { self.graphics.visible = true; } if (this.parent) { this.parent.removeChild(this); } this.destroy(); if (self.activeSkillAnimationInstance === this) { self.activeSkillAnimationInstance = null; } if (typeof onAnimationCompleteCallback === 'function') { onAnimationCompleteCallback(); } else { self.isCurrentlyPlayingAttackAnim = false; } } else { this.removeChildren(); if (this.framesArray[this.currentFrameIndex]) { this.addChild(this.framesArray[this.currentFrameIndex]); } } } }; }; self.update = function () { if (self.isDead || gameState.currentState !== "cursedCrystal") { return; } if (self.isTeleporting || self.activeSkillAnimationInstance) {} else { self.creepDirectionChangeTimer--; if (self.creepDirectionChangeTimer <= 0) { self.pickNewCreepDirection(); } var nextX = self.x + self.creepDirectionX * self.creepSpeed; var nextY = self.y + self.creepDirectionY * self.creepSpeed; var halfWidth = self.actualWidth / 2; var halfHeight = self.actualHeight / 2; var arenaMinX = 50 + halfWidth; var arenaMaxX = 2048 - 50 - halfWidth; var arenaMinY = 50 + halfHeight; var arenaMaxY = 2732 - 50 - halfHeight; if (nextX >= arenaMinX && nextX <= arenaMaxX) { self.x = nextX; } else { self.creepDirectionX *= -1; self.creepDirectionChangeTimer = self.creepDirectionIntervalMin; } if (nextY >= arenaMinY && nextY <= arenaMaxY) { self.y = nextY; } else { self.creepDirectionY *= -1; self.creepDirectionChangeTimer = self.creepDirectionIntervalMin; } } if (self.canTeleportWithClones && !self.isTeleporting) { self.teleportCooldownTimer++; if (self.teleportCooldownTimer >= self.nextTeleportTime) { self.teleport(); return; } } if (!self.isTeleporting) { if (self.attackCooldown > 0) { self.attackCooldown--; } else if (player && !player.dead && !self.isCurrentlyPlayingAttackAnim) { self.isCurrentlyPlayingAttackAnim = true; self.performAttack(); } } }; self.performAttack = function () { if (!player || player.dead || self.isTeleporting) { self.isCurrentlyPlayingAttackAnim = false; return; } var executeAttackLogic = function executeAttackLogic() { if (self.isDead) { self.isCurrentlyPlayingAttackAnim = false; return; } self.currentAttackName = self.attackPattern[self.currentAttackIndex]; if (self.currentAttackName === 'explosiveProjectile') { var 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 numTotalSegments = 7; var middleSegmentIndex = Math.floor(numTotalSegments / 2); var scytheWidth = 160; var scytheHeight = 160; var spacingBetweenSegments = 20; var totalWallEffectiveHeight = numTotalSegments * scytheHeight + (numTotalSegments - 1) * spacingBetweenSegments; var wallPivotX = self.x; var wallPivotY = self.y; var warningDuration = 1500; var activeDuration = 4000; var rotationSpeed = 2 * Math.PI / 120; var laserWallInstance = { pivotX: wallPivotX, pivotY: wallPivotY, segments: [], currentAngle: 0, rotationSpeed: rotationSpeed, warningTimer: warningDuration, activeTimer: activeDuration, isDead: false }; var initialSegmentOffsetY = -(totalWallEffectiveHeight / 2) + scytheHeight / 2; var frameDurationAction = 100; var frameDurationLoop = 100; for (var i = 0; i < numTotalSegments; i++) { var segmentOffsetY = initialSegmentOffsetY + i * (scytheHeight + spacingBetweenSegments); var spawnPosX = wallPivotX; var spawnPosY = wallPivotY + segmentOffsetY; if (i === middleSegmentIndex) { laserWallInstance.segments.push({ isPlaceholder: true, initialOffsetY: segmentOffsetY, width: scytheWidth, height: scytheHeight }); continue; } var scytheAppearVisual = null; var FADE_IN_DURATION = 500; // Czas trwania fade in w milisekundach - dostosuj! try { scytheAppearVisual = LK.getAsset('scythe_appear_0', { anchorX: 0.5, anchorY: 0.5, clone: true, alpha: 0 // Zacznij od przezroczystości 0 }); scytheAppearVisual.x = spawnPosX; scytheAppearVisual.y = spawnPosY; game.addChild(scytheAppearVisual); tween(scytheAppearVisual, { alpha: 1 }, { duration: FADE_IN_DURATION, easing: tween.easeInQuad }); } catch (e) { console.error("Błąd ładowania assetu scythe_appear_0: ", e); scytheAppearVisual = new Shape({ width: scytheWidth, height: scytheHeight, color: 0xCCCCCC, shape: 'box', alpha: 0 // Zacznij od przezroczystości 0 }); scytheAppearVisual.anchor.set(0.5, 0.5); scytheAppearVisual.x = spawnPosX; scytheAppearVisual.y = spawnPosY; game.addChild(scytheAppearVisual); tween(scytheAppearVisual, { alpha: 1 }, { duration: FADE_IN_DURATION, easing: tween.easeInQuad }); } laserWallInstance.segments.push({ isPlaceholder: false, initialOffsetX: 0, initialOffsetY: segmentOffsetY, width: scytheWidth, height: scytheHeight, visual: scytheAppearVisual, currentX: spawnPosX, currentY: spawnPosY, animationPhase: 'appearing_static' }); } LK.setTimeout(function () { if (laserWallInstance.isDead) { return; } laserWallInstance.segments.forEach(function (segment) { if (segment.isPlaceholder || segment.animationPhase !== 'appearing_static') { return; } if (segment.visual && segment.visual.parent) { segment.visual.parent.removeChild(segment.visual); segment.visual.destroy(); } var actionFrames = []; var actionAssetNames = ['scythe_action_0', 'scythe_action_1', 'scythe_action_2', 'scythe_action_3', 'scythe_action_4', 'scythe_action_5']; try { actionAssetNames.forEach(function (name) { actionFrames.push(LK.getAsset(name, { anchorX: 0.5, anchorY: 0.5, clone: true })); }); } catch (e) { console.error("Błąd ładowania klatek akcji kosy: ", e); } if (actionFrames.length === 0) { console.error("Brak klatek dla actionScytheAnim segmentu", segment); segment.visual = new Shape({ width: scytheWidth, height: scytheHeight, color: 0xFF0000, shape: 'box' }); segment.visual.anchor.set(0.5, 0.5); segment.visual.x = segment.currentX; segment.visual.y = segment.currentY; game.addChild(segment.visual); segment.animationPhase = 'error_action'; return; } var actionScytheAnim = new SpriteAnimation({ frames: actionFrames, frameDuration: frameDurationAction, loop: false, x: segment.currentX, y: segment.currentY, anchorX: 0.5, anchorY: 0.5 }); game.addChild(actionScytheAnim); segment.visual = actionScytheAnim; segment.animationPhase = 'action_once'; actionScytheAnim.play(); actionScytheAnim.onComplete = function () { if (segment.isPlaceholder || segment.animationPhase !== 'action_once' || laserWallInstance && laserWallInstance.isDead) { return; } if (segment.visual && segment.visual.parent) { segment.visual.parent.removeChild(segment.visual); segment.visual.destroy(); } var loopFrames = []; var loopAssetIndices = [2, 3, 4, 5]; var loopAssetNames = loopAssetIndices.map(function (idx) { return idx < actionAssetNames.length ? actionAssetNames[idx] : null; }).filter(function (name) { return name !== null; }); try { loopAssetNames.forEach(function (name) { loopFrames.push(LK.getAsset(name, { anchorX: 0.5, anchorY: 0.5, clone: true })); }); } catch (e) { console.error("Błąd ładowania klatek pętli kosy: ", e); } if (loopFrames.length === 0) { console.error("Brak klatek dla loopingScytheAnim segmentu", segment); segment.visual = new Shape({ width: scytheWidth, height: scytheHeight, color: 0xFF0000, shape: 'box' }); segment.visual.anchor.set(0.5, 0.5); segment.visual.x = segment.currentX; segment.visual.y = segment.currentY; game.addChild(segment.visual); segment.animationPhase = 'error_loop'; return; } var loopingScytheAnim = new SpriteAnimation({ frames: loopFrames, frameDuration: frameDurationLoop, loop: true, x: segment.currentX, y: segment.currentY, anchorX: 0.5, anchorY: 0.5 }); game.addChild(loopingScytheAnim); segment.visual = loopingScytheAnim; segment.animationPhase = 'looping_cut'; loopingScytheAnim.play(); }; }); }, warningDuration); if (gameState.cursedCrystalActiveLaserWalls === undefined) { gameState.cursedCrystalActiveLaserWalls = []; } gameState.cursedCrystalActiveLaserWalls.push(laserWallInstance); } self.currentAttackIndex = (self.currentAttackIndex + 1) % self.attackPattern.length; self.attackCooldown = self.attackCooldowns[self.currentAttackName] || 180; self.isCurrentlyPlayingAttackAnim = false; }; self.playMinibossSkillAnim(executeAttackLogic); }; self.teleport = function () { if (self.isDead || self.isTeleporting) { console.log("MinibossCC: Próba teleportacji, ale już martwy lub teleportuje. Dead: " + self.isDead + ", Teleporting: " + self.isTeleporting); return; } if (!self.canTeleportWithClones) { console.log("MinibossCC: Próba teleportacji z klonami, ale jest na cooldownie po klonach lub czeka na klony."); return; } self.canTeleportWithClones = false; self.isWaitingForClonesToDespawn = true; console.log("MinibossCC: TELEPORT START. Pozycja: X=" + self.x.toFixed(0) + ", Y=" + self.y.toFixed(0) + ", Alpha: " + self.alpha); self.isTeleporting = true; self.attackCooldown = 120 + Math.floor(Math.random() * 60); tween(self, { alpha: 0 }, { duration: 600, easing: tween.easeInQuad, onFinish: function onFinish() { if (self.isDead) { self.alpha = 1; self.isTeleporting = false; self.isWaitingForClonesToDespawn = false; self.canTeleportWithClones = true; console.log("MinibossCC: Teleport przerwany, boss martwy w trakcie fade-out."); return; } var halfWidth = self.actualWidth / 2; var halfHeight = self.actualHeight / 2; var arenaMinX = 50 + halfWidth; var arenaMaxX = 2048 - 50 - halfWidth; var arenaMinY = 50 + halfHeight; var arenaMaxY = 2732 - 50 - halfHeight; var newX, newY, distanceToPlayer; var attempts = 0; var minDistanceToPlayer = 300; do { newX = arenaMinX + Math.random() * (arenaMaxX - arenaMinX); newY = arenaMinY + Math.random() * (arenaMaxY - arenaMinY); if (typeof player !== 'undefined' && player && !player.dead) { distanceToPlayer = Math.sqrt(Math.pow(newX - player.x, 2) + Math.pow(newY - player.y, 2)); } else { distanceToPlayer = minDistanceToPlayer + 1; } attempts++; } while (distanceToPlayer < minDistanceToPlayer && attempts < 10); if (attempts >= 10 && distanceToPlayer < minDistanceToPlayer) { console.warn("MinibossCC: Nie udało się znaleźć pozycji wystarczająco daleko od gracza po 10 próbach."); } self.x = newX; self.y = newY; self.pickNewCreepDirection(); var clonesActuallySpawned = 0; var clonesToAttemptSpawn = Math.floor(Math.random() * 2) + 1; for (var i = 0; i < clonesToAttemptSpawn; i++) { if (self.activeClones.length < self.maxClones) { var cloneSpawnAttempts = 0; var cloneX, cloneY, distToBoss, distToPlayerClone; var minDistanceToBoss = 200; var minDistanceToPlayerForClone = 150; do { cloneX = arenaMinX + Math.random() * (arenaMaxX - arenaMinX); cloneY = arenaMinY + Math.random() * (arenaMaxY - arenaMinY); distToBoss = Math.sqrt(Math.pow(cloneX - self.x, 2) + Math.pow(cloneY - self.y, 2)); if (typeof player !== 'undefined' && player && !player.dead) { distToPlayerClone = Math.sqrt(Math.pow(cloneX - player.x, 2) + Math.pow(cloneY - player.y, 2)); } else { distToPlayerClone = minDistanceToPlayerForClone + 1; } cloneSpawnAttempts++; } while ((distToBoss < minDistanceToBoss || distToPlayerClone < minDistanceToPlayerForClone) && cloneSpawnAttempts < 10); var clone = new MinibossCCClone({ x: cloneX, y: cloneY, owner: self }); game.addChild(clone); self.activeClones.push(clone); clonesActuallySpawned++; } } if (clonesActuallySpawned === 0 && self.isWaitingForClonesToDespawn) { console.log("MinibossCC: No clones were spawned, starting teleport cooldown immediately."); self.startTeleportCooldownAfterClones(); } self.teleportCooldownTimer = 0; self.nextTeleportTime = self.teleportIntervalMin + Math.floor(Math.random() * (self.teleportIntervalMax - self.teleportIntervalMin + 1)); tween(self, { alpha: 1 }, { duration: 600, easing: tween.easeOutQuad, onFinish: function onFinishFadeIn() { self.isTeleporting = false; console.log("MinibossCC: TELEPORT FADE_IN_FINISH. Następna normalna teleportacja za: " + (self.nextTeleportTime / 60).toFixed(1) + "s"); } }); } }); }; self.takeDamage = function (amount) { if (self.isDead) { return; } self.hp -= amount; gameState.cursedCrystalMinibossHP = self.hp; LK.effects.flashObject(self.graphics || self, 0xFF0000, 200); if (self.hp <= 0) { self.hp = 0; gameState.cursedCrystalMinibossHP = self.hp; self.die(); } if (ui && ui.updateMinibossHealthCC) { ui.updateMinibossHealthCC(self.hp, self.maxHp, true); } }; self.die = function () { if (self.isDead) { return; } if (self.cloneTeleportTimeoutId) { LK.clearTimeout(self.cloneTeleportTimeoutId); self.cloneTeleportTimeoutId = null; } self.isWaitingForClonesToDespawn = false; self.canTeleportWithClones = true; if (self.activeClones && self.activeClones.length > 0) { console.log("MinibossCC dying, clearing " + self.activeClones.length + " active clones."); var clonesToKill = self.activeClones.slice(); clonesToKill.forEach(function (clone) { if (clone && !clone.isDead) { clone.die(true); } }); self.activeClones = []; } self.isDead = true; console.log("MinibossCC has been defeated!"); if (gameState.cursedCrystalActiveProjectiles) { gameState.cursedCrystalActiveProjectiles.forEach(function (proj) {}); gameState.cursedCrystalActiveProjectiles = []; } if (gameState.cursedCrystalActiveExplosions) { gameState.cursedCrystalActiveExplosions = []; } if (gameState.cursedCrystalActiveLaserWalls) { gameState.cursedCrystalActiveLaserWalls.forEach(function (wall) {}); gameState.cursedCrystalActiveLaserWalls = []; } if (typeof gameState.endCursedCrystalMode === 'function') { gameState.endCursedCrystalMode(true); } if (self.parent) { self.parent.removeChild(self); } if (self.destroy && !self.destroyed) { self.destroy(); } gameState.cursedCrystalMinibossObject = null; gameState.isMinibossActiveCC = false; }; self.startTeleportCooldownAfterClones = function () { if (!self.isWaitingForClonesToDespawn && self.activeClones.length > 0) { return; } if (!self.isWaitingForClonesToDespawn && self.activeClones.length === 0) { return; } console.log("MinibossCC: All clones despawned. Starting " + self.teleportCloneCooldownDuration / 60 + "-second cooldown for clone teleport."); self.isWaitingForClonesToDespawn = false; if (self.cloneTeleportTimeoutId) { LK.clearTimeout(self.cloneTeleportTimeoutId); } self.cloneTeleportTimeoutId = LK.setTimeout(function () { self.cloneTeleportTimeoutId = null; if (!self.isDead) { self.canTeleportWithClones = true; self.nextTeleportTime = self.teleportCooldownTimer; console.log("MinibossCC: Clone teleport is OFF COOLDOWN. Boss can attempt teleport. nextTeleportTime now: " + self.nextTeleportTime); } }, self.teleportCloneCooldownDuration * (1000 / 60)); }; self.x = options.x || 2048 / 2; self.y = options.y || 300; self.pickNewCreepDirection(); return self; }); // -------- KONIEC ZAKTUALIZOWANEJ KLASY MinibossCC -------- // NOWA KLASA DLA KLONA MINIBOSSA CC var MinibossCCClone = Container.expand(function (options) { var self = Container.call(this); options = options || {}; self.owner = options.owner; self.x = options.x || 2048 / 2; self.y = options.y || 300; self.isDead = false; self.lifeTimer = 20 * 60; self.attackCooldown = 4 * 60 + Math.floor(Math.random() * (2 * 60)); self.activeSkillAnimationInstance = null; self.isCurrentlyPlayingAttackAnim = false; self.creepSpeed = 0.9; self.creepDirectionX = 0; self.creepDirectionY = 0; self.creepDirectionChangeIntervalMin = 2 * 60; self.creepDirectionChangeIntervalMax = 5 * 60; self.creepDirectionChangeTimer = 0; // --- DEFINICJA METODY pickNewCreepDirection PRZED PIERWSZYM UŻYCIEM --- self.pickNewCreepDirection = function () { var angle = Math.random() * 2 * Math.PI; self.creepDirectionX = Math.cos(angle); self.creepDirectionY = Math.sin(angle); self.creepDirectionChangeTimer = self.creepDirectionIntervalMin + Math.floor(Math.random() * (self.creepDirectionChangeIntervalMax - self.creepDirectionIntervalMin + 1)); }; // --- PIERWSZE WYWOŁANIE METODY --- self.pickNewCreepDirection(); try { self.graphics = self.attachAsset('miniboss_cc_asset', { anchorX: 0.5, anchorY: 0.5 }); } catch (e) { console.warn("Nie udało się załadować 'miniboss_cc_asset' dla Klona, używam Shape.", e); self.graphics = new Shape({ width: 120, height: 120, color: 0xFF8C00, shape: 'box' }); self.addChild(self.graphics); } self.width = self.graphics && typeof self.graphics.width !== 'undefined' ? self.graphics.width : 120; self.height = self.graphics && typeof self.graphics.height !== 'undefined' ? self.graphics.height : 120; self.createMinibossSkillAnim = function () { var frames = []; var frameBaseName = 'bossSkillAnim0'; var totalSkillFrames = 9; for (var i = 0; i < totalSkillFrames; i++) { var assetName = frameBaseName + (i === 0 ? '' : String(i)); try { frames.push(LK.getAsset(assetName, { anchorX: 0.5, anchorY: 0.5, clone: true })); } catch (e) { console.error("Błąd ładowania klatki animacji Klona: " + assetName, e); if (i > 0 && frames.length > 0) { frames.push(frames[0]); } else { var placeholderFrame = new Shape({ width: 120, height: 120, color: 0xFF00FF, shape: 'box' }); placeholderFrame.anchor.set(0.5, 0.5); frames.push(placeholderFrame); } } } for (var i = totalSkillFrames - 2; i >= 1; i--) { var assetName = frameBaseName + (i === 0 ? '' : String(i)); try { frames.push(LK.getAsset(assetName, { anchorX: 0.5, anchorY: 0.5, clone: true })); } catch (e) { console.error("Błąd ładowania klatki powrotnej animacji Klona: " + assetName, e); if (frames.length > 0) { frames.push(frames[0]); } } } var skillAnimationObject = new SpriteAnimation({ frames: frames, frameDuration: 120, loop: false, anchorX: 0.5, anchorY: 0.5, x: 0, y: 0 }); return skillAnimationObject; }; self.playMinibossSkillAnim = function (onAnimationCompleteCallback) { if (self.isDead) { self.isCurrentlyPlayingAttackAnim = false; if (typeof onAnimationCompleteCallback === 'function') { onAnimationCompleteCallback(); } return; } if (self.activeSkillAnimationInstance && self.activeSkillAnimationInstance.parent) { self.activeSkillAnimationInstance.stop(); self.removeChild(self.activeSkillAnimationInstance); self.activeSkillAnimationInstance.destroy(); self.activeSkillAnimationInstance = null; } if (self.graphics && !self.graphics.destroyed) { self.graphics.visible = false; } var newAnimation = self.createMinibossSkillAnim(); self.activeSkillAnimationInstance = self.addChild(newAnimation); self.activeSkillAnimationInstance.currentFrameIndex = 0; self.activeSkillAnimationInstance.animationTimer = 0; self.activeSkillAnimationInstance.framesArray = newAnimation.frames; self.activeSkillAnimationInstance.singleFrameDuration = newAnimation.frameDuration || 120; self.activeSkillAnimationInstance.playing = true; self.activeSkillAnimationInstance.removeChildren(); if (self.activeSkillAnimationInstance.framesArray && self.activeSkillAnimationInstance.framesArray.length > 0) { self.activeSkillAnimationInstance.addChild(self.activeSkillAnimationInstance.framesArray[0]); } else { self.isCurrentlyPlayingAttackAnim = false; if (self.graphics && !self.graphics.destroyed) { self.graphics.visible = true; } if (typeof onAnimationCompleteCallback === 'function') { onAnimationCompleteCallback(); } if (self.activeSkillAnimationInstance) { if (self.activeSkillAnimationInstance.parent) { self.removeChild(self.activeSkillAnimationInstance); } self.activeSkillAnimationInstance.destroy(); self.activeSkillAnimationInstance = null; } return; } self.activeSkillAnimationInstance.update = function () { if (!this.playing || !this.framesArray || this.framesArray.length === 0) { return; } this.animationTimer++; if (this.animationTimer * (1000 / 60) >= this.singleFrameDuration) { this.animationTimer = 0; this.currentFrameIndex++; if (this.currentFrameIndex >= this.framesArray.length) { this.playing = false; if (self.graphics && !self.graphics.destroyed) { self.graphics.visible = true; } if (this.parent) { this.parent.removeChild(this); } this.destroy(); if (self.activeSkillAnimationInstance === this) { self.activeSkillAnimationInstance = null; } if (typeof onAnimationCompleteCallback === 'function') { onAnimationCompleteCallback(); } else { self.isCurrentlyPlayingAttackAnim = false; } } else { this.removeChildren(); if (this.framesArray[this.currentFrameIndex]) { this.addChild(this.framesArray[this.currentFrameIndex]); } } } }; }; self.takeDamage = function (amount) { if (self.isDead) { return; } LK.effects.flashObject(self.graphics || self, 0xFFFFFF, 150); self.die(); }; self.die = function (isTimeout) { if (self.isDead) { return; } self.isDead = true; if (!isTimeout) { LK.effects.flashObject(self.graphics || self, 0xFFFFFF, 150); } if (self.owner && self.owner.activeClones) { var index = self.owner.activeClones.indexOf(self); if (index > -1) { self.owner.activeClones.splice(index, 1); if (self.owner.activeClones.length === 0 && self.owner.isWaitingForClonesToDespawn) { self.owner.startTeleportCooldownAfterClones(); } } } var deathTween = tween(self, { alpha: 0 }, { duration: 800, easing: tween.easeOutQuad, onFinish: function onFinish() { if (self.parent) { self.parent.removeChild(self); } if (self.destroy && !self.destroyed) { self.destroy(); } } }); }; self.performSimpleAttack = function () { if (self.isDead || !player || player.dead) { self.isCurrentlyPlayingAttackAnim = false; return; } var executeCloneAttackLogic = function executeCloneAttackLogic() { if (self.isDead) { self.isCurrentlyPlayingAttackAnim = false; return; } var projectile = new MinibossExplosiveProjectile({ x: self.x, y: self.y, target: player, speed: 4, explosionRadius: 150 }); game.addChild(projectile); if (gameState.cursedCrystalActiveProjectiles === undefined) { gameState.cursedCrystalActiveProjectiles = []; } gameState.cursedCrystalActiveProjectiles.push(projectile); self.attackCooldown = 8 * 60 + Math.floor(Math.random() * (2 * 60)); self.isCurrentlyPlayingAttackAnim = false; }; self.playMinibossSkillAnim(executeCloneAttackLogic); }; self.update = function () { if (self.isDead || gameState.currentState !== "cursedCrystal") { return; } self.lifeTimer--; if (self.lifeTimer <= 0) { self.die(true); return; } if (self.activeSkillAnimationInstance) {} else { self.creepDirectionChangeTimer--; if (self.creepDirectionChangeTimer <= 0) { self.pickNewCreepDirection(); } var nextX = self.x + self.creepDirectionX * self.creepSpeed; var nextY = self.y + self.creepDirectionY * self.creepSpeed; var halfWidth = (self.width || 120) / 2; var halfHeight = (self.height || 120) / 2; var arenaMinX = 50 + halfWidth; var arenaMaxX = 2048 - 50 - halfWidth; var arenaMinY = 50 + halfHeight; var arenaMaxY = 2732 - 50 - halfHeight; if (nextX >= arenaMinX && nextX <= arenaMaxX) { self.x = nextX; } else { self.creepDirectionX *= -1; self.creepDirectionChangeTimer = self.creepDirectionIntervalMin; } if (nextY >= arenaMinY && nextY <= arenaMaxY) { self.y = nextY; } else { self.creepDirectionY *= -1; self.creepDirectionChangeTimer = self.creepDirectionIntervalMin; } } if (self.attackCooldown > 0) { self.attackCooldown--; } else if (!self.isCurrentlyPlayingAttackAnim && !self.activeSkillAnimationInstance) { self.isCurrentlyPlayingAttackAnim = true; self.performSimpleAttack(); } }; self.alpha = 0; self.scaleX = 0.5; self.scaleY = 0.5; tween(self, { alpha: 1, scaleX: 1, scaleY: 1 }, { duration: 500, easing: tween.easeOutQuad }); return self; }); // -------- KONIEC ZAKTUALIZOWANEJ KLASY MinibossCC -------- var MinibossExplosiveProjectile = Container.expand(function (options) { var self = Container.call(this); options = options || {}; self.x = options.x || 0; self.y = options.y || 0; self.speed = options.speed || 6; self.target = options.target; self.lifeTimer = 240; self.isDead = false; self.hasExploded = false; self.explosionRadius = options.explosionRadius || 100; self.explosionDamage = 1; try { self.graphics = self.attachAsset('projectile_spinning_asset', { // UŻYJ NOWEGO ASSETU DLA POCISKU anchorX: 0.5, anchorY: 0.5, width: 140, height: 140 }); } catch (e) { console.warn("Nie udało się załadować 'projectile_spinning_asset', używam Shape.", e); self.graphics = new Shape({ width: 40, height: 40, color: 0xFF4500, shape: 'ellipse' }); self.addChild(self.graphics); } self.width = self.graphics.width; self.height = self.graphics.height; self.update = function () { if (self.isDead || self.hasExploded || !self.target || self.target.dead) { if (!self.hasExploded && !self.isDead) { self.isDead = true; } return; } self.lifeTimer--; var dx = self.target.x - self.x; var dy = self.target.y - self.y; var distanceToTarget = Math.sqrt(dx * dx + dy * dy); if (distanceToTarget > 0) { var moveX = dx / distanceToTarget * self.speed; var moveY = dy / distanceToTarget * self.speed; self.x += moveX; self.y += moveY; } if (self.graphics) { // Dodajemy rotację pocisku self.graphics.rotation += 0.1; // Dostosuj prędkość obrotu } var playerRadius = (self.target.width || 150) / 2 * 0.7; var projectileRadius = self.width / 2; if (distanceToTarget < playerRadius + projectileRadius) { self.explode(); return; } if (self.lifeTimer <= 0) { self.explode(); return; } }; self.explode = function () { if (self.hasExploded) { return; } self.hasExploded = true; self.isDead = true; if (self.graphics && self.graphics.parent) { self.graphics.parent.removeChild(self.graphics); self.graphics.destroy(); self.graphics = null; } var explosionFramesAssets = []; for (var i = 0; i < 7; i++) { try { explosionFramesAssets.push(LK.getAsset('explosion_frame_' + i, { anchorX: 0.5, anchorY: 0.5, clone: true })); } catch (e) { console.error("Błąd ładowania klatki eksplozji: explosion_frame_" + i, e); var placeholderExplosionFrame = new Shape({ width: 50, height: 50, color: 0xFF8C00, shape: 'ellipse' }); placeholderExplosionFrame.anchor.set(0.5, 0.5); explosionFramesAssets.push(placeholderExplosionFrame); } } if (explosionFramesAssets.length < 7) { // Potrzebujemy wszystkich 7 klatek do tej logiki console.error("Nie załadowano wystarczającej liczby klatek eksplozji."); if (gameState && gameState.cursedCrystalActiveExplosions) { gameState.cursedCrystalActiveExplosions.push({ x: self.x, y: self.y, radius: self.explosionRadius, damage: self.explosionDamage, durationTimer: 30, hitPlayerThisFrame: false }); } return; } var explosionDisplay = game.addChild(new Container()); explosionDisplay.x = self.x; explosionDisplay.y = self.y; var currentFrameGfx = null; var INITIAL_SCALE = 0.2; var MAX_SCALE = self.explosionRadius * 3.0 / (explosionFramesAssets[0] ? explosionFramesAssets[0].width : 50); // ZMIENIONA LINIA (1.5 -> 3.0) var EXP_FRAME_DURATION_MS = 70; var LOOP_FRAME_DURATION_MS = 90; var SHRINK_FADE_DURATION_MS = 600; var totalDurationMs = 2000; var expansionPhaseDuration = 7 * EXP_FRAME_DURATION_MS; // 7 klatek * 70ms = 490ms var loopPhaseDuration = totalDurationMs - expansionPhaseDuration - SHRINK_FADE_DURATION_MS; if (loopPhaseDuration < 0) { // Jeśli nie ma czasu na pętlę, skróć inne fazy lub ustaw min. czas pętli loopPhaseDuration = Math.max(0, LOOP_FRAME_DURATION_MS * 4); // np. przynajmniej jedna pełna pętla 4 klatek SHRINK_FADE_DURATION_MS = Math.max(200, totalDurationMs - expansionPhaseDuration - loopPhaseDuration); } var displayFrame = function displayFrame(frameAsset, scale, alpha) { if (currentFrameGfx && currentFrameGfx.parent) { explosionDisplay.removeChild(currentFrameGfx); // Nie niszczymy frameAsset, bo to klon z puli LK.getAsset } if (frameAsset) { currentFrameGfx = explosionDisplay.addChild(frameAsset); currentFrameGfx.scale.set(scale); currentFrameGfx.alpha = alpha === undefined ? 1 : alpha; } else { currentFrameGfx = null; } }; var currentExpansionFrame = 0; function expandAnimation() { if (currentExpansionFrame < 7) { var progress = currentExpansionFrame / 6.0; var scale = INITIAL_SCALE + (MAX_SCALE - INITIAL_SCALE) * progress; displayFrame(explosionFramesAssets[currentExpansionFrame], Math.max(INITIAL_SCALE, scale)); currentExpansionFrame++; LK.setTimeout(expandAnimation, EXP_FRAME_DURATION_MS); } else { startLoopingPhase(); } } var loopFramesIndices = [3, 4, 5, 6]; // Klatki 3, 4, 5, 6 do pętli var currentLoopArrayIndex = 0; var loopEndTime; function startLoopingPhase() { if (loopPhaseDuration <= 0) { startShrinkingPhase(); return; } loopEndTime = Date.now() + loopPhaseDuration; currentLoopArrayIndex = 0; // Zacznij od pierwszej klatki pętli (indeks 3 globalnie) loopAnimation(); } function loopAnimation() { if (Date.now() < loopEndTime && explosionDisplay && !explosionDisplay.destroyed) { displayFrame(explosionFramesAssets[loopFramesIndices[currentLoopArrayIndex % loopFramesIndices.length]], MAX_SCALE); currentLoopArrayIndex++; LK.setTimeout(loopAnimation, LOOP_FRAME_DURATION_MS); } else { startShrinkingPhase(); } } function startShrinkingPhase() { if (!currentFrameGfx || !currentFrameGfx.parent || explosionDisplay && explosionDisplay.destroyed) { if (explosionDisplay && !explosionDisplay.destroyed) { explosionDisplay.destroy(); } return; } // Aby zmniejszać ostatnio wyświetloną klatkę (lub konkretną np. klatkę 6 jako bazę) // Dla pewności, że mamy co skalować, możemy ponownie wyświetlić ostatnią klatkę pętli (np. klatkę 6) // jeśli currentFrameGfx mógł zostać usunięty lub jest nieoczekiwany. // Jeśli currentFrameGfx jest już ostatnią klatką pętli, to dobrze. // Jeśli chcemy zawsze zmniejszać np. klatkę nr 6: // displayFrame(explosionFramesAssets[6], MAX_SCALE); tween(currentFrameGfx, { scaleX: 0.01, scaleY: 0.01, alpha: 0 }, { duration: SHRINK_FADE_DURATION_MS, easing: tween.easeInQuad, onFinish: function onFinish() { if (explosionDisplay && !explosionDisplay.destroyed) { explosionDisplay.destroy(); } } }); } expandAnimation(); // Rozpocznij pierwszą fazę if (gameState && gameState.cursedCrystalActiveExplosions) { gameState.cursedCrystalActiveExplosions.push({ x: self.x, y: self.y, radius: self.explosionRadius, damage: self.explosionDamage, durationTimer: Math.round(totalDurationMs / (1000 / 60)), // Czas trwania logicznej eksplozji = ~2 sekundy hitPlayerThisFrame: false }); } }; return self; }); // Zamykająca klamra dla Container.expand klasy Boss // --- POCZĄTEK BLOKU DO SKOPIOWANIA --- var Player = Container.expand(function () { var self = Container.call(this); if (gameState.currentState === "cursedCrystal") { console.log("Player Update - Cursed Crystal Tick, Rolling: " + this.rolling + ", Cooldown: " + this.rollCooldown); } else if (gameState.currentState === "game") { console.log("Player Update - Game Tick, Rolling: " + this.rolling + ", Cooldown: " + this.rollCooldown); } // --- Animacja Idle --- var idleFrames = [LK.getAsset('player', { anchorX: 0.5, anchorY: 0.5 }), LK.getAsset('player1', { anchorX: 0.5, anchorY: 0.5 }), LK.getAsset('player2', { anchorX: 0.5, anchorY: 0.5 })]; self.idleAnimationSprite = new SpriteAnimation({ frames: idleFrames, frameDuration: 400, loop: true, anchorX: 0.5, anchorY: 0.5, x: 0, y: 0 }); self.addChild(self.idleAnimationSprite); self.idleAnimationSprite.play(); // --- Właściwości Gracza --- self.health = 5; self.speed = 8; self.rolling = false; self.rollDirection = { x: 0, y: 0 }; self.rollSpeed = 20; self.rollDuration = 300; self.rollCooldown = 0; self.invulnerable = false; self.invulnerabilityFrames = 0; self.defaultInvulnerabilityFrames = 30; self.postHitInvulnerabilityTimer = null; self.dead = false; self.rollTimeoutId = null; self.invulnerabilityTimeoutId = null; self.rollAnimationInterval = null; self.hasRolledThroughBossThisRoll = false; // --- Funkcje Pomocnicze --- // *** TO JEST DEFINICJA FUNKCJI, KTÓREJ BRAKUJE *** self.clearRollTimeouts = function () { if (self.rollTimeoutId) { LK.clearTimeout(self.rollTimeoutId); self.rollTimeoutId = null; } if (self.rollAnimationInterval) { LK.clearInterval(self.rollAnimationInterval); self.rollAnimationInterval = null; } if (self.postHitInvulnerabilityTimer) { LK.clearTimeout(self.postHitInvulnerabilityTimer); self.postHitInvulnerabilityTimer = null; } }; // *** KONIEC DEFINICJI clearRollTimeouts *** // --- Mechanika Uniku (Roll) --- self.roll = function (direction, duration) { if (!self.rolling && self.rollCooldown <= 0 && !self.dead) { var targetScaleX = direction.x >= 0 || direction.x === 0 ? 1 : -1; var currentRollDuration = duration || self.rollDuration; self.rolling = true; self.hasRolledThroughMinibossCCThisRoll = false; self.rollDirection = direction; self.rollCooldown = 45; var durationRatio = currentRollDuration / self.rollDuration; self.invulnerabilityFrames = Math.round(self.defaultInvulnerabilityFrames * durationRatio); self.hasRolledThroughBossThisRoll = false; if (self.idleAnimationSprite && !self.idleAnimationSprite.destroyed) { self.idleAnimationSprite.stop(); self.idleAnimationSprite.visible = false; } var rollFrames = ['roll', 'roll0', 'roll1', 'roll2']; var currentFrame = 0; if (self.rollAnimationInterval) { LK.clearInterval(self.rollAnimationInterval); } var rollAnimationSprite = null; if (self && !self.destroyed) { rollAnimationSprite = self.addChild(LK.getAsset(rollFrames[currentFrame], { anchorX: 0.5, anchorY: 0.5, x: 0, y: 0, scaleX: targetScaleX })); } self.rollAnimationInterval = LK.setInterval(function () { if (!self || self.destroyed) { LK.clearInterval(self.rollAnimationInterval); self.rollAnimationInterval = null; return; } if (rollAnimationSprite && rollAnimationSprite.destroy) { rollAnimationSprite.destroy(); rollAnimationSprite = null; } currentFrame = (currentFrame + 1) % rollFrames.length; if (self && !self.destroyed) { rollAnimationSprite = self.addChild(LK.getAsset(rollFrames[currentFrame], { anchorX: 0.5, anchorY: 0.5, x: 0, y: 0, scaleX: targetScaleX })); } }, 70); if (self.rollTimeoutId) { LK.clearTimeout(self.rollTimeoutId); } self.rollTimeoutId = LK.setTimeout(function () { if (!self || self.destroyed) { return; } self.rolling = false; if (self.rollAnimationInterval) { LK.clearInterval(self.rollAnimationInterval); self.rollAnimationInterval = null; } if (rollAnimationSprite && rollAnimationSprite.destroy) { rollAnimationSprite.destroy(); rollAnimationSprite = null; } var standUpFrames = ['roll3', 'roll4']; var standUpFrame = 0; var standUpSprite = null; if (self && !self.destroyed) { standUpSprite = self.addChild(LK.getAsset(standUpFrames[standUpFrame], { anchorX: 0.5, anchorY: 0.5, x: 0, y: 0, scaleX: targetScaleX })); } var standUpInterval = LK.setInterval(function () { if (!self || self.destroyed) { LK.clearInterval(standUpInterval); return; } if (standUpSprite && standUpSprite.destroy) { standUpSprite.destroy(); standUpSprite = null; } standUpFrame++; if (standUpFrame < standUpFrames.length) { if (self && !self.destroyed) { standUpSprite = self.addChild(LK.getAsset(standUpFrames[standUpFrame], { anchorX: 0.5, anchorY: 0.5, x: 0, y: 0, scaleX: targetScaleX })); } } else { LK.clearInterval(standUpInterval); standUpInterval = null; if (standUpSprite && standUpSprite.destroy) { standUpSprite.destroy(); standUpSprite = null; } if (self.idleAnimationSprite && !self.idleAnimationSprite.destroyed) { self.idleAnimationSprite.scaleX = targetScaleX; self.idleAnimationSprite.visible = true; self.idleAnimationSprite.play(); } } }, 100); self.rollTimeoutId = null; }, currentRollDuration); } }; // --- Otrzymywanie Obrażeń --- self.takeDamage = function (amount) { var timestamp = Date.now(); var currentHealthBeforeDamage = this.health; console.log("[Player.takeDamage] Called at: " + timestamp + " | Amount: " + amount + " | Invulnerable: " + this.invulnerable + " | Dead: " + this.dead + " | Health BEFORE: " + currentHealthBeforeDamage + " | Current State: " + gameState.currentState); if (!this.invulnerable && !this.dead) { if (gameState.currentState === "cursedCrystal") { gameState.cursedCrystalPlayerHealth -= amount; gameState.cursedCrystalPlayerHealth = Math.max(0, gameState.cursedCrystalPlayerHealth); this.health = gameState.cursedCrystalPlayerHealth; console.log(" [CursedCrystal DMG] New Health (gameState & this.health): " + this.health + " at " + timestamp); if (ui && ui.updateHearts) { ui.updateHearts(gameState.cursedCrystalPlayerHealth, gameState.cursedCrystalPlayerMaxHealth); } } else { this.health -= amount; this.health = Math.max(0, this.health); console.log(" [DMG in " + gameState.currentState + "] New Health (this.health): " + this.health + " at " + timestamp); } if (this.health < currentHealthBeforeDamage) { LK.effects.flashObject(this, 0xFF0000, 200); } if (this.health <= 0) { console.log(" Player health is <= 0 at " + timestamp + ". Calling this.die()."); this.die(); return; } this.invulnerable = true; console.log(" Player.invulnerable SET TO TRUE at " + timestamp + ". IMMEDIATELY CHECKING: player.invulnerable is " + this.invulnerable); if (this.postHitInvulnerabilityTimer) { LK.clearTimeout(this.postHitInvulnerabilityTimer); this.postHitInvulnerabilityTimer = null; } var playerInstance = this; this.postHitInvulnerabilityTimer = LK.setTimeout(function () { var endInvulnerableTimestamp = Date.now(); if (!playerInstance || playerInstance.destroyed) { console.log(" Player invulnerability timeout: player destroyed or null at " + endInvulnerableTimestamp); return; } playerInstance.invulnerable = false; console.log(" Player.invulnerable SET TO FALSE at " + endInvulnerableTimestamp + " (after 2000ms)"); var currentVisualSprite = null; if (playerInstance.rolling && playerInstance.children.length > 0 && playerInstance.children[0] !== playerInstance.idleAnimationSprite) {} else if (playerInstance.idleAnimationSprite && playerInstance.idleAnimationSprite.visible && playerInstance.idleAnimationSprite.children.length > 0) { currentVisualSprite = playerInstance.idleAnimationSprite.children[0]; } if (currentVisualSprite && currentVisualSprite.alpha !== 1) { currentVisualSprite.alpha = 1; } playerInstance.postHitInvulnerabilityTimer = null; }, 2000); } else { console.log("[Player.takeDamage] SKIPPED at: " + timestamp + " (Invulnerable: " + this.invulnerable + ", Dead: " + this.dead + ")"); } }; // --- Śmierć (z poprawkami dla storage i onFinish) --- self.die = function () { console.log("!!!! Player.die() called! State:", gameState.currentState, "Current Health:", self.health); if (self.dead) { console.log("Player.die() exited - already dead."); return; } self.dead = true; try { self.clearRollTimeouts(); } catch (e) { console.error("!!! Błąd podczas wywoływania self.clearRollTimeouts w Player.die:", e); } if (gameState.currentState === "cursedCrystal") { console.log("Player died in Cursed Crystal mode. Calling gameState.endCursedCrystalMode(false) via tween."); tween(self, { alpha: 0, scaleX: self.scaleX * 1.2, scaleY: self.scaleY * 1.2 }, { duration: 1000, easing: tween.easeOut, onFinish: function onFinish() { console.log("Player.die tween (Cursed Crystal) onFinish."); if (typeof gameState.endCursedCrystalMode === 'function') { gameState.endCursedCrystalMode(false); } else { console.error("gameState.endCursedCrystalMode is not defined!"); if (typeof gameState.showGrillScreen === 'function') { gameState.showGrillScreen(); } } } }); return; } var currentDeaths = parseInt(storage.totalDeaths, 10) || 0; storage.totalDeaths = currentDeaths + 1; console.log("Updated totalDeaths to:", storage.totalDeaths); var currentMaxHearts = parseInt(storage.maxHearts, 10); if (isNaN(currentMaxHearts) || currentMaxHearts < 5) { storage.maxHearts = 5; } if (storage.maxHearts < 10) { storage.maxHearts = (parseInt(storage.maxHearts, 10) || 5) + 1; } console.log("Updated maxHearts to:", storage.maxHearts); tween(self, { alpha: 0, scaleX: self.scaleX * 1.2, scaleY: self.scaleY * 1.2 }, { duration: 1000, easing: tween.easeOut, onFinish: function onFinish() { console.log("Player.die tween (Non-CC) onFinish. gameState.currentState:", gameState.currentState); if (gameState.currentState === "game") { console.log("Player.die (game mode) onFinish - Calling gameState.gameOver(true)."); if (typeof boss !== 'undefined' && boss && typeof boss.clearAllAttacks === 'function') { boss.clearAllAttacks("Player.die -> tutorial gameOver"); } if (typeof gameState.gameOver === 'function') { gameOverReasonIsDeath = true; gameState.gameOver(true); } else { console.error("gameState.gameOver() is not defined for 'game' mode death!"); if (typeof gameState.showGrillScreen === 'function') { gameState.showGrillScreen(); } } } else if (gameState.currentState === "rollMaster" || gameState.currentState === "rollMasterGameOver") { console.log("Player.die (RollMaster or rollMasterGameOver state) onFinish. No further action needed from Player.die, endRollMasterMode handles the rest."); } else if (gameState.currentState === "gameOver") { console.log("Player.die onFinish while gameState.currentState is already 'gameOver'. No further action needed from Player.die."); } } }); }; // Koniec self.die // --- Aktualizacja (Update) --- self.update = function () { if (gameState.currentState === "cursedCrystal") { console.log("Player Update Tick - CC - Health: " + self.health + ", RollCD: " + self.rollCooldown + ", Rolling: " + self.rolling + ", Invuln: " + self.invulnerable + ", InvulnFrames: " + self.invulnerabilityFrames); } if (gameState.currentState !== "game" && gameState.currentState !== "rollMaster" && gameState.currentState !== "cursedCrystal") { if (self.rolling) { self.rolling = false; self.invulnerabilityFrames = 0; if (self.idleAnimationSprite && !self.idleAnimationSprite.destroyed) { self.idleAnimationSprite.visible = true; self.idleAnimationSprite.play(); if (self.children.length > 1) { for (var i = self.children.length - 1; i >= 0; i--) { if (self.children[i] !== self.idleAnimationSprite && self.children[i].destroy) { self.children[i].destroy(); } } } } } self.clearRollTimeouts(); return; } if (self.dead) { return; } if (self.rollCooldown > 0) { self.rollCooldown--; } var currentVisualSprite = null; if (self.rolling) { for (var i = 0; i < self.children.length; i++) { if (self.children[i] !== self.idleAnimationSprite) { currentVisualSprite = self.children[i]; break; } } } else if (self.idleAnimationSprite && self.idleAnimationSprite.visible && self.idleAnimationSprite.children.length > 0) { currentVisualSprite = self.idleAnimationSprite.children[0]; } if (self.rolling) { if (self.invulnerabilityFrames > 0) { self.invulnerabilityFrames--; if (currentVisualSprite) { currentVisualSprite.alpha = self.invulnerabilityFrames % 10 < 5 ? 0.3 : 1; } } else { if (currentVisualSprite) { currentVisualSprite.alpha = 1; } } var rollDx = self.rollDirection.x * self.rollSpeed; var rollDy = self.rollDirection.y * self.rollSpeed; var nextX = self.x + rollDx; var nextY = self.y + rollDy; var pWidth = currentVisualSprite ? currentVisualSprite.width * Math.abs(currentVisualSprite.scaleX || 1) : self.width; var pHeight = currentVisualSprite ? currentVisualSprite.height * (currentVisualSprite.scaleY || 1) : self.height; var halfWidth = pWidth / 2; var halfHeight = pHeight / 2; var minXBoundary = halfWidth; var maxXBoundary = 2048 - halfWidth; var minYBoundary = halfHeight; var maxYBoundary = 2732 - halfHeight; nextX = Math.max(minXBoundary, Math.min(nextX, maxXBoundary)); nextY = Math.max(minYBoundary, Math.min(nextY, maxYBoundary)); if (gameState.currentState === "cursedCrystal" && typeof crystalCoreObject !== 'undefined' && crystalCoreObject && !crystalCoreObject.destroyed) { var crystalCX = crystalCoreObject.x; var crystalCY = crystalCoreObject.y; var crystalRadius = crystalCoreObject.collisionRadius || 50; var distToCrystalHorizontal = Math.abs(nextX - crystalCX); var distToCrystalVertical = Math.abs(nextY - crystalCY); if (distToCrystalHorizontal < halfWidth + crystalRadius && distToCrystalVertical < halfHeight + crystalRadius) { if (self.x < crystalCX && nextX > self.x) { nextX = crystalCX - crystalRadius - halfWidth - 1; } else if (self.x > crystalCX && nextX < self.x) { nextX = crystalCX + crystalRadius + halfWidth + 1; } if (self.y < crystalCY && nextY > self.y) { nextY = crystalCY - crystalRadius - halfHeight - 1; } else if (self.y > crystalCY && nextY < self.y) { nextY = crystalCY + crystalRadius + halfHeight + 1; } } } self.x = nextX; self.y = nextY; if (gameState.currentState === "game" && boss && !boss.dead && !self.hasRolledThroughBossThisRoll) { var dx_b = self.x - boss.x; var dy_b = self.y - boss.y; var distance_b = Math.sqrt(dx_b * dx_b + dy_b * dy_b); var playerCollisionRadius = pWidth / 2 * 0.8; var hitboxMultiplier = 1.2; var bossCollisionRadius = (boss.width || 180) * (boss.scaleX || 1) / 2 * hitboxMultiplier; if (distance_b < playerCollisionRadius + bossCollisionRadius) { boss.takeDamage(10); self.hasRolledThroughBossThisRoll = true; LK.effects.flashObject(boss, 0x00FF00, 200); } } if (gameState.currentState === "cursedCrystal" && gameState.isMinibossActiveCC === true && gameState.cursedCrystalMinibossObject && !gameState.cursedCrystalMinibossObject.isDead && !self.hasRolledThroughMinibossCCThisRoll) { var minibossCC = gameState.cursedCrystalMinibossObject; var dx_mcc = self.x - minibossCC.x; var dy_mcc = self.y - minibossCC.y; var distance_mcc = Math.sqrt(dx_mcc * dx_mcc + dy_mcc * dy_mcc); var playerRollRadius = pWidth / 2 * 0.8; var minibossCCRadius = (minibossCC.width || 120) / 2 * 0.9; if (distance_mcc < playerRollRadius + minibossCCRadius) { minibossCC.takeDamage(10); self.hasRolledThroughMinibossCCThisRoll = true; console.log("Player rolled through Miniboss CC and dealt damage!"); } } } else if (self.invulnerable && !self.rolling) { if (currentVisualSprite) { currentVisualSprite.alpha = Math.floor(Date.now() / 100) % 4 > 1 ? 0.3 : 1; } } else { if (currentVisualSprite && currentVisualSprite.alpha !== 1) { currentVisualSprite.alpha = 1; } if (gameState.isInputActive && (gameState.currentState === "game" || gameState.currentState === "rollMaster" || gameState.currentState === "cursedCrystal") && player && !player.dead) { var targetX = gameState.currentInputPos.x; var targetY = gameState.currentInputPos.y; var dx_m = targetX - self.x; var dy_m = targetY - self.y; var distance_m = Math.sqrt(dx_m * dx_m + dy_m * dy_m); var moveSpeed = 3; if (distance_m > moveSpeed) { var normalizedX = dx_m / distance_m; var normalizedY = dy_m / distance_m; var nextPlayerX = self.x + normalizedX * moveSpeed; var nextPlayerY = self.y + normalizedY * moveSpeed; var pWidthIdle = self.idleAnimationSprite.children[0] ? self.idleAnimationSprite.children[0].width * (self.idleAnimationSprite.scaleX || 1) : self.width; var pHeightIdle = self.idleAnimationSprite.children[0] ? self.idleAnimationSprite.children[0].height * (self.idleAnimationSprite.scaleY || 1) : self.height; var halfWidthIdle = pWidthIdle / 2; var halfHeightIdle = pHeightIdle / 2; var minXBoundaryIdle = halfWidthIdle; var maxXBoundaryIdle = 2048 - halfWidthIdle; var minYBoundaryIdle = halfHeightIdle; var maxYBoundaryIdle = 2732 - halfHeightIdle; nextPlayerX = Math.max(minXBoundaryIdle, Math.min(nextPlayerX, maxXBoundaryIdle)); nextPlayerY = Math.max(minYBoundaryIdle, Math.min(nextPlayerY, maxYBoundaryIdle)); if (gameState.currentState === "cursedCrystal" && typeof crystalCoreObject !== 'undefined' && crystalCoreObject && !crystalCoreObject.destroyed) { var crystalCXIdle = crystalCoreObject.x; var crystalCYIdle = crystalCoreObject.y; var crystalRadiusIdle = crystalCoreObject.collisionRadius || 50; var distToCrystalHorizontalIdle = Math.abs(nextPlayerX - crystalCXIdle); var distToCrystalVerticalIdle = Math.abs(nextPlayerY - crystalCYIdle); if (distToCrystalHorizontalIdle < halfWidthIdle + crystalRadiusIdle && distToCrystalVerticalIdle < halfHeightIdle + crystalRadiusIdle) {} else { self.x = nextPlayerX; self.y = nextPlayerY; } } else { self.x = nextPlayerX; self.y = nextPlayerY; } if (self.idleAnimationSprite && !self.idleAnimationSprite.destroyed) { if (normalizedX > 0.1) { self.idleAnimationSprite.scaleX = 1; } else if (normalizedX < -0.1) { self.idleAnimationSprite.scaleX = -1; } } } } } if (self.idleAnimationSprite && self.idleAnimationSprite.visible && !self.idleAnimationSprite.destroyed && typeof self.idleAnimationSprite.update === 'function') { if (gameState.currentState === "cursedCrystal") {} self.idleAnimationSprite.update(); } }; // Ten nawias klamrowy zamyka funkcję self.update = function () { ... }; return self; }); // Koniec Player // --- KONIEC BLOKU DO SKOPIOWANIA --- // Koniec Player // <--- To zamyka Container.expand dla gracza var Shape = Container.expand(function (options) { var self = Container.call(this); // <--- Tutaj zaczyna się Shape // ... options = options || {}; var width = options.width || 100; var height = options.height || 100; var color = options.color || 0xFFFFFF; var shape = options.shape || 'box'; // Create the shape as an asset var asset = self.attachAsset(shape, { anchorX: 0.5, anchorY: 0.5, width: width, height: height, tint: color }); // Set width and height for easier access self.width = width; self.height = height; // Add anchor property to Shape for positioning self.anchor = { set: function set(x, y) { // This mimics the behavior of the anchor.set method self.anchorX = x; self.anchorY = y; } }; return self; }); var SoulEnemy = Container.expand(function (options) { var self = Container.call(this); options = options || {}; self.hp = options.hp || 1; self.speed = options.speed || 2; self.isDead = false; self.targetX = options.targetX || 2048 / 2; self.targetY = options.targetY || 2732 / 2; var animFrames = []; for (var i = 0; i < 6; i++) { // 6 klatek animacji try { animFrames.push(LK.getAsset('soul_anim_' + i, { anchorX: 0.5, anchorY: 0.5, clone: true })); } catch (e) { console.error("Błąd ładowania klatki animacji duszy: soul_anim_" + i, e); // Awaryjny placeholder, jeśli klatka się nie załaduje var placeholderFrame = new Shape({ width: options.width || 30, height: options.height || 30, color: 0xFF00FF, shape: 'ellipse' }); placeholderFrame.anchor.set(0.5, 0.5); animFrames.push(placeholderFrame); } } if (animFrames.length > 0) { self.animation = new SpriteAnimation({ frames: animFrames, frameDuration: 120, // Czas trwania klatki w ms - dostosuj loop: true, anchorX: 0.5, anchorY: 0.5 }); self.addChild(self.animation); self.animation.play(); } else { // Jeśli nie udało się załadować żadnej klatki, stwórz awaryjny kształt console.error("Nie udało się załadować żadnej klatki dla SoulEnemy. Tworzenie awaryjnego kształtu."); self.fallbackGraphics = new Shape({ width: options.width || 30, height: options.height || 30, color: 0xADD8E6, shape: 'ellipse' }); self.fallbackGraphics.anchor.set(0.5, 0.5); self.addChild(self.fallbackGraphics); } // Ustawienie self.width i self.height na podstawie pierwszej klatki animacji lub opcji if (animFrames.length > 0 && animFrames[0] && typeof animFrames[0].width !== 'undefined') { self.width = animFrames[0].width; self.height = animFrames[0].height; } else { self.width = options.width || 30; self.height = options.height || 30; } self.update = function () { if (self.isDead || gameState.currentState !== "cursedCrystal") { return; } var currentTargetX = self.targetX; var currentTargetY = self.targetY; if (typeof crystalCoreObject !== 'undefined' && crystalCoreObject && !crystalCoreObject.destroyed) { currentTargetX = crystalCoreObject.x; currentTargetY = crystalCoreObject.y; } var dx = currentTargetX - self.x; var dy = currentTargetY - self.y; var distance = Math.sqrt(dx * dx + dy * dy); if (self.animation) { // Obracanie animacji w kierunku ruchu var angle = Math.atan2(dy, dx); self.animation.rotation = angle; // Jeśli chcesz, aby sprite "patrzył" w prawo, gdy kąt jest 0, możesz dodać: // self.animation.rotation = angle + Math.PI / 2; // Jeśli sprite jest domyślnie skierowany w górę } else if (self.fallbackGraphics) { // Obracanie awaryjnej grafiki var angle = Math.atan2(dy, dx); self.fallbackGraphics.rotation = angle; } if (distance > self.speed) { var moveX = dx / distance * self.speed; var moveY = dy / distance * self.speed; self.x += moveX; self.y += moveY; } else { self.x = currentTargetX; self.y = currentTargetY; if (typeof crystalCoreObject !== 'undefined' && crystalCoreObject && !crystalCoreObject.destroyed && self.x === crystalCoreObject.x && self.y === crystalCoreObject.y) { self.onHitCrystal(); } else if (!crystalCoreObject || crystalCoreObject.destroyed) { self.isDead = true; if (self.parent) { self.parent.removeChild(self); } self.destroy(); } } // SpriteAnimation ma własną metodę update, która jest wywoływana automatycznie, jeśli jest dzieckiem // Nie ma potrzeby wywoływania self.animation.update() ręcznie tutaj. }; self.takeDamage = function (amount) { if (self.isDead) { return; } self.hp -= amount; // Efekt flash na całym obiekcie SoulEnemy (który zawiera animację) LK.effects.flashObject(self, 0xFFFFFF, 150); if (self.hp <= 0) { self.die(); } }; self.die = function () { if (self.isDead) { return; } self.isDead = true; gameState.cursedCrystalScore += 10; if (ui && ui.updateScoreCC) { ui.updateScoreCC(gameState.cursedCrystalScore); } if (self.parent) { self.parent.removeChild(self); } self.destroy(); // To powinno zniszczyć również animację jako dziecko }; self.onHitCrystal = function () { if (self.isDead) { return; } self.isDead = true; // Oznacz jako martwą, aby zatrzymać inne logiki (np. update) gameState.cursedCrystalSoulsHitCrystal++; gameState.cursedCrystalChargeLevel = Math.min(gameState.cursedCrystalTargetCharge, gameState.cursedCrystalChargeLevel + 5); if (ui && ui.updateCrystalCharge) { ui.updateCrystalCharge(gameState.cursedCrystalChargeLevel, gameState.cursedCrystalTargetCharge); } if (typeof gameState.updateCrystalVisual === 'function') { gameState.updateCrystalVisual(); } var FADE_OUT_DURATION = 300; // Czas trwania fade out w ms - dostosuj! // Zatrzymaj animację SpriteAnimation, jeśli istnieje i gra if (self.animation && typeof self.animation.stop === 'function') { self.animation.stop(); } tween(self, { alpha: 0 }, { duration: FADE_OUT_DURATION, easing: tween.easeOutQuad, // Możesz wybrać inną funkcję easing onFinish: function onFinish() { if (self.parent) { self.parent.removeChild(self); } if (self.destroy && !self.destroyed) { // Dodatkowe sprawdzenie !self.destroyed self.destroy(); } } }); }; self.x = options.x || 0; self.y = options.y || 0; return self; }); var SpriteAnimation = Container.expand(function (options) { var self = Container.call(this); // Initialize with default values options = options || {}; self.frames = options.frames || []; self.frameDuration = options.frameDuration || 120; // Default 100ms per frame self.loop = options.loop !== undefined ? options.loop : true; self.currentFrame = 0; self.frameTimer = 0; self.playing = true; // Dodaj tę linię do konstruktora, aby SpriteAnimation pamiętało własne alpha // i mogło przekazywać je do klatek. self.alpha = options.alpha !== undefined ? options.alpha : 1; // Set initial position and anchor if provided if (options.x !== undefined) { self.x = options.x; } if (options.y !== undefined) { self.y = options.y; } // Handle anchor options if (options.anchorX !== undefined || options.anchorY !== undefined) { var anchorX = options.anchorX !== undefined ? options.anchorX : 0; var anchorY = options.anchorY !== undefined ? options.anchorY : 0; // Apply anchor to all frames self.frames.forEach(function (frame) { if (frame && frame.anchor) { frame.anchor.set(anchorX, anchorY); } }); } // Add the first frame to display initially if (self.frames.length > 0) { self.removeChildren(); var firstFrame = self.frames[self.currentFrame]; if (firstFrame && firstFrame.anchor) { firstFrame.anchor.set(options.anchorX || 0, options.anchorY || 0); } // TUTAJ JEST KLUCZOWA ZMIANA W KONSTRUKTORZE: // Ustaw alpha dla pierwszej klatki na podstawie alpha kontenera SpriteAnimation. if (firstFrame) { firstFrame.alpha = self.alpha; } self.addChild(firstFrame); } // Animation update method self.update = function () { if (!self.playing || self.frames.length === 0) { return; } self.frameTimer++; if (self.frameTimer >= self.frameDuration / (1000 / 60)) { self.frameTimer = 0; if (self.children.length > 0) { self.removeChild(self.children[0]); } self.currentFrame++; if (self.currentFrame >= self.frames.length) { if (self.loop) { self.currentFrame = 0; // TUTAJ JEST KLUCZOWA ZMIANA W UPDATE DLA PĘTLI: var nextFrame = self.frames[self.currentFrame]; if (nextFrame) { nextFrame.alpha = self.alpha; // Zastosuj alpha kontenera } self.addChild(nextFrame); } else { self.playing = false; if (typeof self.onComplete === 'function') { self.onComplete(); } return; } } else { // Dodajemy nową klatkę tylko wtedy, gdy animacja jeszcze trwa // TUTAJ JEST KLUCZOWA ZMIANA W UPDATE DLA DALSZYCH KLATEK: var nextFrame = self.frames[self.currentFrame]; if (nextFrame) { nextFrame.alpha = self.alpha; // Zastosuj alpha kontenera } self.addChild(nextFrame); } } }; // Method to stop animation self.stop = function () { self.playing = false; }; // Method to start/resume animation self.play = function () { self.playing = true; }; // Method to set a specific frame self.gotoFrame = function (frameIndex) { if (frameIndex >= 0 && frameIndex < self.frames.length) { self.removeChildren(); self.currentFrame = frameIndex; self.addChild(self.frames[self.currentFrame]); } }; return self; }); var UI = Container.expand(function () { var self = Container.call(this); // Create heart containers (wizualizacja zdrowia) self.hearts = []; self.heartContainer = new Container(); // Kontener na ikony serc self.addChild(self.heartContainer); // Title and messages (teksty na ekranach) self.titleText = new Text2("ROLL SOULS", { size: 150, fill: 0xFFFFFF }); self.titleText.anchor.set(0.5, 0.5); self.addChild(self.titleText); self.messageText = new Text2("", { size: 60, fill: 0xFFFFFF }); self.messageText.anchor.set(0.5, 0.5); self.addChild(self.messageText); // Deaths counter (licznik śmierci) self.deathsText = new Text2("Deaths: 0", { size: 40, fill: 0xFFFFFF }); self.deathsText.anchor.set(1, 0); // Wyrównanie do prawego górnego rogu self.deathsText.x = 2048 - 50; // Pozycja X od prawej krawędzi self.deathsText.y = 50; // Pozycja Y od góry self.addChild(self.deathsText); // Tutorial text (teksty tutoriali) self.tutorialText = new Text2("", { size: 50, fill: 0xFFFFFF }); self.tutorialText.anchor.set(0.5, 0); // Wyrównanie do środka na górze self.tutorialText.x = 2048 / 2; // Pozycja X na środku self.tutorialText.y = 200; // Pozycja Y (może być różna w zależności od stanu) self.addChild(self.tutorialText); // --- Timer Text --- self.timerText = new Text2("00:00", { // Zmieniono tekst początkowy size: 60, fill: 0xFFFFFF // Biały kolor }); self.timerText.x = 2048 / 2; // Wyśrodkuj w poziomie self.timerText.y = 50; // Przysuń do góry self.timerText.anchor.set(0.5, 0); // Pozycja Y od górnej krawędzi self.timerText.alpha = 0; // Domyślnie ukryty self.addChild(self.timerText); // --- DODANY NOWY ELEMENT TEKSTOWY DLA REKORDU --- self.highScoreText = new Text2("Best: 00:00", { // Tekst początkowy size: 50, // Mniejszy rozmiar niż aktualny czas fill: 0xFFFF00 // Żółty kolor dla odróżnienia }); self.highScoreText.x = 2048 - 50; // Przysuń do prawej krawędzi (z marginesem 50) self.highScoreText.y = 50; // Przysuń do góry (ta sama wysokość co timer) self.highScoreText.anchor.set(1, 0); // Pozycja Y (poniżej timerText) self.highScoreText.alpha = 0; // Domyślnie ukryty self.addChild(self.highScoreText); // Dodaj do kontenera UI // --- KONIEC DODAWANIA NOWEGO ELEMENTU --- // --- Boss Health Bar (wizualizacja zdrowia bossa) --- self.bossHealthBarContainer = new Container(); // Kontener na pasek zdrowia bossa self.bossHealthBarContainer.x = 2048 / 2; // Wyśrodkuj poziomo self.bossHealthBarContainer.y = 150; // Pozycja Y (poniżej timera) self.bossHealthBarContainer.alpha = 0; // Domyślnie ukryty self.addChild(self.bossHealthBarContainer); var barWidth = 800; // Szerokość paska zdrowia (musi być taka sama jak szerokość assetu bossHpbar) var barHeight = 30; // Wysokość paska zdrowia (musi być taka sama jak wysokość assetu bossHpbar) // Właściwy pasek zdrowia bossa (czerwony) - UŻYWAMY TERAZ NOWEGO ASSETU bossHpbar self.bossHealthBar = self.attachAsset('bossHpbar', { // Użyj assetu 'bossHpbar' anchorX: 0, // Ustaw punkt odniesienia na lewą krawędź (0), środek pionowo (0.5) anchorY: 0.5, x: -barWidth / 2 // Przesuń w lewo o połowę szerokości tła }); self.bossHealthBarContainer.addChild(self.bossHealthBar); // --- METODY AKTUALIZACJI UI --- // Aktualizuje wizualizację serc na UI self.updateHearts = function (current, max) { current = Math.max(0, current || 0); max = Math.max(1, max || 1); // Max musi być co najmniej 1 if (self.hearts.length !== max) { while (self.hearts.length > 0) { var oldHeart = self.hearts.pop(); if (oldHeart && oldHeart.destroy && !oldHeart.destroyed) { oldHeart.destroy(); } } self.heartContainer.removeChildren(); // Usuń wizualne obiekty serc for (var i = 0; i < max; i++) { var heart = LK.getAsset('heart', { anchorX: 0.5, anchorY: 0.5, x: i * 50, y: 0, tint: i < current ? 0xFF0000 : 0x555555 }); self.hearts.push(heart); self.heartContainer.addChild(heart); } self.heartContainer.x = (2048 - max * 50) / 2 + 25; self.heartContainer.y = 100; // Zmieniono pozycję serc, aby zrobić miejsce } else { for (var j = 0; j < self.hearts.length; j++) { if (self.hearts[j]) { self.hearts[j].tint = j < current ? 0xFF0000 : 0x555555; } } } }; // Aktualizuje wizualizację paska zdrowia bossa self.updateBossHealth = function (current, max) { current = Math.max(0, current || 0); max = Math.max(1, max || 1); var barWidth = 800; var currentWidth = current / max * barWidth; if (self.bossHealthBar) { self.bossHealthBar.width = currentWidth; var shouldBeVisible = (gameState.currentState === "game" || gameState.currentState === "gameOver") && (boss && !boss.dead && current > 0 || gameState.currentState === "gameOver" && !gameOverReasonIsDeath && isNewBossPlusMode); self.bossHealthBarContainer.alpha = shouldBeVisible ? 1 : 0; } if (self.bossHealthBarBg) { // Jeśli używasz tła paska HP self.bossHealthBarBg.alpha = self.bossHealthBarContainer.alpha; } }; // Wyświetla komunikat na środku ekranu self.showMessage = function (message, duration) { self.messageText.setText(message); self.messageText.alpha = 1; if (self.messageTimeout) { LK.clearTimeout(self.messageTimeout); self.messageTimeout = null; } if (duration && duration > 0) { self.messageTimeout = LK.setTimeout(function () { tween(self.messageText, { alpha: 0 }, { duration: 500, onFinish: function onFinish() { self.messageTimeout = null; } }); }, duration); } else { self.messageText.alpha = 1; } }; // Wyświetla tekst tutorialu self.showTutorial = function (text) { self.tutorialText.setText(text); self.tutorialText.alpha = 1; }; // Ukrywa tekst tutorialu (zanikając) self.hideTutorial = function () { tween(self.tutorialText, { alpha: 0 }, { duration: 500 }); }; // Aktualizuje licznik śmierci self.updateDeathsCounter = function () { var deaths = parseInt(storage.totalDeaths, 10) || 0; self.deathsText.setText("Deaths: " + deaths); self.deathsText.visible = gameState.currentState === "game"; }; // Aktualizuje wyświetlanie czasu timera self.updateTimerDisplay = function (seconds) { seconds = Math.max(0, seconds || 0); var minutes = Math.floor(seconds / 60); var remainingSeconds = seconds % 60; var formattedSeconds = remainingSeconds < 10 ? '0' + remainingSeconds : remainingSeconds; var formattedTime = String(minutes).padStart(2, '0') + ':' + formattedSeconds; if (self.timerText) { // W Roll Master wyświetlamy "Time:", w innych trybach tylko czas var prefix = gameState.currentState === "rollMaster" ? "Time: " : ""; self.timerText.setText(prefix + formattedTime); } }; self.ccScoreText = new Text2("Score: 0", { size: 50, fill: 0xFFFF00, // Żółty, dla odróżnienia align: 'left' }); self.ccScoreText.anchor.set(0, 0); // Lewy górny róg tego tekstu self.addChild(self.ccScoreText); // Rekord dla Cursed Crystal self.ccHighScoreText = new Text2("Best: 0", { size: 40, fill: 0xFFFFFF, // Biały align: 'left' }); self.ccHighScoreText.anchor.set(0, 0); // Lewy górny róg tego tekstu self.addChild(self.ccHighScoreText); // Pasek Naładowania Klejnotu self.crystalChargeBarContainer = new Container(); self.addChild(self.crystalChargeBarContainer); var baseAssetID = 'cursedenergybar'; // Nazwa Twojego assetu var chargeBarAssetFullWidth; // Zmienna na pełną szerokość assetu var chargeBarAssetHeight; // Zmienna na wysokość assetu // Tło paska (ten sam asset, ale z większą przezroczystością) try { self.crystalChargeBarBg = self.attachAsset(baseAssetID, { anchorX: 0.5, anchorY: 0.5, alpha: 0.3, // Ustaw przezroczystość tła (np. 30%) clone: true }); chargeBarAssetFullWidth = self.crystalChargeBarBg.width; // Pobierz szerokość z assetu chargeBarAssetHeight = self.crystalChargeBarBg.height; // Pobierz wysokość z assetu self.crystalChargeBarContainer.addChild(self.crystalChargeBarBg); } catch (e) { console.error("Błąd ładowania assetu tła paska energii ('" + baseAssetID + "'): ", e); chargeBarAssetFullWidth = 400; // Awaryjna szerokość chargeBarAssetHeight = 25; // Awaryjna wysokość self.crystalChargeBarBg = new Shape({ width: chargeBarAssetFullWidth, height: chargeBarAssetHeight, color: 0xCCCCCC, alpha: 0.3 }); self.crystalChargeBarBg.anchor.set(0, 0.5); self.crystalChargeBarContainer.addChild(self.crystalChargeBarBg); } // Wypełnienie paska (ten sam asset, w pełni widoczny) try { self.crystalChargeBarFill = self.attachAsset(baseAssetID, { anchorX: 0.5, anchorY: 0.5, alpha: 1, // Pełna widoczność clone: true }); self.crystalChargeBarFill.width = 0; // Zacznij od zerowej szerokości // Jeśli Twoje tło i wypełnienie mają być idealnie na sobie, a oba mają anchorX:0, anchorY:0.5, // to ich .x i .y względem kontenera powinny być takie same (np. 0,0). // self.crystalChargeBarFill.x = 0; // self.crystalChargeBarFill.y = 0; self.crystalChargeBarContainer.addChild(self.crystalChargeBarFill); } catch (e) { console.error("Błąd ładowania assetu wypełnienia paska energii ('" + baseAssetID + "'): ", e); self.crystalChargeBarFill = new Shape({ width: 0, height: chargeBarAssetHeight ? chargeBarAssetHeight : 25, color: 0x8A2BE2 }); self.crystalChargeBarFill.anchor.set(0, 0.5); self.crystalChargeBarContainer.addChild(self.crystalChargeBarFill); } // --- DODANA NOWA FUNKCJA DO AKTUALIZACJI TEKSTU REKORDU --- self.updateHighScoreDisplay = function (seconds) { // 'seconds' to rekord czasu dla Roll Master seconds = Math.max(0, seconds || 0); var minutes = Math.floor(seconds / 60); var remainingSeconds = seconds % 60; var formattedSeconds = String(remainingSeconds).padStart(2, '0'); // Zawsze dwie cyfry dla sekund var formattedTime = String(minutes).padStart(2, '0') + ':' + formattedSeconds; if (this.highScoreText) { // this.highScoreText to element UI dla rekordu Roll Master // Wyświetlaj rekord Roll Master tylko, gdy jesteśmy w tym trybie lub na jego ekranie końca if (gameState.currentState === "rollMaster" || gameState.currentState === "rollMasterGameOver") { this.highScoreText.setText("Best Time: " + formattedTime); // Pozycja i widoczność (alpha) tego elementu są zarządzane przez UI.prototype.positionElements } // W innych stanach gry nie modyfikujemy tekstu; positionElements powinno go ukryć. } }; self.updateScoreCC = function (currentScore) { if (this.ccScoreText) { // this.ccScoreText to element dla "Score: X" w Cursed Crystal if (gameState.currentState === "cursedCrystal") { this.ccScoreText.setText("Score: " + (currentScore || 0)); // Pozycja i widoczność (alpha) są zarządzane przez UI.prototype.positionElements("cursedCrystal") } } }; self.updateHighScoreCC = function (highScore) { // 'highScore' to rekord dla Cursed Crystal if (this.ccHighScoreText) { // this.ccHighScoreText to element dla "Best: Y" w Cursed Crystal if (gameState.currentState === "cursedCrystal") { this.ccHighScoreText.setText("Best: " + (highScore || 0)); // Pozycja i widoczność (alpha) są zarządzane przez UI.prototype.positionElements("cursedCrystal") } } }; // Aktualizuje wizualizację paska naładowania Klejnotu self.updateCrystalCharge = function (currentCharge, maxCharge) { if (self.crystalChargeBarFill && self.crystalChargeBarBg) { maxCharge = Math.max(1, maxCharge || 1); // Max musi być co najmniej 1 currentCharge = Math.max(0, Math.min(currentCharge, maxCharge)); var chargeBarBaseWidth = self.crystalChargeBarBg.width; // Szerokość tła paska var fillWidth = currentCharge / maxCharge * chargeBarBaseWidth; self.crystalChargeBarFill.width = fillWidth; } }; // Funkcja do pokazywania/ukrywania i aktualizacji paska HP Minibossa CC // Wykorzysta istniejący self.bossHealthBar i self.bossHealthBarContainer self.updateMinibossHealthCC = function (currentHP, maxHP, isActive) { if (self.bossHealthBarContainer && self.bossHealthBar) { if (isActive && currentHP > 0) { self.bossHealthBarContainer.alpha = 1; maxHP = Math.max(1, maxHP || 1); currentHP = Math.max(0, Math.min(currentHP, maxHP)); var barWidth = 800; // Upewnij się, że to ta sama szerokość co w updateBossHealth // lub pobierz z self.bossHealthBarBg.width jeśli masz tło var currentBarWidth = currentHP / maxHP * barWidth; self.bossHealthBar.width = currentBarWidth; } else { self.bossHealthBarContainer.alpha = 0; } } }; // --- KONIEC DODAWANIA NOWEJ FUNKCJI --- // Pozycjonuje elementy UI w zależności od stanu gry self.positionElements = function (state) { // Pozycje stałe dla niektórych elementów (mogą być nadpisywane w case'ach) if (self.deathsText) { // Sprawdzenie, czy element istnieje self.deathsText.x = 2048 - 150; self.deathsText.y = 50; } if (self.heartContainer) { self.heartContainer.y = 80; } if (self.bossHealthBarContainer) { self.bossHealthBarContainer.x = 2048 / 2; self.bossHealthBarContainer.y = 160; } // Resetuj widoczność wszystkich głównych elementów UI przed ustawieniem dla danego stanu if (self.titleText) { self.titleText.alpha = 0; } if (self.messageText) { self.messageText.alpha = 0; } if (self.tutorialText) { self.tutorialText.alpha = 0; } if (self.timerText) { self.timerText.alpha = 0; } if (self.heartContainer) { self.heartContainer.alpha = 0; } if (self.bossHealthBarContainer) { self.bossHealthBarContainer.alpha = 0; } if (self.deathsText) { self.deathsText.alpha = 0; } // Ten highScoreText jest specyficzny dla Roll Master, więc domyślnie go ukrywamy // chyba że jesteśmy w stanie rollMaster lub rollMasterGameOver (obsłużone w case) if (self.highScoreText) { self.highScoreText.alpha = 0; } // NOWE ELEMENTY UI DLA CURSED CRYSTAL - też domyślnie ukryte if (self.ccScoreText) { self.ccScoreText.alpha = 0; } if (self.ccHighScoreText) { self.ccHighScoreText.alpha = 0; } if (self.crystalChargeBarContainer) { self.crystalChargeBarContainer.alpha = 0; } // Przywróć domyślną pozycję i styl timerText (może być nadpisane w case "rollMaster") if (self.timerText) { self.timerText.x = 350; // Domyślna pozycja X dla trybu "game" self.timerText.y = 100; // Domyślna pozycja Y dla trybu "game" self.timerText.anchor.set(0, 0); self.timerText.style = { size: 60, fill: 0xFFFFFF }; self.timerText.setText("00:00"); // Resetuj tekst na wszelki wypadek } // Logika dla highScoreText (Roll Master) - resetowanie tekstu poza trybem RM if (self.highScoreText && state !== "rollMaster" && state !== "rollMasterGameOver") { // self.highScoreText.setText("Best RM: 00:00"); // Możesz zresetować tekst lub zostawić ostatni znany } switch (state) { case "title": if (self.titleText) { self.titleText.x = 2048 / 2; self.titleText.y = 800; self.titleText.alpha = 1; } if (self.messageText) { self.messageText.x = 2048 / 2; self.messageText.y = 1000; // self.messageText.alpha jest zarządzane przez showMessage } if (self.tutorialText) { self.tutorialText.x = 2048 / 2; self.tutorialText.y = 1200; // self.tutorialText.alpha jest zarządzane przez showTutorial } break; case "game": // Tryb walki z bossem (tutorial) if (self.heartContainer) { self.heartContainer.alpha = 1; // Pozycja serc jest aktualizowana w updateHearts } if (self.timerText) { // Timer dla standardowego bossa (jeśli jest) self.timerText.alpha = 1; // Ustaw x, y, anchor, style dla tego timera self.timerText.x = 350; // Przykładowa pozycja z Twojego kodu self.timerText.y = 100; self.timerText.anchor.set(0, 0); self.timerText.style = { size: 60, fill: 0xFFFFFF }; } if (self.deathsText) { self.deathsText.alpha = 1; } if (self.bossHealthBarContainer && typeof boss !== 'undefined' && boss && !boss.dead && boss.health > 0) { self.bossHealthBarContainer.alpha = 1; } else if (self.bossHealthBarContainer) { self.bossHealthBarContainer.alpha = 0; } if (self.tutorialText) { // self.tutorialText.alpha jest zarządzane przez showTutorial/hideTutorial self.tutorialText.x = 2048 / 2; self.tutorialText.y = 200; } break; case "grillMenu": if (self.messageText) { // Dla komunikatów w Grill Menu self.messageText.x = 2048 / 2; self.messageText.y = 500; // alpha zarządzane przez showMessage } // Wszystkie inne elementy typowo rozgrywkowe powinny być ukryte przez reset na początku funkcji break; case "gameOver": // Standardowy Game Over (po Boss+ lub tutorialu, jeśli tak zdecydujesz) // Teksty i przyciski dla tego stanu są głównie zarządzane przez gameState.gameOver if (self.titleText) {// Dla "DEFEAT!" lub "TIME'S UP!" // alpha, text, x, y są ustawiane w gameState.gameOver } if (self.messageText) {// Dla dodatkowych wiadomości // alpha, text, x, y są ustawiane w gameState.gameOver } if (self.deathsText) { // Najpierw sprawdź, czy deathsText istnieje // Warunek, kiedy pokazać licznik śmierci: // Tylko jeśli gra się zakończyła (co wiemy, bo state === "gameOver"), // przyczyną była śmierć gracza, // ORAZ NIE był to tryb Boss+ (czyli isNewBossPlusMode jest false). if (gameOverReasonIsDeath && !isNewBossPlusMode) { self.deathsText.alpha = 1; self.deathsText.x = 2048 - 150; // Twoja standardowa pozycja self.deathsText.y = 50; // Twoja standardowa pozycja } else { self.deathsText.alpha = 0; // W innych przypadkach "gameOver" (np. Boss+) ukryj licznik śmierci } } break; // ... (case'y intro, fakeTutorial, realTutorial - zakładam, że są OK) ... case "intro": // case "fakeTutorial": // case "realTutorial": // // Większość elementów UI jest zazwyczaj ukryta, zarządzane przez logikę tych stanów break; // case "rollMaster": // Pokaż elementy UI specyficzne dla aktywnego trybu Roll Master if (self.timerText) { // Timer/wynik dla Roll Master self.timerText.alpha = 1; self.timerText.x = 2048 / 2; // Środek-góra self.timerText.y = 50; self.timerText.anchor.set(0.5, 0); self.timerText.style = { size: 60, fill: 0xFFFFFF }; // Tekst jest aktualizowany w ui.updateTimerDisplay } if (self.highScoreText) { // Rekord dla Roll Master (ten właściwy, nie ccHighScoreText) self.highScoreText.alpha = 1; self.highScoreText.x = 2048 - 50; // Prawy górny róg self.highScoreText.y = 50; self.highScoreText.anchor.set(1, 0); // Tekst jest aktualizowany w ui.updateHighScoreDisplay } // Jawnie ukryj elementy UI z Cursed Crystal if (self.ccScoreText) { self.ccScoreText.alpha = 0; } if (self.ccHighScoreText) { self.ccHighScoreText.alpha = 0; } // <--- BARDZO WAŻNE if (self.crystalChargeBarContainer) { self.crystalChargeBarContainer.alpha = 0; } // Ukryj inne standardowe elementy gry if (self.heartContainer) { self.heartContainer.alpha = 0; } if (self.bossHealthBarContainer) { self.bossHealthBarContainer.alpha = 0; } if (self.deathsText) { self.deathsText.alpha = 0; } if (self.tutorialText) { self.tutorialText.alpha = 0; } if (self.titleText) { self.titleText.alpha = 0; } // Główny tytuł gry break; case "rollMasterGameOver": // Jawnie ukryj elementy UI z Cursed Crystal if (self.ccScoreText) { self.ccScoreText.alpha = 0; } if (self.ccHighScoreText) { self.ccHighScoreText.alpha = 0; } if (self.crystalChargeBarContainer) { self.crystalChargeBarContainer.alpha = 0; } // Ukryj elementy aktywnej gry Roll Master (timer na żywo) if (self.timerText) { self.timerText.alpha = 0; } if (self.highScoreText) { self.highScoreText.alpha = 0; } // Ukryj inne standardowe elementy gry if (self.heartContainer) { self.heartContainer.alpha = 0; } if (self.bossHealthBarContainer) { self.bossHealthBarContainer.alpha = 0; } if (self.deathsText) { self.deathsText.alpha = 0; } if (self.tutorialText) { self.tutorialText.alpha = 0; } if (self.titleText) { self.titleText.alpha = 0; } break; case "cursedCrystal": // TYLKO JEDNO WYSTĄPIENIE TEGO CASE if (self.timerText) { self.timerText.alpha = 0; } if (self.highScoreText) { // To jest highScoreText dla Roll Master, więc ukrywamy self.highScoreText.alpha = 0; } if (self.deathsText) { // Licznik śmierci też ukrywamy w tym trybie self.deathsText.alpha = 0; } if (self.tutorialText) { // Tutorial też ukrywamy self.tutorialText.alpha = 0; } // --- Elementy specyficzne dla Cursed Crystal --- if (self.ccScoreText) { self.ccScoreText.alpha = 1; self.ccScoreText.x = 50; self.ccScoreText.y = 20; self.ccScoreText.anchor.set(0, 0); } if (self.ccHighScoreText) { self.ccHighScoreText.alpha = 1; self.ccHighScoreText.x = 2048 - 50; self.ccHighScoreText.y = 20; self.ccHighScoreText.anchor.set(1, 0); } // --- Serca Gracza --- if (self.heartContainer) { self.heartContainer.alpha = 1; // UPEWNIJ SIĘ, ŻE SERCA SĄ WIDOCZNE var ccMaxHearts = typeof gameState !== 'undefined' && gameState.cursedCrystalPlayerMaxHealth ? gameState.cursedCrystalPlayerMaxHealth : 10; var heartsWidth = ccMaxHearts * 50; self.heartContainer.x = (2048 - heartsWidth) / 2 + 25; self.heartContainer.y = 70; // Stała pozycja Y dla serc } // --- Pasek Ładowania Kryształu --- if (self.crystalChargeBarContainer && self.crystalChargeBarBg) { if (typeof gameState !== 'undefined' && gameState.isMinibossActiveCC) { self.crystalChargeBarContainer.alpha = 0; } else { self.crystalChargeBarContainer.alpha = 1; var chargeBarWidth = self.crystalChargeBarBg.width || 400; self.crystalChargeBarContainer.anchorX = 0.5; self.crystalChargeBarContainer.x = 2048 / 2; self.crystalChargeBarContainer.y = (self.heartContainer ? self.heartContainer.y + (self.heartContainer.height || 40) : 70 + 40) + 50; } } // --- Pasek HP Minibossa --- if (self.bossHealthBarContainer) { var heartsBottomY = self.heartContainer ? self.heartContainer.y + (self.heartContainer.height || 40) : 70 + 40; // Poprzednio było: heartsBottomY + 20 (ok. 130), potem heartsBottomY + 70 (ok. 180) // Aby przesunąć o kolejne 50px w dół od Y=180, potrzebujemy Y = 230 // Więc offset od heartsBottomY (ok. 110) musi być 120. var desiredOffsetYBelowHearts = 120; self.bossHealthBarContainer.y = heartsBottomY + desiredOffsetYBelowHearts; // Widoczność paska HP bossa jest zarządzana przez updateMinibossHealthCC, // więc tutaj tylko pozycjonujemy. } break; case "cursedCrystalGameOver": // Ukryj elementy rozgrywki CC (oprócz score i highscore, które mogą być częścią ekranu końca) if (self.timerText) { self.timerText.alpha = 0; } if (self.highScoreText) { self.highScoreText.alpha = 0; } // highScoreText dla RollMaster if (self.crystalChargeBarContainer) { self.crystalChargeBarContainer.alpha = 0; } if (self.heartContainer) { self.heartContainer.alpha = 0; } // Ukryj serca gracza na ekranie końca gry CC if (self.bossHealthBarContainer) { self.bossHealthBarContainer.alpha = 0; } if (self.deathsText) { self.deathsText.alpha = 0; } if (self.tutorialText) { self.tutorialText.alpha = 0; } break; } }; // Koniec positionElements return self; }); /**** * Initialize Game ****/ // Koniec var UI = Container.expand(...) var game = new LK.Game({ backgroundColor: 0x111111 // Ciemne tło domyślne }); /**** * Game Code ****/ // Pomarańczowy kwadrat dla Minibossa // Fioletowy klejnot // Ciemnofioletowe tło // Komentarze o assetach pominięte dla zwięzłości // Globalny kontener na elementy scen intro/tutoriali i Grill Screena (do łatwego czyszczenia) // Dodana funkcja pomocnicza do pobierania czasu gry w milisekundach // Dodana funkcja pomocnicza do pobierania czasu gry w milisekundach // Placeholder, zmień ID // Jasnoniebieska elipsa dla Duszy function _typeof(o) { "@babel/helpers - typeof"; return _typeof = "function" == typeof Symbol && "symbol" == typeof Symbol.iterator ? function (o) { return typeof o; } : function (o) { return o && "function" == typeof Symbol && o.constructor === Symbol && o !== Symbol.prototype ? "symbol" : typeof o; }, _typeof(o); } function _defineProperty(e, r, t) { return (r = _toPropertyKey(r)) in e ? Object.defineProperty(e, r, { value: t, enumerable: !0, configurable: !0, writable: !0 }) : e[r] = t, e; } function _toPropertyKey(t) { var i = _toPrimitive(t, "string"); return "symbol" == _typeof(i) ? i : i + ""; } function _toPrimitive(t, r) { if ("object" != _typeof(t) || !t) { return t; } var e = t[Symbol.toPrimitive]; if (void 0 !== e) { var i = e.call(t, r || "default"); if ("object" != _typeof(i)) { return i; } throw new TypeError("@@toPrimitive must return a primitive value."); } return ("string" === r ? String : Number)(t); } if (typeof LK.getGameTime === 'undefined') { LK.getGameTime = function () { return Date.now(); // Zwraca aktualny czas w milisekundach }; } if (typeof LK.getGameTime === 'undefined') { LK.getGameTime = function () { return Date.now(); // Zwraca aktualny czas w milisekundach }; } var currentSceneElements = new Container(); // Zmienne gry var player; var boss; var ui; var walls = []; // Ściany areny bossa // Zmienna do przechowywania aktywnego tła sceny var currentBackground = null; // Flaga do śledzenia, czy jesteśmy w trybie New Boss+ var isNewBossPlusMode = false; // Flaga do śledzenia przyczyny Game Over (true = gracz zginął, false = czas minął) var gameOverReasonIsDeath = false; var coffinMemeImage = null; // Funkcja do czyszczenia elementów z kontenera currentSceneElements (nie usuwa UI ani tła) function clearScene() { while (currentSceneElements.children.length > 0) { var child = currentSceneElements.children[0]; if (child && child.destroy) { child.destroy(); // Użyj destroy jeśli dostępne } else if (child && child.parent) { child.parent.removeChild(child); // Fallback } else { // Jeśli obiekt nie ma ani destroy ani parent, usuń go z tablicy (choć to nie powinno się zdarzyć) currentSceneElements.children.shift(); } } // Usuń też dzieci bezpośrednio z 'game', które mogły zostać dodane poza 'currentSceneElements' w scenach, // ale UWAŻAJ, aby nie usunąć stałych elementów jak UI, walls, currentSceneElements sam w sobie. // Lepsze podejście: zawsze dodawaj elementy specyficzne dla sceny do currentSceneElements. } // Obiekt zarządzający stanami gry var gameState = _defineProperty(_defineProperty(_defineProperty({ currentState: "title", gameDuration: 120, remainingTime: 0, crystalExploding: false, gameTimerInterval: null, fakeTutorialTimerId: null, bossSpeedIncreased: false, currentIntroMusicInstance: null, currentRollSoulsInstance: null, currentRollMasterMusicInstance: null, currentCursedCrystalMusicInstance: null, // Nowa właściwość cursedCrystalPlayerHealth: 10, // Startowe HP gracza w tym trybie cursedCrystalPlayerMaxHealth: 10, cursedCrystalChargeLevel: 0, // Aktualny poziom naładowania Klejnotu (0-100%) cursedCrystalTargetCharge: 30, // 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; // Nie potrzebujemy już flagi testButtonClickedThisFrame console.log("[showTitleScreen] State: Title Screen (TEST MODE ACTIVE - Click to Grill Menu)"); // Dodano informację o trybie testowym var needsIntroMusic = true; // Zatrzymywanie muzyki z innych trybów (bez zmian) if (this.currentRollSoulsInstance && this.currentRollSoulsInstance.stop) { if (typeof this.currentRollSoulsInstance.volume === 'number') { this.currentRollSoulsInstance.volume = 0; } this.currentRollSoulsInstance.stop(); this.currentRollSoulsInstance = null; } // ... (i tak dalej dla currentRollMasterMusicInstance) if (this.currentRollMasterMusicInstance && this.currentRollMasterMusicInstance.stop) { if (typeof this.currentRollMasterMusicInstance.volume === 'number') { this.currentRollMasterMusicInstance.volume = 0; } this.currentRollMasterMusicInstance.stop(); this.currentRollMasterMusicInstance = null; } if (this.currentCursedCrystalMusicInstance && this.currentCursedCrystalMusicInstance.stop) { this.currentCursedCrystalMusicInstance.stop(); this.currentCursedCrystalMusicInstance = null; } // <<< DODAJ TUTAJ // Logika muzyki intro (bez zmian, ale zostanie szybko przerwana) if (this.currentIntroMusicInstance && this.currentIntroMusicInstance.playing) { needsIntroMusic = false; } else { var lkMusicCheck = LK.music; if (lkMusicCheck && lkMusicCheck.assetId === 'introMusic' && lkMusicCheck.playing) { this.currentIntroMusicInstance = lkMusicCheck; needsIntroMusic = false; } } if (needsIntroMusic) { 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!"); } } // Czyszczenie timerów (bez zmian) 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(); // Usuwamy elementy z currentSceneElements, w tym stary przycisk testowy, jeśli tam był // Ustawienie tła i cząsteczek (bez zmian) if (currentBackground) { tween.stop(currentBackground); currentBackground.destroy(); currentBackground = null; } this.currentState = "title"; game.setBackgroundColor(0x1a1a1a); currentBackground = LK.getAsset('titleBg', { anchorX: 0, anchorY: 0, x: 0, y: 0 }); game.addChildAt(currentBackground, 0); if (typeof particleIntervalId !== 'undefined' && particleIntervalId) { LK.clearInterval(particleIntervalId); particleIntervalId = null; } if (typeof startScreenParticles !== 'undefined') { startScreenParticles.forEach(function (p) { if (p && p.destroy) { p.destroy(); } }); startScreenParticles = []; } var localStartScreenParticles = []; function localSpawnBackgroundParticle() {/* ... logika cząsteczek ... */} var localParticleIntervalId = LK.setInterval(localSpawnBackgroundParticle, 400); // Reset gracza i bossa (bez zmian) if (player && player.destroy) { player.destroy(); } player = null; if (boss && boss.destroy) { boss.destroy(); } boss = null; walls.forEach(function (wall) { if (wall) { wall.alpha = 1; } }); // Chociaż ściany raczej nie są tu relevantne // Ustawienie UI (bez zmian) ui.positionElements("title"); ui.titleText.setText("Welcome Unchosen"); ui.showMessage("Tap to GRILL MENU (TEST)", 0); // Zmieniony tekst, żeby było jasne ui.showTutorial("Swipe to Roll - Death is Progress"); // Ten tekst można zostawić lub też zmienić ui.updateHearts(storage.maxHearts, storage.maxHearts); ui.updateDeathsCounter(); this.touchStart = { x: 0, y: 0 }; this.touchEnd = { x: 0, y: 0 }; // Usunęliśmy cały kod tworzący przycisk testowy game.off('down'); game.on('down', function (x, y, obj) { if (gameState.currentState === 'title') { console.log("Title screen clicked - TEST MODE: Skipping to Grill Menu."); // Zatrzymaj i wyczyść cząsteczki ekranu tytułowego if (localParticleIntervalId) { LK.clearInterval(localParticleIntervalId); localParticleIntervalId = null; } if (localStartScreenParticles) { localStartScreenParticles.forEach(function (p) { if (p && p.destroy) { p.destroy(); } }); localStartScreenParticles = []; } // Zatrzymaj muzykę intro, jeśli gra if (gameState.currentIntroMusicInstance && gameState.currentIntroMusicInstance.stop) { if (typeof gameState.currentIntroMusicInstance.volume === 'number') { gameState.currentIntroMusicInstance.volume = 0; } gameState.currentIntroMusicInstance.stop(); gameState.currentIntroMusicInstance = null; } // Przejdź bezpośrednio do Grill Menu gameState.showGrillScreen(); } }); }, 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.currentCursedCrystalMusicInstance && this.currentCursedCrystalMusicInstance.stop) { // DODANA LOGIKA console.log("[StartGame] Zatrzymywanie currentCursedCrystalMusicInstance."); if (typeof this.currentCursedCrystalMusicInstance.volume === 'number') { this.currentCursedCrystalMusicInstance.volume = 0; } this.currentCursedCrystalMusicInstance.stop(); this.currentCursedCrystalMusicInstance = null; } if (this.currentRollSoulsInstance && this.currentRollSoulsInstance.stop) { console.log("[StartGame] Zatrzymywanie poprzedniej currentRollSoulsInstance."); if (typeof this.currentRollSoulsInstance.volume === 'number') { this.currentRollSoulsInstance.volume = 0; } this.currentRollSoulsInstance.stop(); this.currentRollSoulsInstance = null; // Upewnij się, że zerujesz po zatrzymaniu, aby odtworzyć nową } console.log("[StartGame] Uruchamianie RollSouls."); this.currentRollSoulsInstance = LK.playMusic('RollSouls', { // Zakładam, że 'RollSouls' to muzyka dla tego trybu loop: true, volume: 0.7 }); if (!this.currentRollSoulsInstance) { console.error("[StartGame] LK.playMusic('RollSouls') nie zwróciło instancji!"); } if (this.fakeTutorialTimerId) { LK.clearTimeout(this.fakeTutorialTimerId); this.fakeTutorialTimerId = null; } if (this.gameTimerInterval) { LK.clearInterval(this.gameTimerInterval); this.gameTimerInterval = null; } clearScene(); if (currentBackground) { tween.stop(currentBackground); currentBackground.destroy(); currentBackground = null; } this.currentState = "game"; if (gameState.grillMenuEffects && gameState.grillMenuEffects.length > 0) { gameState.grillMenuEffects.forEach(function (effect) { if (effect && effect.parent) { tween.stop(effect); effect.parent.removeChild(effect); if (effect.destroy && !effect.destroyed) { effect.destroy(); } } else if (effect && effect.destroy && !effect.destroyed) { console.warn("Sparkle/Star object found without parent but has destroy method:", effect); effect.destroy(); } else { console.warn("Sparkle/Star object found without parent or destroy method:", effect); } }); gameState.grillMenuEffects = []; } game.setBackgroundColor(0x111111); var arenaBg = LK.getAsset('arena', { anchorX: 0.5, anchorY: 0.5, x: 2048 / 2, y: 2732 / 2 }); game.addChildAt(arenaBg, 0); if (player && player.destroy) { player.destroy(); } player = game.addChild(new Player()); if (player) { player.health = parseInt(storage.maxHearts, 10) || 5; console.log("DEBUG: Inicjalizacja zdrowia gracza. player.health:", player.health, "storage.maxHearts:", storage.maxHearts); } player.rolling = false; player.invulnerable = false; player.invulnerabilityFrames = 0; player.rollCooldown = 0; if (player && typeof player.clearRollTimeouts === 'function') { player.clearRollTimeouts(); } player.hasRolledThroughBossThisRoll = false; player.x = 2048 / 2; player.y = 2732 / 2 + 400; player.alpha = 1; player.dead = false; if (boss && boss.destroy) { boss.destroy(); } boss = game.addChild(new Boss()); boss.x = 2048 / 2; boss.y = 2732 / 2 - 400; boss.alpha = 1; boss.attackCooldown = 90; boss.attacks = []; boss.attackSpeedMultiplier = 1; boss.repositioning = false; boss.dead = false; boss.phase = 1; boss.tint = 0xFFFFFF; boss.scale.set(1, 1); if (isNewBossPlusMode) { boss.maxHealth = 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"); if (ui && player) { ui.updateHearts(player.health, parseInt(storage.maxHearts, 10) || 5); } else if (ui) { ui.updateHearts(0, parseInt(storage.maxHearts, 10) || 5); } ui.showTutorial("Swipe to Roll!"); ui.updateBossHealth(boss.health, boss.maxHealth); if (coffinMemeImage && !coffinMemeImage.destroyed) { coffinMemeImage.destroy(); coffinMemeImage = null; } try { var assetMeta = null; try { assetMeta = LK.getAssetMeta('coffinDanceMeme'); } catch (metaError) { console.warn("LK.getAssetMeta nie działa lub brak assetu 'coffinDanceMeme'. Używam domyślnej wysokości 200."); } var memeHeight = assetMeta ? assetMeta.height : 200; coffinMemeImage = LK.getAsset('coffinDanceMeme', { anchorX: 0.5, anchorY: 1.0, x: 2048 / 2, y: 2732 + memeHeight, alpha: 1 }); var uiIndex = game.getChildIndex(ui); if (uiIndex > -1) { game.addChildAt(coffinMemeImage, uiIndex); } else { game.addChild(coffinMemeImage); } console.log("Coffin dance meme załadowany i schowany, y start:", coffinMemeImage.y, "Height:", memeHeight); } catch (e) { console.error("Nie udało się stworzyć/załadować assetu coffinDanceMeme:", e); coffinMemeImage = null; } this.remainingTime = this.gameDuration; ui.updateTimerDisplay(this.remainingTime); if (this.gameTimerInterval) { LK.clearInterval(this.gameTimerInterval); } this.bossSpeedIncreased = false; this.gameTimerInterval = LK.setInterval(function () { if (_this.currentState === "game") { _this.remainingTime--; ui.updateTimerDisplay(_this.remainingTime); if (!isNewBossPlusMode) { var accelerationThreshold = _this.gameDuration - 60; if (_this.remainingTime <= accelerationThreshold && !_this.bossSpeedIncreased) { _this.bossSpeedIncreased = true; if (boss && !boss.dead) { boss.attackSpeedMultiplier *= 0.7; boss.speed += 1; ui.showMessage("Boss attacks faster!", 2000); } } } if (_this.remainingTime <= 0) { LK.clearInterval(_this.gameTimerInterval); _this.gameTimerInterval = null; if (isNewBossPlusMode) { gameOverReasonIsDeath = false; gameState.gameOver(false); } else { gameState.showGrillScreen(); } } } else { if (_this.gameTimerInterval) { LK.clearInterval(_this.gameTimerInterval); } _this.gameTimerInterval = null; } }, 1000); LK.setTimeout(function () { if (_this.currentState === "game") { ui.hideTutorial(); } }, 3000); }, // Koniec funkcji startGame // victory() - nieużywane, logika w timerze i boss.die() showGrillScreen: function showGrillScreen() { this.rollMasterTime = 0; // <-- DODAJ TĘ LINIĘ isNewBossPlusMode = false; console.log("State: Grill Menu (isNewBossPlusMode reset to false)"); // Wyczyść timery (pozostaw ten fragment) if (this.gameTimerInterval) { LK.clearInterval(this.gameTimerInterval); } this.gameTimerInterval = null; if (this.fakeTutorialTimerId) { LK.clearTimeout(this.fakeTutorialTimerId); } this.fakeTutorialTimerId = null; this.bossSpeedIncreased = false; // Reset flagi // --- DODANY KOD: Tablica do przechowywania efektów z menu grilla --- gameState.grillMenuEffects = []; // Tworzymy nową tablicę przy wejściu do menu // --- KONIEC DODANEGO KODU --- // --- Rozpoczęcie agresywnego czyszczenia sceny --- // Stwórz listę obiektów (kontenerów, UI, ścian), które powinny pozostać w 'game'. // Upewnij się, że 'ui', 'currentSceneElements' i wszystkie obiekty w tablicy 'walls' są tutaj. var childrenToKeep = [ui, currentSceneElements].concat(walls); // Przejdź przez wszystkie dzieci głównego obiektu 'game' od końca do początku for (var i = game.children.length - 1; i >= 0; i--) { var child = game.children[i]; // Sprawdź, czy obecne dziecko NIE jest na liście obiektów do zachowania if (childrenToKeep.indexOf(child) === -1) { // console.log("Usuwam obiekt:", child); // Możesz tymczasowo odkomentować do debugowania // Usuń i zniszcz obiekt if (child && child.destroy) { child.destroy(); // Użyj destroy jeśli dostępne (zalecane w LK) } else if (child && child.parent) { child.parent.removeChild(child); // Fallback - usuń z rodzica } } } // Po agresywnym czyszczeniu, upewnij się, że globalne zmienne gracza, bossa i tła są null // Te obiekty powinny zostać usunięte przez powyższą pętlę, ale warto zresetować zmienne. currentBackground = null; player = null; boss = null; // --- Koniec agresywnego czyszczenia sceny --- this.currentState = "grillMenu"; game.setBackgroundColor(0x333333); // Tło grilla // Teraz dodaj nowe tło dla ekranu Grilla (ten fragment zostaje taki, jak go poprawiliśmy ostatnio - JEDNO dodanie grillMenu) currentBackground = LK.getAsset('grillMenu', { anchorX: 0.5, anchorY: 0.5, x: 2048 / 2, y: 2732 / 2 }); // Dodaj nowe tło na spód if (currentBackground) { // Sprawdź czy asset został poprawnie załadowany game.addChildAt(currentBackground, 0); } var confirmRestButton = null; // ✨ DODAJEMY 5 SPARKLINGÓW ✨ // Sparkle 1 var sparkle1 = game.addChild(LK.getAsset('sparkle', { anchorX: 0.5, anchorY: 0.5, x: 2048 / 2 - 460, y: 2732 / 2 + 690 })); gameState.grillMenuEffects.push(sparkle1); // <--- DODANA LINIJA // Sparkle 2 var sparkle2 = game.addChild(LK.getAsset('sparkle', { anchorX: 0.5, anchorY: 0.5, x: 2048 / 2 - 520, y: 2732 / 2 + 580 })); gameState.grillMenuEffects.push(sparkle2); // <--- DODANA LINIJA // Sparkle 3 var sparkle3 = game.addChild(LK.getAsset('sparkle', { anchorX: 0.5, anchorY: 0.5, x: 2048 / 2 - 660, y: 2732 / 2 + 550 })); gameState.grillMenuEffects.push(sparkle3); // <--- DODANA LINIJA // Sparkle 4 var sparkle4 = game.addChild(LK.getAsset('sparkle', { anchorX: 0.5, anchorY: 0.5, x: 2048 / 2 - 500, y: 2732 / 2 + 680 })); gameState.grillMenuEffects.push(sparkle4); // <--- DODANA LINIJA // Sparkle 5 var sparkle5 = game.addChild(LK.getAsset('sparkle', { anchorX: 0.5, anchorY: 0.5, x: 2048 / 2 - 620, y: 2732 / 2 + 720 })); gameState.grillMenuEffects.push(sparkle5); // <--- DODANA LINIJA // Funkcja animacji sparkle function animateSparkle(s) { // Sprawdzenie stanu (pozostaje bez zmian) if (gameState.currentState !== "grillMenu" || !s || s.destroyed) { return; } // ZAPISZ pozycję Y na początku TEGO cyklu animacji var initialYThisCycle = s.y; s.alpha = 0; // Rozpocznij od przezroczystości 0 // Pozycję Y ustawimy za chwilę, nie ma potrzeby resetowania jej tutaj od razu // Animacja pojawienia się i ruchu w górę tween(s, { alpha: 1, y: initialYThisCycle - 40 // Przesuń w górę WZGLĘDEM ZAPISANEJ pozycji }, { duration: 800, easing: tween.easeOut, onFinish: function onFinish() { // Po zakończeniu ruchu w górę if (gameState.currentState !== "grillMenu" || !s || s.destroyed) { return; } // Animacja zanikania (na tej wyższej pozycji) tween(s, { alpha: 0 }, { duration: 300, easing: tween.easeIn, onFinish: function onFinish() { // Po zakończeniu zanikania if (gameState.currentState !== "grillMenu" || !s || s.destroyed) { return; } // *** KLUCZOWA ZMIANA: Resetuj pozycję Y PRZED następnym cyklem *** s.y = initialYThisCycle; // <-- WRÓĆ do pozycji Y z początku tego cyklu // Zaplanuj kolejny cykl animacji LK.setTimeout(function () { animateSparkle(s); // Wywołaj kolejny cykl (teraz zacznie z poprawnej pozycji Y) }, Math.random() * 500 + 300); } }); } }); } // Start animacji sparklingów // Upewnij się, że animacje startują z pewnym opóźnieniem po dodaniu do tablicy LK.setTimeout(function () { if (gameState.currentState === "grillMenu") { animateSparkle(sparkle1); } }, Math.random() * 500); LK.setTimeout(function () { if (gameState.currentState === "grillMenu") { animateSparkle(sparkle2); } }, Math.random() * 500 + 500); LK.setTimeout(function () { if (gameState.currentState === "grillMenu") { animateSparkle(sparkle3); } }, Math.random() * 500 + 1000); LK.setTimeout(function () { if (gameState.currentState === "grillMenu") { animateSparkle(sparkle4); } }, Math.random() * 500 + 1500); LK.setTimeout(function () { if (gameState.currentState === "grillMenu") { animateSparkle(sparkle5); } }, Math.random() * 500 + 2000); // 🌟 DODAJEMY 3 GWIAZDKI NA NIEBIE 🌟 // Star 1 var star1 = game.addChild(LK.getAsset('star', { anchorX: 0.5, anchorY: 0.5, x: 2048 / 2 - 800, y: 2732 / 2 - 1000 })); gameState.grillMenuEffects.push(star1); // <--- DODANA LINIJA // Star 2 var star2 = game.addChild(LK.getAsset('star', { anchorX: 0.5, anchorY: 0.5, x: 2048 / 2 - 300, y: 2732 / 2 - 1150 })); gameState.grillMenuEffects.push(star2); // <--- DODANA LINIJA // Star 3 var star3 = game.addChild(LK.getAsset('star', { anchorX: 0.5, anchorY: 0.5, x: 2048 / 2 - 200, y: 2732 / 2 - 900 })); gameState.grillMenuEffects.push(star3); // <--- DODANA LINIJA // Funkcja animacji gwiazdek function animateStar(star) { // --- DODANE SPRAWDZENIE STANU --- if (gameState.currentState !== "grillMenu" || !star || star.destroyed) { return; } // --- KONIEC DODANEGO SPRAWDZENIA --- star.alpha = 0; tween(star, { alpha: 1 }, { duration: 2000, easing: tween.easeIn, onFinish: function onFinish() { // --- DODANE SPRAWDZENIE STANU W CALLBACKU --- if (gameState.currentState !== "grillMenu" || !star || star.destroyed) { return; } // --- KONIEC DODANEGO SPRAWDZENIA --- tween(star, { alpha: 0 }, { duration: 2000, easing: tween.easeOut, onFinish: function onFinish() { // --- DODANE SPRAWDZENIE STANU W WEWNĘTRZNYM CALLBACKU --- if (gameState.currentState !== "grillMenu" || !star || star.destroyed) { return; } // --- KONIEC DODANEGO SPRAWDZENIA --- // Po całym cyklu czekamy losowo 2-5 sekund przed kolejnym pojawieniem się LK.setTimeout(function () { animateStar(star); }, Math.random() * 3000 + 2000); } }); } }); } // Start animacji gwiazdek // Upewnij się, że animacje startują z pewnym opóźnieniem po dodaniu do tablicy LK.setTimeout(function () { if (gameState.currentState === "grillMenu") { animateStar(star1); } }, Math.random() * 1000); LK.setTimeout(function () { if (gameState.currentState === "grillMenu") { animateStar(star2); } }, Math.random() * 1000 + 500); LK.setTimeout(function () { if (gameState.currentState === "grillMenu") { animateStar(star3); } }, Math.random() * 1000 + 1000); // --- KONIEC gwiazdek --- // --- KONIEC sparklingów --- // Ukryj ściany (jeśli są widoczne w menu) walls.forEach(function (wall) { if (wall) { wall.alpha = 0; } }); // Ustaw UI dla Grill Menu ui.positionElements("grillMenu"); ui.updateBossHealth(0, 1); // Ukryj pasek HP bossa ui.updateHearts(0, storage.maxHearts); // Ukryj serca gracza // --- Przyciski na ekranie Grilla --- var buttonYStart = 1000; var buttonYOffset = 150; // Przycisk "Rest" var restButton = new Container(); restButton.interactive = true; restButton.cursor = "pointer"; restButton.x = 600; restButton.y = 650; currentSceneElements.addChild(restButton); var restButtonBg = LK.getAsset('buttonRest', { anchorX: 0.5, anchorY: 0.5 }); restButton.addChild(restButtonBg); restButton.down = function () { if (!confirmRestButton || confirmRestButton && confirmRestButton.destroyed) { confirmRestButton = new Container(); confirmRestButton.interactive = true; confirmRestButton.cursor = "pointer"; // Zamieniamy tekst i tło na konkretny asset var confirmButtonGraphic = LK.getAsset('confirmRestButton', { anchorX: 0.5, anchorY: 0.5 }); confirmRestButton.addChild(confirmButtonGraphic); var restButtonActualWidth = restButtonBg.width || 500; var confirmButtonActualWidth = confirmButtonGraphic.width || 550; var paddingBetweenButtons = 30; confirmRestButton.x = restButton.x + restButtonActualWidth / 2 + confirmButtonActualWidth / 2 + paddingBetweenButtons; confirmRestButton.y = restButton.y; currentSceneElements.addChild(confirmRestButton); confirmRestButton.down = function () { var farewellGraphic = null; try { farewellGraphic = LK.getAsset('grillMenuFarewellGraphic', { anchorX: 0.5, anchorY: 0.5, x: 2048 / 2, y: 2732 / 2, alpha: 1 // od razu widoczna }); currentSceneElements.addChild(farewellGraphic); LK.setTimeout(function () { if (gameState.currentState === "grillMenu" && farewellGraphic && !farewellGraphic.destroyed) { tween(farewellGraphic, { alpha: 0 }, { duration: 500, onFinish: function onFinish() { if (farewellGraphic && !farewellGraphic.destroyed) { currentSceneElements.removeChild(farewellGraphic); farewellGraphic.destroy(); } if (confirmRestButton && !confirmRestButton.destroyed) { currentSceneElements.removeChild(confirmRestButton); confirmRestButton.destroy(); confirmRestButton = null; } } }); } else if (farewellGraphic && !farewellGraphic.destroyed) { currentSceneElements.removeChild(farewellGraphic); farewellGraphic.destroy(); } }, 6000); } catch (e) { console.error("Błąd grillMenuFarewellGraphic:", e); farewellGraphic = new Shape({ width: 2048, height: 2732, color: 0x101030 }); farewellGraphic.x = 2048 / 2; farewellGraphic.y = 2732 / 2; farewellGraphic.alpha = 0; currentSceneElements.addChild(farewellGraphic); tween(farewellGraphic, { alpha: 0.8 }, { duration: 500 }); LK.setTimeout(function () { if (farewellGraphic && !farewellGraphic.destroyed) { tween(farewellGraphic, { alpha: 0 }, { duration: 500, onFinish: function onFinish() { if (farewellGraphic && !farewellGraphic.destroyed) { currentSceneElements.removeChild(farewellGraphic); farewellGraphic.destroy(); } if (confirmRestButton && !confirmRestButton.destroyed) { currentSceneElements.removeChild(confirmRestButton); confirmRestButton.destroy(); confirmRestButton = null; } } }); } }, 6000); if (ui && ui.showMessage) { ui.showMessage("Błąd: Brak assetu grafiki. Placeholder.", 3000); } } }; } else { console.log("Przycisk 'Potwierdź' już istnieje."); } }; // Przycisk "Upgrade Roll" (TEN I KOLEJNE PRZYCISKI POZOSTAJĄ BEZ ZMIAN) var upgradeButton = new Container(); upgradeButton.interactive = true; upgradeButton.x = 600; upgradeButton.y = 850; currentSceneElements.addChild(upgradeButton); var upgradeButtonBg = LK.getAsset('buttonUpgrade', { anchorX: 0.5, anchorY: 0.5 }); upgradeButton.addChild(upgradeButtonBg); upgradeButton.down = function () { if (currentBackground) { tween(currentBackground, { alpha: 0 }, { duration: 600, easing: tween.easeIn, onFinish: function onFinish() { currentBackground.destroy(); currentBackground = LK.getAsset('upgradebg', { anchorX: 0.5, anchorY: 0.5, x: 2048 / 2, y: 2732 / 2, alpha: 0 }); game.addChildAt(currentBackground, 0); tween(currentBackground, { alpha: 1 }, { duration: 1200, easing: tween.easeOut }); } }); } }; // Przycisk "New Boss+" var newBossButton = new Container(); newBossButton.interactive = true; newBossButton.cursor = "pointer"; newBossButton.x = 600; newBossButton.y = 1050; // jeszcze niżej currentSceneElements.addChild(newBossButton); // Dodaj do kontenera sceny var newBossButtonBg = LK.getAsset('buttonBoss', { anchorX: 0.5, anchorY: 0.5 }); newBossButton.addChild(newBossButtonBg); newBossButton.down = function () { isNewBossPlusMode = true; // Poprzedni kod usuwający sparkle tutaj jest już niepotrzebny, // ponieważ obsługa usuwania jest teraz w startGame z wykorzystaniem gameState.grillMenuEffects gameState.startGame(); }; // Przycisk "Roll Master" var rollMasterButton = new Container(); // Tworzymy kontener na przycisk rollMasterButton.interactive = true; // Ustawiamy interaktywność rollMasterButton.cursor = "pointer"; // Zmieniamy kursor po najechaniu rollMasterButton.x = 600; // Ta sama pozycja X co inne przyciski rollMasterButton.y = 2500; // Pozycja Y pod przyciskiem "New Boss+" currentSceneElements.addChild(rollMasterButton); // Dodajemy kontener do sceny // --- NOWY KOD TŁA --- // Pobieramy asset obrazka dla przycisku Roll Master var rollMasterButtonImage = LK.getAsset('buttonRollMaster', { anchorX: 0.5, // Ustawiamy punkt zaczepienia na środek obrazka anchorY: 0.5 }); rollMasterButton.addChild(rollMasterButtonImage); // Dodajemy obrazek do kontenera przycisku // --- KONIEC NOWEGO KODU TŁA --- // Usunęliśmy kod tworzący i dodający obiekt Text2 // Akcja przycisku (kliknięcie) pozostaje bez zmian rollMasterButton.down = function () { gameState.startRollMasterMode(); // Uruchom tryb Roll Master }; // Przycisk "Cursed Crystal" var cursedCrystalButton = new Container(); cursedCrystalButton.interactive = true; cursedCrystalButton.cursor = "pointer"; cursedCrystalButton.x = 600; // Ta sama kolumna co inne główne przyciski // Ustaw pozycję Y pod przyciskiem "New Boss+" // Zakładamy, że newBossButton i newBossButtonBg istnieją i są zdefiniowane powyżej var newBossButtonHeight = newBossButtonBg && newBossButtonBg.height ? newBossButtonBg.height : 200; // Domyślna wysokość, jeśli nieznana var verticalSpacing = 700; // Odstęp między przyciskami // --- POCZĄTEK POPRAWKI dla LK.getAssetMeta --- var cursedCrystalButtonAssetForSizing; var cursedCrystalButtonHeight = 250; // Domyślna wysokość assetu buttonCursedCrystal (zgodnie z definicją w LK.init.image) try { // Pobierz asset, aby spróbować odczytać jego rzeczywistą wysokość cursedCrystalButtonAssetForSizing = LK.getAsset('buttonCursedCrystal', {}); if (cursedCrystalButtonAssetForSizing && typeof cursedCrystalButtonAssetForSizing.height === 'number') { cursedCrystalButtonHeight = cursedCrystalButtonAssetForSizing.height; } } catch (e) { // Jeśli wystąpi błąd przy pobieraniu assetu (np. jeszcze niezaładowany lub błąd ID), // użyjemy domyślnej wysokości i wyświetlimy ostrzeżenie. console.warn("Nie można pobrać assetu 'buttonCursedCrystal' do ustalenia wysokości, używam domyślnej: " + cursedCrystalButtonHeight, e); } // Oblicz pozycję Y przycisku Cursed Crystal cursedCrystalButton.y = newBossButton.y + newBossButtonHeight / 2 + verticalSpacing + cursedCrystalButtonHeight / 2; // --- KONIEC POPRAWKI dla LK.getAssetMeta --- currentSceneElements.addChild(cursedCrystalButton); var cursedCrystalButtonImage = LK.getAsset('buttonCursedCrystal', { anchorX: 0.5, anchorY: 0.5 }); cursedCrystalButton.addChild(cursedCrystalButtonImage); cursedCrystalButton.down = function () { if (gameState.currentState === "grillMenu") { console.log("Przycisk Cursed Crystal kliknięty!"); gameState.startCursedCrystalMode(); } }; var creditsButton = new Container(); creditsButton.interactive = true; creditsButton.cursor = "pointer"; // Ustaw pozycję dla prawego dolnego rogu // Te wartości (X, Y) są odległościami od lewego górnego rogu ekranu (0,0) // Musisz je dostosować do rozmiaru swojego przycisku i ekranu. // Załóżmy, że Twój przycisk 'buttoncredits' ma np. 200px szerokości i 80px wysokości. // Aby umieścić go w prawym dolnym rogu z marginesem 50px: var creditsButtonAsset = LK.getAsset('buttoncredits', {}); // Pobierz asset, aby sprawdzić wymiary jeśli trzeba var creditsButtonWidth = creditsButtonAsset.width || 200; // Domyślna szerokość, jeśli nie ma w assecie var creditsButtonHeight = creditsButtonAsset.height || 80; // Domyślna wysokość creditsButton.x = 2048 - creditsButtonWidth / 2 - 50; // Od prawej krawędzi (2048) odejmij połowę szerokości przycisku i margines creditsButton.y = 2732 - creditsButtonHeight / 2 - 50; // Od dolnej krawędzi (2732) odejmij połowę wysokości przycisku i margines currentSceneElements.addChild(creditsButton); // Użyj swojego nowego assetu 'buttoncredits' jako tła/grafiki przycisku var creditsButtonBg = LK.getAsset('buttoncredits', { anchorX: 0.5, // Anchor na środek, bo pozycjonujemy kontener anchorY: 0.5 // Nie potrzebujesz tutaj scale, chyba że Twój asset jest za duży/mały }); creditsButton.addChild(creditsButtonBg); // Tekst na przycisku (jeśli Twój asset 'buttoncredits' nie ma już napisu) // Jeśli 'buttoncredits' to już gotowy obrazek z napisem "Credits", ten obiekt Text2 nie jest potrzebny. var creditsButtonText = new Text2("Credits", { size: 30, // Dostosuj rozmiar, aby pasował do Twojego przycisku fill: 0xFFFFFF // Kolor tekstu // Możesz dodać stroke, jeśli tekst jest na skomplikowanym tle // stroke: 0x000000, // strokeThickness: 2 }); creditsButtonText.anchor.set(0.5, 0.5); // Wyśrodkuj tekst na przycisku creditsButton.addChild(creditsButtonText); // Dodaj tekst DO kontenera przycisku creditsButton.down = function () { if (gameState.currentState === "grillMenu") { console.log("Przycisk Credits kliknięty!"); // Dodaj log dla testu gameState.showCredits(); } }; }, showCredits: function showCredits() { var self = this; var previousState = this.currentState; var wasInputActive = self.isInputActive; self.isInputActive = false; // Tymczasowo zablokuj input gry // self.creditsPlaying = true; // Opcjonalna flaga, jeśli potrzebujesz console.log("[Credits] Pokazywanie Creditsów z własnym tłem"); // 1. Stwórz tło 'creditsbg' var creditsBackground; // Zmienna na nasze tło try { creditsBackground = LK.getAsset('creditsbg', { anchorX: 0.5, // Zakładamy, że chcesz je wyśrodkować anchorY: 0.5, x: 2048 / 2, y: 2732 / 2, alpha: 0 // Zacznij od przezroczystości }); game.addChild(creditsBackground); // Dodaj do sceny } catch (e) { console.error("[Credits] Błąd podczas ładowania assetu 'creditsbg':", e); // Awaryjnie: stwórz czarne tło, jeśli asset się nie załadował creditsBackground = new Shape({ width: 2048, height: 2732, color: 0x111010 }); // Użyjmy Twojego działającego koloru creditsBackground.x = 2048 / 2; creditsBackground.y = 2732 / 2; creditsBackground.alpha = 0; game.addChild(creditsBackground); } var creditsText = null; // Zmienna na tekst creditsów var creditsTextTween = null; // Zmienna na animację tekstu // Handler do pomijania creditsów var _skipCreditsHandler = function skipCreditsHandler() { console.log("[Credits] Pominięto creditsy"); // Zatrzymaj animacje tween.stop(creditsBackground); if (creditsTextTween) { tween.stop(creditsTextTween); } // Usuń obiekty if (creditsText && !creditsText.destroyed) { creditsText.destroy(); } if (creditsBackground && !creditsBackground.destroyed) { creditsBackground.destroy(); } // Przywróć stan self.isInputActive = wasInputActive; // self.creditsPlaying = false; game.off('down', _skipCreditsHandler); // Usuń listener // Jeśli currentState było zmieniane na coś w stylu "creditsScreenActive", przywróć je teraz // self.currentState = previousState; // W tej wersji nie zmieniamy currentState, więc nie ma potrzeby przywracania }; // Animacja pojawienia się tła tween(creditsBackground, { alpha: 1 }, { duration: 500, // Krótki fade-in onFinish: function onFinish() { // Stwórz tekst "Silas" creditsText = new Text2("Silas", { size: 150, fill: 0xFFFFFF, align: 'center' }); creditsText.anchor.set(0.5, 0.5); creditsText.x = 2048 / 2; creditsText.y = 2732 + creditsText.height; game.addChild(creditsText); var textAnimationDuration = 8000; var textEndY = -creditsText.height; // Animacja tekstu w górę creditsTextTween = tween(creditsText, { y: textEndY }, { duration: textAnimationDuration, easing: tween.linear, onFinish: function onFinish() { if (creditsText && !creditsText.destroyed) { creditsText.destroy(); } // Animacja zanikania tła 'creditsbg' tween(creditsBackground, { alpha: 0 }, { duration: 500, onFinish: function onFinish() { if (creditsBackground && !creditsBackground.destroyed) { creditsBackground.destroy(); } console.log("[Credits] Koniec Creditsów, powrót do", previousState); self.isInputActive = wasInputActive; // self.creditsPlaying = false; game.off('down', _skipCreditsHandler); // Usuń listener, jeśli nie został wcześniej wywołany } }); } }); // Dodaj listener do pomijania dopiero po pojawieniu się tła i tekstu (lub od razu, jeśli wolisz) game.once('down', _skipCreditsHandler); } }); }, // Koniec funkcji showGrillScreen (przecinek oddziela ją od następnej metody gameState) // --- NOWA FUNKCJA: Start przejścia do trybu Roll Master --- startRollMasterMode: function startRollMasterMode() { console.log("Rozpoczynanie trybu Roll Master..."); this.currentState = "rollMaster"; // Ustawienie nowego stanu gry // Wyczyszczenie elementów z poprzedniej sceny (np. Grill Menu) // Upewnij się, że 'currentSceneElements' to poprawny kontener dla elementów Grill Menu // lub użyj innej metody czyszczenia specyficznej dla Twojej konfiguracji. // Poniżej przykład czyszczenia globalnego kontenera, jeśli go używasz: if (typeof clearScene === 'function') { // Sprawdź, czy funkcja clearScene istnieje clearScene(); // Użyj globalnej funkcji clearScene, jeśli jest dostępna } else if (currentSceneElements && typeof currentSceneElements.removeChildren === 'function') { // Alternatywnie, jeśli currentSceneElements zawiera tylko elementy menu: currentSceneElements.removeChildren(); } else { console.warn("Nie można automatycznie wyczyścić sceny przed Roll Master!"); } // Dodatkowo, usuń specyficzne efekty z Grill Menu, jeśli istnieją if (this.grillMenuEffects && this.grillMenuEffects.length > 0) { this.grillMenuEffects.forEach(function (effect) { if (effect && effect.destroy) { effect.destroy(); } }); this.grillMenuEffects = []; } // Usuń tło Grill Menu if (currentBackground && currentBackground.destroy) { tween.stop(currentBackground); // Zatrzymaj animacje, jeśli były currentBackground.destroy(); currentBackground = null; } // Wywołanie funkcji konfigurującej scenę tego trybu this.setupRollMasterScene(); }, // <-- WAŻNE: Przecinek tutaj, bo następuje kolejna funkcja gameState // --- NOWA FUNKCJA: Konfiguracja sceny Roll Master --- setupRollMasterScene: function setupRollMasterScene() { var _this2 = this; // _this2 dla callbacków w timerach poniżej console.log("[SetupRollMaster] Konfiguracja sceny Roll Master..."); // --- POCZĄTEK SEKCJI ZARZĄDZANIA MUZYKĄ --- if (this.currentIntroMusicInstance && this.currentIntroMusicInstance.stop) { console.log("[SetupRollMaster] Zatrzymywanie currentIntroMusicInstance."); if (typeof this.currentIntroMusicInstance.volume === 'number') { this.currentIntroMusicInstance.volume = 0; } this.currentIntroMusicInstance.stop(); this.currentIntroMusicInstance = null; } if (this.currentCursedCrystalMusicInstance && this.currentCursedCrystalMusicInstance.stop) { this.currentCursedCrystalMusicInstance.stop(); this.currentCursedCrystalMusicInstance = null; } // <<< DODAJ TUTAJ if (this.currentRollSoulsInstance && this.currentRollSoulsInstance.stop) { console.log("[SetupRollMaster] Zatrzymywanie currentRollSoulsInstance."); if (typeof this.currentRollSoulsInstance.volume === 'number') { this.currentRollSoulsInstance.volume = 0; } this.currentRollSoulsInstance.stop(); this.currentRollSoulsInstance = null; } // Zatrzymaj poprzednią instancję rollmaster music, jeśli jakimś cudem istnieje if (this.currentRollMasterMusicInstance && this.currentRollMasterMusicInstance.stop) { console.log("[SetupRollMaster] Zatrzymywanie poprzedniej currentRollMasterMusicInstance."); if (typeof this.currentRollMasterMusicInstance.volume === 'number') { this.currentRollMasterMusicInstance.volume = 0; } this.currentRollMasterMusicInstance.stop(); // this.currentRollMasterMusicInstance = null; // Zerujemy, bo zaraz przypiszemy nową } console.log("[SetupRollMaster] Odtwarzanie muzyki 'rollmaster'."); this.currentRollMasterMusicInstance = LK.playMusic('rollmaster', { loop: true, volume: 0.7, fade: { start: 0, end: 0.7, duration: 1000 } }); if (!this.currentRollMasterMusicInstance) { console.error("[SetupRollMaster] LK.playMusic('rollmaster') nie zwróciło instancji!"); } // --- KONIEC SEKCJI ZARZĄDZANIA MUZYKĄ --- try { currentBackground = LK.getAsset('rollMasterBg', { anchorX: 0.5, anchorY: 0.5, x: 2048 / 2, y: 2732 / 2 }); game.addChildAt(currentBackground, 0); } catch (e) { console.warn("Nie znaleziono assetu 'rollMasterBg', używam koloru tła."); game.setBackgroundColor(0x222233); } if (typeof Player !== 'undefined') { player = game.addChild(new Player()); player.x = 2048 / 2; player.y = 2732 / 2 + 400; player.health = 1; player.rolling = false; player.invulnerable = false; player.invulnerabilityFrames = 0; player.rollCooldown = 0; if (player && typeof player.clearRollTimeouts === 'function') { player.clearRollTimeouts(); } player.hasRolledThroughBossThisRoll = false; player.alpha = 1; player.dead = false; } else { console.error("Klasa Player nie jest zdefiniowana!"); this.showGrillScreen(); return; } this.rollMasterTime = 0; this.rollMasterDifficulty = 1; this.rollMasterAttacks = []; this.attackUnlockOrder = ['rmattack1', 'rmattack2', 'rmattack3', 'rmattack4']; this.unlockedAttacks = ['rmattack1']; this.nextUnlockTime = 15; console.log("Ataki startowe:", this.unlockedAttacks, "Następne odblokowanie w:", this.nextUnlockTime + "s"); this.attackSpawnTimer = 0; this.attackSpawnInterval = 120; this.explosionSpawnTimer = 0; this.explosionSpawnInterval = 240; this.laserSpawnTimer = 0; this.laserSpawnInterval = 300; this.laserWarningTime = 1500; this.spreaderSpawnTimer = 0; this.spreaderSpawnInterval = 600; this.spreaderSplitTime = 180; if (this.randomRmattack1Timer) { LK.clearTimeout(this.randomRmattack1Timer); this.randomRmattack1Timer = null; } this.rollMasterHighScore = storage.rollMasterHighScore || 0; if (ui && ui.updateHighScoreDisplay) { ui.updateHighScoreDisplay(this.rollMasterHighScore); } else { console.error("Nie można zaktualizować wyświetlania rekordu - brak ui.updateHighScoreDisplay"); } if (ui && ui.timerText) { ui.updateTimerDisplay(this.rollMasterTime); } else { console.error("Nie można znaleźć ui.timerText do skonfigurowania!"); } if (ui && ui.updateHearts) { ui.updateHearts(1, 1); } if (ui && ui.updateBossHealth) { ui.updateBossHealth(0, 1); } if (ui && ui.positionElements) { ui.positionElements("rollMaster"); } else { console.error("Nie można ustawić elementów UI dla Roll Master - brak ui.positionElements"); } if (this.rollMasterTimerInterval) { LK.clearInterval(this.rollMasterTimerInterval); } this.rollMasterTimerInterval = LK.setInterval(function () { if (_this2.currentState !== "rollMaster") { LK.clearInterval(_this2.rollMasterTimerInterval); _this2.rollMasterTimerInterval = null; return; } _this2.rollMasterTime++; if (ui && ui.updateTimerDisplay) { ui.updateTimerDisplay(_this2.rollMasterTime); } if (_this2.rollMasterTime > 0 && _this2.rollMasterTime % 20 === 0) { var newAttackJustUnlocked = false; var newAttackFriendlyName = ""; if (_this2.unlockedAttacks.length < _this2.attackUnlockOrder.length) { var nextAttackIndex = _this2.unlockedAttacks.length; var attackToUnlockTechnicalName = _this2.attackUnlockOrder[nextAttackIndex]; _this2.unlockedAttacks.push(attackToUnlockTechnicalName); newAttackJustUnlocked = true; newAttackFriendlyName = attackToUnlockTechnicalName.replace('rmattack1', 'Fart of the Forgotten Pyromancer').replace('rmattack2', 'Blood Ball Z').replace('rmattack3', 'Holy Wifi Beam').replace('rmattack4', 'Flame Skull.exe'); console.log("Odblokowano nowy atak:", attackToUnlockTechnicalName, "w", _this2.rollMasterTime + "s"); } _this2.increaseRollMasterDifficulty(); if (ui && ui.showMessage) { var messageToShow = ""; var currentDifficulty = _this2.rollMasterDifficulty; if (newAttackJustUnlocked) { messageToShow = "New Attack: " + newAttackFriendlyName + "! (Difficulty: " + currentDifficulty + ")"; } else { switch (currentDifficulty) { case 2: case 3: case 4: messageToShow = "Even the pause button is afraid."; break; case 5: messageToShow = "Alright, that’s probably enough difficulty... (Poziom: " + currentDifficulty + ")"; break; case 6: messageToShow = "Wait, are you still playing? I ran out of messages. (Poziom: " + currentDifficulty + ")"; break; case 7: messageToShow = "Okay seriously, stop. I didn’t test this far. (Poziom: " + currentDifficulty + ")"; break; default: messageToShow = "I stopped writing messages after level 7. You're on your own. Level: " + currentDifficulty; break; } } ui.showMessage(messageToShow, 2500); } } }, 1000); if (ui && ui.showMessage && this.unlockedAttacks.length > 0) { var firstAttackName = this.unlockedAttacks[0]; var friendlyFirstName = firstAttackName.replace('rmattack1', 'Fart of the Forgotten Pyromancer').replace('rmattack2', 'Blood Ball Z').replace('rmattack3', 'Holy Wifi Beam').replace('rmattack4', 'Flame Skull.exe'); LK.setTimeout(function () { if (gameState.currentState === "rollMaster") { ui.showMessage("First Attack: " + friendlyFirstName, 3000); } }, 1000); } console.log("Scena Roll Master skonfigurowana i muzyka 'rollmaster' powinna grać."); }, // <-- WAŻNE: Przecinek tutaj // --- NOWA FUNKCJA: Zwiększanie trudności w Roll Master --- increaseRollMasterDifficulty: function increaseRollMasterDifficulty() { this.rollMasterDifficulty++; console.log("Trudność Roll Master zwiększona do:", this.rollMasterDifficulty); var decreaseAmountProjectile = 10; var minIntervalProjectile = 30; this.attackSpawnInterval = Math.max(minIntervalProjectile, this.attackSpawnInterval - decreaseAmountProjectile); console.log("Nowy interwał projectile:", this.attackSpawnInterval); var decreaseAmountExplosion = 15; var minIntervalExplosion = 60; this.explosionSpawnInterval = Math.max(minIntervalExplosion, this.explosionSpawnInterval - decreaseAmountExplosion); console.log("Nowy interwał explosion:", this.explosionSpawnInterval); // --- NOWA LOGIKA DLA LASERA --- var decreaseAmountLaser = 20; // Jak szybko ma skracać się interwał lasera var minIntervalLaser = 120; // Minimalny interwał lasera (np. 2 sekundy) this.laserSpawnInterval = Math.max(minIntervalLaser, this.laserSpawnInterval - decreaseAmountLaser); console.log("Nowy interwał laser:", this.laserSpawnInterval); // --- NOWA LOGIKA DLA SPREADERA --- var decreaseAmountSpreader = 30; // Jak szybko ma skracać się interwał spreadera var minIntervalSpreader = 180; // Minimalny interwał spreadera (np. 3 sekundy) this.spreaderSpawnInterval = Math.max(minIntervalSpreader, this.spreaderSpawnInterval - decreaseAmountSpreader); console.log("Nowy interwał spreader:", this.spreaderSpawnInterval); }, // Przejście do stanu Game Over // isDeath: true (gracz zginął), false (czas minął w Boss+) gameOver: function gameOver(isDeathParam) { // Krok 1: Zabezpieczenie parametru isDeathParam i przechwycenie aktualnego stanu isNewBossPlusMode var localIsDeath = typeof isDeathParam === 'boolean' ? isDeathParam : true; var calledDuringBossPlusMode = isNewBossPlusMode; // Przechwyć stan na początku wywołania! console.log("gameState.gameOver CALLED. isDeathParam:", isDeathParam, "=> localIsDeath:", localIsDeath, ". Flag isNewBossPlusMode at call time (captured as calledDuringBossPlusMode):", calledDuringBossPlusMode, ". Current global isNewBossPlusMode:", isNewBossPlusMode // Dla porównania, jeśli coś by go zmieniło w międzyczasie ); this.currentState = "gameOver"; // Ustaw stan gry gameOverReasonIsDeath = localIsDeath; // Ustaw globalną flagę (jeśli nadal jej używasz) // Krok 2: Zatrzymaj timery i inne procesy specyficzne dla trybu "game" if (this.gameTimerInterval) { LK.clearInterval(this.gameTimerInterval); this.gameTimerInterval = null; } // if (this.rollMasterTimerInterval) { // To jest dla Roll Master, nie powinno być tutaj zatrzymywane // LK.clearInterval(this.rollMasterTimerInterval); // this.rollMasterTimerInterval = null; // } this.bossSpeedIncreased = false; // Reset flagi dla standardowego bossa // Krok 3: Ukryj aktywne elementy gry (gracz, boss, ściany) if (player && player.alpha !== 0) { // Nie niszczymy gracza tutaj, jego animacja śmierci powinna się zakończyć, // a obiekt player zostanie zniszczony przed pokazaniem przycisków lub restartem. player.alpha = 0; } if (boss && boss.alpha !== 0) { // Podobnie dla bossa, jego animacja śmierci (jeśli jest) powinna się zakończyć. // Zostanie zniszczony później. boss.alpha = 0; } walls.forEach(function (wall) { if (wall) { wall.alpha = 0; } }); // Krok 4: Wstępne ustawienie UI (czyszczenie tekstów) if (ui) { ui.positionElements("gameOver"); // Może ukryć niepotrzebne elementy, pokazać stałe dla gameOver ui.titleText.setText(""); ui.titleText.alpha = 0; ui.messageText.setText(""); ui.messageText.alpha = 0; ui.tutorialText.setText(""); ui.tutorialText.alpha = 0; // Ukryj tutorial, jeśli był widoczny } // Krok 5: Wyświetl mema śmierci (jeśli localIsDeath) lub komunikat "Time's Up" var memeDisplayDuration = 3000; // Czas wyświetlania mema/komunikatu przed przyciskami var deathMemeVisual = null; // Zmienna do przechowywania obiektu mema if (localIsDeath) { var memeAssets = ['deathMeme1', 'deathMeme2', 'deathMeme3', 'deathMeme4', 'deathMeme5']; var randomMemeAssetName = memeAssets[Math.floor(Math.random() * memeAssets.length)]; console.log("[GameOver] Wyświetlanie mema:", randomMemeAssetName); try { deathMemeVisual = LK.getAsset(randomMemeAssetName, { anchorX: 0.5, anchorY: 0.5, alpha: 0, x: 2048 / 2, y: 2732 / 2 - 150, // Trochę wyżej, żeby zrobić miejsce na przyciski scaleX: 0.8, scaleY: 0.8 }); currentSceneElements.addChild(deathMemeVisual); // Dodaj do kontenera, który będzie czyszczony tween(deathMemeVisual, { alpha: 1 }, { duration: 500, easing: tween.easeIn }); } catch (e) { console.error("[GameOver] Błąd ładowania assetu mema:", randomMemeAssetName, e); if (ui) { // Awaryjny tekst, jeśli mem się nie załaduje ui.titleText.setText("DEFEAT!"); // Tłumaczenie ui.titleText.style = { size: 150, fill: 0xFF0000, align: 'center' }; ui.titleText.alpha = 1; } } } else if (calledDuringBossPlusMode && !localIsDeath) { // Boss+ Time's Up if (ui) { ui.titleText.setText("TIME'S UP!"); // Tłumaczenie ui.titleText.style = { size: 150, fill: 0xFFA500, align: 'center', stroke: 0x000000, strokeThickness: 6 }; ui.titleText.alpha = 1; // Można dodać dodatkowy komunikat poniżej tytułu // ui.messageText.setText("The challenge ends."); // ui.messageText.alpha = 1; } } // Krok 6: Timeout, po którym wykonają się akcje końcowe (przyciski lub przejście) LK.setTimeout(function () { // Ten console.log był źródłem błędu, teraz używamy localIsDeath i calledDuringBossPlusMode console.log("DEBUG: Timeout #1 (po memie) w gameState.gameOver. localIsDeath:", localIsDeath, "Captured BossPlus state (calledDuringBossPlusMode):", calledDuringBossPlusMode); // Usuń mema, jeśli był wyświetlony if (deathMemeVisual && !deathMemeVisual.destroyed) { tween(deathMemeVisual, { alpha: 0 }, { duration: 300, onFinish: function onFinish() { if (deathMemeVisual && deathMemeVisual.parent) { deathMemeVisual.parent.removeChild(deathMemeVisual); } if (deathMemeVisual && deathMemeVisual.destroy) { deathMemeVisual.destroy(); } deathMemeVisual = null; } }); } // Wyczyść tytuł/wiadomość, która była z memem, aby przygotować miejsce na nowy tytuł z przyciskami if (ui) { ui.titleText.alpha = 0; ui.messageText.alpha = 0; } // Ostateczne czyszczenie obiektów gry (gracz, boss, tło areny) // clearScene() może być zbyt agresywne, jeśli currentSceneElements zawiera UI. Lepiej ręcznie. if (player && player.destroy && !player.destroyed) { player.destroy(); player = null; } if (boss && boss.destroy && !boss.destroyed) { boss.destroy(); boss = null; } if (currentBackground) { // Usuń tło areny, jeśli istnieje if (currentBackground.parent) { currentBackground.parent.removeChild(currentBackground); } if (currentBackground.destroy && !currentBackground.destroyed) { currentBackground.destroy(); } currentBackground = null; } game.setBackgroundColor(0x1a1a1a); // Ciemne tło dla ekranu z przyciskami // Usuń poprzednie aktywne przyciski (np. z Cursed Crystal) if (gameState.activeButtons && gameState.activeButtons.length > 0) { gameState.activeButtons.forEach(function (btn) { if (btn && btn.destroy && !btn.destroyed) { // Dodatkowe sprawdzenie !btn.destroyed if (btn.parent) { btn.parent.removeChild(btn); } // Usuń z rodzica przed zniszczeniem btn.destroy(); } }); } gameState.activeButtons = []; // Wyczyść tablicę na nowe przyciski // Logika przycisków lub restartu if (calledDuringBossPlusMode) { // Przypadek Boss+ (śmierć LUB koniec czasu) - wyświetl przyciski console.log("gameOver: Scenariusz Boss+. Wyświetlanie tytułu i przycisków."); var titleTextForBossPlus = localIsDeath ? "DEFEAT!" : "TIME'S UP!"; if (ui) { ui.titleText.setText(titleTextForBossPlus); ui.titleText.style = { size: 120, fill: localIsDeath ? 0xFF0000 : 0xFFA500, align: 'center', stroke: 0x000000, strokeThickness: 6 }; ui.titleText.alpha = 1; ui.titleText.x = 2048 / 2; ui.titleText.y = 700; // Przykładowa pozycja Y dla tytułu } var buttonYStart = (ui && ui.titleText ? ui.titleText.y : 700) + 200; var buttonSpacing = 120; // Przycisk "Main Menu" var menuButton = new Container(); menuButton.interactive = true; menuButton.cursor = "pointer"; try { var menuButtonAsset = LK.getAsset('mainmenu', { anchorX: 0.5, anchorY: 0.5 }); menuButton.addChild(menuButtonAsset); } catch (e) { console.error("Błąd ładowania assetu przycisku 'mainmenu':", e); // Prosty fallback - tylko kształt, bez tekstu, bo zakładamy, że go nie potrzebujemy, // skoro główny asset ma tekst. Jeśli chcesz, możesz dodać tu tekst awaryjny. var menuButtonBgFallback = new Shape({ width: 400, height: 100, color: 0x555555, shape: 'box' }); menuButton.addChild(menuButtonBgFallback); } menuButton.x = 2048 / 2; menuButton.y = buttonYStart; menuButton.down = function () { if (gameState.currentState !== "gameOver") { return; } // Dodatkowe zabezpieczenie gameState.activeButtons.forEach(function (btn) { if (btn && btn.destroy && !btn.destroyed) { if (btn.parent) { btn.parent.removeChild(btn); } btn.destroy(); } }); gameState.activeButtons = []; isNewBossPlusMode = false; gameState.showGrillScreen(); }; currentSceneElements.addChild(menuButton); // Dodajemy do currentSceneElements gameState.activeButtons.push(menuButton); // Przycisk "Restart Boss+" var restartButton = new Container(); restartButton.interactive = true; restartButton.cursor = "pointer"; try { var restartButtonAsset = LK.getAsset('restartgame', { anchorX: 0.5, anchorY: 0.5 }); restartButton.addChild(restartButtonAsset); } catch (e) { console.error("Błąd ładowania assetu przycisku 'restartgame':", e); // Prosty fallback var restartButtonBgFallback = new Shape({ width: 400, height: 100, color: 0x4477FF, shape: 'box' }); // Dostosuj wymiary i kolor restartButton.addChild(restartButtonBgFallback); } restartButton.x = 2048 / 2; restartButton.y = buttonYStart + buttonSpacing; restartButton.down = function () { if (gameState.currentState !== "gameOver") { return; } gameState.activeButtons.forEach(function (btn) { if (btn && btn.destroy && !btn.destroyed) { if (btn.parent) { btn.parent.removeChild(btn); } btn.destroy(); } }); gameState.activeButtons = []; isNewBossPlusMode = true; gameState.startGame(); }; currentSceneElements.addChild(restartButton); gameState.activeButtons.push(restartButton); } else if (localIsDeath && !calledDuringBossPlusMode) { // Śmierć w standardowym trybie (tutorial) - BEZ PRZYCISKÓW, od razu restart console.log("gameOver: Standardowa śmierć (tutorial). Restartowanie poziomu."); isNewBossPlusMode = false; gameState.startGame(); } else { // Inny, nieoczekiwany scenariusz (np. !localIsDeath i !calledDuringBossPlusMode) // To jest fallback, np. jeśli jakimś cudem czas minął w trybie non-Boss+ (co nie powinno się zdarzyć) console.warn("gameOver: Nieoczekiwany scenariusz (np. timeout w trybie non-Boss+). Przejście do Grill Menu."); isNewBossPlusMode = false; gameState.showGrillScreen(); } }, memeDisplayDuration); // Czas na wyświetlenie mema/komunikatu przed pokazaniem przycisków/przejściem }, // --- NOWA FUNKCJA: Zakończenie trybu Roll Master --- // Wewnątrz obiektu gameState: endRollMasterMode: function endRollMasterMode(finalTime) { if (this.currentState === "rollMasterGameOver") { return; } console.log("[EndRollMasterMode] Zakończono tryb Roll Master. Czas:", finalTime); this.currentState = "rollMasterGameOver"; if (this.currentRollMasterMusicInstance && this.currentRollMasterMusicInstance.stop) { this.currentRollMasterMusicInstance.stop(); this.currentRollMasterMusicInstance = null; } // ... (zatrzymanie timerów, czyszczenie ataków RM, logika rekordu - jak w poprzedniej odpowiedzi) ... if (this.rollMasterTimerInterval) { LK.clearInterval(this.rollMasterTimerInterval); this.rollMasterTimerInterval = null; } if (this.randomRmattack1Timer) { LK.clearTimeout(this.randomRmattack1Timer); this.randomRmattack1Timer = null; } if (this.rollMasterAttacks && this.rollMasterAttacks.length > 0) { this.rollMasterAttacks.forEach(function (atk) { if (atk.visual && atk.visual.destroy && !atk.visual.destroyed) { if (atk.visual.parent) { atk.visual.parent.removeChild(atk.visual); } atk.visual.destroy(); } }); this.rollMasterAttacks = []; } if (player && typeof player.clearRollTimeouts === 'function') { player.clearRollTimeouts(); } var oldHighScore = parseInt(storage.rollMasterHighScore, 10) || 0; var newRecordRM = false; if (finalTime > oldHighScore) { storage.rollMasterHighScore = finalTime; this.rollMasterHighScore = finalTime; newRecordRM = true; } if (player && player.alpha !== 0) { player.alpha = 0; // Ukryj gracza, ale nie niszcz od razu } var memeAndScoreDisplayDuration = 3000; var deathMemeVisualRM = null; var scoreTextVisualRM = null; var memeAssets = ['deathMeme1', 'deathMeme2', 'deathMeme3', 'deathMeme4', 'deathMeme5']; var randomMemeAssetName = memeAssets[Math.floor(Math.random() * memeAssets.length)]; try { deathMemeVisualRM = LK.getAsset(randomMemeAssetName, { anchorX: 0.5, anchorY: 0.5, alpha: 0, x: 2048 / 2, y: 2732 / 2 - 200, scaleX: 0.8, scaleY: 0.8 }); // Dodaj mema NAD istniejącym tłem (rollMasterBg), jeśli currentSceneElements jest nad nim // lub bezpośrednio do 'game' na odpowiedniej warstwie. // Jeśli currentSceneElements jest używane dla elementów pop-up, to jest dobre miejsce. game.addChild(deathMemeVisualRM); // Lub currentSceneElements.addChild(deathMemeVisualRM); tween(deathMemeVisualRM, { alpha: 1 }, { duration: 500, easing: tween.easeIn }); } catch (e) { console.error("[EndRollMasterMode] Błąd ładowania mema:", e); } var timeMessage = "Your Time: " + String(Math.floor(finalTime / 60)).padStart(2, '0') + ':' + String(finalTime % 60).padStart(2, '0'); if (newRecordRM) { timeMessage += "\n(NEW HIGH SCORE!)"; } else { timeMessage += "\nBest: " + String(Math.floor(this.rollMasterHighScore / 60)).padStart(2, '0') + ':' + String(this.rollMasterHighScore % 60).padStart(2, '0'); } scoreTextVisualRM = new Text2(timeMessage, { size: 70, fill: 0xFFFFFF, align: 'center', stroke: 0x000000, strokeThickness: 4, alpha: 0 }); scoreTextVisualRM.anchor.set(0.5, 0.5); scoreTextVisualRM.x = 2048 / 2; var memeHeightEstimate = deathMemeVisualRM && deathMemeVisualRM.height ? deathMemeVisualRM.height * (deathMemeVisualRM.scaleY || 1) : 560; // 700*0.8 scoreTextVisualRM.y = (deathMemeVisualRM ? deathMemeVisualRM.y + memeHeightEstimate / 2 : 2732 / 2 - 200 + 300) + 100; game.addChild(scoreTextVisualRM); // Lub currentSceneElements.addChild(scoreTextVisualRM); tween(scoreTextVisualRM, { alpha: 1 }, { duration: 500, delay: 300, easing: tween.easeIn }); if (ui && ui.positionElements) { ui.positionElements("rollMasterGameOver"); } if (ui && ui.updateHighScoreDisplay) { ui.updateHighScoreDisplay(this.rollMasterHighScore); } // Pokaż rekord w UI // Usuń stare aktywne przyciski (jeśli jakieś były) if (gameState.activeButtons && gameState.activeButtons.length > 0) { gameState.activeButtons.forEach(function (btn) { if (btn && btn.destroy && !btn.destroyed) { if (btn.parent) { btn.parent.removeChild(btn); } btn.destroy(); } }); } gameState.activeButtons = []; LK.setTimeout(function () { console.log("DEBUG: Timeout #1 (po memie/wyniku) w endRollMasterMode. Pokazywanie przycisków."); if (deathMemeVisualRM && !deathMemeVisualRM.destroyed) { tween(deathMemeVisualRM, { alpha: 0 }, { duration: 300, onFinish: function onFinish() { if (deathMemeVisualRM.parent) { deathMemeVisualRM.parent.removeChild(deathMemeVisualRM); } if (deathMemeVisualRM.destroy) { deathMemeVisualRM.destroy(); } deathMemeVisualRM = null; } }); } if (scoreTextVisualRM && !scoreTextVisualRM.destroyed) { tween(scoreTextVisualRM, { alpha: 0 }, { duration: 300, onFinish: function onFinish() { if (scoreTextVisualRM.parent) { scoreTextVisualRM.parent.removeChild(scoreTextVisualRM); } if (scoreTextVisualRM.destroy) { scoreTextVisualRM.destroy(); } scoreTextVisualRM = null; } }); } // Zniszcz obiekt gracza teraz, gdy już nie jest potrzebny if (player && player.destroy && !player.destroyed) { player.destroy(); player = null; } // TŁO: currentBackground (czyli rollMasterBg) POWINNO POZOSTAĆ. Nie niszczymy go. // NIE wywołuj game.setBackgroundColor, aby nie nadpisać rollMasterBg. // if (currentBackground) { // // Jeśli currentBackground to było coś specyficznego dla mema, a nie rollMasterBg, // // to tutaj trzeba by je usunąć i ewentualnie przywrócić rollMasterBg. // // Ale zakładamy, że currentBackground to rollMasterBg od początku tego trybu. // } // game.setBackgroundColor(0x1a1a1a); // <--- USUWAMY TĘ LINIĘ (lub podobną) var buttonYStart = 900; var buttonSpacing = 220; // Przycisk "Main Menu" var menuButton = new Container(); /* ... reszta definicji bez zmian ... */ menuButton.interactive = true; menuButton.cursor = "pointer"; var menuButtonBg = new Shape({ width: 400, height: 100, color: 0x555555, shape: 'box' }); try { var menuButtonAsset = LK.getAsset('mainmenu', { anchorX: 0.5, anchorY: 0.5 }); menuButton.addChild(menuButtonAsset); } catch (e) { console.error("Błąd ładowania assetu przycisku 'mainmenu':", e); var menuButtonBgFallback = new Shape({ width: 400, height: 100, color: 0x555555, shape: 'box' }); menuButton.addChild(menuButtonBgFallback); // Możesz dodać awaryjny Text2, jeśli chcesz, gdyby asset się nie załadował // var menuButtonTextFallback = new Text2("Main Menu", { size: 40, fill: 0xFFFFFF, anchor: { x: 0.5, y: 0.5 } }); // menuButton.addChild(menuButtonTextFallback); } menuButton.x = 2048 / 2; menuButton.y = buttonYStart; menuButton.down = function () { if (gameState.currentState !== "rollMasterGameOver") { return; } gameState.activeButtons.forEach(function (btn) { if (btn && btn.destroy && !btn.destroyed) { if (btn.parent) { btn.parent.removeChild(btn); } btn.destroy(); } }); gameState.activeButtons = []; // WAŻNE: Zanim przejdziesz do GrillMenu, zniszcz currentBackground (rollMasterBg) if (currentBackground && currentBackground.destroy && !currentBackground.destroyed) { if (currentBackground.parent) { currentBackground.parent.removeChild(currentBackground); } currentBackground.destroy(); currentBackground = null; } gameState.showGrillScreen(); }; game.addChild(menuButton); // Lub currentSceneElements.addChild(menuButton); gameState.activeButtons.push(menuButton); // Przycisk "Restart Mode" var restartButton = new Container(); /* ... reszta definicji bez zmian ... */ try { var restartButtonAsset = LK.getAsset('restartgame', { anchorX: 0.5, anchorY: 0.5 }); restartButton.addChild(restartButtonAsset); } catch (e) { console.error("Błąd ładowania assetu przycisku 'restartgame':", e); var restartButtonBgFallback = new Shape({ width: 400, height: 100, color: 0x4477FF, shape: 'box' }); restartButton.addChild(restartButtonBgFallback); // Możesz dodać awaryjny Text2 // var restartButtonTextFallback = new Text2("Restart Mode", { size: 40, fill: 0xFFFFFF, anchor: { x: 0.5, y: 0.5 } }); // restartButton.addChild(restartButtonTextFallback); } restartButton.x = 2048 / 2; restartButton.y = buttonYStart + buttonSpacing; restartButton.down = function () { if (gameState.currentState !== "rollMasterGameOver") { return; } gameState.activeButtons.forEach(function (btn) { if (btn && btn.destroy && !btn.destroyed) { if (btn.parent) { btn.parent.removeChild(btn); } btn.destroy(); } }); gameState.activeButtons = []; // WAŻNE: Zanim zrestartujesz tryb, zniszcz currentBackground (rollMasterBg) if (currentBackground && currentBackground.destroy && !currentBackground.destroyed) { if (currentBackground.parent) { currentBackground.parent.removeChild(currentBackground); } currentBackground.destroy(); currentBackground = null; } gameState.startRollMasterMode(); }; game.addChild(restartButton); // Lub currentSceneElements.addChild(restartButton); gameState.activeButtons.push(restartButton); }, memeAndScoreDisplayDuration); }, startCursedCrystalMode: function startCursedCrystalMode() { if (this.currentIntroMusicInstance && this.currentIntroMusicInstance.stop) { this.currentIntroMusicInstance.stop(); this.currentIntroMusicInstance = null; } if (this.currentRollSoulsInstance && this.currentRollSoulsInstance.stop) { this.currentRollSoulsInstance.stop(); this.currentRollSoulsInstance = null; } if (this.currentRollMasterMusicInstance && this.currentRollMasterMusicInstance.stop) { this.currentRollMasterMusicInstance.stop(); this.currentRollMasterMusicInstance = null; } if (this.currentCursedCrystalMusicInstance && this.currentCursedCrystalMusicInstance.stop) { this.currentCursedCrystalMusicInstance.stop(); this.currentCursedCrystalMusicInstance = null; } this.currentCursedCrystalMusicInstance = LK.playMusic('CursedCrystal', { loop: true, volume: 0.7 }); if (!this.currentCursedCrystalMusicInstance) { console.error("Nie udało się odtworzyć muzyki Cursed Crystal"); } console.log("[StartCursedCrystal] Rozpoczynanie trybu Cursed Crystal..."); this.currentState = "cursedCrystal"; // 1. Zatrzymaj muzykę z innych trybów if (this.currentIntroMusicInstance && this.currentIntroMusicInstance.stop) { this.currentIntroMusicInstance.stop(); this.currentIntroMusicInstance = null; } if (this.currentRollSoulsInstance && this.currentRollSoulsInstance.stop) { this.currentRollSoulsInstance.stop(); this.currentRollSoulsInstance = null; } if (this.currentRollMasterMusicInstance && this.currentRollMasterMusicInstance.stop) { this.currentRollMasterMusicInstance.stop(); this.currentRollMasterMusicInstance = null; } // TODO: Odtwórz muzykę specyficzną dla Cursed Crystal, jeśli ją masz // np. this.currentCursedCrystalMusic = LK.playMusic('cursedCrystalMusic_asset', { loop: true, volume: 0.7 }); // 2. Wyczyść scenę z elementów poprzedniego trybu if (typeof clearScene === 'function') { clearScene(); // Usuwa elementy z currentSceneElements } else { console.warn("Funkcja clearScene nie jest zdefiniowana globalnie!"); } // Dokładne czyszczenie specyficznych elementów z innych trybów: // Czyszczenie timerów i ataków z Roll Master if (this.rollMasterTimerInterval) { LK.clearInterval(this.rollMasterTimerInterval); this.rollMasterTimerInterval = null; } if (this.randomRmattack1Timer) { // Timer dla rmattack1 w Roll Master LK.clearTimeout(this.randomRmattack1Timer); this.randomRmattack1Timer = null; } if (this.rollMasterAttacks && this.rollMasterAttacks.length > 0) { console.log("[StartCursedCrystal] Czyszczenie " + this.rollMasterAttacks.length + " ataków z Roll Master."); this.rollMasterAttacks.forEach(function (atk) { if (atk.visual && atk.visual.destroy && !atk.visual.destroyed) { if (atk.visual.parent) { // Dodatkowe zabezpieczenie atk.visual.parent.removeChild(atk.visual); } atk.visual.destroy(); } // Jeśli atak miał inne zasoby (np. własne timery), też powinny być tu czyszczone }); this.rollMasterAttacks = []; } // Usuń specyficzne efekty z Grill Menu, jeśli istnieją if (this.grillMenuEffects && this.grillMenuEffects.length > 0) { console.log("[StartCursedCrystal] Czyszczenie " + this.grillMenuEffects.length + " efektów z Grill Menu."); this.grillMenuEffects.forEach(function (effect) { if (effect && effect.destroy && !effect.destroyed) { if (effect.parent) { // Dodatkowe zabezpieczenie effect.parent.removeChild(effect); } effect.destroy(); } }); this.grillMenuEffects = []; } // Usuń globalne obiekty gracza i bossa, jeśli istnieją if (player && player.destroy && !player.destroyed) { // Jeśli gracz ma jakieś specyficzne timery/interwały, które nie są czyszczone w jego .destroy() lub .clearRollTimeouts() // to tutaj byłoby miejsce na ich wyczyszczenie przed zniszczeniem obiektu player. // player.clearRollTimeouts(); // To powinno być wywoływane w Player.die() lub przed destroy() player.destroy(); } player = null; if (boss && boss.destroy && !boss.destroyed) { if (typeof boss.clearAllAttacks === 'function') { boss.clearAllAttacks("startCursedCrystalMode"); // Upewnij się, że ataki bossa są czyszczone } // Podobnie, jeśli boss ma jakieś globalne timery nieczyszczone w .destroy() boss.destroy(); } boss = null; // Usuń istniejące tło, jeśli jest if (currentBackground && currentBackground.destroy) { tween.stop(currentBackground); // Zatrzymaj animacje tła, jeśli były currentBackground.destroy(); currentBackground = null; } // Usuń obiekt Klejnotu, jeśli istniał z poprzedniej sesji tego trybu if (typeof crystalCoreObject !== 'undefined' && crystalCoreObject && crystalCoreObject.destroy && !crystalCoreObject.destroyed) { crystalCoreObject.destroy(); } crystalCoreObject = null; // 3. Wywołaj konfigurację sceny dla Cursed Crystal // Zamiast this.setupCursedCrystalScene_MinimalTest(); wywołaj normalną funkcję: this.setupCursedCrystalScene(); // Aby użyć minimalnej sceny testowej, odkomentuj poniższe i zakomentuj powyższe: // this.setupCursedCrystalScene_MinimalTest(); }, updateCrystalVisual: function updateCrystalVisual() { if (!crystalCoreObject || crystalCoreObject.destroyed || this.isMinibossActiveCC || this.cursedCrystalChargeLevel >= this.cursedCrystalTargetCharge || this.crystalExploding) { return; } var newAssetId = 'crystal_state_0'; var charge = this.cursedCrystalChargeLevel; var targetCharge = this.cursedCrystalTargetCharge; var thresholds = { state1: 0.15 * targetCharge, state2: 0.30 * targetCharge, state3: 0.55 * targetCharge, state4: 0.80 * targetCharge }; if (charge >= thresholds.state4) { newAssetId = 'crystal_state_4'; } else if (charge >= thresholds.state3) { newAssetId = 'crystal_state_3'; } else if (charge >= thresholds.state2) { newAssetId = 'crystal_state_2'; } else if (charge >= thresholds.state1) { newAssetId = 'crystal_state_1'; } if (crystalCoreObject.currentVisual && crystalCoreObject.currentVisual.assetId === newAssetId) { return; } if (crystalCoreObject.currentVisual) { crystalCoreObject.removeChild(crystalCoreObject.currentVisual); if (crystalCoreObject.currentVisual.destroy && !crystalCoreObject.currentVisual.destroyed) { crystalCoreObject.currentVisual.destroy(); } crystalCoreObject.currentVisual = null; } try { var newVisual = LK.getAsset(newAssetId, { anchorX: 0.5, anchorY: 0.5 }); crystalCoreObject.addChild(newVisual); crystalCoreObject.currentVisual = newVisual; crystalCoreObject.currentVisual.assetId = newAssetId; console.log("Wizualizacja kryształu zmieniona na: " + newAssetId + " (naładowanie: " + charge + ")"); } catch (e) { console.error("Błąd ładowania assetu kryształu: " + newAssetId, e); if (newAssetId !== 'crystal_state_0' && (!crystalCoreObject.currentVisual || crystalCoreObject.currentVisual.assetId !== 'crystal_state_0')) { try { var baseVisual = LK.getAsset('crystal_state_0', { anchorX: 0.5, anchorY: 0.5 }); crystalCoreObject.addChild(baseVisual); crystalCoreObject.currentVisual = baseVisual; crystalCoreObject.currentVisual.assetId = 'crystal_state_0'; } catch (e2) { console.error("Błąd ładowania awaryjnego assetu 'crystal_state_0'", e2); } } } }, playCrystalExplosionAnimation: function playCrystalExplosionAnimation() { var crystalOriginalX = 2048 / 2; // Domyślna pozycja X na środek ekranu var crystalOriginalY = 2732 / 2; // Domyślna pozycja Y na środek ekranu if (crystalCoreObject && !crystalCoreObject.destroyed) { // Jeśli crystalCoreObject istnieje, użyj jego aktualnej pozycji crystalOriginalX = crystalCoreObject.x; crystalOriginalY = crystalCoreObject.y; console.log("Odtwarzanie animacji eksplozji kryształu w pozycji: X=" + crystalOriginalX + ", Y=" + crystalOriginalY); // Zatrzymaj istniejące tweeny na kontenerze kryształu (lewitacja, pulsowanie) if (crystalCoreObject.levitationTween && crystalCoreObject.levitationTween.stop) { crystalCoreObject.levitationTween.stop(); crystalCoreObject.levitationTween = null; // Wyczyść referencję } if (crystalCoreObject.scaleTween && crystalCoreObject.scaleTween.stop) { crystalCoreObject.scaleTween.stop(); crystalCoreObject.scaleTween = null; // Wyczyść referencję } // Można dodać reset pozycji/skali, jeśli tweeny mogły je zmienić w sposób niepożądany dla animacji crystalCoreObject.scale.set(1); // Przywróć domyślną skalę kontenera // crystalCoreObject.y = crystalCoreObject.originalY; // Jeśli chcesz przywrócić oryginalną Y przed animacją // Usuń obecną statyczną grafikę kryształu (dziecko kontenera crystalCoreObject) if (crystalCoreObject.currentVisual) { crystalCoreObject.removeChild(crystalCoreObject.currentVisual); if (crystalCoreObject.currentVisual.destroy && !crystalCoreObject.currentVisual.destroyed) { crystalCoreObject.currentVisual.destroy(); } crystalCoreObject.currentVisual = null; } var explosionFramesAssets = []; for (var i = 0; i < 4; i++) { // Załóżmy 4 klatki animacji try { // Upewnij się, że anchorX i anchorY są ustawione, jeśli Twoje klatki tego wymagają // dla poprawnego pozycjonowania wewnątrz SpriteAnimation explosionFramesAssets.push(LK.getAsset('crystal_explosion_f' + i, { anchorX: 0.5, anchorY: 0.5, clone: true })); } catch (e) { console.error("Błąd ładowania klatki eksplozji: crystal_explosion_f" + i, e); // Awaryjny placeholder jeśli klatki brakuje, aby animacja nadal miała odpowiednią długość var placeholderFrame = new Shape({ width: 150, height: 150, color: 0xFF8C00, shape: 'ellipse' }); placeholderFrame.anchor.set(0.5, 0.5); // Ustaw anchor dla Shape explosionFramesAssets.push(placeholderFrame); } } var explosionAnim = new SpriteAnimation({ frames: explosionFramesAssets, frameDuration: 150, // Czas trwania klatki w ms (np. 150ms * 4 klatki = 0.6s) - dostosuj loop: false, anchorX: 0.5, // Anchor samej animacji SpriteAnimation, jeśli ma być inaczej pozycjonowana w crystalCoreObject anchorY: 0.5 // x i y będą 0,0 względem kontenera crystalCoreObject, jeśli animacja ma być na jego środku }); crystalCoreObject.addChild(explosionAnim); // Dodaj animację eksplozji do kontenera kryształu explosionAnim.play(); // TODO: Dodaj tutaj swój dźwięk eksplozji, jeśli masz // np. LK.getSound('twoj_dzwiek_eksplozji_krysztalu').play(); } else { // Ten blok jest awaryjny, jeśli crystalCoreObject nie istnieje, gdy funkcja jest wołana console.warn("Próba eksplozji, ale crystalCoreObject nie istnieje lub został zniszczony przed rozpoczęciem animacji. Bezpośrednie spawnowanie Minibossa w domyślnej pozycji."); this.spawnCursedCrystalMiniboss(crystalOriginalX, crystalOriginalY); this.crystalExploding = false; // Zresetuj flagę return; // Zakończ funkcję, jeśli nie ma kryształu do animowania } var selfGameState = this; // Zachowaj referencję 'this' (gameState) dla callbacku onComplete explosionAnim.onComplete = function () { console.log("Animacja eksplozji kryształu zakończona."); // Zniszcz kontener crystalCoreObject po zakończeniu animacji if (crystalCoreObject && !crystalCoreObject.destroyed) { if (crystalCoreObject.parent) { crystalCoreObject.parent.removeChild(crystalCoreObject); } crystalCoreObject.destroy(); } crystalCoreObject = null; // Wyczyść globalną/dostępną referencję // Wywołaj logikę spawnowania Minibossa, przekazując zapisaną pozycję selfGameState.spawnCursedCrystalMiniboss(crystalOriginalX, crystalOriginalY); selfGameState.crystalExploding = false; // Zresetuj flagę po zakończeniu całego procesu }; }, spawnCursedCrystalMiniboss: function spawnCursedCrystalMiniboss(spawnX, spawnY) { // Argumenty to spawnX i spawnY console.log("MINIBOSS SPAWNING SEQUENCE INITIATED! Pozycja: X=" + spawnX + ", Y=" + spawnY); var self = this; self.isMinibossActiveCC = true; if (ui && ui.crystalChargeBarContainer) { ui.crystalChargeBarContainer.alpha = 0; } var timeSurvivedInMinutes = Math.floor(self.cursedCrystalTimeSurvived / (60 * 60)); var baseMinibossHp = 100; var hpPerMinute = 100; var scaledMaxHp = baseMinibossHp + timeSurvivedInMinutes * hpPerMinute; scaledMaxHp = Math.max(baseMinibossHp, scaledMaxHp); self.cursedCrystalMinibossMaxHP = scaledMaxHp; self.cursedCrystalMinibossHP = scaledMaxHp; self.cursedCrystalEnemies.forEach(function (soul) { if (soul && !soul.isDead) { if (soul.parent) { soul.parent.removeChild(soul); } if (soul.destroy && !soul.destroyed) { soul.destroy(); } } }); self.cursedCrystalEnemies = []; self.cursedCrystalChargeLevel = 0; if (ui && ui.updateCrystalCharge) { ui.updateCrystalCharge(self.cursedCrystalChargeLevel, self.cursedCrystalTargetCharge); } LK.setTimeout(function () { if (self.currentState !== "cursedCrystal" || !self.isMinibossActiveCC) { console.log("Spawn Minibossa anulowany - zmiana stanu gry lub isMinibossActiveCC jest false."); return; } console.log("FAZA SPAWNU MINIBOSSA PO OPÓŹNIENIU"); var minibossOptions = { maxHp: scaledMaxHp, x: spawnX, // ***** POPRAWKA: Używamy argumentu funkcji spawnX ***** y: spawnY // ***** POPRAWKA: Używamy argumentu funkcji spawnY ***** }; self.cursedCrystalMinibossObject = game.addChild(new MinibossCC(minibossOptions)); if (self.cursedCrystalMinibossObject) { self.cursedCrystalMinibossObject.alpha = 0; tween(self.cursedCrystalMinibossObject, { alpha: 1 }, { duration: 1500, easing: tween.easeInQuad, onFinish: function onFinish() { console.log("Miniboss w pełni widoczny."); } }); if (ui && ui.updateMinibossHealthCC) { ui.updateMinibossHealthCC(self.cursedCrystalMinibossHP, self.cursedCrystalMinibossMaxHP, true); } if (ui && ui.showMessage) { ui.showMessage("Demon Pepe is here", 3000); } } else { console.error("Nie udało się stworzyć obiektu MinibossCC!"); } }, 3000); }, setupCursedCrystalScene: function setupCursedCrystalScene() { console.log("[SetupCursedCrystal] Konfiguracja sceny Cursed Crystal..."); // 1. Ustaw tło dla areny try { // Upewnij się, że stary currentBackground jest niszczony, jeśli istnieje if (typeof currentBackground !== 'undefined' && currentBackground && currentBackground.destroy && !currentBackground.destroyed) { currentBackground.destroy(); } currentBackground = LK.getAsset('cursedCrystalArenaBg_asset', { anchorX: 0, // Zgodnie z Twoim kodem anchorY: 0, // Zgodnie z Twoim kodem x: 0, // Zgodnie z Twoim kodem y: 0 // Zgodnie z Twoim kodem }); game.addChildAt(currentBackground, 0); } catch (e) { console.warn("Nie znaleziono assetu tła Cursed Crystal, używam domyślnego koloru.", e); game.setBackgroundColor(0x100510); if (typeof currentBackground !== 'undefined' && currentBackground && currentBackground.destroy && !currentBackground.destroyed) { currentBackground.destroy(); } currentBackground = null; } // 2. Stwórz obiekt Klejnotu Dusz (CrystalCore) jako KONTENER if (typeof crystalCoreObject !== 'undefined' && crystalCoreObject) { if (crystalCoreObject.levitationTween && crystalCoreObject.levitationTween.stop) { crystalCoreObject.levitationTween.stop(); } if (crystalCoreObject.scaleTween && crystalCoreObject.scaleTween.stop) { crystalCoreObject.scaleTween.stop(); } if (crystalCoreObject.destroy && !crystalCoreObject.destroyed) { crystalCoreObject.destroy(); } } crystalCoreObject = new Container(); // *** NOWOŚĆ: crystalCoreObject jest teraz kontenerem *** crystalCoreObject.x = 2048 / 2; crystalCoreObject.y = 2732 / 2; crystalCoreObject.collisionRadius = 60; game.addChild(crystalCoreObject); // Dodaj początkową wizualizację kryształu (0% naładowania) try { var initialCrystalVisual = LK.getAsset('crystal_state_0', { anchorX: 0.5, anchorY: 0.5 }); crystalCoreObject.addChild(initialCrystalVisual); crystalCoreObject.currentVisual = initialCrystalVisual; // Referencja do aktualnej grafiki crystalCoreObject.currentVisual.assetId = 'crystal_state_0'; // Do sprawdzania, co jest wyświetlane } catch (e) { console.error("KRYTYCZNY BŁĄD: Nie można załadować 'crystal_state_0'!", e); // Awaryjny kształt, jeśli assetu nie ma var fallbackVisual = new Shape({ width: 100, height: 100, color: 0x8A2BE2, shape: 'ellipse' }); fallbackVisual.anchor.set(0.5); crystalCoreObject.addChild(fallbackVisual); crystalCoreObject.currentVisual = fallbackVisual; crystalCoreObject.currentVisual.assetId = 'fallback_crystal'; } // Animacje lewitacji i pulsowania dla kontenera crystalCoreObject if (crystalCoreObject && !crystalCoreObject.destroyed) { crystalCoreObject.originalY = crystalCoreObject.y; var levitationHeight = 30; var oneWayDuration = 1250; var levitateCrystalUp = function levitateCrystalUp(targetObject) { if (!targetObject || targetObject.destroyed) { return; } if (targetObject.levitationTween && targetObject.levitationTween.stop) { targetObject.levitationTween.stop(); } targetObject.y = targetObject.originalY; targetObject.levitationTween = tween(targetObject, { y: targetObject.originalY - levitationHeight }, { duration: oneWayDuration, easing: tween.easeInOut, onFinish: function onFinish() { if (targetObject && !targetObject.destroyed) { levitateCrystalDown(targetObject); } } }); }; var levitateCrystalDown = function levitateCrystalDown(targetObject) { if (!targetObject || targetObject.destroyed) { return; } if (targetObject.levitationTween && targetObject.levitationTween.stop) { targetObject.levitationTween.stop(); } targetObject.levitationTween = tween(targetObject, { y: targetObject.originalY }, { duration: oneWayDuration, easing: tween.easeInOut, onFinish: function onFinish() { if (targetObject && !targetObject.destroyed) { levitateCrystalUp(targetObject); } } }); }; levitateCrystalUp(crystalCoreObject); if (crystalCoreObject.scaleTween && crystalCoreObject.scaleTween.stop) { crystalCoreObject.scaleTween.stop(); } crystalCoreObject.baseScaleX = crystalCoreObject.scaleX || 1; // Kontener zwykle ma scale 1 crystalCoreObject.baseScaleY = crystalCoreObject.scaleY || 1; crystalCoreObject.scaleTween = tween(crystalCoreObject, { scaleX: crystalCoreObject.baseScaleX * 1.05, scaleY: crystalCoreObject.baseScaleY * 1.05 }, { duration: oneWayDuration * 2 * 0.75, easing: tween.easeInOut, yoyo: true, repeat: Infinity }); } // 3. Stwórz nowy obiekt gracza (bez zmian w tej sekcji) if (typeof player !== 'undefined' && player && player.destroy && !player.destroyed) { player.destroy(); } player = game.addChild(new Player()); player.health = this.cursedCrystalPlayerMaxHealth; player.x = 2048 / 2; player.y = (crystalCoreObject && crystalCoreObject.y ? crystalCoreObject.y : 2732 / 2) + (crystalCoreObject && crystalCoreObject.currentVisual && crystalCoreObject.currentVisual.height ? crystalCoreObject.currentVisual.height / 2 : 50) + player.height / 2 + 50; // Pozycjonowanie względem środka/dolnej krawędzi kryształu player.dead = false; player.rolling = false; player.invulnerable = false; player.rollCooldown = 0; if (player.clearRollTimeouts) { player.clearRollTimeouts(); } this.currentInputPos.x = player.x; this.currentInputPos.y = player.y; this.isInputActive = false; // 4. Zresetuj zmienne gameState specyficzne dla "Cursed Crystal" (dodajemy crystalExploding) this.cursedCrystalPlayerHealth = this.cursedCrystalPlayerMaxHealth; this.cursedCrystalChargeLevel = 0; this.cursedCrystalSoulsHitCrystal = 0; this.cursedCrystalScore = 0; this.cursedCrystalHighScore = parseInt(storage.cursedCrystalHighScore, 10) || 0; this.cursedCrystalEnemies = []; this.cursedCrystalEnemySpawnTimer = 0; this.cursedCrystalCurrentSpawnInterval = this.cursedCrystalBaseSpawnInterval; this.cursedCrystalDifficultyTimer = 0; this.cursedCrystalEnemyCurrentMaxSpeed = this.cursedCrystalEnemyBaseSpeed; this.cursedCrystalTimeSurvived = 0; this.isMinibossActiveCC = false; this.crystalExploding = false; // *** NOWA FLAGA *** if (this.cursedCrystalMinibossObject && this.cursedCrystalMinibossObject.destroy && !this.cursedCrystalMinibossObject.destroyed) { this.cursedCrystalMinibossObject.destroy(); } this.cursedCrystalMinibossObject = null; this.cursedCrystalMinibossHP = 0; this.cursedCrystalActiveProjectiles = []; this.cursedCrystalActiveExplosions = []; this.cursedCrystalActiveLaserWalls = []; // 5. Konfiguracja UI (bez zmian w tej sekcji) if (ui) { ui.positionElements("cursedCrystal"); if (ui.updateScoreCC) { ui.updateScoreCC(this.cursedCrystalScore); } if (ui.updateHighScoreCC) { ui.updateHighScoreCC(this.cursedCrystalHighScore); } if (ui.updateCrystalCharge) { ui.updateCrystalCharge(this.cursedCrystalChargeLevel, this.cursedCrystalTargetCharge); } if (ui.updateHearts) { ui.updateHearts(this.cursedCrystalPlayerHealth, this.cursedCrystalPlayerMaxHealth); } if (ui.updateMinibossHealthCC) { ui.updateMinibossHealthCC(0, this.cursedCrystalMinibossMaxHP, false); } if (ui.bossHealthBarContainer) { ui.bossHealthBarContainer.alpha = 0; } if (ui.timerText) { ui.timerText.alpha = 0; } if (ui.highScoreText) { ui.highScoreText.alpha = 0; } if (ui.deathsText) { ui.deathsText.alpha = 0; } } // 6. Wyświetl startowy komunikat (bez zmian) if (ui && ui.showMessage) { ui.showMessage("Protect the Soul Gem!", 3000); } console.log("[SetupCursedCrystal] Scena Cursed Crystal skonfigurowana. Gracz HP: " + player.health); } }, "setupCursedCrystalScene", function setupCursedCrystalScene() { // <<< NOWY KOD setupCursedCrystalScene ZACZYNA SIĘ TUTAJ >>> console.log("[SetupCursedCrystal] Konfiguracja sceny Cursed Crystal..."); // 1. Ustaw tło dla areny try { if (typeof currentBackground !== 'undefined' && currentBackground && currentBackground.destroy && !currentBackground.destroyed) { currentBackground.destroy(); } currentBackground = LK.getAsset('cursedCrystalArenaBg_asset', { // Podmień na swój asset tła anchorX: 0, anchorY: 0, x: 0, y: -100 }); game.addChildAt(currentBackground, 0); } catch (e) { console.warn("Nie znaleziono assetu tła Cursed Crystal, używam domyślnego koloru.", e); game.setBackgroundColor(0x100510); if (typeof currentBackground !== 'undefined' && currentBackground && currentBackground.destroy && !currentBackground.destroyed) { currentBackground.destroy(); } currentBackground = null; } // 2. Stwórz obiekt Klejnotu Dusz (CrystalCore) jako KONTENER if (typeof crystalCoreObject !== 'undefined' && crystalCoreObject) { if (crystalCoreObject.levitationTween && crystalCoreObject.levitationTween.stop) { crystalCoreObject.levitationTween.stop(); } if (crystalCoreObject.scaleTween && crystalCoreObject.scaleTween.stop) { crystalCoreObject.scaleTween.stop(); } if (crystalCoreObject.destroy && !crystalCoreObject.destroyed) { crystalCoreObject.destroy(); } } crystalCoreObject = new Container(); // *** NOWOŚĆ: crystalCoreObject jest teraz kontenerem *** crystalCoreObject.x = 2048 / 2; crystalCoreObject.y = 2732 / 2; game.addChild(crystalCoreObject); try { var initialCrystalVisual = LK.getAsset('crystal_state_0', { anchorX: 0.5, anchorY: 0.5 }); crystalCoreObject.addChild(initialCrystalVisual); crystalCoreObject.currentVisual = initialCrystalVisual; crystalCoreObject.currentVisual.assetId = 'crystal_state_0'; } catch (e) { console.error("KRYTYCZNY BŁĄD: Nie można załadować 'crystal_state_0'!", e); var fallbackVisual = new Shape({ width: 100, height: 100, color: 0x8A2BE2, shape: 'ellipse' }); fallbackVisual.anchor.set(0.5); crystalCoreObject.addChild(fallbackVisual); crystalCoreObject.currentVisual = fallbackVisual; crystalCoreObject.currentVisual.assetId = 'fallback_crystal'; } if (crystalCoreObject && !crystalCoreObject.destroyed) { crystalCoreObject.originalY = crystalCoreObject.y; var levitationHeight = 30; var oneWayDuration = 1250; var levitateCrystalUp = function levitateCrystalUp(targetObject) { if (!targetObject || targetObject.destroyed) { return; } if (targetObject.levitationTween && targetObject.levitationTween.stop) { targetObject.levitationTween.stop(); } targetObject.y = targetObject.originalY; targetObject.levitationTween = tween(targetObject, { y: targetObject.originalY - levitationHeight }, { duration: oneWayDuration, easing: tween.easeInOut, onFinish: function onFinish() { if (targetObject && !targetObject.destroyed) { levitateCrystalDown(targetObject); } } }); }; var levitateCrystalDown = function levitateCrystalDown(targetObject) { if (!targetObject || targetObject.destroyed) { return; } if (targetObject.levitationTween && targetObject.levitationTween.stop) { targetObject.levitationTween.stop(); } targetObject.levitationTween = tween(targetObject, { y: targetObject.originalY }, { duration: oneWayDuration, easing: tween.easeInOut, onFinish: function onFinish() { if (targetObject && !targetObject.destroyed) { levitateCrystalUp(targetObject); } } }); }; levitateCrystalUp(crystalCoreObject); if (crystalCoreObject.scaleTween && crystalCoreObject.scaleTween.stop) { crystalCoreObject.scaleTween.stop(); } crystalCoreObject.baseScaleX = crystalCoreObject.scaleX || 1; crystalCoreObject.baseScaleY = crystalCoreObject.scaleY || 1; crystalCoreObject.scaleTween = tween(crystalCoreObject, { scaleX: crystalCoreObject.baseScaleX * 1.05, scaleY: crystalCoreObject.baseScaleY * 1.05 }, { duration: oneWayDuration * 2 * 0.75, easing: tween.easeInOut, yoyo: true, repeat: Infinity }); } if (typeof player !== 'undefined' && player && player.destroy && !player.destroyed) { player.destroy(); } player = game.addChild(new Player()); player.health = this.cursedCrystalPlayerMaxHealth; // Użyj this player.x = 2048 / 2; player.y = (crystalCoreObject && crystalCoreObject.y ? crystalCoreObject.y : 2732 / 2) + (crystalCoreObject && crystalCoreObject.currentVisual && crystalCoreObject.currentVisual.height ? crystalCoreObject.currentVisual.height / 2 : 50) + player.height / 2 + 50; player.dead = false; player.rolling = false; player.invulnerable = false; player.rollCooldown = 0; if (player.clearRollTimeouts) { player.clearRollTimeouts(); } this.currentInputPos.x = player.x; // Użyj this this.currentInputPos.y = player.y; // Użyj this this.isInputActive = false; // Użyj this this.cursedCrystalPlayerHealth = this.cursedCrystalPlayerMaxHealth; // Użyj this this.cursedCrystalChargeLevel = 0; // Użyj this this.cursedCrystalSoulsHitCrystal = 0; // Użyj this this.cursedCrystalScore = 0; // Użyj this this.cursedCrystalHighScore = parseInt(storage.cursedCrystalHighScore, 10) || 0; // Użyj this this.cursedCrystalEnemies = []; // Użyj this this.cursedCrystalEnemySpawnTimer = 0; // Użyj this this.cursedCrystalCurrentSpawnInterval = this.cursedCrystalBaseSpawnInterval; // Użyj this this.cursedCrystalDifficultyTimer = 0; // Użyj this this.cursedCrystalEnemyCurrentMaxSpeed = this.cursedCrystalEnemyBaseSpeed; // Użyj this this.cursedCrystalTimeSurvived = 0; // Użyj this this.isMinibossActiveCC = false; // Użyj this this.crystalExploding = false; // *** NOWA FLAGA, użyj this *** if (this.cursedCrystalMinibossObject && this.cursedCrystalMinibossObject.destroy && !this.cursedCrystalMinibossObject.destroyed) { // Użyj this this.cursedCrystalMinibossObject.destroy(); } this.cursedCrystalMinibossObject = null; // Użyj this this.cursedCrystalMinibossHP = 0; // Użyj this this.cursedCrystalActiveProjectiles = []; // Użyj this this.cursedCrystalActiveExplosions = []; // Użyj this this.cursedCrystalActiveLaserWalls = []; // Użyj this if (ui) { ui.positionElements("cursedCrystal"); if (ui.updateScoreCC) { ui.updateScoreCC(this.cursedCrystalScore); } if (ui.updateHighScoreCC) { ui.updateHighScoreCC(this.cursedCrystalHighScore); } if (ui.updateCrystalCharge) { ui.updateCrystalCharge(this.cursedCrystalChargeLevel, this.cursedCrystalTargetCharge); } if (ui.updateHearts) { ui.updateHearts(this.cursedCrystalPlayerHealth, this.cursedCrystalPlayerMaxHealth); } if (ui.updateMinibossHealthCC) { ui.updateMinibossHealthCC(0, this.cursedCrystalMinibossMaxHP, false); } if (ui.bossHealthBarContainer) { ui.bossHealthBarContainer.alpha = 0; } if (ui.timerText) { ui.timerText.alpha = 0; } if (ui.highScoreText) { ui.highScoreText.alpha = 0; } if (ui.deathsText) { ui.deathsText.alpha = 0; } } if (ui && ui.showMessage) { ui.showMessage("Catch them all!", 3000); } console.log("[SetupCursedCrystal] Scena Cursed Crystal skonfigurowana. Gracz HP: " + player.health); // <<< NOWY KOD setupCursedCrystalScene KOŃCZY SIĘ TUTAJ >>> }), "endCursedCrystalMode", function endCursedCrystalMode(isVictory) { console.log("DEBUG: gameState.endCursedCrystalMode called. Victory: " + isVictory); this.currentState = "cursedCrystalGameOver"; // --- CZYSZCZENIE AKTYWNYCH ELEMENTÓW TRYBU CC --- // 1. Dusze (SoulEnemy) if (this.cursedCrystalEnemies && this.cursedCrystalEnemies.length > 0) { console.log("DEBUG: Clearing " + this.cursedCrystalEnemies.length + " remaining SoulEnemies."); this.cursedCrystalEnemies.forEach(function (enemy) { if (enemy && enemy.destroy && !enemy.destroyed) { if (enemy.parent) { enemy.parent.removeChild(enemy); } enemy.destroy(); } }); } this.cursedCrystalEnemies = []; // 2. Pociski Minibossa CC (jeśli gracz zginął, a boss jeszcze strzelał) if (this.cursedCrystalActiveProjectiles && this.cursedCrystalActiveProjectiles.length > 0) { console.log("DEBUG: Clearing " + this.cursedCrystalActiveProjectiles.length + " remaining projectiles."); this.cursedCrystalActiveProjectiles.forEach(function (proj) { if (proj && proj.destroy && !proj.destroyed) { if (proj.graphics && proj.graphics.parent) { proj.graphics.parent.removeChild(proj.graphics); } if (proj.graphics && proj.graphics.destroy) { proj.graphics.destroy(); } proj.destroy(); } }); } this.cursedCrystalActiveProjectiles = []; // 3. Eksplozje Minibossa CC if (this.cursedCrystalActiveExplosions && this.cursedCrystalActiveExplosions.length > 0) { console.log("DEBUG: Clearing " + this.cursedCrystalActiveExplosions.length + " active explosions."); // Wizualizacje eksplozji są zarządzane przez tweeny i powinny same zniknąć, // ale czyścimy tablicę logiczną. } this.cursedCrystalActiveExplosions = []; // 4. Ściany Laserowe Minibossa CC if (this.cursedCrystalActiveLaserWalls && this.cursedCrystalActiveLaserWalls.length > 0) { console.log("DEBUG: Clearing " + this.cursedCrystalActiveLaserWalls.length + " laser walls."); this.cursedCrystalActiveLaserWalls.forEach(function (wall) { if (wall && wall.segments) { wall.segments.forEach(function (seg) { if (seg.visual && seg.visual.destroy && !seg.visual.destroyed) { if (seg.visual.parent) { seg.visual.parent.removeChild(seg.visual); } seg.visual.destroy(); } }); } if (wall && wall.warningVisuals && wall.warningVisuals.length > 0) { wall.warningVisuals.forEach(function (warnVis) { if (warnVis && warnVis.destroy && !warnVis.destroyed) { if (warnVis.parent) { warnVis.parent.removeChild(warnVis); } warnVis.destroy(); } }); } }); } this.cursedCrystalActiveLaserWalls = []; // 5. Sam Miniboss CC (jeśli gracz zginął, a Miniboss nie został pokonany) // Metoda MinibossCC.die() czyści po sobie, gdy jest pokonany. // Jeśli gracz umiera, obiekt Minibossa może nadal istnieć. if (!isVictory && this.cursedCrystalMinibossObject && !this.cursedCrystalMinibossObject.isDead) { console.log("DEBUG: Player died, clearing active MinibossCC."); if (this.cursedCrystalMinibossObject.parent) { this.cursedCrystalMinibossObject.parent.removeChild(this.cursedCrystalMinibossObject); } if (this.cursedCrystalMinibossObject.destroy) { this.cursedCrystalMinibossObject.destroy(); } } this.cursedCrystalMinibossObject = null; this.isMinibossActiveCC = false; // 6. Obiekt Klejnotu (jeśli był i nie został zniszczony przy spawnie minibossa) if (typeof crystalCoreObject !== 'undefined' && crystalCoreObject && crystalCoreObject.destroy && !crystalCoreObject.destroyed) { if (crystalCoreObject.parent) { crystalCoreObject.parent.removeChild(crystalCoreObject); } crystalCoreObject.destroy(); } crystalCoreObject = null; // 7. Zatrzymaj muzykę Cursed Crystal, jeśli jest // if (this.currentCursedCrystalMusic && this.currentCursedCrystalMusic.stop) { // this.currentCursedCrystalMusic.stop(); // this.currentCursedCrystalMusic = null; // } // Uruchom muzykę menu/grilla (jeśli jest taka potrzeba, np. showGrillScreen to zrobi) // --- LOGIKA WYNIKU I REKORDU --- var finalScore = this.cursedCrystalScore; if (isVictory) { var minutesSurvived = Math.floor(this.cursedCrystalTimeSurvived / (60 * 60)); // Zakładając 60 FPS var bonusPoints = 1000 + minutesSurvived * 100; finalScore += bonusPoints; console.log("DEBUG: Victory! Base score: " + this.cursedCrystalScore + ", Time survived (frames): " + this.cursedCrystalTimeSurvived + " (" + minutesSurvived + " min), Bonus: " + bonusPoints + ", Final score: " + finalScore); } this.cursedCrystalScore = finalScore; // Zaktualizuj wynik w gameState var highScore = parseInt(storage.cursedCrystalHighScore, 10) || 0; var newRecord = false; if (finalScore > highScore) { storage.cursedCrystalHighScore = finalScore; highScore = finalScore; newRecord = true; console.log("DEBUG: New High Score for Cursed Crystal: " + highScore); } if (ui) { ui.titleText.setText(isVictory ? "VICTORY!" : "DEFEAT!"); // Przetłumaczone ui.titleText.style = { size: 120, fill: isVictory ? 0x00FF00 : 0xFF0000, align: 'center', stroke: 0x000000, strokeThickness: 6 }; var message = "Your Score: " + finalScore + "\n"; // Przetłumaczone message += "Best Score: " + highScore; // Przetłumaczone if (newRecord && isVictory) { message += "\n(NEW HIGH SCORE!)"; // Przetłumaczone } else if (newRecord && !isVictory) { message += "\n(New High Score... Pepe sad)"; // Przetłumaczone (z lekką adaptacją) } ui.messageText.setText(message); ui.messageText.style = { size: 70, fill: 0xFFFFFF, align: 'center', stroke: 0x000000, strokeThickness: 4 }; // Usunięcie starych przycisków, jeśli istnieją (bez zmian) if (this.activeButtons && this.activeButtons.length > 0) { this.activeButtons.forEach(function (btn) { if (btn && btn.destroy) { btn.destroy(); } }); } this.activeButtons = []; // Upewnij się, że teksty tytułu i wiadomości są od razu widoczne ui.titleText.alpha = 1; ui.messageText.alpha = 1; // Ustaw pozycje dla titleText i messageText (przeniesione z ui.positionElements dla jasności) // Zakładam, że te pozycje są poprawne dla ekranu końca gry ui.titleText.x = 2048 / 2; ui.titleText.y = 600; ui.titleText.anchor.set(0.5); ui.messageText.x = 2048 / 2; ui.messageText.y = 800; ui.messageText.anchor.set(0.5, 0.5); // Opóźnienie tworzenia i wyświetlania przycisków LK.setTimeout(function () { // Sprawdź, czy nadal jesteśmy w tym samym stanie gry, aby uniknąć dodawania przycisków, jeśli stan się zmienił if (gameState.currentState !== "cursedCrystalGameOver") { return; } // Definiujemy oczekiwane wysokości na początku, aby były dostępne dla obu przycisków var mainMenuButtonExpectedHeight = 100; var restartButtonExpectedHeight = 100; var spacingBetweenButtons = 60; var menuButton = new Container(); menuButton.interactive = true; menuButton.cursor = "pointer"; var menuButtonVisualHeight = mainMenuButtonExpectedHeight; // Użyjemy tej zmiennej try { var menuButtonAsset = LK.getAsset('mainmenu', { anchorX: 0.5, anchorY: 0.5 }); menuButton.addChild(menuButtonAsset); if (menuButtonAsset && typeof menuButtonAsset.height === 'number') { menuButtonVisualHeight = menuButtonAsset.height; // Użyj rzeczywistej wysokości assetu, jeśli dostępna } } catch (e) { console.error("Błąd ładowania assetu przycisku 'mainmenu':", e); var menuButtonBgFallback = new Shape({ width: 400, height: mainMenuButtonExpectedHeight, color: 0x555555, shape: 'box' }); menuButton.addChild(menuButtonBgFallback); } menuButton.x = 2048 / 2; // Poprawiona linia dla menuButton.y: menuButton.y = (ui.messageText.y || 800) + (ui.messageText.height || 70) / 2 + menuButtonVisualHeight / 2 + 80; menuButton.down = function () { if (gameState.currentState === "cursedCrystalGameOver") { gameState.activeButtons.forEach(function (btn) { if (btn && btn.destroy && !btn.destroyed) { if (btn.parent) { btn.parent.removeChild(btn); } btn.destroy(); } }); gameState.activeButtons = []; gameState.showGrillScreen(); } }; currentSceneElements.addChild(menuButton); gameState.activeButtons.push(menuButton); // --- Przycisk "Restart Mode" --- var restartButton = new Container(); restartButton.interactive = true; restartButton.cursor = "pointer"; var restartButtonVisualHeight = restartButtonExpectedHeight; // Użyjemy tej zmiennej try { var restartButtonAsset = LK.getAsset('restartgame', { anchorX: 0.5, anchorY: 0.5 }); restartButton.addChild(restartButtonAsset); if (restartButtonAsset && typeof restartButtonAsset.height === 'number') { restartButtonVisualHeight = restartButtonAsset.height; // Użyj rzeczywistej wysokości assetu } } catch (e) { console.error("Błąd ładowania assetu przycisku 'restartgame':", e); var restartButtonBgFallback = new Shape({ width: 400, height: restartButtonExpectedHeight, color: 0x555555, shape: 'box' }); restartButton.addChild(restartButtonBgFallback); } restartButton.x = 2048 / 2; // Poprawiona linia dla restartButton.y, z większym odstępem: restartButton.y = menuButton.y + menuButtonVisualHeight / 2 + restartButtonVisualHeight / 2 + spacingBetweenButtons; restartButton.down = function () { if (gameState.currentState === "cursedCrystalGameOver") { gameState.activeButtons.forEach(function (btn) { if (btn && btn.destroy && !btn.destroyed) { if (btn.parent) { btn.parent.removeChild(btn); } btn.destroy(); } }); gameState.activeButtons = []; gameState.startCursedCrystalMode(); } }; currentSceneElements.addChild(restartButton); gameState.activeButtons.push(restartButton); // ui.positionElements("cursedCrystalGameOver"); }, 3000); // Opóźnienie 3000 ms (3 sekundy) } }), "processTouchGesture", function processTouchGesture() { // ... (kod obsługi fake tutorial) ... var dx = this.touchEnd.x - this.touchStart.x; var dy = this.touchEnd.y - this.touchStart.y; var distance = Math.sqrt(dx * dx + dy * dy); var swipeThreshold = 50; // Minimalny dystans, by uznać za swipe if (distance < swipeThreshold) { // Logika Tapnięcia (bez zmian) // ... return; } // --- Obsługa Swipe (Turlania) --- if ((this.currentState === "game" || this.currentState === "rollMaster" || this.currentState === "cursedCrystal") && player && !player.dead) { // Normalizuj kierunek (bez zmian) var direction = { x: 0, y: 0 }; if (distance > 0) { direction.x = dx / distance; direction.y = dy / distance; } // <-- NOWA LOGIKA: Obliczanie czasu trwania na podstawie dystansu swipe --> var minSwipeDist = swipeThreshold; // Minimalny dystans swipe'a (np. 50) var maxSwipeDist = 400; // <-- ZWIĘKSZONA dla większej skali var minRollDuration = 50; // <-- LEKKO ZWIĘKSZONA dla płynności minimum var maxRollDuration = 400; // Maksymalny czas uniku w ms (np. 0.45s - dostosuj!) // Ogranicz dystans do zakresu min/max var clampedDistance = Math.max(minSwipeDist, Math.min(distance, maxSwipeDist)); // Znormalizuj dystans do zakresu 0-1 var normalizedDistance = (clampedDistance - minSwipeDist) / (maxSwipeDist - minSwipeDist); // Oblicz czas trwania uniku na podstawie znormalizowanego dystansu var currentRollDuration = minRollDuration + normalizedDistance * (maxRollDuration - minRollDuration); // <-- KONIEC NOWEJ LOGIKI --> // Wywołaj turlanie, przekazując kierunek ORAZ obliczony czas trwania player.roll(direction, currentRollDuration); // Przekazujemy nowy parametr! } }); // --- Obsługa inputu --- game.down = function (x, y, obj) { // Rejestruj początek dotyku/kliknięcia // <-- ZMIANA: Natychmiastowa obsługa kliknięcia w fakeTutorial --> if (gameState.currentState === "fakeTutorial") { // Sprawdźmy dodatkowo, czy timer jeszcze istnieje, na wszelki wypadek if (gameState.fakeTutorialTimerId) { gameState.handleFakeTutorialInput(); // Wywołaj logikę "fałszywej śmierci" return; // Zakończ, nie rób nic więcej w game.down } } // Stany, w których śledzimy gesty lub kliknięcia przycisków: var trackStates = ["title", "game", /* Usunięto "fakeTutorial" */"fakeGameOver", "realTutorial", "grillMenu", "gameOver", "rollMaster", "cursedCrystal"]; if (trackStates.indexOf(gameState.currentState) !== -1) { gameState.touchStart.x = x; gameState.touchStart.y = y; gameState.touchEnd.x = x; gameState.touchEnd.y = y; gameState.isInputActive = true; gameState.currentInputPos.x = x; gameState.currentInputPos.y = y; gameState.swipeStartTime = Date.now(); } // Obsługa kliknięć przycisków jest robiona przez ich własne handlery .down // Note: If you have complex button logic that consumes clicks, // you might need to check if 'obj' is null here before setting isInputActive. // For simple cases, setting it always on 'down' might be fine, // but make sure your player movement check in update respects the game state. }; game.up = function (x, y, obj) { // Rejestruj koniec dotyku/kliknięcia i przetwarzaj gest var trackStates = ["title", "game", "fakeTutorial", "fakeGameOver", "realTutorial", "grillMenu", "gameOver", "rollMaster", "cursedCrystal"]; if (trackStates.indexOf(gameState.currentState) !== -1) { gameState.touchEnd.x = x; gameState.touchEnd.y = y; // Zawsze ustawiaj isInputActive na false przy puszczeniu, aby zatrzymać chodzenie gameState.isInputActive = false; // --- TUTAJ JEST KLUCZOWA ZMIANA --- // Oblicz czas trwania gestu var swipeDuration = Date.now() - gameState.swipeStartTime; // Odczytujemy startTime var holdThreshold = 1000; // Próg w ms - PAMIĘTAJ O DOSTROJENIU! // Wywołaj logikę rolla tylko jeśli gest był krótszy niż próg przytrzymania if (swipeDuration < holdThreshold) { // Gest był szybki (potencjalny swipe/tap) console.log("game.up - GEST KRÓTKI (" + swipeDuration + "ms), przetwarzam..."); // Opcjonalny log gameState.processTouchGesture(); // Wywołujemy tylko tutaj! } else { console.log("game.up - GEST DŁUGI (" + swipeDuration + "ms), IGNORUJĘ rolla"); // Opcjonalny log } // Jeśli gest był długi, nic więcej nie robimy. // --- KONIEC KLUCZOWEJ ZMIANY --- } // Obsługa puszczenia przycisków (.up) może być dodana tutaj, jeśli potrzebna }; game.move = function (x, y, obj) { var trackStates = ["title", "game", "fakeTutorial", "fakeGameOver", "realTutorial", "grillMenu", "gameOver", "rollMaster", "cursedCrystal"]; if (trackStates.indexOf(gameState.currentState) !== -1) { gameState.touchEnd.x = x; gameState.touchEnd.y = y; if (gameState.isInputActive) { gameState.currentInputPos.x = x; gameState.currentInputPos.y = y; } } }; function launchRmattack1() { console.log("Launch rmattack1 (projectile)"); var projectileRadius = 45; var startX, startY; var edge = Math.floor(Math.random() * 4); var gameWidth = 2048; // Szerokość pola gry var gameHeight = 2732; // Wysokość pola gry // Pozycjonowanie startowe (bez zmian) if (edge === 0) { // top startX = Math.random() * gameWidth; startY = -projectileRadius; } else if (edge === 1) { // right startX = gameWidth + projectileRadius; startY = Math.random() * gameHeight; } else if (edge === 2) { // bottom startX = Math.random() * gameWidth; startY = gameHeight + projectileRadius; } else { // left startX = -projectileRadius; startY = Math.random() * gameHeight; } // Celowanie w środek (bez zmian) var targetX = gameWidth / 2; var targetY = gameHeight / 2; var dx = targetX - startX; var dy = targetY - startY; var angle = Math.atan2(dy, dx); // --- ZMIANA: Obliczanie prędkości (vx, vy) zamiast endX, endY --- var speed = 4 + gameState.rollMasterDifficulty * 0.65; // Prędkość pocisku (dostosuj wg potrzeb, może być zależna od trudności) var vx = Math.cos(angle) * speed; var vy = Math.sin(angle) * speed; // --- KONIEC ZMIANY --- var rotation = angle; var scaleX = 1; // Tworzenie animacji (bez zmian) var frames = [LK.getAsset('rmattack1_0', { anchorX: 0.5, anchorY: 0.5 }), LK.getAsset('rmattack1_1', { anchorX: 0.5, anchorY: 0.5 }), LK.getAsset('rmattack1_2', { anchorX: 0.5, anchorY: 0.5 }), LK.getAsset('rmattack1_3', { anchorX: 0.5, anchorY: 0.5 }), LK.getAsset('rmattack1_4', { anchorX: 0.5, anchorY: 0.5 }), LK.getAsset('rmattack1_5', { anchorX: 0.5, anchorY: 0.5 }), LK.getAsset('rmattack1_6', { anchorX: 0.5, anchorY: 0.5 })]; var sprite = new SpriteAnimation({ frames: frames, frameDuration: 120, loop: true, x: startX, y: startY, anchorX: 0.5, anchorY: 0.5 }); sprite.scaleX = scaleX; sprite.rotation = rotation; game.addChild(sprite); // --- ZMIANA: Dodanie do listy ataków zamiast tween --- gameState.rollMasterAttacks.push({ type: 'projectile', visual: sprite, vx: vx, vy: vy, radius: projectileRadius // Promień do sprawdzania kolizji }); // Usunięto tween - ruch będzie obsługiwany w game.update // --- KONIEC ZMIANY --- } // Funkcja launchRmattack2 ZE ZMIANĄ POZYCJONOWANIA function launchRmattack2() { var topSafeMargin = 700; console.log("Launch rmattack2 (explosion)"); var bombWidth = 120; var bombHeight = 120; var halfBombWidth = bombWidth / 2; var halfBombHeight = bombHeight / 2; var minX_b = halfBombWidth; var maxX_b = 2048 - halfBombWidth; var minY_b = halfBombHeight; var maxY_b = 2732 - halfBombHeight; var x = minX_b + Math.random() * (maxX_b - minX_b); var spawnHeight = maxY_b - topSafeMargin; var y = spawnHeight > 0 ? topSafeMargin + Math.random() * spawnHeight : topSafeMargin; var idleFrame = LK.getAsset('rmattack2_explode_0', { anchorX: 0.5, anchorY: 0.5 }); var bomb = new SpriteAnimation({ frames: [idleFrame], frameDuration: 500, loop: true, x: x, y: y, anchorX: 0.5, anchorY: 0.5 }); game.addChild(bomb); bomb.idleTween = tween(bomb, { y: y - 10 }, { duration: 500, yoyo: true, repeat: Infinity, easing: tween.inOutQuad }); LK.setTimeout(function () { if (bomb.destroyed) { return; } try { if (bomb.idleTween && typeof bomb.idleTween.stop === "function") { bomb.idleTween.stop(); } } catch (e) { console.warn("idleTween stop error:", e); } var explosionFrames = []; var finalScale = 0; for (var i = 0; i < 11; i++) { var scale = 1 + i * 0.4; finalScale = scale; var frame = LK.getAsset("rmattack2_explode_" + i, { anchorX: 0.5, anchorY: 0.5, scaleX: scale, scaleY: scale }); explosionFrames.push(frame); } var explosion = new SpriteAnimation({ frames: explosionFrames, frameDuration: 80, loop: false, x: bomb.x, y: bomb.y, anchorX: 0.5, anchorY: 0.5 }); // Nowe: natychmiastowe niszczenie po zakończeniu animacji explosion.onComplete = function () { var index = gameState.rollMasterAttacks.indexOf(explosionData); if (index > -1) { gameState.rollMasterAttacks.splice(index, 1); console.log("Usunięto eksplozję z listy przez onComplete."); } if (!explosion.destroyed) { explosion.destroy(); } }; bomb.destroy(); game.addChild(explosion); var explosionDurationFrames = explosionFrames.length * (80 / (1000 / 60)); var explosionMaxRadius = bombWidth * finalScale / 2 * 0.8; var explosionData = { type: 'explosion', visual: explosion, radius: 0, maxRadius: explosionMaxRadius, currentFrame: 0, totalFrames: explosionFrames.length, frameDurationTicks: 80 / (1000 / 60), timer: 0, damageDealt: false }; gameState.rollMasterAttacks.push(explosionData); console.log("Dodano eksplozję do śledzenia, maxRadius:", explosionMaxRadius); }, 2000); } // Funkcja launchRmattack3 (bez zmian, już miała bezpieczne pozycjonowanie i pulsowanie) function launchRmattack3() { var topSafeMargin = 600; console.log("Launch rmattack3 (laser)"); // Bezpieczne pozycjonowanie (bez zmian) var margin = 100; var halfLaserWidth = 128 / 2; var halfLaserHeight = 1012 / 2; // [cite: 95, 96] var minX = margin + halfLaserWidth; var maxX = 2048 - margin - halfLaserWidth; // [cite: 96, 97] var calculatedMinY = margin + halfLaserHeight; // Oryginalne obliczenie minY var minY = Math.max(calculatedMinY, topSafeMargin); // Użyj większej wartości: marginesu lub safe zone var maxY = 2732 - margin - halfLaserHeight; // Dolna granica // var laserY = minY + Math.random() * (maxY - minY); // <-- ZAKOMENTUJ LUB USUŃ STARĄ LINIĘ var spawnHeight = maxY - minY; // Oblicz dostępną wysokość var laserY = spawnHeight > 0 ? minY + Math.random() * spawnHeight : minY; // Losuj Y w bezpiecznej strefie // --- KONIEC ZMIAN --- var laserX = minX + Math.random() * (maxX - minX); // Animacja ostrzeżenia (bez zmian) var warningFrames = []; // [cite: 99] for (var i = 0; i < 5; i++) { warningFrames.push(LK.getAsset('rmattack3_warning_' + i, { anchorX: 0.5, anchorY: 0.5, width: 128, height: 1012 })); } // [cite: 100] var warningAnim = new SpriteAnimation({ frames: warningFrames, frameDuration: 100, loop: true, x: laserX, y: laserY, anchorX: 0.5, anchorY: 0.5, alpha: 1.0 }); // [cite: 101] warningAnim.stopPulsing = false; var currentPulseTween = null; // [cite: 101] game.addChild(warningAnim); // [cite: 102] var pulseDuration = 250; // [cite: 102] function pulseUp() { if (!warningAnim || warningAnim.destroyed || warningAnim.stopPulsing) { currentPulseTween = null; return; } currentPulseTween = tween(warningAnim, { alpha: 1.0 }, { duration: pulseDuration, easing: tween.inOutQuad, onFinish: function onFinish() { if (warningAnim && !warningAnim.destroyed && !warningAnim.stopPulsing) { pulseDown(); } else { currentPulseTween = null; } } }); } // [cite: 102, 103, 104] function pulseDown() { if (!warningAnim || warningAnim.destroyed || warningAnim.stopPulsing) { currentPulseTween = null; return; } currentPulseTween = tween(warningAnim, { alpha: 0.1 }, { duration: pulseDuration, easing: tween.inOutQuad, onFinish: function onFinish() { if (warningAnim && !warningAnim.destroyed && !warningAnim.stopPulsing) { pulseUp(); } else { currentPulseTween = null; } } }); } // [cite: 104, 105] pulseDown(); // [cite: 106] // Timer aktywacji lasera LK.setTimeout(function () { // [cite: 106] if (warningAnim && !warningAnim.destroyed) { warningAnim.stopPulsing = true; } // [cite: 106] if (currentPulseTween && typeof currentPulseTween.stop === "function") { try { currentPulseTween.stop(); currentPulseTween = null; } catch (e) { console.warn("Nie udało się zatrzymać tweenu pulsowania:", e); } } // [cite: 106] if (warningAnim && !warningAnim.destroyed) { // Jeśli ostrzeżenie nadal istnieje // Tworzenie klatek ataku (bez zmian) var activeFrames = []; // [cite: 106] var laserStrikeWidth = 228; // Szerokość kolizji var laserStrikeHeight = 1212; // Wysokość kolizji for (var j = 0; j < 5; j++) { activeFrames.push(LK.getAsset('rmattack3_strike_' + j, { anchorX: 0.5, anchorY: 0.5, width: laserStrikeWidth, height: laserStrikeHeight })); } // [cite: 106] // Tworzenie animacji ataku (bez zmian) var strikeAnim = new SpriteAnimation({ // [cite: 106] frames: activeFrames, frameDuration: 40, loop: true, x: laserX, y: laserY, anchorX: 0.5, anchorY: 0.5, alpha: 1 // [cite: 106] }); game.addChild(strikeAnim); // [cite: 106] var laserDuration = 4000; // Czas życia lasera w ms // --- ZMIANA: Dodanie lasera do listy śledzonych ataków --- var laserData = { type: 'laser', visual: strikeAnim, // Referencja do animacji uderzenia // Przekazujemy wymiary potrzebne do kolizji AABB w game.update width: laserStrikeWidth, height: laserStrikeHeight, lifeTime: laserDuration / (1000 / 60) // Czas życia w klatkach gry }; gameState.rollMasterAttacks.push(laserData); console.log("Dodano laser do śledzenia."); // --- KONIEC ZMIANY --- warningAnim.destroy(); // Usuń ostrzeżenie [cite: 106] // Timer usuwania lasera LK.setTimeout(function () { // [cite: 106] // Znajdź i usuń z listy, jeśli nadal tam jest var index = gameState.rollMasterAttacks.indexOf(laserData); if (index > -1) { gameState.rollMasterAttacks.splice(index, 1); console.log("Usunięto laser z listy po czasie."); } if (strikeAnim && !strikeAnim.destroyed) { strikeAnim.destroy(); } // [cite: 106] }, laserDuration); // [cite: 106] } }, 3000); // Czas trwania ostrzeżenia [cite: 107] } // Funkcja launchRmattack4 POPRAWIONA (tylko tworzenie, z bezpiecznym pozycjonowaniem 200px margin) function launchRmattack4() { // Obliczanie bezpiecznej strefy dla spreadera z marginesem 200px var spreaderParentRadius = 60 * 1.2; var margin = 200; var startX = margin + Math.random() * (2048 - 2 * margin); var startY = margin + Math.random() * (2732 - 2 * margin); // Klatki animacji dla Spreader Parent var spreaderParentFrames = [LK.getAsset('rmattack4_parent_0', { anchorX: 0.5, anchorY: 0.5 }), LK.getAsset('rmattack4_parent_1', { anchorX: 0.5, anchorY: 0.5 }), LK.getAsset('rmattack4_parent_2', { anchorX: 0.5, anchorY: 0.5 }), LK.getAsset('rmattack4_parent_3', { anchorX: 0.5, anchorY: 0.5 })]; // Stwórz obiekt SpriteAnimation dla rodzica var spreaderParentAnim = new SpriteAnimation({ frames: spreaderParentFrames, frameDuration: 250, loop: true, x: startX, y: startY, anchorX: 0.5, anchorY: 0.5 }); // Dodaj animację rodzica do sceny gry game.addChild(spreaderParentAnim); // Dodaj obiekt logiczny ataku do listy gameState gameState.rollMasterAttacks.push({ type: 'spreader_parent', visual: spreaderParentAnim, radius: spreaderParentRadius, timer: gameState.spreaderSplitTime }); } function spawnCursedSoul() { // 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 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) { var dx_soul = player.x - enemy.x; var dy_soul = player.y - enemy.y; var distance_soul = Math.sqrt(dx_soul * dx_soul + dy_soul * dy_soul); var playerRollRadius_soul = (player.width || 150) / 2 * 0.7; var soulRadius_coll = (enemy.width || 30) / 2 * 0.8; if (distance_soul < playerRollRadius_soul + soulRadius_coll) { enemy.takeDamage(1); } } } if (gameState.currentState === "cursedCrystal" && !gameState.isMinibossActiveCC && gameState.cursedCrystalChargeLevel >= gameState.cursedCrystalTargetCharge && !gameState.crystalExploding) { gameState.crystalExploding = true; // Ustaw flagę, że proces eksplozji/spawnu się rozpoczął // Zdefiniuj współrzędne spawnu minibossa na podstawie crystalCoreObject lub domyślnych var tempCrystalSpawnX, tempCrystalSpawnY; if (typeof crystalCoreObject !== 'undefined' && crystalCoreObject && !crystalCoreObject.destroyed) { tempCrystalSpawnX = crystalCoreObject.x; tempCrystalSpawnY = crystalCoreObject.y; } else { tempCrystalSpawnX = 2048 / 2; // Domyślna pozycja X na środek ekranu tempCrystalSpawnY = 2732 / 2; // Domyślna pozycja Y na środek ekranu console.warn("crystalCoreObject nie istnieje podczas próby spawnu minibossa, używam domyślnych współrzędnych."); } if (typeof gameState.playCrystalExplosionAnimation === 'function') { // Ta funkcja powinna teraz poprawnie używać wewnętrznych crystalOriginalX/Y // i przekazywać je do spawnCursedCrystalMiniboss gameState.playCrystalExplosionAnimation(); } else { // Awaryjne spawnowanie, jeśli playCrystalExplosionAnimation nie istnieje console.error("gameState.playCrystalExplosionAnimation is not defined! Spawning miniboss directly."); // Przekaż zdefiniowane wyżej współrzędne gameState.spawnCursedCrystalMiniboss(tempCrystalSpawnX, tempCrystalSpawnY); gameState.crystalExploding = false; // Resetuj flagę, jeśli spawn był bezpośredni i synchroniczny } // UWAGA: Poniższy blok kodu, który oryginalnie tworzył minibossOptions i spawnował minibossa // w tym miejscu, został usunięty. Logika ta jest teraz w całości obsługiwana przez // gameState.playCrystalExplosionAnimation() -> gameState.spawnCursedCrystalMiniboss() // lub przez awaryjne, bezpośrednie wywołanie gameState.spawnCursedCrystalMiniboss(tempCrystalSpawnX, tempCrystalSpawnY) powyżej. // To eliminuje redundancję i błąd ReferenceError. } // Koniec if'a spawującego Minibossa // Aktualizacja Minibossa CC, jeśli jest aktywny if (gameState.isMinibossActiveCC && gameState.cursedCrystalMinibossObject && !gameState.cursedCrystalMinibossObject.isDead) { gameState.cursedCrystalMinibossObject.update(); } if (gameState.cursedCrystalMinibossObject && gameState.cursedCrystalMinibossObject.activeClones) { for (var cIdx = gameState.cursedCrystalMinibossObject.activeClones.length - 1; cIdx >= 0; cIdx--) { var cloneInstance = gameState.cursedCrystalMinibossObject.activeClones[cIdx]; if (cloneInstance.isDead) { // Już obsłużone w cloneInstance.die(), ale dla pewności gameState.cursedCrystalMinibossObject.activeClones.splice(cIdx, 1); continue; } cloneInstance.update(); // Kolizja gracza (roll) z klonem if (player && player.rolling && !cloneInstance.isDead) { var dx_clone_roll = player.x - cloneInstance.x; var dy_clone_roll = player.y - cloneInstance.y; var distance_clone_roll = Math.sqrt(dx_clone_roll * dx_clone_roll + dy_clone_roll * dy_clone_roll); var playerRollRadius_clone = (player.width || 150) / 2 * 0.7; var cloneRadius_coll = (cloneInstance.width || 120) / 2 * 0.9; if (distance_clone_roll < playerRollRadius_clone + cloneRadius_coll) { cloneInstance.takeDamage(1); // Klon ginie po jednym trafieniu rollem } } } } // --- Aktualizacja Pocisków Minibossa --- if (typeof gameState.cursedCrystalActiveProjectiles === 'undefined' || !gameState.cursedCrystalActiveProjectiles) { gameState.cursedCrystalActiveProjectiles = []; } for (var k = gameState.cursedCrystalActiveProjectiles.length - 1; k >= 0; k--) { var projectile = gameState.cursedCrystalActiveProjectiles[k]; if (!projectile || projectile.isDead) { if (projectile && projectile.graphics && projectile.graphics.parent) { projectile.graphics.parent.removeChild(projectile.graphics); if (projectile.graphics.destroy) { projectile.graphics.destroy(); } } gameState.cursedCrystalActiveProjectiles.splice(k, 1); continue; } projectile.update(); if (projectile.isDead) { if (projectile.graphics && projectile.graphics.parent) { projectile.graphics.parent.removeChild(projectile.graphics); if (projectile.graphics.destroy) { projectile.graphics.destroy(); } } gameState.cursedCrystalActiveProjectiles.splice(k, 1); } } // --- Sprawdzanie Obrażeń od Eksplozji Minibossa --- if (typeof gameState.cursedCrystalActiveExplosions === 'undefined' || !gameState.cursedCrystalActiveExplosions) { gameState.cursedCrystalActiveExplosions = []; } for (var expIdx = gameState.cursedCrystalActiveExplosions.length - 1; expIdx >= 0; expIdx--) { var explosion = gameState.cursedCrystalActiveExplosions[expIdx]; if (!explosion) { gameState.cursedCrystalActiveExplosions.splice(expIdx, 1); continue; } explosion.durationTimer--; if (player && !player.dead && !player.invulnerable && !explosion.hitPlayerThisFrame) { var distToExplosion = Math.sqrt(Math.pow(player.x - explosion.x, 2) + Math.pow(player.y - explosion.y, 2)); if (distToExplosion < (player.width || 150) / 2 * 0.7 + explosion.radius) { player.takeDamage(explosion.damage); explosion.hitPlayerThisFrame = true; } } if (explosion.durationTimer <= 0) { gameState.cursedCrystalActiveExplosions.splice(expIdx, 1); } } // --- LOGIKA DLA AKTYWNYCH ŚCIAN LASEROWYCH MINIBOSSA --- if (typeof gameState.cursedCrystalActiveLaserWalls === 'undefined' || !gameState.cursedCrystalActiveLaserWalls) { gameState.cursedCrystalActiveLaserWalls = []; } if (gameState.cursedCrystalActiveLaserWalls && gameState.cursedCrystalActiveLaserWalls.length > 0) { for (var wallIdx = gameState.cursedCrystalActiveLaserWalls.length - 1; wallIdx >= 0; wallIdx--) { var wall = gameState.cursedCrystalActiveLaserWalls[wallIdx]; if (!wall || wall.isDead) { if (wall && wall.segments) { wall.segments.forEach(function (seg) { if (seg.isPlaceholder) { return; } if (seg.visual && seg.visual.parent) { seg.visual.parent.removeChild(seg.visual); if (seg.visual.destroy && !seg.visual.destroyed) { seg.visual.destroy(); } } }); } gameState.cursedCrystalActiveLaserWalls.splice(wallIdx, 1); continue; } if (gameState.cursedCrystalMinibossObject && !gameState.cursedCrystalMinibossObject.isDead) { wall.pivotX = gameState.cursedCrystalMinibossObject.x; wall.pivotY = gameState.cursedCrystalMinibossObject.y; } if (wall.warningTimer > 0) { wall.warningTimer -= 1000 / 60; wall.segments.forEach(function (segment) { if (segment.isPlaceholder) { return; } var initialAngleForStaticDisplay = 0; var rotatedOffsetX = segment.initialOffsetX * Math.cos(initialAngleForStaticDisplay) - segment.initialOffsetY * Math.sin(initialAngleForStaticDisplay); var rotatedOffsetY = segment.initialOffsetX * Math.sin(initialAngleForStaticDisplay) + segment.initialOffsetY * Math.cos(initialAngleForStaticDisplay); segment.currentX = wall.pivotX + rotatedOffsetX; segment.currentY = wall.pivotY + rotatedOffsetY; if (segment.visual && !segment.visual.destroyed) { segment.visual.x = segment.currentX; segment.visual.y = segment.currentY; segment.visual.rotation = initialAngleForStaticDisplay; } }); } else { if (wall.activeTimer > 0) { wall.activeTimer -= 1000 / 60; wall.currentAngle += wall.rotationSpeed; wall.segments.forEach(function (segment) { if (segment.isPlaceholder) { return; } var rotatedOffsetX = segment.initialOffsetX * Math.cos(wall.currentAngle) - segment.initialOffsetY * Math.sin(wall.currentAngle); var rotatedOffsetY = segment.initialOffsetX * Math.sin(wall.currentAngle) + segment.initialOffsetY * Math.cos(wall.currentAngle); segment.currentX = wall.pivotX + rotatedOffsetX; segment.currentY = wall.pivotY + rotatedOffsetY; if (segment.visual && !segment.visual.destroyed) { segment.visual.x = segment.currentX; segment.visual.y = segment.currentY; var baseRotation = wall.currentAngle; if (segment.animationPhase === 'action_once' || segment.animationPhase === 'looping_cut') { if (segment.currentIndividualSpinAngle === undefined) { segment.currentIndividualSpinAngle = 0; } var RAPID_SPIN_SPEED = 0.4; segment.currentIndividualSpinAngle += RAPID_SPIN_SPEED; segment.visual.rotation = baseRotation + segment.currentIndividualSpinAngle; } else { segment.visual.rotation = baseRotation; } if (player && !player.dead && !player.invulnerable) { var playerHalfWidth_lw = (player.width || 150) / 2 * 0.7; var playerHalfHeight_lw = (player.height || 150) / 2 * 0.7; var segmentHalfWidth_lw = (segment.width || 120) / 2; var segmentHalfHeight_lw = (segment.height || 120) / 2; if (Math.abs(player.x - segment.currentX) < playerHalfWidth_lw + segmentHalfWidth_lw && Math.abs(player.y - segment.currentY) < playerHalfHeight_lw + segmentHalfHeight_lw) { player.takeDamage(1); } } } }); if (wall.activeTimer <= 0) { wall.isDead = true; } } else { wall.isDead = true; } } } } if (player && player.dead) {/* Obsługiwane przez player.die() */} } }; if (typeof gameState !== 'undefined' && typeof gameState.init === 'function') { gameState.init(); } else { console.error("Obiekt gameState lub gameState.init nie jest zdefiniowany przed wywołaniem init!"); }
===================================================================
--- original.js
+++ change.js
@@ -3462,22 +3462,18 @@
self.ccScoreText.alpha = 0;
}
if (self.ccHighScoreText) {
self.ccHighScoreText.alpha = 0;
- } // <--- BARDZO WAŻNE
+ }
if (self.crystalChargeBarContainer) {
self.crystalChargeBarContainer.alpha = 0;
}
// Ukryj elementy aktywnej gry Roll Master (timer na żywo)
if (self.timerText) {
self.timerText.alpha = 0;
}
- // Rekord Roll Master (highScoreText) może być pokazywany przez gameState.endRollMasterMode
- // jako część nowego obiektu Text2 z finalnym komunikatem.
- // Jeśli chcesz, żeby dedykowany self.highScoreText był tu widoczny, ustaw mu alpha = 1 i pozycję.
- // W przeciwnym razie, jeśli jest częścią messageText, to tu powinien mieć alpha = 0.
if (self.highScoreText) {
- self.highScoreText.alpha = 0; // Zakładając, że wynik i rekord są w nowym Text2 w endRollMasterMode
+ self.highScoreText.alpha = 0;
}
// Ukryj inne standardowe elementy gry
if (self.heartContainer) {
self.heartContainer.alpha = 0;
@@ -3495,15 +3491,25 @@
self.titleText.alpha = 0;
}
break;
case "cursedCrystal":
- case "cursedCrystal":
+ // TYLKO JEDNO WYSTĄPIENIE TEGO CASE
if (self.timerText) {
self.timerText.alpha = 0;
}
if (self.highScoreText) {
+ // To jest highScoreText dla Roll Master, więc ukrywamy
self.highScoreText.alpha = 0;
}
+ if (self.deathsText) {
+ // Licznik śmierci też ukrywamy w tym trybie
+ self.deathsText.alpha = 0;
+ }
+ if (self.tutorialText) {
+ // Tutorial też ukrywamy
+ self.tutorialText.alpha = 0;
+ }
+ // --- Elementy specyficzne dla Cursed Crystal ---
if (self.ccScoreText) {
self.ccScoreText.alpha = 1;
self.ccScoreText.x = 50;
self.ccScoreText.y = 20;
@@ -3514,89 +3520,63 @@
self.ccHighScoreText.x = 2048 - 50;
self.ccHighScoreText.y = 20;
self.ccHighScoreText.anchor.set(1, 0);
}
+ // --- Serca Gracza ---
if (self.heartContainer) {
- self.heartContainer.alpha = 1;
+ self.heartContainer.alpha = 1; // UPEWNIJ SIĘ, ŻE SERCA SĄ WIDOCZNE
var ccMaxHearts = typeof gameState !== 'undefined' && gameState.cursedCrystalPlayerMaxHealth ? gameState.cursedCrystalPlayerMaxHealth : 10;
- // Wyśrodkowanie serc - BEZ ZMIAN
- var heartsWidth = ccMaxHearts * 50; // Szerokość zajmowana przez serca (zakładając 50px na serce włącznie z odstępem)
- self.heartContainer.x = (2048 - heartsWidth) / 2 + 25; // 25 to połowa szerokości serca, aby wyrównać od lewej krawędzi pierwszego serca
- self.heartContainer.y = 70;
+ var heartsWidth = ccMaxHearts * 50;
+ self.heartContainer.x = (2048 - heartsWidth) / 2 + 25;
+ self.heartContainer.y = 70; // Stała pozycja Y dla serc
}
+ // --- Pasek Ładowania Kryształu ---
if (self.crystalChargeBarContainer && self.crystalChargeBarBg) {
- // Upewnij się, że crystalChargeBarBg istnieje
- // Sprawdź, czy miniboss jest aktywny
if (typeof gameState !== 'undefined' && gameState.isMinibossActiveCC) {
- self.crystalChargeBarContainer.alpha = 0; // Ukryj pasek ładowania, gdy boss jest aktywny
+ self.crystalChargeBarContainer.alpha = 0;
} else {
- self.crystalChargeBarContainer.alpha = 1; // Pokaż pasek ładowania
- // Wyśrodkowanie paska ładowania pod sercami
- var chargeBarWidth = self.crystalChargeBarBg.width || 400; // Pobierz szerokość tła paska
- self.crystalChargeBarContainer.anchorX = 0.5; // Ustaw anchor kontenera na jego środek
- self.crystalChargeBarContainer.x = 2048 / 2; // Ustaw środek kontenera na środek ekranu
- // Pozycja Y - pod sercami + mały odstęp
+ self.crystalChargeBarContainer.alpha = 1;
+ var chargeBarWidth = self.crystalChargeBarBg.width || 400;
+ self.crystalChargeBarContainer.anchorX = 0.5;
+ self.crystalChargeBarContainer.x = 2048 / 2;
self.crystalChargeBarContainer.y = (self.heartContainer ? self.heartContainer.y + (self.heartContainer.height || 40) : 70 + 40) + 50;
}
}
+ // --- Pasek HP Minibossa ---
if (self.bossHealthBarContainer) {
- // Pasek HP Minibossa CC - widoczność zarządzana przez updateMinibossHealthCC
- // Ustawiamy tylko pozycję Y, jeśli jest inna niż domyślna dla tego trybu
- self.bossHealthBarContainer.y = self.crystalChargeBarContainer && self.crystalChargeBarContainer.alpha === 1 ? self.crystalChargeBarContainer.y + (self.crystalChargeBarContainer.height || 25) + 20 : (self.heartContainer ? self.heartContainer.y + (self.heartContainer.height || 40) : 70 + 40) + 20;
+ var heartsBottomY = self.heartContainer ? self.heartContainer.y + (self.heartContainer.height || 40) : 70 + 40;
+ // Poprzednio było: heartsBottomY + 20 (ok. 130), potem heartsBottomY + 70 (ok. 180)
+ // Aby przesunąć o kolejne 50px w dół od Y=180, potrzebujemy Y = 230
+ // Więc offset od heartsBottomY (ok. 110) musi być 120.
+ var desiredOffsetYBelowHearts = 120;
+ self.bossHealthBarContainer.y = heartsBottomY + desiredOffsetYBelowHearts;
+ // Widoczność paska HP bossa jest zarządzana przez updateMinibossHealthCC,
+ // więc tutaj tylko pozycjonujemy.
}
- if (self.deathsText) {
- self.deathsText.alpha = 0;
- }
break;
case "cursedCrystalGameOver":
- //
- // Ukryj elementy rozgrywki CC
- if (self.ccScoreText) {
- self.ccScoreText.alpha = 0;
- } //
- if (self.ccHighScoreText) {
- self.ccHighScoreText.alpha = 0;
- } //
- if (self.crystalChargeBarContainer) {
- self.crystalChargeBarContainer.alpha = 0;
- } //
- if (self.heartContainer) {
- self.heartContainer.alpha = 0;
- } //
- // Ukryj również elementy RM i inne ogólne na wszelki wypadek
+ // Ukryj elementy rozgrywki CC (oprócz score i highscore, które mogą być częścią ekranu końca)
if (self.timerText) {
self.timerText.alpha = 0;
- } //
+ }
if (self.highScoreText) {
self.highScoreText.alpha = 0;
- } //
+ } // highScoreText dla RollMaster
+ if (self.crystalChargeBarContainer) {
+ self.crystalChargeBarContainer.alpha = 0;
+ }
+ if (self.heartContainer) {
+ self.heartContainer.alpha = 0;
+ } // Ukryj serca gracza na ekranie końca gry CC
if (self.bossHealthBarContainer) {
self.bossHealthBarContainer.alpha = 0;
- } //
+ }
if (self.deathsText) {
self.deathsText.alpha = 0;
- } //
+ }
if (self.tutorialText) {
self.tutorialText.alpha = 0;
- } //
- // Pokaż elementy ekranu końca gry CC (tytuł, wiadomość)
- // - ich alpha, tekst, pozycje są ustawiane w gameState.endCursedCrystalMode
- if (self.titleText) {
- //
- self.titleText.alpha = 1; //
- // Pozycje ustawione w endCursedCrystalMode, tutaj można potwierdzić lub dostosować
- self.titleText.x = 2048 / 2; //
- self.titleText.y = 600; //
- self.titleText.anchor.set(0.5); //
}
- if (self.messageText) {
- //
- self.messageText.alpha = 1; //
- self.messageText.x = 2048 / 2; //
- self.messageText.y = 800; //
- self.messageText.anchor.set(0.5, 0.5); //
- }
- // Pozycjonowanie przycisków jest obsługiwane w gameState.endCursedCrystalMode
break;
}
}; // Koniec positionElements
return self;
@@ -5949,9 +5929,9 @@
// // Ale zakładamy, że currentBackground to rollMasterBg od początku tego trybu.
// }
// game.setBackgroundColor(0x1a1a1a); // <--- USUWAMY TĘ LINIĘ (lub podobną)
var buttonYStart = 900;
- var buttonSpacing = 120;
+ var buttonSpacing = 220;
// Przycisk "Main Menu"
var menuButton = new Container(); /* ... reszta definicji bez zmian ... */
menuButton.interactive = true;
menuButton.cursor = "pointer";
@@ -5960,15 +5940,27 @@
height: 100,
color: 0x555555,
shape: 'box'
});
- var menuButtonText = new Text2("Main Menu", {
- size: 40,
- fill: 0xFFFFFF
- });
- menuButtonText.anchor.set(0.5);
- menuButton.addChild(menuButtonBg);
- menuButton.addChild(menuButtonText);
+ try {
+ var menuButtonAsset = LK.getAsset('mainmenu', {
+ anchorX: 0.5,
+ anchorY: 0.5
+ });
+ menuButton.addChild(menuButtonAsset);
+ } catch (e) {
+ console.error("Błąd ładowania assetu przycisku 'mainmenu':", e);
+ var menuButtonBgFallback = new Shape({
+ width: 400,
+ height: 100,
+ color: 0x555555,
+ shape: 'box'
+ });
+ menuButton.addChild(menuButtonBgFallback);
+ // Możesz dodać awaryjny Text2, jeśli chcesz, gdyby asset się nie załadował
+ // var menuButtonTextFallback = new Text2("Main Menu", { size: 40, fill: 0xFFFFFF, anchor: { x: 0.5, y: 0.5 } });
+ // menuButton.addChild(menuButtonTextFallback);
+ }
menuButton.x = 2048 / 2;
menuButton.y = buttonYStart;
menuButton.down = function () {
if (gameState.currentState !== "rollMasterGameOver") {
@@ -5996,23 +5988,27 @@
game.addChild(menuButton); // Lub currentSceneElements.addChild(menuButton);
gameState.activeButtons.push(menuButton);
// Przycisk "Restart Mode"
var restartButton = new Container(); /* ... reszta definicji bez zmian ... */
- restartButton.interactive = true;
- restartButton.cursor = "pointer";
- var restartButtonBg = new Shape({
- width: 400,
- height: 100,
- color: 0x4477FF,
- shape: 'box'
- });
- var restartButtonText = new Text2("Restart Mode", {
- size: 40,
- fill: 0xFFFFFF
- });
- restartButtonText.anchor.set(0.5);
- restartButton.addChild(restartButtonBg);
- restartButton.addChild(restartButtonText);
+ try {
+ var restartButtonAsset = LK.getAsset('restartgame', {
+ anchorX: 0.5,
+ anchorY: 0.5
+ });
+ restartButton.addChild(restartButtonAsset);
+ } catch (e) {
+ console.error("Błąd ładowania assetu przycisku 'restartgame':", e);
+ var restartButtonBgFallback = new Shape({
+ width: 400,
+ height: 100,
+ color: 0x4477FF,
+ shape: 'box'
+ });
+ restartButton.addChild(restartButtonBgFallback);
+ // Możesz dodać awaryjny Text2
+ // var restartButtonTextFallback = new Text2("Restart Mode", { size: 40, fill: 0xFFFFFF, anchor: { x: 0.5, y: 0.5 } });
+ // restartButton.addChild(restartButtonTextFallback);
+ }
restartButton.x = 2048 / 2;
restartButton.y = buttonYStart + buttonSpacing;
restartButton.down = function () {
if (gameState.currentState !== "rollMasterGameOver") {
@@ -6930,105 +6926,99 @@
// Sprawdź, czy nadal jesteśmy w tym samym stanie gry, aby uniknąć dodawania przycisków, jeśli stan się zmienił
if (gameState.currentState !== "cursedCrystalGameOver") {
return;
}
- // Przycisk "Main Menu"
+ // Definiujemy oczekiwane wysokości na początku, aby były dostępne dla obu przycisków
+ var mainMenuButtonExpectedHeight = 100;
+ var restartButtonExpectedHeight = 100;
+ var spacingBetweenButtons = 60;
var menuButton = new Container();
menuButton.interactive = true;
menuButton.cursor = "pointer";
+ var menuButtonVisualHeight = mainMenuButtonExpectedHeight; // Użyjemy tej zmiennej
try {
var menuButtonAsset = LK.getAsset('mainmenu', {
anchorX: 0.5,
anchorY: 0.5
});
menuButton.addChild(menuButtonAsset);
+ if (menuButtonAsset && typeof menuButtonAsset.height === 'number') {
+ menuButtonVisualHeight = menuButtonAsset.height; // Użyj rzeczywistej wysokości assetu, jeśli dostępna
+ }
} catch (e) {
console.error("Błąd ładowania assetu przycisku 'mainmenu':", e);
- // Awaryjny fallback, jeśli chcesz zachować jakąś funkcjonalność gdy assetu nie ma
var menuButtonBgFallback = new Shape({
width: 400,
- height: 100,
+ height: mainMenuButtonExpectedHeight,
color: 0x555555,
shape: 'box'
});
menuButton.addChild(menuButtonBgFallback);
- var menuButtonTextFallback = new Text2("Main Menu", {
- size: 40,
- fill: 0xFFFFFF,
- anchor: {
- x: 0.5,
- y: 0.5
- }
- });
- menuButton.addChild(menuButtonTextFallback);
}
menuButton.x = 2048 / 2;
- menuButton.y = (ui.messageText.y || 800) + (ui.messageText.height || 70) / 2 + menuButtonBg.height / 2 + 80;
- // Alternatywnie, stała wartość: menuButton.y = 1050;
+ // Poprawiona linia dla menuButton.y:
+ menuButton.y = (ui.messageText.y || 800) + (ui.messageText.height || 70) / 2 + menuButtonVisualHeight / 2 + 80;
menuButton.down = function () {
if (gameState.currentState === "cursedCrystalGameOver") {
gameState.activeButtons.forEach(function (btn) {
- if (btn && btn.destroy) {
+ if (btn && btn.destroy && !btn.destroyed) {
+ if (btn.parent) {
+ btn.parent.removeChild(btn);
+ }
btn.destroy();
}
});
gameState.activeButtons = [];
gameState.showGrillScreen();
}
};
currentSceneElements.addChild(menuButton);
- // gameState.activeButtons.push(menuButton); // Dodajemy do activeButtons wewnątrz gameState, więc 'this' nie jest potrzebne
- // Poprawka: 'this' wewnątrz setTimeout może nie odnosić się do gameState. Użyj gameState bezpośrednio.
gameState.activeButtons.push(menuButton);
- // Przycisk "Restart Mode"
+ // --- Przycisk "Restart Mode" ---
var restartButton = new Container();
restartButton.interactive = true;
restartButton.cursor = "pointer";
+ var restartButtonVisualHeight = restartButtonExpectedHeight; // Użyjemy tej zmiennej
try {
var restartButtonAsset = LK.getAsset('restartgame', {
anchorX: 0.5,
anchorY: 0.5
});
restartButton.addChild(restartButtonAsset);
+ if (restartButtonAsset && typeof restartButtonAsset.height === 'number') {
+ restartButtonVisualHeight = restartButtonAsset.height; // Użyj rzeczywistej wysokości assetu
+ }
} catch (e) {
console.error("Błąd ładowania assetu przycisku 'restartgame':", e);
- // Prosty fallback
var restartButtonBgFallback = new Shape({
width: 400,
- height: 100,
- color: 0x4477FF,
+ height: restartButtonExpectedHeight,
+ color: 0x555555,
shape: 'box'
- }); // Dostosuj wymiary i kolor
+ });
restartButton.addChild(restartButtonBgFallback);
}
restartButton.x = 2048 / 2;
- restartButton.y = menuButton.y + menuButtonBg.height + 40;
- // Alternatywnie, stała wartość: restartButton.y = 1200;
+ // Poprawiona linia dla restartButton.y, z większym odstępem:
+ restartButton.y = menuButton.y + menuButtonVisualHeight / 2 + restartButtonVisualHeight / 2 + spacingBetweenButtons;
restartButton.down = function () {
if (gameState.currentState === "cursedCrystalGameOver") {
gameState.activeButtons.forEach(function (btn) {
- if (btn && btn.destroy) {
+ if (btn && btn.destroy && !btn.destroyed) {
+ if (btn.parent) {
+ btn.parent.removeChild(btn);
+ }
btn.destroy();
}
});
gameState.activeButtons = [];
gameState.startCursedCrystalMode();
}
};
currentSceneElements.addChild(restartButton);
- // gameState.activeButtons.push(restartButton); // Podobnie jak wyżej
gameState.activeButtons.push(restartButton);
- // Wywołanie positionElements może nie być już potrzebne, jeśli wszystko jest pozycjonowane tutaj,
- // ale zostawiam na wypadek, gdyby robiło coś jeszcze.
- // Jeśli ui.positionElements("cursedCrystalGameOver") tylko ukrywało elementy,
- // to teraz jest to robione na początku tej funkcji endCursedCrystalMode (co jest dobre).
- // Jeśli pozycjonowało titleText i messageText, to już to zrobiliśmy powyżej.
// ui.positionElements("cursedCrystalGameOver");
}, 3000); // Opóźnienie 3000 ms (3 sekundy)
- // Usuń wywołanie ui.positionElements z końca bloku if(ui), jeśli było tam bezpośrednio
- // Zamiast tego, upewniliśmy się, że titleText i messageText są widoczne,
- // a przyciski pojawią się z opóźnieniem.
- // Jeśli ui.positionElements robiło coś więcej, trzeba to rozważyć.
}
}), "processTouchGesture", function processTouchGesture() {
// ... (kod obsługi fake tutorial) ...
var dx = this.touchEnd.x - this.touchStart.x;