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