Code edit (2 edits merged)
Please save this source code
User prompt
Please fix the bug: 'Button2 is not defined' in or related to this line: 'var startButton = new Button2("Start Game", function () {' Line Number: 574
User prompt
Please fix the bug: 'clearScene is not defined' in or related to this line: 'clearScene();' Line Number: 555
Code edit (2 edits merged)
Please save this source code
User prompt
Please fix the bug: 'Button2 is not defined' in or related to this line: 'var startButton = new Button2("Start Game", function () {' Line Number: 563
User prompt
Please fix the bug: 'clearScene is not defined' in or related to this line: 'clearScene();' Line Number: 555
Code edit (1 edits merged)
Please save this source code
User prompt
Roll Souls
Initial prompt
reate a complete 2D meme parody game called Roll Souls using only placeholder graphics (squares, text, colors). The game should have a full flow from main menu to final boss and ending, all based around rolling to survive. Do not include custom art. Only use simple placeholder visuals like white square = player, red square = boss, gray = background, orange = grill, etc. The game flow should work like this: Main Menu Scene Name: mainMenuScene Show dark background Text: Welcome, Unchosen One. Button: Start Game → goes to introScene Intro Scene Name: introScene Show text: From the creators of FromSoftware... After 5 seconds, replace with: ...I have no idea. I don’t know them. After 2 more seconds → go to howToScene Fake Tutorial Scene Name: howToScene Show black screen Text: Press E to block. Or don’t press. If player presses E → Show YOU DIED Text: Did you check the title of the game? Button: How to Play AGAIN → goes to realTutorialScene If player does nothing for 6 seconds → auto go to realTutorialScene Real Tutorial Scene Name: realTutorialScene Show 3 tutorial messages: Click Left Mouse Button to ROLL toward the cursor. You can roll up to 3 times before stamina runs out. That’s it. That’s the whole game. Button: Let’s Roll → goes to boss1Scene Boss 1 Scene Name: boss1Scene Player: white square Player always moves slowly toward the mouse cursor Left-click = roll in direction of cursor Max 3 rolls, then cooldown Boss: red square, stands still, shoots blue bullets at player Player dies in 1 hit unless they have extra hearts Boss HP goes down slowly over 2 minutes If player survives 2 minutes → show GRILL LIT, go to grillScene HP/Heart System: Player starts with 1 heart = dies in 1 hit Every 5 total deaths, the player gains +1 heart (permanent) After 5 deaths = 2 hearts After 10 deaths = 3 hearts After 15 deaths = 4 hearts Taking damage removes 1 heart. If hearts = 0, show YOU DIED Grill Scene (Hub) Name: grillScene Background with orange square = grill Text: GRILL LIT Show 3 buttons: Upgrade Roll → shows random silly text like “You feel... no different.” Rest at Grill → shows “Resting...” Final Boss → goes to boss2Scene Final Boss Scene Name: boss2Scene Harder boss: faster bullets, less warning Same roll mechanics, same 2-minute survival Player movement = same: follows mouse If player survives → go to endingScene Ending Scene Name: endingScene Zoom in on player at grill Show text: TRUE ENDING UNLOCKED You have conquered all... but was it worth it? Thanks for playing Roll Souls. Try touching grass now. Optional fake buttons: New Game+, Summon Friend (does nothing) ✅ Use only placeholder graphics ✅ The game starts at mainMenuScene and must flow all the way to endingScene ✅ Player always moves toward cursor ✅ Full heart system included ✅ Every scene and transition should be implemented
/**** * Plugins ****/ var tween = LK.import("@upit/tween.v1"); var storage = LK.import("@upit/storage.v1"); /**** * Classes ****/ // Klasy z stworzylo zloto.txt (bardziej zaawansowana walka z bossem) // Klasy z stworzylo zloto.txt (bardziej zaawansowana walka z bossem) var Boss = Container.expand(function () { var self = Container.call(this); self.isCharging = false; self.ultimateAttackCooldownTimer = 0; self.ultimateAttackMinInterval = 30 * 60; self.ultimateAttackMaxInterval = 40 * 60; self.nextUltimateAttackAvailableTime = 0; self.circleAttackCooldownDuration = 11 * 60; self.circleAttackActiveCooldown = self.circleAttackCooldownDuration; self.bossGraphics = self.addChild(LK.getAsset('bossIdle', { anchorX: 0.5, anchorY: 0.5, x: 0, y: 0 })); self.bossGraphics.scaleX = 2; self.bossGraphics.scaleY = 2; self.createBossAttackAnim = function () { var frames = []; for (var i = 0; i <= 6; i++) { frames.push(LK.getAsset('bossAttack' + i, {})); } for (var i = 5; i >= 1; i--) { frames.push(LK.getAsset('bossAttack' + i, {})); } var bossAttackAnim = new SpriteAnimation({ frames: frames, frameDuration: 100, loop: false, anchorX: 0.5, anchorY: 0.5, x: 0, y: 0 }); bossAttackAnim.scaleX = 2; bossAttackAnim.scaleY = 2; return bossAttackAnim; }; self.playBossAttackAnim = function (attackType) { if (self.bossAttackAnim) { self.bossAttackAnim.stop(); if (self.bossAttackAnim.parent) { self.bossAttackAnim.parent.removeChild(self.bossAttackAnim); } self.bossAttackAnim.destroy(); self.bossAttackAnim = null; } if (attackType !== 2 && attackType !== 3) { if (self.bossGraphics && self.bossGraphics.parent) { self.bossGraphics.parent.removeChild(self.bossGraphics); } self.bossGraphics = null; self.bossAttackAnim = self.addChild(self.createBossAttackAnim()); self.bossAttackAnim.update = function () { if (self.bossAttackAnim !== this || !this.playing || !this.frames || this.frames.length === 0) { return; } this.frameTimer = (this.frameTimer || 0) + 1; if (this.frameTimer >= this.frameDuration / (1000 / 60)) { this.frameTimer = 0; this.removeChildren(); this.currentFrame = (this.currentFrame || 0) + 1; if (this.currentFrame >= this.frames.length) { self.bossGraphics = self.addChild(LK.getAsset('bossIdle', { anchorX: 0.5, anchorY: 0.5, x: 0, y: 0 })); self.bossGraphics.scaleX = 2; self.bossGraphics.scaleY = 2; if (this.parent) { this.parent.removeChild(this); } this.destroy(); self.bossAttackAnim = null; } else { if (this.frames[this.currentFrame]) { this.addChild(this.frames[this.currentFrame]); } } } }; } }; self.createAttack = function (x, y, duration, type) { var framesToUse = []; var scaleMultiplier = 1; var attackRadius = 60; if (type === 'circle') { framesToUse = [LK.getAsset('fireball0', { anchorX: 0.5, anchorY: 0.5, clone: true }), LK.getAsset('fireball00', { anchorX: 0.5, anchorY: 0.5, clone: true }), LK.getAsset('fireball01', { anchorX: 0.5, anchorY: 0.5, clone: true }), LK.getAsset('fireball02', { anchorX: 0.5, anchorY: 0.5, clone: true }), LK.getAsset('fireball03', { anchorX: 0.5, anchorY: 0.5, clone: true }), LK.getAsset('fireball04', { anchorX: 0.5, anchorY: 0.5, clone: true }), LK.getAsset('fireball05', { anchorX: 0.5, anchorY: 0.5, clone: true }), LK.getAsset('fireball06', { anchorX: 0.5, anchorY: 0.5, clone: true }), LK.getAsset('fireball07', { anchorX: 0.5, anchorY: 0.5, clone: true }), LK.getAsset('fireball08', { anchorX: 0.5, anchorY: 0.5, clone: true }), LK.getAsset('fireball09', { anchorX: 0.5, anchorY: 0.5, clone: true }), LK.getAsset('fireball1', { anchorX: 0.5, anchorY: 0.5, clone: true }), LK.getAsset('fireball10', { anchorX: 0.5, anchorY: 0.5, clone: true }), LK.getAsset('fireball11', { anchorX: 0.5, anchorY: 0.5, clone: true }), LK.getAsset('fireball12', { anchorX: 0.5, anchorY: 0.5, clone: true }), LK.getAsset('fireball13', { anchorX: 0.5, anchorY: 0.5, clone: true }), LK.getAsset('fireball14', { anchorX: 0.5, anchorY: 0.5, clone: true }), LK.getAsset('fireballnew1', { anchorX: 0.5, anchorY: 0.5, clone: true }), LK.getAsset('fireballnew2', { anchorX: 0.5, anchorY: 0.5, clone: true }), LK.getAsset('fireballnew3', { anchorX: 0.5, anchorY: 0.5, clone: true }), LK.getAsset('fireballnew4', { anchorX: 0.5, anchorY: 0.5, clone: true })]; } else if (type === 'line') { framesToUse = [LK.getAsset('fireball2', { anchorX: 0.5, anchorY: 0.5, clone: true }), LK.getAsset('fireball3', { anchorX: 0.5, anchorY: 0.5, clone: true }), LK.getAsset('fireball4', { anchorX: 0.5, anchorY: 0.5, clone: true }), LK.getAsset('fireball5', { anchorX: 0.5, anchorY: 0.5, clone: true }), LK.getAsset('fireball6', { anchorX: 0.5, anchorY: 0.5, clone: true }), LK.getAsset('fireball7', { anchorX: 0.5, anchorY: 0.5, clone: true }), LK.getAsset('fireball8', { anchorX: 0.5, anchorY: 0.5, clone: true }), LK.getAsset('fireball9', { anchorX: 0.5, anchorY: 0.5, clone: true }), LK.getAsset('fireball15', { anchorX: 0.5, anchorY: 0.5, clone: true }), LK.getAsset('fireball16', { anchorX: 0.5, anchorY: 0.5, clone: true }), LK.getAsset('linearattack1', { anchorX: 0.5, anchorY: 0.5, clone: true }), LK.getAsset('linearattack2', { anchorX: 0.5, anchorY: 0.5, clone: true }), LK.getAsset('linearattack3', { anchorX: 0.5, anchorY: 0.5, clone: true }), LK.getAsset('linearattack4', { anchorX: 0.5, anchorY: 0.5, clone: true }), LK.getAsset('linearattack5', { anchorX: 0.5, anchorY: 0.5, clone: true })]; scaleMultiplier = 1.3; attackRadius = 60 * scaleMultiplier; } else { framesToUse = [LK.getAsset('fireball0', { anchorX: 0.5, anchorY: 0.5, clone: true })]; } var currentFrameDuration = type === 'line' ? 250 : type === 'circle' ? 150 : 100; var spriteAnim = game.addChild(new SpriteAnimation({ frames: framesToUse, frameDuration: currentFrameDuration, loop: false, anchorX: 0.5, anchorY: 0.5, x: x, y: y })); spriteAnim.scaleX = 1.6 * scaleMultiplier; spriteAnim.scaleY = 1.6 * scaleMultiplier; spriteAnim.play(); var attackData = { x: x, y: y, radius: attackRadius, visual: spriteAnim, lifeTime: Math.floor(duration / (1000 / 60)), isActive: true, attackObjectType: type }; self.attacks.push(attackData); var animationTotalDurationMs = framesToUse.length * currentFrameDuration; LK.setTimeout(function () { var index = self.attacks.indexOf(attackData); if (index !== -1) { self.attacks.splice(index, 1); } if (spriteAnim && !spriteAnim.destroyed) { if (spriteAnim.parent) { spriteAnim.parent.removeChild(spriteAnim); } spriteAnim.destroy(); } }, animationTotalDurationMs); }; self.circleAttack = function () { LK.getSound('bossAttack').play(); var count = isNewBossPlusMode ? 8 : 4; var radius = 300; var orbitDurationFrames = 230; var flightDurationMs = isNewBossPlusMode ? 3500 : 4500; var baseAngle = Math.random() * Math.PI * 2; var angularSpeed = 0.02; var attackOriginX = self.x; var attackOriginY = self.y; for (var i = 0; i < count; i++) { var angleOffset = i / count * Math.PI * 2; var initialAngle = baseAngle + angleOffset; var x = attackOriginX + Math.cos(initialAngle) * radius; var y = attackOriginY + Math.sin(initialAngle) * radius; var orbFrames = [LK.getAsset('fireball0', { anchorX: 0.5, anchorY: 0.5, clone: true }), LK.getAsset('fireball00', { anchorX: 0.5, anchorY: 0.5, clone: true }), LK.getAsset('fireball01', { anchorX: 0.5, anchorY: 0.5, clone: true }), LK.getAsset('fireball02', { anchorX: 0.5, anchorY: 0.5, clone: true }), LK.getAsset('fireball03', { anchorX: 0.5, anchorY: 0.5, clone: true }), LK.getAsset('fireball04', { anchorX: 0.5, anchorY: 0.5, clone: true }), LK.getAsset('fireball05', { anchorX: 0.5, anchorY: 0.5, clone: true }), LK.getAsset('fireball06', { anchorX: 0.5, anchorY: 0.5, clone: true })]; var spriteAnim = new SpriteAnimation({ frames: orbFrames, frameDuration: 100, loop: true, anchorX: 0.5, anchorY: 0.5, x: x, y: y }); spriteAnim.scaleX = 1.6; spriteAnim.scaleY = 1.6; spriteAnim.play(); spriteAnim.update = function () { this.frameTimer = (this.frameTimer || 0) + 1; if (this.frameTimer >= this.frameDuration / (1000 / 60)) { this.frameTimer = 0; this.removeChildren(); this.currentFrame = ((this.currentFrame || 0) + 1) % this.frames.length; if (this.frames[this.currentFrame]) { this.addChild(this.frames[this.currentFrame]); } } }; game.addChild(spriteAnim); self.attacks.push({ type: 'circle_orbiting', angleOffset: angleOffset, baseAngle: baseAngle, currentAngle: initialAngle, angularSpeed: angularSpeed, radius: radius, collisionRadius: 60, centerX: attackOriginX, centerY: attackOriginY, detachCounter: orbitDurationFrames, lifeTime: Math.floor(flightDurationMs / (1000 / 60)), isActive: true, visual: spriteAnim, x: x, y: y, detached: false, vx: 0, vy: 0 }); } }; self.takeDamage = function (amount) { console.log("DEBUG: Boss.takeDamage CALLED. Amount:", amount, "Boss dead:", self.dead, "Current state:", gameState.currentState, "Boss health BEFORE:", self.health); if (self.dead || typeof gameState !== 'undefined' && gameState.currentState !== "game") { console.log("DEBUG: Boss.takeDamage REJECTED. Dead or wrong game state."); return; } self.health -= amount; self.health = Math.max(0, self.health); console.log("DEBUG: Boss health AFTER:", self.health, "/", self.maxHealth); LK.effects.flashObject(self, 0xFFFFFF, 200); if (typeof isNewBossPlusMode !== 'undefined' && !isNewBossPlusMode) { if (self.health <= self.maxHealth / 2 && self.phase === 1) { console.log("DEBUG: Boss entering phase 2."); self.phase = 2; self.speed += 2; self.attackSpeedMultiplier = (self.attackSpeedMultiplier || 1) * 0.8; tween(self, { tint: 0xFF3300 }, { duration: 1000, easing: tween.easeInOut }); } } if (self.health <= 0) { console.log("DEBUG: Boss health <= 0, calling self.die()."); self.die(); } }; // NOWA METODA DO CZYSZCZENIA ATAKÓW self.clearAllAttacks = function () { console.log("DEBUG: Boss.clearAllAttacks CALLED. Clearing " + self.attacks.length + " attacks."); var attacksToClear = self.attacks.slice(); // Iteruj po kopii attacksToClear.forEach(function (attack) { if (attack.visual && !attack.visual.destroyed) { if (attack.visual.parent) { attack.visual.parent.removeChild(attack.visual); // Usuń z rodzica (np. 'game') } attack.visual.destroy(); } }); self.attacks = []; // Wyczyść tablicę ataków bossa console.log("DEBUG: Boss.clearAllAttacks FINISHED. Attacks remaining: " + self.attacks.length); }; self.die = function () { var currentGameState = typeof gameState !== 'undefined' ? gameState.currentState : "gameState undefined"; console.log("DEBUG: Boss.die() called. Current state:", currentGameState, "Is boss already dead?", self.dead, "Is NewBossPlusMode?", isNewBossPlusMode); if (self.dead && currentGameState !== "game") { console.log("DEBUG: Boss.die() - Already dead and not in 'game' state. Attempting cleanup again just in case."); self.clearAllAttacks("Die - Already dead, not in game state"); return; } if (self.dead) { console.log("DEBUG: Boss.die() - Already dead. Exiting."); return; } self.dead = true; console.log("DEBUG: Boss.die() - Boss marked as dead. Clearing attacks."); self.clearAllAttacks("Die - Normal death sequence"); // Dźwięk zwycięstwa tylko w trybie standardowym if (typeof isNewBossPlusMode !== 'undefined' && !isNewBossPlusMode) { LK.getSound('victory').play(); } if (typeof gameState !== 'undefined' && gameState.currentState === "game") { console.log("DEBUG: Boss.die - In 'game' state."); // ZMIANA TUTAJ: Boss+ będzie miał animację śmierci i przejdzie do Grill Menu // Niezależnie od tego, czy to Boss+ czy standardowy, wykonaj animację śmierci console.log("DEBUG: Boss.die - Starting death animation tween for boss (standard or Boss+)."); tween(self, { alpha: 0, scaleX: self.scaleX * 1.2, scaleY: self.scaleY * 1.2 }, { duration: 2000, easing: tween.easeOut, onFinish: function onFinish() { console.log("DEBUG: Boss.die tween onFinish reached."); // Zwiększ licznik pokonanych bossów (możesz chcieć osobny licznik dla Boss+) storage.bossesDefeated = (parseInt(storage.bossesDefeated, 10) || 0) + 1; console.log("DEBUG: Boss.die onFinish - Bosses defeated:", storage.bossesDefeated); if (typeof gameState !== 'undefined' && typeof gameState.showGrillScreen === 'function') { // Dla Boss+ możesz przekazać specjalną flagę, jeśli chcesz inny komunikat na Grill Menu var wasBossPlusDefeated = typeof isNewBossPlusMode !== 'undefined' && isNewBossPlusMode; gameState.showGrillScreen(false, wasBossPlusDefeated); // (survivedBossPlusChallenge = false, defeatedBossPlus = wasBossPlusDefeated) } } }); } else { console.log("DEBUG: Boss.die - Not in 'game' state (e.g., during restart), skipping animations. Attacks should be cleared."); } }; self.update = function () { if (typeof gameState !== 'undefined' && gameState.currentState !== "game" && gameState.currentState !== "rollMaster") { if (self.rolling) { self.rolling = false; } return; } if (self.dead) { return; } self.ultimateAttackCooldownTimer++; if (self.circleAttackActiveCooldown < self.circleAttackCooldownDuration) { self.circleAttackActiveCooldown++; } if (self.attackCooldown > 30 && typeof player !== 'undefined' && player && !player.dead && !self.isCharging) { var dx = player.x - self.x; var dy = player.y - self.y; var distance = Math.sqrt(dx * dx + dy * dy); var moveSpeed = self.speed || 2; if (distance > 150) { var moveX = dx / distance * moveSpeed; var moveY = dy / distance * moveSpeed; var nextX = self.x + moveX; var nextY = self.y + moveY; var halfWidth = (self.width || 100) * (self.scaleX || 1) / 2; var halfHeight = (self.height || 100) * (self.scaleY || 1) / 2; var minX = 100 + halfWidth; var maxX = 2048 - 100 - halfWidth; var minY = 300 + halfHeight; var maxY = 2732 - 100 - halfHeight; self.x = Math.max(minX, Math.min(nextX, maxX)); self.y = Math.max(minY, Math.min(nextY, maxY)); } } for (var i = self.attacks.length - 1; i >= 0; i--) { var attack = self.attacks[i]; var shouldRemove = false; if (attack.type !== 'line_controller' && (!attack || !attack.visual || attack.visual.destroyed)) { self.attacks.splice(i, 1); continue; } if (attack.visual && typeof attack.visual.update === 'function') { attack.visual.update(); } if (attack.type === 'circle_orbiting') { if (!attack.detached) { attack.baseAngle += attack.angularSpeed; var currentOrbAngle = attack.baseAngle + attack.angleOffset; attack.x = attack.centerX + Math.cos(currentOrbAngle) * attack.radius; attack.y = attack.centerY + Math.sin(currentOrbAngle) * attack.radius; if (attack.visual) { attack.visual.x = attack.x; attack.visual.y = attack.y; } attack.detachCounter--; if (attack.detachCounter <= 0) { attack.detached = true; var tangentialAngle = currentOrbAngle + Math.PI / 2; var launchSpeed = 6; attack.vx = Math.cos(tangentialAngle) * launchSpeed; attack.vy = Math.sin(tangentialAngle) * launchSpeed; } } else { attack.x += attack.vx; attack.y += attack.vy; if (attack.visual) { attack.visual.x = attack.x; attack.visual.y = attack.y; } attack.lifeTime--; if (attack.x < -200 || attack.x > 2248 || attack.y < -200 || attack.y > 2932) { shouldRemove = true; } if (attack.lifeTime <= 0) { shouldRemove = true; } } } else if (attack.type === 'ultimate_orb') { attack.x += attack.vx; attack.y += attack.vy; if (attack.visual) { attack.visual.x = attack.x; attack.visual.y = attack.y; } attack.lifeTime--; if (attack.x < -200 || attack.x > 2048 + 200 || attack.y < -200 || attack.y > 2732 + 200) { shouldRemove = true; } if (attack.lifeTime <= 0) { shouldRemove = true; } } else if (attack.type === 'line_controller') { attack.x += attack.directionX * attack.speed; attack.y += attack.directionY * attack.speed; var currentTime = LK.getGameTime(); if (attack.currentSegmentIndex < attack.totalSegmentsToSpawn && currentTime - attack.lastSpawnTime >= attack.segmentDelay) { self.createAttack(attack.x, attack.y, attack.segmentLifeTime, 'line'); attack.currentSegmentIndex++; attack.lastSpawnTime = currentTime; } var controllerLifeTimeAfterSpawning = attack.segmentLifeTime; if (attack.currentSegmentIndex >= attack.totalSegmentsToSpawn && LK.getGameTime() - attack.lastSpawnTime > controllerLifeTimeAfterSpawning) { shouldRemove = true; } } else if (attack.attackObjectType && attack.type !== 'line_controller') { attack.lifeTime--; if (attack.lifeTime <= 0) { shouldRemove = true; } } if (!shouldRemove && attack.isActive && typeof player !== 'undefined' && player && !player.dead && !player.invulnerable) { if (attack.type !== 'line_controller') { var dx_p = player.x - attack.x; var dy_p = player.y - attack.y; var distance_p = Math.sqrt(dx_p * dx_p + dy_p * dy_p); var playerRadius_p = player.width / 2 * 0.8; var colRadius = attack.collisionRadius || attack.radius || 60; if (distance_p < playerRadius_p + colRadius) { player.takeDamage(1); shouldRemove = true; } } } if (shouldRemove) { if (attack.visual && !attack.visual.destroyed) { if (attack.visual.parent) { attack.visual.parent.removeChild(attack.visual); } attack.visual.destroy(); } self.attacks.splice(i, 1); } } if (self.attackCooldown > 0) { self.attackCooldown--; } if (self.attackCooldown <= 0 && !self.isCharging) { self.startAttackPattern(); } }; // Koniec self.update dla Bossa // Pełne implementacje funkcji lineAttack, chargeAttack, ultimateAttack, startAttackPattern // powinny być tutaj wklejone z Twojej poprzedniej, kompletnej wersji. // Poniżej skrócone wersje dla kompletności struktury. self.lineAttack = function () { LK.getSound('bossAttack').play(); if (typeof player === 'undefined' || !player) { return; } var startX = self.x; var startY = self.y; var targetX = player.x; var targetY = player.y; var dx = targetX - startX; var dy = targetY - startY; var distance = Math.sqrt(dx * dx + dy * dy); var normalizedDx = 0; var normalizedDy = 0; if (distance > 0) { normalizedDx = dx / distance; normalizedDy = dy / distance; } var wallSpeed = typeof isNewBossPlusMode !== 'undefined' && isNewBossPlusMode ? 3 : 2; var numberOfSegments = typeof isNewBossPlusMode !== 'undefined' && isNewBossPlusMode ? 10 : 8; var segmentSpawnDuration = typeof isNewBossPlusMode !== 'undefined' && isNewBossPlusMode ? 2500 : 3000; var segmentLifeTime = typeof isNewBossPlusMode !== 'undefined' && isNewBossPlusMode ? 2000 : 2500; var delayPerSegment = segmentSpawnDuration / numberOfSegments; var lineAttackController = { type: 'line_controller', currentSegmentIndex: 0, lastSpawnTime: LK.getGameTime(), totalSegmentsToSpawn: numberOfSegments, segmentDelay: delayPerSegment, segmentLifeTime: segmentLifeTime, directionX: normalizedDx, directionY: normalizedDy, speed: wallSpeed, x: startX, y: startY, targetX: targetX, targetY: targetY }; self.attacks.push(lineAttackController); }; self.chargeAttack = function () { LK.getSound('bossAttack').play(); if (typeof player === 'undefined' || !player) { return; } if (self.bossAttackAnim) { self.bossAttackAnim.stop(); if (self.bossAttackAnim.parent) { self.bossAttackAnim.parent.removeChild(self.bossAttackAnim); } self.bossAttackAnim.destroy(); self.bossAttackAnim = null; } if (!self.bossGraphics || self.bossGraphics && self.bossGraphics.destroyed) { if (self.bossGraphics && self.bossGraphics.parent) { self.bossGraphics.parent.removeChild(self.bossGraphics); } self.bossGraphics = self.addChild(LK.getAsset('bossIdle', { anchorX: 0.5, anchorY: 0.5, x: 0, y: 0 })); self.bossGraphics.scaleX = 2; self.bossGraphics.scaleY = 2; } self.isCharging = true; var dx = player.x - self.x; var dy = player.y - self.y; var distance = Math.sqrt(dx * dx + dy * dy); if (distance > 0) { dx /= distance; dy /= distance; } var chargeDistance = typeof isNewBossPlusMode !== 'undefined' && isNewBossPlusMode ? 700 : 500; var chargeDuration = typeof isNewBossPlusMode !== 'undefined' && isNewBossPlusMode ? 600 : 800; tween(self, { x: self.x + dx * chargeDistance, y: self.y + dy * chargeDistance }, { duration: chargeDuration * (self.attackSpeedMultiplier || 1), easing: tween.easeIn, onFinish: function onFinish() { self.isCharging = false; } }); }; self.ultimateAttack = function () { if (self.dead || typeof gameState !== 'undefined' && gameState.currentState !== "game" || typeof isNewBossPlusMode !== 'undefined' && !isNewBossPlusMode) { return; } if (self.bossAttackAnim) { self.bossAttackAnim.stop(); if (self.bossAttackAnim.parent) { self.bossAttackAnim.parent.removeChild(self.bossAttackAnim); } self.bossAttackAnim.destroy(); self.bossAttackAnim = null; } if (!self.bossGraphics || self.bossGraphics && self.bossGraphics.destroyed) { if (self.bossGraphics && self.bossGraphics.parent) { self.bossGraphics.parent.removeChild(self.bossGraphics); } self.bossGraphics = self.addChild(LK.getAsset('bossIdle', { anchorX: 0.5, anchorY: 0.5, x: 0, y: 0 })); self.bossGraphics.scaleX = 2; self.bossGraphics.scaleY = 2; } var screenWidth = 2048; var screenHeight = 2732; var iconWidth = 700; var iconHeight = 700; var startX = -iconWidth; var middleX = screenWidth / 2; var endX = screenWidth + iconWidth; var targetY = 250; var hoverAmplitudeY = 20; var hoverDurationY = 1000; var rotationAmplitude = 0.05; var rotationDuration = 1500; var moveInDuration = 400; var hoverDurationTotal = 2000; var moveOutDuration = 400; var attackIcon = game.addChild(LK.getAsset('ultimateBossAttack_icon', { anchorX: 0.5, anchorY: 0.5, x: startX, y: targetY, alpha: 0, scaleX: 0.8, scaleY: 0.8, rotation: 0 })); tween(attackIcon, { alpha: 1, scaleX: 1, scaleY: 1, x: middleX }, { duration: moveInDuration, easing: tween.easeOutQuad, onFinish: function onFinish() { if (attackIcon.destroyed) { return; } var hoverTweenY = tween(attackIcon, { y: attackIcon.y + hoverAmplitudeY }, { duration: hoverDurationY / 2, easing: tween.easeInOut, yoyo: true, repeat: Infinity }); var rotationTween = tween(attackIcon, { rotation: attackIcon.rotation + rotationAmplitude }, { duration: rotationDuration / 2, easing: tween.easeInOut, yoyo: true, repeat: Infinity }); LK.setTimeout(function () { if (attackIcon.destroyed) { if (hoverTweenY) { hoverTweenY.stop(); } if (rotationTween) { rotationTween.stop(); } return; } if (hoverTweenY) { hoverTweenY.stop(); } if (rotationTween) { rotationTween.stop(); } tween(attackIcon, { y: targetY, rotation: 0 }, { duration: 100, onFinish: function onFinish() { tween(attackIcon, { alpha: 0, x: endX }, { duration: moveOutDuration, easing: tween.easeInQuad, onFinish: function onFinish() { if (attackIcon && !attackIcon.destroyed) { if (attackIcon.parent) { attackIcon.parent.removeChild(attackIcon); } attackIcon.destroy(); } } }); } }); }, hoverDurationTotal); } }); var originalOrbWidth = 300; var originalOrbHeight = 300; var baseOrbRadius = Math.max(originalOrbWidth, originalOrbHeight) / 2; var growAndFadeInDuration = 6000; var finalScale = 2.6; var orbSpeed = 8; var travelDurationMs = 6000; function createAndLaunchOrb(spawnX, spawnDelay) { LK.setTimeout(function () { if (self.dead || typeof gameState !== 'undefined' && gameState.currentState !== "game") { return; } var orbFrames = []; for (var i = 0; i <= 7; i++) { orbFrames.push(LK.getAsset('ultimatebossattack_orb_' + i, { anchorX: 0.5, anchorY: 0.5, clone: true })); } var orbAnim = new SpriteAnimation({ frames: orbFrames, frameDuration: 70, loop: true, x: spawnX, y: 300, anchorX: 0.5, anchorY: 0.5, scaleX: 0.1, scaleY: 0.1, alpha: 0 }); game.addChild(orbAnim); tween(orbAnim, { scaleX: finalScale, scaleY: finalScale, alpha: 1 }, { duration: growAndFadeInDuration, easing: tween.easeOutQuad, onFinish: function onFinish() { if (orbAnim.destroyed) { return; } var targetXPlayer = typeof player !== 'undefined' && player ? player.x : screenWidth / 2; var targetYPlayer = typeof player !== 'undefined' && player ? player.y : screenHeight / 2; var actualOrbRadius = baseOrbRadius * finalScale; var dxOrb = targetXPlayer - orbAnim.x; var dyOrb = targetYPlayer - orbAnim.y; var distOrb = Math.sqrt(dxOrb * dxOrb + dyOrb * dyOrb); var vxOrb = distOrb > 0 ? dxOrb / distOrb * orbSpeed : 0; var vyOrb = distOrb > 0 ? dyOrb / distOrb * orbSpeed : 0; self.attacks.push({ type: 'ultimate_orb', visual: orbAnim, vx: vxOrb, vy: vyOrb, radius: actualOrbRadius, lifeTime: travelDurationMs / (1000 / 60), x: orbAnim.x, y: orbAnim.y, isActive: true }); } }); }, spawnDelay); } var spawnDelayBetweenOrbs = 1000; createAndLaunchOrb(screenWidth / 2, 0); createAndLaunchOrb(screenWidth / 2 - 600, spawnDelayBetweenOrbs); createAndLaunchOrb(screenWidth / 2 + 600, spawnDelayBetweenOrbs * 2); }; self.startAttackPattern = function () { self.attackCooldown = typeof isNewBossPlusMode !== 'undefined' && isNewBossPlusMode ? 150 : 190; var attackType; var availableAttacks = []; if (self.circleAttackActiveCooldown >= self.circleAttackCooldownDuration) { availableAttacks.push(0); } availableAttacks.push(1); availableAttacks.push(2); var isUltimateAttackReady = self.ultimateAttackCooldownTimer >= self.nextUltimateAttackAvailableTime; if (typeof isNewBossPlusMode !== 'undefined' && isNewBossPlusMode && isUltimateAttackReady) { availableAttacks.push(3); } if (availableAttacks.length === 0) { return; } attackType = availableAttacks[Math.floor(Math.random() * availableAttacks.length)]; self.playBossAttackAnim(attackType); if (attackType === 0) { self.circleAttack(); self.circleAttackActiveCooldown = 0; } else if (attackType === 1) { self.lineAttack(); } else if (attackType === 2) { self.chargeAttack(); } else if (attackType === 3 && typeof isNewBossPlusMode !== 'undefined' && isNewBossPlusMode) { self.ultimateAttack(); self.ultimateAttackCooldownTimer = 0; self.nextUltimateAttackAvailableTime = self.ultimateAttackMinInterval + Math.floor(Math.random() * (self.ultimateAttackMaxInterval - self.ultimateAttackMinInterval + 1)); self.attackCooldown = 6 * 60; } }; // Inicjalizacja zmiennych bossa self.nextUltimateAttackAvailableTime = self.ultimateAttackMinInterval + Math.floor(Math.random() * (self.ultimateAttackMaxInterval - self.ultimateAttackMinInterval + 1)); self.health = typeof isNewBossPlusMode !== 'undefined' && isNewBossPlusMode ? 300 : 150; self.maxHealth = self.health; self.speed = typeof isNewBossPlusMode !== 'undefined' && isNewBossPlusMode ? 3 : 2; self.attackSpeedMultiplier = 1; self.phase = 1; self.attacks = []; self.attackCooldown = 120; // Początkowy cooldown przed pierwszym atakiem self.dead = false; return self; }); var MinibossCC = Container.expand(function (options) { var self = Container.call(this); options = options || {}; self.maxHp = options.maxHp || 200; self.hp = self.maxHp; self.speed = options.speed || 0; self.isDead = false; self.attackPattern = ['explosiveProjectile', 'laserWall']; self.currentAttackIndex = 0; self.attackCooldowns = { 'explosiveProjectile': 8 * 60, 'laserWall': 5 * 60 }; self.attackCooldown = 120; self.currentAttackName = ''; self.isCurrentlyPlayingAttackAnim = false; self.activeSkillAnimationInstance = null; self.isTeleporting = false; self.teleportCooldownTimer = 0; self.teleportIntervalMin = 9 * 60; self.teleportIntervalMax = 16 * 60; self.nextTeleportTime = self.teleportIntervalMin + Math.floor(Math.random() * (self.teleportIntervalMax - self.teleportIntervalMin + 1)); self.activeClones = []; self.maxClones = 2; self.canTeleportWithClones = true; self.teleportCloneCooldownDuration = 10 * 60; self.isWaitingForClonesToDespawn = false; self.cloneTeleportTimeoutId = null; self.creepSpeed = 0.9; self.creepDirectionX = 0; self.creepDirectionY = 0; self.creepDirectionChangeIntervalMin = 3 * 60; self.creepDirectionChangeIntervalMax = 7 * 60; self.creepDirectionChangeTimer = 0; self.actualWidth = 120; self.actualHeight = 120; try { self.graphics = self.attachAsset('miniboss_cc_asset', { anchorX: 0.5, anchorY: 0.5 }); } catch (e) { console.warn("Nie udało się załadować 'miniboss_cc_asset', używam awaryjnego Shape:", e); self.graphics = new Shape({ width: 120, height: 120, color: 0xFF8C00, shape: 'box' }); self.addChild(self.graphics); } if (self.graphics && typeof self.graphics.width !== 'undefined') { self.width = self.graphics.width * (self.graphics.scaleX || 1); self.height = self.graphics.height * (self.graphics.scaleY || 1); } else { self.width = 120; self.height = 120; } if (self.graphics && typeof self.graphics.width === 'number' && typeof self.graphics.height === 'number') { self.actualWidth = self.graphics.width * (self.graphics.scaleX || 1); self.actualHeight = self.graphics.height * (self.graphics.scaleY || 1); } else { self.actualWidth = self.width; self.actualHeight = self.height; } self.pickNewCreepDirection = function () { var angle = Math.random() * 2 * Math.PI; self.creepDirectionX = Math.cos(angle); self.creepDirectionY = Math.sin(angle); self.creepDirectionChangeTimer = self.creepDirectionIntervalMin + Math.floor(Math.random() * (self.creepDirectionChangeIntervalMax - self.creepDirectionIntervalMin + 1)); }; self.createMinibossSkillAnim = function () { var frames = []; var frameBaseName = 'bossSkillAnim0'; var totalSkillFrames = 9; for (var i = 0; i < totalSkillFrames; i++) { var assetName = frameBaseName + (i === 0 ? '' : String(i)); try { frames.push(LK.getAsset(assetName, { anchorX: 0.5, anchorY: 0.5, clone: true })); } catch (e) { console.error("Błąd ładowania klatki animacji Klona: " + assetName, e); if (i > 0 && frames.length > 0) { frames.push(frames[0]); } else { var placeholderFrame = new Shape({ width: 120, height: 120, color: 0xFF00FF, shape: 'box' }); placeholderFrame.anchor.set(0.5, 0.5); frames.push(placeholderFrame); } } } for (var i = totalSkillFrames - 2; i >= 1; i--) { var assetName = frameBaseName + (i === 0 ? '' : String(i)); try { frames.push(LK.getAsset(assetName, { anchorX: 0.5, anchorY: 0.5, clone: true })); } catch (e) { console.error("Błąd ładowania klatki powrotnej animacji Klona: " + assetName, e); if (frames.length > 0) { frames.push(frames[0]); } } } var skillAnimationObject = new SpriteAnimation({ frames: frames, frameDuration: 120, loop: false, anchorX: 0.5, anchorY: 0.5, x: 0, y: 0 }); return skillAnimationObject; }; self.playMinibossSkillAnim = function (onAnimationCompleteCallback) { if (self.isDead || self.isTeleporting) { self.isCurrentlyPlayingAttackAnim = false; if (typeof onAnimationCompleteCallback === 'function') { onAnimationCompleteCallback(); } return; } if (self.activeSkillAnimationInstance && self.activeSkillAnimationInstance.parent) { self.activeSkillAnimationInstance.stop(); self.removeChild(self.activeSkillAnimationInstance); self.activeSkillAnimationInstance.destroy(); self.activeSkillAnimationInstance = null; } if (self.graphics && !self.graphics.destroyed) { self.graphics.visible = false; } var newAnimation = self.createMinibossSkillAnim(); self.activeSkillAnimationInstance = self.addChild(newAnimation); self.activeSkillAnimationInstance.currentFrameIndex = 0; self.activeSkillAnimationInstance.animationTimer = 0; self.activeSkillAnimationInstance.framesArray = newAnimation.frames; self.activeSkillAnimationInstance.singleFrameDuration = newAnimation.frameDuration || 120; self.activeSkillAnimationInstance.playing = true; self.activeSkillAnimationInstance.removeChildren(); if (self.activeSkillAnimationInstance.framesArray && self.activeSkillAnimationInstance.framesArray.length > 0) { self.activeSkillAnimationInstance.addChild(self.activeSkillAnimationInstance.framesArray[0]); } else { self.isCurrentlyPlayingAttackAnim = false; if (self.graphics && !self.graphics.destroyed) { self.graphics.visible = true; } if (typeof onAnimationCompleteCallback === 'function') { onAnimationCompleteCallback(); } if (self.activeSkillAnimationInstance) { if (self.activeSkillAnimationInstance.parent) { self.removeChild(self.activeSkillAnimationInstance); } self.activeSkillAnimationInstance.destroy(); self.activeSkillAnimationInstance = null; } return; } self.activeSkillAnimationInstance.update = function () { if (!this.playing || !this.framesArray || this.framesArray.length === 0) { return; } this.animationTimer++; if (this.animationTimer * (1000 / 60) >= this.singleFrameDuration) { this.animationTimer = 0; this.currentFrameIndex++; if (this.currentFrameIndex >= this.framesArray.length) { this.playing = false; if (self.graphics && !self.graphics.destroyed) { self.graphics.visible = true; } if (this.parent) { this.parent.removeChild(this); } this.destroy(); if (self.activeSkillAnimationInstance === this) { self.activeSkillAnimationInstance = null; } if (typeof onAnimationCompleteCallback === 'function') { onAnimationCompleteCallback(); } else { self.isCurrentlyPlayingAttackAnim = false; } } else { this.removeChildren(); if (this.framesArray[this.currentFrameIndex]) { this.addChild(this.framesArray[this.currentFrameIndex]); } } } }; }; self.update = function () { if (self.isDead || gameState.currentState !== "cursedCrystal") { return; } if (self.isTeleporting || self.activeSkillAnimationInstance) {} else { self.creepDirectionChangeTimer--; if (self.creepDirectionChangeTimer <= 0) { self.pickNewCreepDirection(); } var nextX = self.x + self.creepDirectionX * self.creepSpeed; var nextY = self.y + self.creepDirectionY * self.creepSpeed; var halfWidth = self.actualWidth / 2; var halfHeight = self.actualHeight / 2; var arenaMinX = 50 + halfWidth; var arenaMaxX = 2048 - 50 - halfWidth; var arenaMinY = 50 + halfHeight; var arenaMaxY = 2732 - 50 - halfHeight; if (nextX >= arenaMinX && nextX <= arenaMaxX) { self.x = nextX; } else { self.creepDirectionX *= -1; self.creepDirectionChangeTimer = self.creepDirectionIntervalMin; } if (nextY >= arenaMinY && nextY <= arenaMaxY) { self.y = nextY; } else { self.creepDirectionY *= -1; self.creepDirectionChangeTimer = self.creepDirectionIntervalMin; } } if (self.canTeleportWithClones && !self.isTeleporting) { self.teleportCooldownTimer++; if (self.teleportCooldownTimer >= self.nextTeleportTime) { self.teleport(); return; } } if (!self.isTeleporting) { if (self.attackCooldown > 0) { self.attackCooldown--; } else if (player && !player.dead && !self.isCurrentlyPlayingAttackAnim) { self.isCurrentlyPlayingAttackAnim = true; self.performAttack(); } } }; self.performAttack = function () { if (!player || player.dead || self.isTeleporting) { self.isCurrentlyPlayingAttackAnim = false; return; } var executeAttackLogic = function executeAttackLogic() { if (self.isDead) { self.isCurrentlyPlayingAttackAnim = false; return; } self.currentAttackName = self.attackPattern[self.currentAttackIndex]; if (self.currentAttackName === 'explosiveProjectile') { var createProjectileWithDelay = function createProjectileWithDelay(delayMs) { LK.setTimeout(function () { if (self.isDead || !player || player.dead) { return; } var projectile = new MinibossExplosiveProjectile({ x: self.x, y: self.y, target: player, speed: 7, explosionRadius: 150 }); game.addChild(projectile); if (gameState.cursedCrystalActiveProjectiles === undefined) { gameState.cursedCrystalActiveProjectiles = []; } gameState.cursedCrystalActiveProjectiles.push(projectile); console.log("MinibossCC: Wystrzelono pocisk eksplozywny (opóźnienie: " + delayMs + "ms)"); }, delayMs); }; createProjectileWithDelay(0); // Pierwszy pocisk natychmiast if (gameState.minibossEnhancedProjectile) { // Załóżmy, że ta flaga istnieje w gameState console.log("MinibossCC (Ulepszony): Wystrzeliwuje drugi pocisk!"); createProjectileWithDelay(2000); // Drugi pocisk po 2000ms (2 sekundy) } } else if (self.currentAttackName === 'laserWall') { var numTotalSegments = 7; var middleSegmentIndex = Math.floor(numTotalSegments / 2); var scytheWidth = 160; var scytheHeight = 160; var spacingBetweenSegments = 20; var totalWallEffectiveHeight = numTotalSegments * scytheHeight + (numTotalSegments - 1) * spacingBetweenSegments; var wallPivotX = self.x; var wallPivotY = self.y; var warningDuration = 1500; var activeDurationStandard = 4000; // Dla 2 obrotów var rotationSpeedStandard = 2 * Math.PI / 120; // 1 obrót na 120 klatek (2s) var activeDurationEnhanced = 4500; // Czas na 3 obroty (3 * 1.5s) var rotationSpeedEnhanced = 2 * Math.PI / 90; // 1 obrót na 90 klatek (1.5s) var currentActiveDuration = activeDurationStandard; var currentRotationSpeed = rotationSpeedStandard; if (gameState.minibossEnhancedLaserWall) { // Zakładamy dostęp do gameState console.log("Miniboss: Ulepszony LaserWall - szybszy i 3 obroty!"); currentActiveDuration = activeDurationEnhanced; currentRotationSpeed = rotationSpeedEnhanced; } var activeDuration = 4000; var rotationSpeed = 2 * Math.PI / 120; var laserWallInstance = { pivotX: wallPivotX, pivotY: wallPivotY, segments: [], currentAngle: 0, rotationSpeed: currentRotationSpeed, warningTimer: warningDuration, activeTimer: currentActiveDuration, isDead: false }; var initialSegmentOffsetY = -(totalWallEffectiveHeight / 2) + scytheHeight / 2; var frameDurationAction = 100; var frameDurationLoop = 100; for (var i = 0; i < numTotalSegments; i++) { var segmentOffsetY = initialSegmentOffsetY + i * (scytheHeight + spacingBetweenSegments); var spawnPosX = wallPivotX; var spawnPosY = wallPivotY + segmentOffsetY; if (i === middleSegmentIndex) { laserWallInstance.segments.push({ isPlaceholder: true, initialOffsetY: segmentOffsetY, width: scytheWidth, height: scytheHeight }); continue; } var scytheAppearVisual = null; var FADE_IN_DURATION = 500; // Czas trwania fade in w milisekundach - dostosuj! try { scytheAppearVisual = LK.getAsset('scythe_appear_0', { anchorX: 0.5, anchorY: 0.5, clone: true, alpha: 0 // Zacznij od przezroczystości 0 }); scytheAppearVisual.x = spawnPosX; scytheAppearVisual.y = spawnPosY; game.addChild(scytheAppearVisual); tween(scytheAppearVisual, { alpha: 1 }, { duration: FADE_IN_DURATION, easing: tween.easeInQuad }); } catch (e) { console.error("Błąd ładowania assetu scythe_appear_0: ", e); scytheAppearVisual = new Shape({ width: scytheWidth, height: scytheHeight, color: 0xCCCCCC, shape: 'box', alpha: 0 // Zacznij od przezroczystości 0 }); scytheAppearVisual.anchor.set(0.5, 0.5); scytheAppearVisual.x = spawnPosX; scytheAppearVisual.y = spawnPosY; game.addChild(scytheAppearVisual); tween(scytheAppearVisual, { alpha: 1 }, { duration: FADE_IN_DURATION, easing: tween.easeInQuad }); } laserWallInstance.segments.push({ isPlaceholder: false, initialOffsetX: 0, initialOffsetY: segmentOffsetY, width: scytheWidth, height: scytheHeight, visual: scytheAppearVisual, currentX: spawnPosX, currentY: spawnPosY, animationPhase: 'appearing_static' }); } LK.setTimeout(function () { if (laserWallInstance.isDead) { return; } laserWallInstance.segments.forEach(function (segment) { if (segment.isPlaceholder || segment.animationPhase !== 'appearing_static') { return; } if (segment.visual && segment.visual.parent) { segment.visual.parent.removeChild(segment.visual); segment.visual.destroy(); } var actionFrames = []; var actionAssetNames = ['scythe_action_0', 'scythe_action_1', 'scythe_action_2', 'scythe_action_3', 'scythe_action_4', 'scythe_action_5']; try { actionAssetNames.forEach(function (name) { actionFrames.push(LK.getAsset(name, { anchorX: 0.5, anchorY: 0.5, clone: true })); }); } catch (e) { console.error("Błąd ładowania klatek akcji kosy: ", e); } if (actionFrames.length === 0) { console.error("Brak klatek dla actionScytheAnim segmentu", segment); segment.visual = new Shape({ width: scytheWidth, height: scytheHeight, color: 0xFF0000, shape: 'box' }); segment.visual.anchor.set(0.5, 0.5); segment.visual.x = segment.currentX; segment.visual.y = segment.currentY; game.addChild(segment.visual); segment.animationPhase = 'error_action'; return; } var actionScytheAnim = new SpriteAnimation({ frames: actionFrames, frameDuration: frameDurationAction, loop: false, x: segment.currentX, y: segment.currentY, anchorX: 0.5, anchorY: 0.5 }); game.addChild(actionScytheAnim); segment.visual = actionScytheAnim; segment.animationPhase = 'action_once'; actionScytheAnim.play(); actionScytheAnim.onComplete = function () { if (segment.isPlaceholder || segment.animationPhase !== 'action_once' || laserWallInstance && laserWallInstance.isDead) { return; } if (segment.visual && segment.visual.parent) { segment.visual.parent.removeChild(segment.visual); segment.visual.destroy(); } var loopFrames = []; var loopAssetIndices = [2, 3, 4, 5]; var loopAssetNames = loopAssetIndices.map(function (idx) { return idx < actionAssetNames.length ? actionAssetNames[idx] : null; }).filter(function (name) { return name !== null; }); try { loopAssetNames.forEach(function (name) { loopFrames.push(LK.getAsset(name, { anchorX: 0.5, anchorY: 0.5, clone: true })); }); } catch (e) { console.error("Błąd ładowania klatek pętli kosy: ", e); } if (loopFrames.length === 0) { console.error("Brak klatek dla loopingScytheAnim segmentu", segment); segment.visual = new Shape({ width: scytheWidth, height: scytheHeight, color: 0xFF0000, shape: 'box' }); segment.visual.anchor.set(0.5, 0.5); segment.visual.x = segment.currentX; segment.visual.y = segment.currentY; game.addChild(segment.visual); segment.animationPhase = 'error_loop'; return; } var loopingScytheAnim = new SpriteAnimation({ frames: loopFrames, frameDuration: frameDurationLoop, loop: true, x: segment.currentX, y: segment.currentY, anchorX: 0.5, anchorY: 0.5 }); game.addChild(loopingScytheAnim); segment.visual = loopingScytheAnim; segment.animationPhase = 'looping_cut'; loopingScytheAnim.play(); }; }); }, warningDuration); if (gameState.cursedCrystalActiveLaserWalls === undefined) { gameState.cursedCrystalActiveLaserWalls = []; } gameState.cursedCrystalActiveLaserWalls.push(laserWallInstance); } self.currentAttackIndex = (self.currentAttackIndex + 1) % self.attackPattern.length; self.attackCooldown = self.attackCooldowns[self.currentAttackName] || 180; self.isCurrentlyPlayingAttackAnim = false; }; self.playMinibossSkillAnim(executeAttackLogic); }; self.teleport = function () { if (self.isDead || self.isTeleporting) { console.log("MinibossCC: Próba teleportacji, ale już martwy lub teleportuje. Dead: " + self.isDead + ", Teleporting: " + self.isTeleporting); return; } if (!self.canTeleportWithClones) { console.log("MinibossCC: Próba teleportacji z klonami, ale jest na cooldownie po klonach lub czeka na klony."); return; } self.canTeleportWithClones = false; self.isWaitingForClonesToDespawn = true; console.log("MinibossCC: TELEPORT START. Pozycja: X=" + self.x.toFixed(0) + ", Y=" + self.y.toFixed(0) + ", Alpha: " + self.alpha); self.isTeleporting = true; self.attackCooldown = 120 + Math.floor(Math.random() * 60); tween(self, { alpha: 0 }, { duration: 600, easing: tween.easeInQuad, onFinish: function onFinish() { if (self.isDead) { self.alpha = 1; self.isTeleporting = false; self.isWaitingForClonesToDespawn = false; self.canTeleportWithClones = true; console.log("MinibossCC: Teleport przerwany, boss martwy w trakcie fade-out."); return; } var halfWidth = self.actualWidth / 2; var halfHeight = self.actualHeight / 2; var arenaMinX = 50 + halfWidth; var arenaMaxX = 2048 - 50 - halfWidth; var arenaMinY = 50 + halfHeight; var arenaMaxY = 2732 - 50 - halfHeight; var newX, newY, distanceToPlayer; var attempts = 0; var minDistanceToPlayer = 300; do { newX = arenaMinX + Math.random() * (arenaMaxX - arenaMinX); newY = arenaMinY + Math.random() * (arenaMaxY - arenaMinY); if (typeof player !== 'undefined' && player && !player.dead) { distanceToPlayer = Math.sqrt(Math.pow(newX - player.x, 2) + Math.pow(newY - player.y, 2)); } else { distanceToPlayer = minDistanceToPlayer + 1; } attempts++; } while (distanceToPlayer < minDistanceToPlayer && attempts < 10); if (attempts >= 10 && distanceToPlayer < minDistanceToPlayer) { console.warn("MinibossCC: Nie udało się znaleźć pozycji wystarczająco daleko od gracza po 10 próbach."); } self.x = newX; self.y = newY; self.pickNewCreepDirection(); var clonesActuallySpawned = 0; var minClonesToSpawn = 1; var maxClonesToSpawn = 3; // Domyślnie 1-2 klony // Użyj jednej flagi, np. gameState.minibossIsEnhanced, lub sprawdź jedną z istniejących // Zakładam, że masz flagę np. gameState.minibossEnhancedProjectile lub stworzysz ogólną gameState.minibossIsEnhanced if (gameState.minibossEnhancedProjectile) { // Zmień ten warunek na swoją flagę ulepszenia bossa console.log("MinibossCC (Ulepszony): Tworzy więcej klonów przy teleportacji!"); minClonesToSpawn = 3; maxClonesToSpawn = 5; } var clonesToAttemptSpawn = minClonesToSpawn + Math.floor(Math.random() * (maxClonesToSpawn - minClonesToSpawn + 1)); console.log("MinibossCC będzie próbował stworzyć klonów:", clonesToAttemptSpawn); for (var i = 0; i < clonesToAttemptSpawn; i++) { if (self.activeClones.length < self.maxClones) { // Nadal respektujemy self.maxClones var cloneSpawnAttempts = 0; var cloneX, cloneY, distToBoss, distToPlayerClone; var minDistanceToBoss = 200; var minDistanceToPlayerForClone = 150; // Logika do...while do znajdowania pozycji klona POZOSTAJE BEZ ZMIAN do { cloneX = arenaMinX + Math.random() * (arenaMaxX - arenaMinX); cloneY = arenaMinY + Math.random() * (arenaMaxY - arenaMinY); distToBoss = Math.sqrt(Math.pow(cloneX - self.x, 2) + Math.pow(cloneY - self.y, 2)); if (typeof player !== 'undefined' && player && !player.dead) { distToPlayerClone = Math.sqrt(Math.pow(cloneX - player.x, 2) + Math.pow(cloneY - player.y, 2)); } else { distToPlayerClone = minDistanceToPlayerForClone + 1; } cloneSpawnAttempts++; } while ((distToBoss < minDistanceToBoss || distToPlayerClone < minDistanceToPlayerForClone) && cloneSpawnAttempts < 10); var clone = new MinibossCCClone({ x: cloneX, y: cloneY, owner: self }); game.addChild(clone); self.activeClones.push(clone); clonesActuallySpawned++; } else { console.log("MinibossCC: Osiągnięto maksymalną liczbę klonów (" + self.maxClones + "), nie można stworzyć więcej."); break; } } if (clonesActuallySpawned === 0 && self.isWaitingForClonesToDespawn) { console.log("MinibossCC: No clones were spawned, starting teleport cooldown immediately."); self.startTeleportCooldownAfterClones(); } self.teleportCooldownTimer = 0; self.nextTeleportTime = self.teleportIntervalMin + Math.floor(Math.random() * (self.teleportIntervalMax - self.teleportIntervalMin + 1)); tween(self, { alpha: 1 }, { duration: 600, easing: tween.easeOutQuad, onFinish: function onFinishFadeIn() { self.isTeleporting = false; console.log("MinibossCC: TELEPORT FADE_IN_FINISH. Następna normalna teleportacja za: " + (self.nextTeleportTime / 60).toFixed(1) + "s"); } }); } }); }; self.takeDamage = function (amount) { if (self.isDead) { return; } self.hp -= amount; gameState.cursedCrystalMinibossHP = self.hp; LK.effects.flashObject(self.graphics || self, 0xFF0000, 200); if (self.hp <= 0) { self.hp = 0; gameState.cursedCrystalMinibossHP = self.hp; self.die(); } if (ui && ui.updateMinibossHealthCC) { ui.updateMinibossHealthCC(self.hp, self.maxHp, true); } }; self.die = function () { if (self.isDead) { return; } if (self.cloneTeleportTimeoutId) { LK.clearTimeout(self.cloneTeleportTimeoutId); self.cloneTeleportTimeoutId = null; } self.isWaitingForClonesToDespawn = false; self.canTeleportWithClones = true; if (self.activeClones && self.activeClones.length > 0) { console.log("MinibossCC dying, clearing " + self.activeClones.length + " active clones."); var clonesToKill = self.activeClones.slice(); clonesToKill.forEach(function (clone) { if (clone && !clone.isDead) { clone.die(true); } }); self.activeClones = []; } self.isDead = true; console.log("MinibossCC has been defeated!"); if (gameState.cursedCrystalActiveProjectiles) { gameState.cursedCrystalActiveProjectiles.forEach(function (proj) {}); gameState.cursedCrystalActiveProjectiles = []; } if (gameState.cursedCrystalActiveExplosions) { gameState.cursedCrystalActiveExplosions = []; } if (gameState.cursedCrystalActiveLaserWalls) { gameState.cursedCrystalActiveLaserWalls.forEach(function (wall) {}); gameState.cursedCrystalActiveLaserWalls = []; } if (typeof gameState.endCursedCrystalMode === 'function') { gameState.endCursedCrystalMode(true); } if (self.parent) { self.parent.removeChild(self); } if (self.destroy && !self.destroyed) { self.destroy(); } gameState.cursedCrystalMinibossObject = null; gameState.isMinibossActiveCC = false; }; self.startTeleportCooldownAfterClones = function () { if (!self.isWaitingForClonesToDespawn && self.activeClones.length > 0) { return; } if (!self.isWaitingForClonesToDespawn && self.activeClones.length === 0) { return; } console.log("MinibossCC: All clones despawned. Starting " + self.teleportCloneCooldownDuration / 60 + "-second cooldown for clone teleport."); self.isWaitingForClonesToDespawn = false; if (self.cloneTeleportTimeoutId) { LK.clearTimeout(self.cloneTeleportTimeoutId); } self.cloneTeleportTimeoutId = LK.setTimeout(function () { self.cloneTeleportTimeoutId = null; if (!self.isDead) { self.canTeleportWithClones = true; self.nextTeleportTime = self.teleportCooldownTimer; console.log("MinibossCC: Clone teleport is OFF COOLDOWN. Boss can attempt teleport. nextTeleportTime now: " + self.nextTeleportTime); } }, self.teleportCloneCooldownDuration * (1000 / 60)); }; self.x = options.x || 2048 / 2; self.y = options.y || 300; self.pickNewCreepDirection(); return self; }); // -------- KONIEC ZAKTUALIZOWANEJ KLASY MinibossCC -------- // NOWA KLASA DLA KLONA MINIBOSSA CC var MinibossCCClone = Container.expand(function (options) { var self = Container.call(this); options = options || {}; self.owner = options.owner; self.x = options.x || 2048 / 2; self.y = options.y || 300; self.isDead = false; self.lifeTimer = 30 * 60; self.attackCooldown = 4 * 60 + Math.floor(Math.random() * (2 * 60)); self.activeSkillAnimationInstance = null; self.isCurrentlyPlayingAttackAnim = false; self.creepSpeed = 0.9; self.creepDirectionX = 0; self.creepDirectionY = 0; self.creepDirectionChangeIntervalMin = 2 * 60; self.creepDirectionChangeIntervalMax = 5 * 60; self.creepDirectionChangeTimer = 0; // --- DEFINICJA METODY pickNewCreepDirection PRZED PIERWSZYM UŻYCIEM --- self.pickNewCreepDirection = function () { var angle = Math.random() * 2 * Math.PI; self.creepDirectionX = Math.cos(angle); self.creepDirectionY = Math.sin(angle); self.creepDirectionChangeTimer = self.creepDirectionIntervalMin + Math.floor(Math.random() * (self.creepDirectionChangeIntervalMax - self.creepDirectionIntervalMin + 1)); }; // --- PIERWSZE WYWOŁANIE METODY --- self.pickNewCreepDirection(); try { self.graphics = self.attachAsset('miniboss_cc_asset', { anchorX: 0.5, anchorY: 0.5 }); } catch (e) { console.warn("Nie udało się załadować 'miniboss_cc_asset' dla Klona, używam Shape.", e); self.graphics = new Shape({ width: 120, height: 120, color: 0xFF8C00, shape: 'box' }); self.addChild(self.graphics); } self.width = self.graphics && typeof self.graphics.width !== 'undefined' ? self.graphics.width : 120; self.height = self.graphics && typeof self.graphics.height !== 'undefined' ? self.graphics.height : 120; self.createMinibossSkillAnim = function () { var frames = []; var frameBaseName = 'bossSkillAnim0'; var totalSkillFrames = 9; for (var i = 0; i < totalSkillFrames; i++) { var assetName = frameBaseName + (i === 0 ? '' : String(i)); try { frames.push(LK.getAsset(assetName, { anchorX: 0.5, anchorY: 0.5, clone: true })); } catch (e) { console.error("Błąd ładowania klatki animacji Klona: " + assetName, e); if (i > 0 && frames.length > 0) { frames.push(frames[0]); } else { var placeholderFrame = new Shape({ width: 120, height: 120, color: 0xFF00FF, shape: 'box' }); placeholderFrame.anchor.set(0.5, 0.5); frames.push(placeholderFrame); } } } for (var i = totalSkillFrames - 2; i >= 1; i--) { var assetName = frameBaseName + (i === 0 ? '' : String(i)); try { frames.push(LK.getAsset(assetName, { anchorX: 0.5, anchorY: 0.5, clone: true })); } catch (e) { console.error("Błąd ładowania klatki powrotnej animacji Klona: " + assetName, e); if (frames.length > 0) { frames.push(frames[0]); } } } var skillAnimationObject = new SpriteAnimation({ frames: frames, frameDuration: 120, loop: false, anchorX: 0.5, anchorY: 0.5, x: 0, y: 0 }); return skillAnimationObject; }; self.playMinibossSkillAnim = function (onAnimationCompleteCallback) { if (self.isDead) { self.isCurrentlyPlayingAttackAnim = false; if (typeof onAnimationCompleteCallback === 'function') { onAnimationCompleteCallback(); } return; } if (self.activeSkillAnimationInstance && self.activeSkillAnimationInstance.parent) { self.activeSkillAnimationInstance.stop(); self.removeChild(self.activeSkillAnimationInstance); self.activeSkillAnimationInstance.destroy(); self.activeSkillAnimationInstance = null; } if (self.graphics && !self.graphics.destroyed) { self.graphics.visible = false; } var newAnimation = self.createMinibossSkillAnim(); self.activeSkillAnimationInstance = self.addChild(newAnimation); self.activeSkillAnimationInstance.currentFrameIndex = 0; self.activeSkillAnimationInstance.animationTimer = 0; self.activeSkillAnimationInstance.framesArray = newAnimation.frames; self.activeSkillAnimationInstance.singleFrameDuration = newAnimation.frameDuration || 120; self.activeSkillAnimationInstance.playing = true; self.activeSkillAnimationInstance.removeChildren(); if (self.activeSkillAnimationInstance.framesArray && self.activeSkillAnimationInstance.framesArray.length > 0) { self.activeSkillAnimationInstance.addChild(self.activeSkillAnimationInstance.framesArray[0]); } else { self.isCurrentlyPlayingAttackAnim = false; if (self.graphics && !self.graphics.destroyed) { self.graphics.visible = true; } if (typeof onAnimationCompleteCallback === 'function') { onAnimationCompleteCallback(); } if (self.activeSkillAnimationInstance) { if (self.activeSkillAnimationInstance.parent) { self.removeChild(self.activeSkillAnimationInstance); } self.activeSkillAnimationInstance.destroy(); self.activeSkillAnimationInstance = null; } return; } self.activeSkillAnimationInstance.update = function () { if (!this.playing || !this.framesArray || this.framesArray.length === 0) { return; } this.animationTimer++; if (this.animationTimer * (1000 / 60) >= this.singleFrameDuration) { this.animationTimer = 0; this.currentFrameIndex++; if (this.currentFrameIndex >= this.framesArray.length) { this.playing = false; if (self.graphics && !self.graphics.destroyed) { self.graphics.visible = true; } if (this.parent) { this.parent.removeChild(this); } this.destroy(); if (self.activeSkillAnimationInstance === this) { self.activeSkillAnimationInstance = null; } if (typeof onAnimationCompleteCallback === 'function') { onAnimationCompleteCallback(); } else { self.isCurrentlyPlayingAttackAnim = false; } } else { this.removeChildren(); if (this.framesArray[this.currentFrameIndex]) { this.addChild(this.framesArray[this.currentFrameIndex]); } } } }; }; self.takeDamage = function (amount) { if (self.isDead) { return; } LK.effects.flashObject(self.graphics || self, 0xFFFFFF, 150); self.die(); }; self.die = function (isTimeout) { if (self.isDead) { return; } self.isDead = true; if (!isTimeout) { LK.effects.flashObject(self.graphics || self, 0xFFFFFF, 150); } if (self.owner && self.owner.activeClones) { var index = self.owner.activeClones.indexOf(self); if (index > -1) { self.owner.activeClones.splice(index, 1); if (self.owner.activeClones.length === 0 && self.owner.isWaitingForClonesToDespawn) { self.owner.startTeleportCooldownAfterClones(); } } } var deathTween = tween(self, { alpha: 0 }, { duration: 800, easing: tween.easeOutQuad, onFinish: function onFinish() { if (self.parent) { self.parent.removeChild(self); } if (self.destroy && !self.destroyed) { self.destroy(); } } }); }; self.performSimpleAttack = function () { if (self.isDead || !player || player.dead) { self.isCurrentlyPlayingAttackAnim = false; return; } var executeCloneAttackLogic = function executeCloneAttackLogic() { if (self.isDead) { self.isCurrentlyPlayingAttackAnim = false; return; } var projectile = new MinibossExplosiveProjectile({ x: self.x, y: self.y, target: player, speed: 4, explosionRadius: 150 }); game.addChild(projectile); if (gameState.cursedCrystalActiveProjectiles === undefined) { gameState.cursedCrystalActiveProjectiles = []; } gameState.cursedCrystalActiveProjectiles.push(projectile); self.attackCooldown = 8 * 60 + Math.floor(Math.random() * (2 * 60)); self.isCurrentlyPlayingAttackAnim = false; }; self.playMinibossSkillAnim(executeCloneAttackLogic); }; self.update = function () { if (self.isDead || gameState.currentState !== "cursedCrystal") { return; } self.lifeTimer--; if (self.lifeTimer <= 0) { self.die(true); return; } if (self.activeSkillAnimationInstance) {} else { self.creepDirectionChangeTimer--; if (self.creepDirectionChangeTimer <= 0) { self.pickNewCreepDirection(); } var nextX = self.x + self.creepDirectionX * self.creepSpeed; var nextY = self.y + self.creepDirectionY * self.creepSpeed; var halfWidth = (self.width || 120) / 2; var halfHeight = (self.height || 120) / 2; var arenaMinX = 50 + halfWidth; var arenaMaxX = 2048 - 50 - halfWidth; var arenaMinY = 50 + halfHeight; var arenaMaxY = 2732 - 50 - halfHeight; if (nextX >= arenaMinX && nextX <= arenaMaxX) { self.x = nextX; } else { self.creepDirectionX *= -1; self.creepDirectionChangeTimer = self.creepDirectionIntervalMin; } if (nextY >= arenaMinY && nextY <= arenaMaxY) { self.y = nextY; } else { self.creepDirectionY *= -1; self.creepDirectionChangeTimer = self.creepDirectionIntervalMin; } } if (self.attackCooldown > 0) { self.attackCooldown--; } else if (!self.isCurrentlyPlayingAttackAnim && !self.activeSkillAnimationInstance) { self.isCurrentlyPlayingAttackAnim = true; self.performSimpleAttack(); } }; self.alpha = 0; self.scaleX = 0.5; self.scaleY = 0.5; tween(self, { alpha: 1, scaleX: 1, scaleY: 1 }, { duration: 500, easing: tween.easeOutQuad }); return self; }); // -------- KONIEC ZAKTUALIZOWANEJ KLASY MinibossCC -------- var MinibossExplosiveProjectile = Container.expand(function (options) { var self = Container.call(this); options = options || {}; self.x = options.x || 0; self.y = options.y || 0; self.speed = options.speed || 6; self.target = options.target; self.lifeTimer = 240; self.isDead = false; self.hasExploded = false; self.explosionRadius = options.explosionRadius || 100; self.explosionDamage = 1; try { self.graphics = self.attachAsset('projectile_spinning_asset', { // UŻYJ NOWEGO ASSETU DLA POCISKU anchorX: 0.5, anchorY: 0.5, width: 140, height: 140 }); } catch (e) { console.warn("Nie udało się załadować 'projectile_spinning_asset', używam Shape.", e); self.graphics = new Shape({ width: 40, height: 40, color: 0xFF4500, shape: 'ellipse' }); self.addChild(self.graphics); } self.width = self.graphics.width; self.height = self.graphics.height; self.update = function () { if (self.isDead || self.hasExploded || !self.target || self.target.dead) { if (!self.hasExploded && !self.isDead) { self.isDead = true; } return; } self.lifeTimer--; var dx = self.target.x - self.x; var dy = self.target.y - self.y; var distanceToTarget = Math.sqrt(dx * dx + dy * dy); if (distanceToTarget > 0) { var moveX = dx / distanceToTarget * self.speed; var moveY = dy / distanceToTarget * self.speed; self.x += moveX; self.y += moveY; } if (self.graphics) { // Dodajemy rotację pocisku self.graphics.rotation += 0.1; // Dostosuj prędkość obrotu } var playerRadius = (self.target.width || 150) / 2 * 0.7; var projectileRadius = self.width / 2; if (distanceToTarget < playerRadius + projectileRadius) { self.explode(); return; } if (self.lifeTimer <= 0) { self.explode(); return; } }; self.explode = function () { if (self.hasExploded) { return; } self.hasExploded = true; self.isDead = true; if (self.graphics && self.graphics.parent) { self.graphics.parent.removeChild(self.graphics); self.graphics.destroy(); self.graphics = null; } var explosionFramesAssets = []; for (var i = 0; i < 7; i++) { try { explosionFramesAssets.push(LK.getAsset('explosion_frame_' + i, { anchorX: 0.5, anchorY: 0.5, clone: true })); } catch (e) { console.error("Błąd ładowania klatki eksplozji: explosion_frame_" + i, e); var placeholderExplosionFrame = new Shape({ width: 50, height: 50, color: 0xFF8C00, shape: 'ellipse' }); placeholderExplosionFrame.anchor.set(0.5, 0.5); explosionFramesAssets.push(placeholderExplosionFrame); } } if (explosionFramesAssets.length < 7) { // Potrzebujemy wszystkich 7 klatek do tej logiki console.error("Nie załadowano wystarczającej liczby klatek eksplozji."); if (gameState && gameState.cursedCrystalActiveExplosions) { gameState.cursedCrystalActiveExplosions.push({ x: self.x, y: self.y, radius: self.explosionRadius, damage: self.explosionDamage, durationTimer: 30, hitPlayerThisFrame: false }); } return; } var explosionDisplay = game.addChild(new Container()); explosionDisplay.x = self.x; explosionDisplay.y = self.y; var currentFrameGfx = null; var INITIAL_SCALE = 0.2; var MAX_SCALE = self.explosionRadius * 3.0 / (explosionFramesAssets[0] ? explosionFramesAssets[0].width : 50); // ZMIENIONA LINIA (1.5 -> 3.0) var EXP_FRAME_DURATION_MS = 70; var LOOP_FRAME_DURATION_MS = 90; var SHRINK_FADE_DURATION_MS = 600; var totalDurationMs = 2000; var expansionPhaseDuration = 7 * EXP_FRAME_DURATION_MS; // 7 klatek * 70ms = 490ms var loopPhaseDuration = totalDurationMs - expansionPhaseDuration - SHRINK_FADE_DURATION_MS; if (loopPhaseDuration < 0) { // Jeśli nie ma czasu na pętlę, skróć inne fazy lub ustaw min. czas pętli loopPhaseDuration = Math.max(0, LOOP_FRAME_DURATION_MS * 4); // np. przynajmniej jedna pełna pętla 4 klatek SHRINK_FADE_DURATION_MS = Math.max(200, totalDurationMs - expansionPhaseDuration - loopPhaseDuration); } var displayFrame = function displayFrame(frameAsset, scale, alpha) { if (currentFrameGfx && currentFrameGfx.parent) { explosionDisplay.removeChild(currentFrameGfx); // Nie niszczymy frameAsset, bo to klon z puli LK.getAsset } if (frameAsset) { currentFrameGfx = explosionDisplay.addChild(frameAsset); currentFrameGfx.scale.set(scale); currentFrameGfx.alpha = alpha === undefined ? 1 : alpha; } else { currentFrameGfx = null; } }; var currentExpansionFrame = 0; function expandAnimation() { if (currentExpansionFrame < 7) { var progress = currentExpansionFrame / 6.0; var scale = INITIAL_SCALE + (MAX_SCALE - INITIAL_SCALE) * progress; displayFrame(explosionFramesAssets[currentExpansionFrame], Math.max(INITIAL_SCALE, scale)); currentExpansionFrame++; LK.setTimeout(expandAnimation, EXP_FRAME_DURATION_MS); } else { startLoopingPhase(); } } var loopFramesIndices = [3, 4, 5, 6]; // Klatki 3, 4, 5, 6 do pętli var currentLoopArrayIndex = 0; var loopEndTime; function startLoopingPhase() { if (loopPhaseDuration <= 0) { startShrinkingPhase(); return; } loopEndTime = Date.now() + loopPhaseDuration; currentLoopArrayIndex = 0; // Zacznij od pierwszej klatki pętli (indeks 3 globalnie) loopAnimation(); } function loopAnimation() { if (Date.now() < loopEndTime && explosionDisplay && !explosionDisplay.destroyed) { displayFrame(explosionFramesAssets[loopFramesIndices[currentLoopArrayIndex % loopFramesIndices.length]], MAX_SCALE); currentLoopArrayIndex++; LK.setTimeout(loopAnimation, LOOP_FRAME_DURATION_MS); } else { startShrinkingPhase(); } } function startShrinkingPhase() { if (!currentFrameGfx || !currentFrameGfx.parent || explosionDisplay && explosionDisplay.destroyed) { if (explosionDisplay && !explosionDisplay.destroyed) { explosionDisplay.destroy(); } return; } // Aby zmniejszać ostatnio wyświetloną klatkę (lub konkretną np. klatkę 6 jako bazę) // Dla pewności, że mamy co skalować, możemy ponownie wyświetlić ostatnią klatkę pętli (np. klatkę 6) // jeśli currentFrameGfx mógł zostać usunięty lub jest nieoczekiwany. // Jeśli currentFrameGfx jest już ostatnią klatką pętli, to dobrze. // Jeśli chcemy zawsze zmniejszać np. klatkę nr 6: // displayFrame(explosionFramesAssets[6], MAX_SCALE); tween(currentFrameGfx, { scaleX: 0.01, scaleY: 0.01, alpha: 0 }, { duration: SHRINK_FADE_DURATION_MS, easing: tween.easeInQuad, onFinish: function onFinish() { if (explosionDisplay && !explosionDisplay.destroyed) { explosionDisplay.destroy(); } } }); } expandAnimation(); // Rozpocznij pierwszą fazę if (gameState && gameState.cursedCrystalActiveExplosions) { gameState.cursedCrystalActiveExplosions.push({ x: self.x, y: self.y, radius: self.explosionRadius, damage: self.explosionDamage, durationTimer: Math.round(totalDurationMs / (1000 / 60)), // Czas trwania logicznej eksplozji = ~2 sekundy hitPlayerThisFrame: false }); } }; return self; }); // Zamykająca klamra dla Container.expand klasy Boss // --- POCZĄTEK BLOKU DO SKOPIOWANIA --- var Player = Container.expand(function () { var self = Container.call(this); if (gameState.currentState === "cursedCrystal") { console.log("Player Update - Cursed Crystal Tick, Rolling: " + this.rolling + ", Cooldown: " + this.rollCooldown); } else if (gameState.currentState === "game") { console.log("Player Update - Game Tick, Rolling: " + this.rolling + ", Cooldown: " + this.rollCooldown); } // --- Animacja Idle --- var idleFrames = [LK.getAsset('player', { anchorX: 0.5, anchorY: 0.5 }), LK.getAsset('player1', { anchorX: 0.5, anchorY: 0.5 }), LK.getAsset('player2', { anchorX: 0.5, anchorY: 0.5 })]; self.idleAnimationSprite = new SpriteAnimation({ frames: idleFrames, frameDuration: 400, loop: true, anchorX: 0.5, anchorY: 0.5, x: 0, y: 0 }); self.addChild(self.idleAnimationSprite); self.idleAnimationSprite.play(); // --- Właściwości Gracza --- self.health = 5; self.speed = 8; self.rolling = false; self.rollDirection = { x: 0, y: 0 }; self.rollSpeed = 20; self.rollDuration = 300; self.rollCooldown = 0; self.invulnerable = false; self.invulnerabilityFrames = 0; self.defaultInvulnerabilityFrames = 30; self.postHitInvulnerabilityTimer = null; self.dead = false; self.rollTimeoutId = null; self.invulnerabilityTimeoutId = null; self.rollAnimationInterval = null; self.hasRolledThroughBossThisRoll = false; // --- Funkcje Pomocnicze --- // *** TO JEST DEFINICJA FUNKCJI, KTÓREJ BRAKUJE *** self.clearRollTimeouts = function () { if (self.rollTimeoutId) { LK.clearTimeout(self.rollTimeoutId); self.rollTimeoutId = null; } if (self.rollAnimationInterval) { LK.clearInterval(self.rollAnimationInterval); self.rollAnimationInterval = null; } if (self.postHitInvulnerabilityTimer) { LK.clearTimeout(self.postHitInvulnerabilityTimer); self.postHitInvulnerabilityTimer = null; } }; // *** KONIEC DEFINICJI clearRollTimeouts *** // --- Mechanika Uniku (Roll) --- self.roll = function (direction, duration) { if (!self.rolling && self.rollCooldown <= 0 && !self.dead) { var targetScaleX = direction.x >= 0 || direction.x === 0 ? 1 : -1; var currentRollDuration = duration || self.rollDuration; self.rolling = true; self.hasRolledThroughMinibossCCThisRoll = false; self.rollDirection = direction; self.rollCooldown = 45; var durationRatio = currentRollDuration / self.rollDuration; self.invulnerabilityFrames = Math.round(self.defaultInvulnerabilityFrames * durationRatio); self.hasRolledThroughBossThisRoll = false; if (self.idleAnimationSprite && !self.idleAnimationSprite.destroyed) { self.idleAnimationSprite.stop(); self.idleAnimationSprite.visible = false; } var rollFrames = ['roll', 'roll0', 'roll1', 'roll2']; var currentFrame = 0; if (self.rollAnimationInterval) { LK.clearInterval(self.rollAnimationInterval); } var rollAnimationSprite = null; if (self && !self.destroyed) { rollAnimationSprite = self.addChild(LK.getAsset(rollFrames[currentFrame], { anchorX: 0.5, anchorY: 0.5, x: 0, y: 0, scaleX: targetScaleX })); } self.rollAnimationInterval = LK.setInterval(function () { if (!self || self.destroyed) { LK.clearInterval(self.rollAnimationInterval); self.rollAnimationInterval = null; return; } if (rollAnimationSprite && rollAnimationSprite.destroy) { rollAnimationSprite.destroy(); rollAnimationSprite = null; } currentFrame = (currentFrame + 1) % rollFrames.length; if (self && !self.destroyed) { rollAnimationSprite = self.addChild(LK.getAsset(rollFrames[currentFrame], { anchorX: 0.5, anchorY: 0.5, x: 0, y: 0, scaleX: targetScaleX })); } }, 70); if (self.rollTimeoutId) { LK.clearTimeout(self.rollTimeoutId); } self.rollTimeoutId = LK.setTimeout(function () { if (!self || self.destroyed) { return; } self.rolling = false; if (self.rollAnimationInterval) { LK.clearInterval(self.rollAnimationInterval); self.rollAnimationInterval = null; } if (rollAnimationSprite && rollAnimationSprite.destroy) { rollAnimationSprite.destroy(); rollAnimationSprite = null; } var standUpFrames = ['roll3', 'roll4']; var standUpFrame = 0; var standUpSprite = null; if (self && !self.destroyed) { standUpSprite = self.addChild(LK.getAsset(standUpFrames[standUpFrame], { anchorX: 0.5, anchorY: 0.5, x: 0, y: 0, scaleX: targetScaleX })); } var standUpInterval = LK.setInterval(function () { if (!self || self.destroyed) { LK.clearInterval(standUpInterval); return; } if (standUpSprite && standUpSprite.destroy) { standUpSprite.destroy(); standUpSprite = null; } standUpFrame++; if (standUpFrame < standUpFrames.length) { if (self && !self.destroyed) { standUpSprite = self.addChild(LK.getAsset(standUpFrames[standUpFrame], { anchorX: 0.5, anchorY: 0.5, x: 0, y: 0, scaleX: targetScaleX })); } } else { LK.clearInterval(standUpInterval); standUpInterval = null; if (standUpSprite && standUpSprite.destroy) { standUpSprite.destroy(); standUpSprite = null; } if (self.idleAnimationSprite && !self.idleAnimationSprite.destroyed) { self.idleAnimationSprite.scaleX = targetScaleX; self.idleAnimationSprite.visible = true; self.idleAnimationSprite.play(); } } }, 100); self.rollTimeoutId = null; }, currentRollDuration); } }; // --- Otrzymywanie Obrażeń --- self.takeDamage = function (amount) { var timestamp = Date.now(); var currentHealthBeforeDamage = this.health; console.log("[Player.takeDamage] Called at: " + timestamp + " | Amount: " + amount + " | Invulnerable: " + this.invulnerable + " | Dead: " + this.dead + " | Health BEFORE: " + currentHealthBeforeDamage + " | Current State: " + gameState.currentState); if (!this.invulnerable && !this.dead) { if (gameState.currentState === "cursedCrystal") { gameState.cursedCrystalPlayerHealth -= amount; gameState.cursedCrystalPlayerHealth = Math.max(0, gameState.cursedCrystalPlayerHealth); this.health = gameState.cursedCrystalPlayerHealth; console.log(" [CursedCrystal DMG] New Health (gameState & this.health): " + this.health + " at " + timestamp); if (ui && ui.updateHearts) { ui.updateHearts(gameState.cursedCrystalPlayerHealth, gameState.cursedCrystalPlayerMaxHealth); } } else { this.health -= amount; this.health = Math.max(0, this.health); console.log(" [DMG in " + gameState.currentState + "] New Health (this.health): " + this.health + " at " + timestamp); } if (this.health < currentHealthBeforeDamage) { LK.effects.flashObject(this, 0xFF0000, 200); } if (this.health <= 0) { console.log(" Player health is <= 0 at " + timestamp + ". Calling this.die()."); this.die(); return; } this.invulnerable = true; console.log(" Player.invulnerable SET TO TRUE at " + timestamp + ". IMMEDIATELY CHECKING: player.invulnerable is " + this.invulnerable); if (this.postHitInvulnerabilityTimer) { LK.clearTimeout(this.postHitInvulnerabilityTimer); this.postHitInvulnerabilityTimer = null; } var playerInstance = this; this.postHitInvulnerabilityTimer = LK.setTimeout(function () { var endInvulnerableTimestamp = Date.now(); if (!playerInstance || playerInstance.destroyed) { console.log(" Player invulnerability timeout: player destroyed or null at " + endInvulnerableTimestamp); return; } playerInstance.invulnerable = false; console.log(" Player.invulnerable SET TO FALSE at " + endInvulnerableTimestamp + " (after 2000ms)"); var currentVisualSprite = null; if (playerInstance.rolling && playerInstance.children.length > 0 && playerInstance.children[0] !== playerInstance.idleAnimationSprite) {} else if (playerInstance.idleAnimationSprite && playerInstance.idleAnimationSprite.visible && playerInstance.idleAnimationSprite.children.length > 0) { currentVisualSprite = playerInstance.idleAnimationSprite.children[0]; } if (currentVisualSprite && currentVisualSprite.alpha !== 1) { currentVisualSprite.alpha = 1; } playerInstance.postHitInvulnerabilityTimer = null; }, 2000); } else { console.log("[Player.takeDamage] SKIPPED at: " + timestamp + " (Invulnerable: " + this.invulnerable + ", Dead: " + this.dead + ")"); } }; // --- Śmierć (z poprawkami dla storage i onFinish) --- self.die = function () { console.log("!!!! Player.die() called! State:", gameState.currentState, "Current Health:", self.health); if (self.dead) { console.log("Player.die() exited - already dead."); return; } self.dead = true; try { self.clearRollTimeouts(); } catch (e) { console.error("!!! Błąd podczas wywoływania self.clearRollTimeouts w Player.die:", e); } if (gameState.currentState === "cursedCrystal") { console.log("Player died in Cursed Crystal mode. Calling gameState.endCursedCrystalMode(false) via tween."); tween(self, { alpha: 0, scaleX: self.scaleX * 1.2, scaleY: self.scaleY * 1.2 }, { duration: 1000, easing: tween.easeOut, onFinish: function onFinish() { console.log("Player.die tween (Cursed Crystal) onFinish."); if (typeof gameState.endCursedCrystalMode === 'function') { gameState.endCursedCrystalMode(false); } else { console.error("gameState.endCursedCrystalMode is not defined!"); if (typeof gameState.showGrillScreen === 'function') { gameState.showGrillScreen(); } } } }); return; } var currentDeaths = parseInt(storage.totalDeaths, 10) || 0; storage.totalDeaths = currentDeaths + 1; console.log("Updated totalDeaths to:", storage.totalDeaths); var currentMaxHearts = parseInt(storage.maxHearts, 10); if (isNaN(currentMaxHearts) || currentMaxHearts < 5) { storage.maxHearts = 5; } if (storage.maxHearts < 10) { storage.maxHearts = (parseInt(storage.maxHearts, 10) || 5) + 1; } console.log("Updated maxHearts to:", storage.maxHearts); tween(self, { alpha: 0, scaleX: self.scaleX * 1.2, scaleY: self.scaleY * 1.2 }, { duration: 1000, easing: tween.easeOut, onFinish: function onFinish() { console.log("Player.die tween (Non-CC) onFinish. gameState.currentState:", gameState.currentState); if (gameState.currentState === "game") { console.log("Player.die (game mode) onFinish - Calling gameState.gameOver(true)."); if (typeof boss !== 'undefined' && boss && typeof boss.clearAllAttacks === 'function') { boss.clearAllAttacks("Player.die -> tutorial gameOver"); } if (typeof gameState.gameOver === 'function') { gameOverReasonIsDeath = true; gameState.gameOver(true); } else { console.error("gameState.gameOver() is not defined for 'game' mode death!"); if (typeof gameState.showGrillScreen === 'function') { gameState.showGrillScreen(); } } } else if (gameState.currentState === "rollMaster" || gameState.currentState === "rollMasterGameOver") { console.log("Player.die (RollMaster or rollMasterGameOver state) onFinish. No further action needed from Player.die, endRollMasterMode handles the rest."); } else if (gameState.currentState === "gameOver") { console.log("Player.die onFinish while gameState.currentState is already 'gameOver'. No further action needed from Player.die."); } } }); }; // Koniec self.die // --- Aktualizacja (Update) --- self.update = function () { if (gameState.currentState === "cursedCrystal") { //console.log("Player Update Tick - CC - Health: " + self.health + ", RollCD: " + self.rollCooldown + ", Rolling: " + self.rolling + ", Invuln: " + self.invulnerable + ", InvulnFrames: " + self.invulnerabilityFrames); } if (gameState.currentState !== "game" && gameState.currentState !== "rollMaster" && gameState.currentState !== "cursedCrystal") { if (self.rolling) { self.rolling = false; self.invulnerabilityFrames = 0; if (self.idleAnimationSprite && !self.idleAnimationSprite.destroyed) { self.idleAnimationSprite.visible = true; self.idleAnimationSprite.play(); if (self.children.length > 1) { for (var i = self.children.length - 1; i >= 0; i--) { if (self.children[i] !== self.idleAnimationSprite && self.children[i].destroy) { self.children[i].destroy(); } } } } } self.clearRollTimeouts(); return; } if (self.dead) { return; } if (self.rollCooldown > 0) { self.rollCooldown--; } var currentVisualSprite = null; if (self.rolling) { for (var i = 0; i < self.children.length; i++) { if (self.children[i] !== self.idleAnimationSprite) { currentVisualSprite = self.children[i]; break; } } } else if (self.idleAnimationSprite && self.idleAnimationSprite.visible && self.idleAnimationSprite.children.length > 0) { currentVisualSprite = self.idleAnimationSprite.children[0]; } if (self.rolling) { if (self.invulnerabilityFrames > 0) { self.invulnerabilityFrames--; if (currentVisualSprite) { currentVisualSprite.alpha = self.invulnerabilityFrames % 10 < 5 ? 0.3 : 1; } } else { if (currentVisualSprite) { currentVisualSprite.alpha = 1; } } var rollDx = self.rollDirection.x * self.rollSpeed; var rollDy = self.rollDirection.y * self.rollSpeed; var nextX = self.x + rollDx; var nextY = self.y + rollDy; var pWidth = currentVisualSprite ? currentVisualSprite.width * Math.abs(currentVisualSprite.scaleX || 1) : self.width; var pHeight = currentVisualSprite ? currentVisualSprite.height * (currentVisualSprite.scaleY || 1) : self.height; var halfWidth = pWidth / 2; var halfHeight = pHeight / 2; var minXBoundary = halfWidth; var maxXBoundary = 2048 - halfWidth; var minYBoundary = halfHeight; var maxYBoundary = 2732 - halfHeight; nextX = Math.max(minXBoundary, Math.min(nextX, maxXBoundary)); nextY = Math.max(minYBoundary, Math.min(nextY, maxYBoundary)); if (gameState.currentState === "cursedCrystal" && typeof crystalCoreObject !== 'undefined' && crystalCoreObject && !crystalCoreObject.destroyed) { var crystalCX = crystalCoreObject.x; var crystalCY = crystalCoreObject.y; var crystalRadius = crystalCoreObject.collisionRadius || 50; var distToCrystalHorizontal = Math.abs(nextX - crystalCX); var distToCrystalVertical = Math.abs(nextY - crystalCY); if (distToCrystalHorizontal < halfWidth + crystalRadius && distToCrystalVertical < halfHeight + crystalRadius) { if (self.x < crystalCX && nextX > self.x) { nextX = crystalCX - crystalRadius - halfWidth - 1; } else if (self.x > crystalCX && nextX < self.x) { nextX = crystalCX + crystalRadius + halfWidth + 1; } if (self.y < crystalCY && nextY > self.y) { nextY = crystalCY - crystalRadius - halfHeight - 1; } else if (self.y > crystalCY && nextY < self.y) { nextY = crystalCY + crystalRadius + halfHeight + 1; } } } self.x = nextX; self.y = nextY; if (gameState.currentState === "game" && boss && !boss.dead && !self.hasRolledThroughBossThisRoll) { var dx_b = self.x - boss.x; var dy_b = self.y - boss.y; var distance_b = Math.sqrt(dx_b * dx_b + dy_b * dy_b); var playerCollisionRadius = pWidth / 2 * 0.8; var hitboxMultiplier = 1.2; var bossCollisionRadius = (boss.width || 180) * (boss.scaleX || 1) / 2 * hitboxMultiplier; if (distance_b < playerCollisionRadius + bossCollisionRadius) { boss.takeDamage(10); self.hasRolledThroughBossThisRoll = true; LK.effects.flashObject(boss, 0x00FF00, 200); } } if (gameState.currentState === "cursedCrystal" && gameState.isMinibossActiveCC === true && gameState.cursedCrystalMinibossObject && !gameState.cursedCrystalMinibossObject.isDead && !self.hasRolledThroughMinibossCCThisRoll) { var minibossCC = gameState.cursedCrystalMinibossObject; var dx_mcc = self.x - minibossCC.x; var dy_mcc = self.y - minibossCC.y; var distance_mcc = Math.sqrt(dx_mcc * dx_mcc + dy_mcc * dy_mcc); var playerRollRadius = pWidth / 2 * 0.8; var minibossCCRadius = (minibossCC.width || 120) / 2 * 0.9; if (distance_mcc < playerRollRadius + minibossCCRadius) { minibossCC.takeDamage(10); self.hasRolledThroughMinibossCCThisRoll = true; console.log("Player rolled through Miniboss CC and dealt damage!"); } } } else if (self.invulnerable && !self.rolling) { if (currentVisualSprite) { currentVisualSprite.alpha = Math.floor(Date.now() / 100) % 4 > 1 ? 0.3 : 1; } } else { if (currentVisualSprite && currentVisualSprite.alpha !== 1) { currentVisualSprite.alpha = 1; } if (gameState.isInputActive && (gameState.currentState === "game" || gameState.currentState === "rollMaster" || gameState.currentState === "cursedCrystal") && player && !player.dead) { var targetX = gameState.currentInputPos.x; var targetY = gameState.currentInputPos.y; var dx_m = targetX - self.x; var dy_m = targetY - self.y; var distance_m = Math.sqrt(dx_m * dx_m + dy_m * dy_m); var moveSpeed = 3; if (distance_m > moveSpeed) { var normalizedX = dx_m / distance_m; var normalizedY = dy_m / distance_m; var nextPlayerX = self.x + normalizedX * moveSpeed; var nextPlayerY = self.y + normalizedY * moveSpeed; var pWidthIdle = self.idleAnimationSprite.children[0] ? self.idleAnimationSprite.children[0].width * (self.idleAnimationSprite.scaleX || 1) : self.width; var pHeightIdle = self.idleAnimationSprite.children[0] ? self.idleAnimationSprite.children[0].height * (self.idleAnimationSprite.scaleY || 1) : self.height; var halfWidthIdle = pWidthIdle / 2; var halfHeightIdle = pHeightIdle / 2; var minXBoundaryIdle = halfWidthIdle; var maxXBoundaryIdle = 2048 - halfWidthIdle; var minYBoundaryIdle = halfHeightIdle; var maxYBoundaryIdle = 2732 - halfHeightIdle; nextPlayerX = Math.max(minXBoundaryIdle, Math.min(nextPlayerX, maxXBoundaryIdle)); nextPlayerY = Math.max(minYBoundaryIdle, Math.min(nextPlayerY, maxYBoundaryIdle)); if (gameState.currentState === "cursedCrystal" && typeof crystalCoreObject !== 'undefined' && crystalCoreObject && !crystalCoreObject.destroyed) { var crystalCXIdle = crystalCoreObject.x; var crystalCYIdle = crystalCoreObject.y; var crystalRadiusIdle = crystalCoreObject.collisionRadius || 50; var distToCrystalHorizontalIdle = Math.abs(nextPlayerX - crystalCXIdle); var distToCrystalVerticalIdle = Math.abs(nextPlayerY - crystalCYIdle); if (distToCrystalHorizontalIdle < halfWidthIdle + crystalRadiusIdle && distToCrystalVerticalIdle < halfHeightIdle + crystalRadiusIdle) {} else { self.x = nextPlayerX; self.y = nextPlayerY; } } else { self.x = nextPlayerX; self.y = nextPlayerY; } if (self.idleAnimationSprite && !self.idleAnimationSprite.destroyed) { if (normalizedX > 0.1) { self.idleAnimationSprite.scaleX = 1; } else if (normalizedX < -0.1) { self.idleAnimationSprite.scaleX = -1; } } } } } if (self.idleAnimationSprite && self.idleAnimationSprite.visible && !self.idleAnimationSprite.destroyed && typeof self.idleAnimationSprite.update === 'function') { if (gameState.currentState === "cursedCrystal") {} self.idleAnimationSprite.update(); } }; // Ten nawias klamrowy zamyka funkcję self.update = function () { ... }; return self; }); // Koniec Player // --- KONIEC BLOKU DO SKOPIOWANIA --- // Koniec Player // <--- To zamyka Container.expand dla gracza var Shape = Container.expand(function (options) { var self = Container.call(this); // <--- Tutaj zaczyna się Shape // ... options = options || {}; var width = options.width || 100; var height = options.height || 100; var color = options.color || 0xFFFFFF; var shape = options.shape || 'box'; // Create the shape as an asset var asset = self.attachAsset(shape, { anchorX: 0.5, anchorY: 0.5, width: width, height: height, tint: color }); // Set width and height for easier access self.width = width; self.height = height; // Add anchor property to Shape for positioning self.anchor = { set: function set(x, y) { // This mimics the behavior of the anchor.set method self.anchorX = x; self.anchorY = y; } }; return self; }); var SoulEnemy = Container.expand(function (options) { var self = Container.call(this); options = options || {}; self.hp = options.hp || 1; self.speed = options.speed || 2; self.isDead = false; self.targetX = options.targetX || 2048 / 2; self.targetY = options.targetY || 2732 / 2; var animFrames = []; for (var i = 0; i < 6; i++) { // 6 klatek animacji try { animFrames.push(LK.getAsset('soul_anim_' + i, { anchorX: 0.5, anchorY: 0.5, clone: true })); } catch (e) { console.error("Błąd ładowania klatki animacji duszy: soul_anim_" + i, e); // Awaryjny placeholder, jeśli klatka się nie załaduje var placeholderFrame = new Shape({ width: options.width || 30, height: options.height || 30, color: 0xFF00FF, shape: 'ellipse' }); placeholderFrame.anchor.set(0.5, 0.5); animFrames.push(placeholderFrame); } } if (animFrames.length > 0) { self.animation = new SpriteAnimation({ frames: animFrames, frameDuration: 120, // Czas trwania klatki w ms - dostosuj loop: true, anchorX: 0.5, anchorY: 0.5 }); self.addChild(self.animation); self.animation.play(); } else { // Jeśli nie udało się załadować żadnej klatki, stwórz awaryjny kształt console.error("Nie udało się załadować żadnej klatki dla SoulEnemy. Tworzenie awaryjnego kształtu."); self.fallbackGraphics = new Shape({ width: options.width || 30, height: options.height || 30, color: 0xADD8E6, shape: 'ellipse' }); self.fallbackGraphics.anchor.set(0.5, 0.5); self.addChild(self.fallbackGraphics); } // Ustawienie self.width i self.height na podstawie pierwszej klatki animacji lub opcji if (animFrames.length > 0 && animFrames[0] && typeof animFrames[0].width !== 'undefined') { self.width = animFrames[0].width; self.height = animFrames[0].height; } else { self.width = options.width || 30; self.height = options.height || 30; } self.update = function () { if (self.isDead || gameState.currentState !== "cursedCrystal") { return; } var currentTargetX = self.targetX; var currentTargetY = self.targetY; if (typeof crystalCoreObject !== 'undefined' && crystalCoreObject && !crystalCoreObject.destroyed) { currentTargetX = crystalCoreObject.x; currentTargetY = crystalCoreObject.y; } var dx = currentTargetX - self.x; var dy = currentTargetY - self.y; var distance = Math.sqrt(dx * dx + dy * dy); if (self.animation) { // Obracanie animacji w kierunku ruchu var angle = Math.atan2(dy, dx); self.animation.rotation = angle; // Jeśli chcesz, aby sprite "patrzył" w prawo, gdy kąt jest 0, możesz dodać: // self.animation.rotation = angle + Math.PI / 2; // Jeśli sprite jest domyślnie skierowany w górę } else if (self.fallbackGraphics) { // Obracanie awaryjnej grafiki var angle = Math.atan2(dy, dx); self.fallbackGraphics.rotation = angle; } if (distance > self.speed) { var moveX = dx / distance * self.speed; var moveY = dy / distance * self.speed; self.x += moveX; self.y += moveY; } else { self.x = currentTargetX; self.y = currentTargetY; if (typeof crystalCoreObject !== 'undefined' && crystalCoreObject && !crystalCoreObject.destroyed && self.x === crystalCoreObject.x && self.y === crystalCoreObject.y) { self.onHitCrystal(); } else if (!crystalCoreObject || crystalCoreObject.destroyed) { self.isDead = true; if (self.parent) { self.parent.removeChild(self); } self.destroy(); } } // SpriteAnimation ma własną metodę update, która jest wywoływana automatycznie, jeśli jest dzieckiem // Nie ma potrzeby wywoływania self.animation.update() ręcznie tutaj. }; self.takeDamage = function (amount) { if (self.isDead) { return; } self.hp -= amount; // Efekt flash na całym obiekcie SoulEnemy (który zawiera animację) LK.effects.flashObject(self, 0xFFFFFF, 150); if (self.hp <= 0) { self.die(); } }; self.die = function () { if (self.isDead) { return; } self.isDead = true; gameState.cursedCrystalScore += 10; if (ui && ui.updateScoreCC) { ui.updateScoreCC(gameState.cursedCrystalScore); } if (self.parent) { self.parent.removeChild(self); } self.destroy(); // To powinno zniszczyć również animację jako dziecko }; self.onHitCrystal = function () { if (self.isDead) { return; } self.isDead = true; // Oznacz jako martwą, aby zatrzymać inne logiki (np. update) gameState.cursedCrystalSoulsHitCrystal++; gameState.cursedCrystalChargeLevel = Math.min(gameState.cursedCrystalTargetCharge, gameState.cursedCrystalChargeLevel + 3); if (ui && ui.updateCrystalCharge) { ui.updateCrystalCharge(gameState.cursedCrystalChargeLevel, gameState.cursedCrystalTargetCharge); } if (typeof gameState.updateCrystalVisual === 'function') { gameState.updateCrystalVisual(); } var FADE_OUT_DURATION = 300; // Czas trwania fade out w ms - dostosuj! // Zatrzymaj animację SpriteAnimation, jeśli istnieje i gra if (self.animation && typeof self.animation.stop === 'function') { self.animation.stop(); } tween(self, { alpha: 0 }, { duration: FADE_OUT_DURATION, easing: tween.easeOutQuad, // Możesz wybrać inną funkcję easing onFinish: function onFinish() { if (self.parent) { self.parent.removeChild(self); } if (self.destroy && !self.destroyed) { // Dodatkowe sprawdzenie !self.destroyed self.destroy(); } } }); }; self.x = options.x || 0; self.y = options.y || 0; return self; }); var SpriteAnimation = Container.expand(function (options) { var self = Container.call(this); // Initialize with default values options = options || {}; self.frames = options.frames || []; self.frameDuration = options.frameDuration || 120; // Default 100ms per frame self.loop = options.loop !== undefined ? options.loop : true; self.currentFrame = 0; self.frameTimer = 0; self.playing = true; // Dodaj tę linię do konstruktora, aby SpriteAnimation pamiętało własne alpha // i mogło przekazywać je do klatek. self.alpha = options.alpha !== undefined ? options.alpha : 1; // Set initial position and anchor if provided if (options.x !== undefined) { self.x = options.x; } if (options.y !== undefined) { self.y = options.y; } // Handle anchor options if (options.anchorX !== undefined || options.anchorY !== undefined) { var anchorX = options.anchorX !== undefined ? options.anchorX : 0; var anchorY = options.anchorY !== undefined ? options.anchorY : 0; // Apply anchor to all frames self.frames.forEach(function (frame) { if (frame && frame.anchor) { frame.anchor.set(anchorX, anchorY); } }); } // Add the first frame to display initially if (self.frames.length > 0) { self.removeChildren(); var firstFrame = self.frames[self.currentFrame]; if (firstFrame && firstFrame.anchor) { firstFrame.anchor.set(options.anchorX || 0, options.anchorY || 0); } // TUTAJ JEST KLUCZOWA ZMIANA W KONSTRUKTORZE: // Ustaw alpha dla pierwszej klatki na podstawie alpha kontenera SpriteAnimation. if (firstFrame) { firstFrame.alpha = self.alpha; } self.addChild(firstFrame); } // Animation update method self.update = function () { if (!self.playing || self.frames.length === 0) { return; } self.frameTimer++; if (self.frameTimer >= self.frameDuration / (1000 / 60)) { self.frameTimer = 0; if (self.children.length > 0) { self.removeChild(self.children[0]); } self.currentFrame++; if (self.currentFrame >= self.frames.length) { if (self.loop) { self.currentFrame = 0; // TUTAJ JEST KLUCZOWA ZMIANA W UPDATE DLA PĘTLI: var nextFrame = self.frames[self.currentFrame]; if (nextFrame) { nextFrame.alpha = self.alpha; // Zastosuj alpha kontenera } self.addChild(nextFrame); } else { self.playing = false; if (typeof self.onComplete === 'function') { self.onComplete(); } return; } } else { // Dodajemy nową klatkę tylko wtedy, gdy animacja jeszcze trwa // TUTAJ JEST KLUCZOWA ZMIANA W UPDATE DLA DALSZYCH KLATEK: var nextFrame = self.frames[self.currentFrame]; if (nextFrame) { nextFrame.alpha = self.alpha; // Zastosuj alpha kontenera } self.addChild(nextFrame); } } }; // Method to stop animation self.stop = function () { self.playing = false; }; // Method to start/resume animation self.play = function () { self.playing = true; }; // Method to set a specific frame self.gotoFrame = function (frameIndex) { if (frameIndex >= 0 && frameIndex < self.frames.length) { self.removeChildren(); self.currentFrame = frameIndex; self.addChild(self.frames[self.currentFrame]); } }; return self; }); var UI = Container.expand(function () { var self = Container.call(this); // Create heart containers (wizualizacja zdrowia) self.hearts = []; self.heartContainer = new Container(); // Kontener na ikony serc self.addChild(self.heartContainer); // Title and messages (teksty na ekranach) self.titleText = new Text2("ROLL SOULS", { size: 150, fill: 0xFFFFFF }); self.titleText.anchor.set(0.5, 0.5); self.addChild(self.titleText); self.messageText = new Text2("", { size: 60, fill: 0xFFFFFF }); self.messageText.anchor.set(0.5, 0.5); self.addChild(self.messageText); // Deaths counter (licznik śmierci) self.deathsText = new Text2("Deaths: 0", { size: 40, fill: 0xFFFFFF }); self.deathsText.anchor.set(1, 0); // Wyrównanie do prawego górnego rogu self.deathsText.x = 2048 - 50; // Pozycja X od prawej krawędzi self.deathsText.y = 50; // Pozycja Y od góry self.addChild(self.deathsText); // Tutorial text (teksty tutoriali) self.tutorialText = new Text2("", { size: 50, fill: 0xFFFFFF }); self.tutorialText.anchor.set(0.5, 0); // Wyrównanie do środka na górze self.tutorialText.x = 2048 / 2; // Pozycja X na środku self.tutorialText.y = 200; // Pozycja Y (może być różna w zależności od stanu) self.addChild(self.tutorialText); // --- Timer Text --- self.timerText = new Text2("00:00", { // Zmieniono tekst początkowy size: 60, fill: 0xFFFFFF // Biały kolor }); self.timerText.x = 2048 / 2; // Wyśrodkuj w poziomie self.timerText.y = 50; // Przysuń do góry self.timerText.anchor.set(0.5, 0); // Pozycja Y od górnej krawędzi self.timerText.alpha = 0; // Domyślnie ukryty self.addChild(self.timerText); // --- DODANY NOWY ELEMENT TEKSTOWY DLA REKORDU --- self.highScoreText = new Text2("Best: 00:00", { // Tekst początkowy size: 50, // Mniejszy rozmiar niż aktualny czas fill: 0xFFFF00 // Żółty kolor dla odróżnienia }); self.highScoreText.x = 2048 - 50; // Przysuń do prawej krawędzi (z marginesem 50) self.highScoreText.y = 50; // Przysuń do góry (ta sama wysokość co timer) self.highScoreText.anchor.set(1, 0); // Pozycja Y (poniżej timerText) self.highScoreText.alpha = 0; // Domyślnie ukryty self.addChild(self.highScoreText); // Dodaj do kontenera UI // --- KONIEC DODAWANIA NOWEGO ELEMENTU --- // --- Boss Health Bar (wizualizacja zdrowia bossa) --- self.bossHealthBarContainer = new Container(); // Kontener na pasek zdrowia bossa self.bossHealthBarContainer.x = 2048 / 2; // Wyśrodkuj poziomo self.bossHealthBarContainer.y = 150; // Pozycja Y (poniżej timera) self.bossHealthBarContainer.alpha = 0; // Domyślnie ukryty self.addChild(self.bossHealthBarContainer); var barWidth = 800; // Szerokość paska zdrowia (musi być taka sama jak szerokość assetu bossHpbar) var barHeight = 30; // Wysokość paska zdrowia (musi być taka sama jak wysokość assetu bossHpbar) // Właściwy pasek zdrowia bossa (czerwony) - UŻYWAMY TERAZ NOWEGO ASSETU bossHpbar self.bossHealthBar = self.attachAsset('bossHpbar', { // Użyj assetu 'bossHpbar' anchorX: 0, // Ustaw punkt odniesienia na lewą krawędź (0), środek pionowo (0.5) anchorY: 0.5, x: -barWidth / 2 // Przesuń w lewo o połowę szerokości tła }); self.bossHealthBarContainer.addChild(self.bossHealthBar); // --- METODY AKTUALIZACJI UI --- // Aktualizuje wizualizację serc na UI self.updateHearts = function (current, max) { current = Math.max(0, current || 0); max = Math.max(1, max || 1); // Max musi być co najmniej 1 if (self.hearts.length !== max) { while (self.hearts.length > 0) { var oldHeart = self.hearts.pop(); if (oldHeart && oldHeart.destroy && !oldHeart.destroyed) { oldHeart.destroy(); } } self.heartContainer.removeChildren(); // Usuń wizualne obiekty serc for (var i = 0; i < max; i++) { var heart = LK.getAsset('heart', { anchorX: 0.5, anchorY: 0.5, x: i * 50, y: 0, tint: i < current ? 0xFF0000 : 0x555555 }); self.hearts.push(heart); self.heartContainer.addChild(heart); } self.heartContainer.x = (2048 - max * 50) / 2 + 25; self.heartContainer.y = 100; // Zmieniono pozycję serc, aby zrobić miejsce } else { for (var j = 0; j < self.hearts.length; j++) { if (self.hearts[j]) { self.hearts[j].tint = j < current ? 0xFF0000 : 0x555555; } } } }; // Aktualizuje wizualizację paska zdrowia bossa self.updateBossHealth = function (current, max) { current = Math.max(0, current || 0); max = Math.max(1, max || 1); var barWidth = 800; var currentWidth = current / max * barWidth; if (self.bossHealthBar) { self.bossHealthBar.width = currentWidth; var shouldBeVisible = (gameState.currentState === "game" || gameState.currentState === "gameOver") && (boss && !boss.dead && current > 0 || gameState.currentState === "gameOver" && !gameOverReasonIsDeath && isNewBossPlusMode); self.bossHealthBarContainer.alpha = shouldBeVisible ? 1 : 0; } if (self.bossHealthBarBg) { // Jeśli używasz tła paska HP self.bossHealthBarBg.alpha = self.bossHealthBarContainer.alpha; } }; // Wyświetla komunikat na środku ekranu self.showMessage = function (message, duration) { self.messageText.setText(message); self.messageText.alpha = 1; if (self.messageTimeout) { LK.clearTimeout(self.messageTimeout); self.messageTimeout = null; } if (duration && duration > 0) { self.messageTimeout = LK.setTimeout(function () { tween(self.messageText, { alpha: 0 }, { duration: 500, onFinish: function onFinish() { self.messageTimeout = null; } }); }, duration); } else { self.messageText.alpha = 1; } }; // Wyświetla tekst tutorialu self.showTutorial = function (text) { self.tutorialText.setText(text); self.tutorialText.alpha = 1; }; // Ukrywa tekst tutorialu (zanikając) self.hideTutorial = function () { tween(self.tutorialText, { alpha: 0 }, { duration: 500 }); }; // Aktualizuje licznik śmierci self.updateDeathsCounter = function () { var deaths = parseInt(storage.totalDeaths, 10) || 0; self.deathsText.setText("Deaths: " + deaths); self.deathsText.visible = gameState.currentState === "game"; }; // Aktualizuje wyświetlanie czasu timera self.updateTimerDisplay = function (seconds) { seconds = Math.max(0, seconds || 0); var minutes = Math.floor(seconds / 60); var remainingSeconds = seconds % 60; var formattedSeconds = remainingSeconds < 10 ? '0' + remainingSeconds : remainingSeconds; var formattedTime = String(minutes).padStart(2, '0') + ':' + formattedSeconds; if (self.timerText) { // W Roll Master wyświetlamy "Time:", w innych trybach tylko czas var prefix = gameState.currentState === "rollMaster" ? "Time: " : ""; self.timerText.setText(prefix + formattedTime); } }; self.ccScoreText = new Text2("Score: 0", { size: 50, fill: 0xFFFF00, // Żółty, dla odróżnienia align: 'left' }); self.ccScoreText.anchor.set(0, 0); // Lewy górny róg tego tekstu self.addChild(self.ccScoreText); // Rekord dla Cursed Crystal self.ccHighScoreText = new Text2("Best: 0", { size: 40, fill: 0xFFFFFF, // Biały align: 'left' }); self.ccHighScoreText.anchor.set(0, 0); // Lewy górny róg tego tekstu self.addChild(self.ccHighScoreText); // Pasek Naładowania Klejnotu self.crystalChargeBarContainer = new Container(); self.addChild(self.crystalChargeBarContainer); var baseAssetID = 'cursedenergybar'; // Nazwa Twojego assetu var chargeBarAssetFullWidth; // Zmienna na pełną szerokość assetu var chargeBarAssetHeight; // Zmienna na wysokość assetu // Tło paska (ten sam asset, ale z większą przezroczystością) try { self.crystalChargeBarBg = self.attachAsset(baseAssetID, { anchorX: 0.5, anchorY: 0.5, alpha: 0.3, // Ustaw przezroczystość tła (np. 30%) clone: true }); chargeBarAssetFullWidth = self.crystalChargeBarBg.width; // Pobierz szerokość z assetu chargeBarAssetHeight = self.crystalChargeBarBg.height; // Pobierz wysokość z assetu self.crystalChargeBarContainer.addChild(self.crystalChargeBarBg); } catch (e) { console.error("Błąd ładowania assetu tła paska energii ('" + baseAssetID + "'): ", e); chargeBarAssetFullWidth = 400; // Awaryjna szerokość chargeBarAssetHeight = 25; // Awaryjna wysokość self.crystalChargeBarBg = new Shape({ width: chargeBarAssetFullWidth, height: chargeBarAssetHeight, color: 0xCCCCCC, alpha: 0.3 }); self.crystalChargeBarBg.anchor.set(0, 0.5); self.crystalChargeBarContainer.addChild(self.crystalChargeBarBg); } // Wypełnienie paska (ten sam asset, w pełni widoczny) try { self.crystalChargeBarFill = self.attachAsset(baseAssetID, { anchorX: 0.5, anchorY: 0.5, alpha: 1, // Pełna widoczność clone: true }); self.crystalChargeBarFill.width = 0; // Zacznij od zerowej szerokości // Jeśli Twoje tło i wypełnienie mają być idealnie na sobie, a oba mają anchorX:0, anchorY:0.5, // to ich .x i .y względem kontenera powinny być takie same (np. 0,0). // self.crystalChargeBarFill.x = 0; // self.crystalChargeBarFill.y = 0; self.crystalChargeBarContainer.addChild(self.crystalChargeBarFill); } catch (e) { console.error("Błąd ładowania assetu wypełnienia paska energii ('" + baseAssetID + "'): ", e); self.crystalChargeBarFill = new Shape({ width: 0, height: chargeBarAssetHeight ? chargeBarAssetHeight : 25, color: 0x8A2BE2 }); self.crystalChargeBarFill.anchor.set(0, 0.5); self.crystalChargeBarContainer.addChild(self.crystalChargeBarFill); } // --- DODANA NOWA FUNKCJA DO AKTUALIZACJI TEKSTU REKORDU --- self.updateHighScoreDisplay = function (seconds) { // 'seconds' to rekord czasu dla Roll Master seconds = Math.max(0, seconds || 0); var minutes = Math.floor(seconds / 60); var remainingSeconds = seconds % 60; var formattedSeconds = String(remainingSeconds).padStart(2, '0'); // Zawsze dwie cyfry dla sekund var formattedTime = String(minutes).padStart(2, '0') + ':' + formattedSeconds; if (this.highScoreText) { // this.highScoreText to element UI dla rekordu Roll Master // Wyświetlaj rekord Roll Master tylko, gdy jesteśmy w tym trybie lub na jego ekranie końca if (gameState.currentState === "rollMaster" || gameState.currentState === "rollMasterGameOver") { this.highScoreText.setText("Best Time: " + formattedTime); // Pozycja i widoczność (alpha) tego elementu są zarządzane przez UI.prototype.positionElements } // W innych stanach gry nie modyfikujemy tekstu; positionElements powinno go ukryć. } }; self.updateScoreCC = function (currentScore) { if (this.ccScoreText) { // this.ccScoreText to element dla "Score: X" w Cursed Crystal if (gameState.currentState === "cursedCrystal") { this.ccScoreText.setText("Score: " + (currentScore || 0)); // Pozycja i widoczność (alpha) są zarządzane przez UI.prototype.positionElements("cursedCrystal") } } }; self.updateHighScoreCC = function (highScore) { // 'highScore' to rekord dla Cursed Crystal if (this.ccHighScoreText) { // this.ccHighScoreText to element dla "Best: Y" w Cursed Crystal if (gameState.currentState === "cursedCrystal") { this.ccHighScoreText.setText("Best: " + (highScore || 0)); // Pozycja i widoczność (alpha) są zarządzane przez UI.prototype.positionElements("cursedCrystal") } } }; // Aktualizuje wizualizację paska naładowania Klejnotu self.updateCrystalCharge = function (currentCharge, maxCharge) { if (self.crystalChargeBarFill && self.crystalChargeBarBg) { maxCharge = Math.max(1, maxCharge || 1); // Max musi być co najmniej 1 currentCharge = Math.max(0, Math.min(currentCharge, maxCharge)); var chargeBarBaseWidth = self.crystalChargeBarBg.width; // Szerokość tła paska var fillWidth = currentCharge / maxCharge * chargeBarBaseWidth; self.crystalChargeBarFill.width = fillWidth; } }; // Funkcja do pokazywania/ukrywania i aktualizacji paska HP Minibossa CC // Wykorzysta istniejący self.bossHealthBar i self.bossHealthBarContainer self.updateMinibossHealthCC = function (currentHP, maxHP, isActive) { if (self.bossHealthBarContainer && self.bossHealthBar) { if (isActive && currentHP > 0) { self.bossHealthBarContainer.alpha = 1; maxHP = Math.max(1, maxHP || 1); currentHP = Math.max(0, Math.min(currentHP, maxHP)); var barWidth = 800; // Upewnij się, że to ta sama szerokość co w updateBossHealth // lub pobierz z self.bossHealthBarBg.width jeśli masz tło var currentBarWidth = currentHP / maxHP * barWidth; self.bossHealthBar.width = currentBarWidth; } else { self.bossHealthBarContainer.alpha = 0; } } }; // --- KONIEC DODAWANIA NOWEJ FUNKCJI --- // Pozycjonuje elementy UI w zależności od stanu gry self.positionElements = function (state) { // Pozycje stałe dla niektórych elementów (mogą być nadpisywane w case'ach) if (self.deathsText) { // Sprawdzenie, czy element istnieje self.deathsText.x = 2048 - 150; self.deathsText.y = 50; } if (self.heartContainer) { self.heartContainer.y = 80; } if (self.bossHealthBarContainer) { self.bossHealthBarContainer.x = 2048 / 2; self.bossHealthBarContainer.y = 160; } // Resetuj widoczność wszystkich głównych elementów UI przed ustawieniem dla danego stanu if (self.titleText) { self.titleText.alpha = 0; } if (self.messageText) { self.messageText.alpha = 0; } if (self.tutorialText) { self.tutorialText.alpha = 0; } if (self.timerText) { self.timerText.alpha = 0; } if (self.heartContainer) { self.heartContainer.alpha = 0; } if (self.bossHealthBarContainer) { self.bossHealthBarContainer.alpha = 0; } if (self.deathsText) { self.deathsText.alpha = 0; } // Ten highScoreText jest specyficzny dla Roll Master, więc domyślnie go ukrywamy // chyba że jesteśmy w stanie rollMaster lub rollMasterGameOver (obsłużone w case) if (self.highScoreText) { self.highScoreText.alpha = 0; } // NOWE ELEMENTY UI DLA CURSED CRYSTAL - też domyślnie ukryte if (self.ccScoreText) { self.ccScoreText.alpha = 0; } if (self.ccHighScoreText) { self.ccHighScoreText.alpha = 0; } if (self.crystalChargeBarContainer) { self.crystalChargeBarContainer.alpha = 0; } // Przywróć domyślną pozycję i styl timerText (może być nadpisane w case "rollMaster") if (self.timerText) { self.timerText.x = 350; // Domyślna pozycja X dla trybu "game" self.timerText.y = 100; // Domyślna pozycja Y dla trybu "game" self.timerText.anchor.set(0, 0); self.timerText.style = { size: 60, fill: 0xFFFFFF }; self.timerText.setText("00:00"); // Resetuj tekst na wszelki wypadek } // Logika dla highScoreText (Roll Master) - resetowanie tekstu poza trybem RM if (self.highScoreText && state !== "rollMaster" && state !== "rollMasterGameOver") { // self.highScoreText.setText("Best RM: 00:00"); // Możesz zresetować tekst lub zostawić ostatni znany } switch (state) { case "title": if (self.titleText) { self.titleText.x = 2048 / 2; self.titleText.y = 800; self.titleText.alpha = 1; } if (self.messageText) { self.messageText.x = 2048 / 2; self.messageText.y = 1000; // self.messageText.alpha jest zarządzane przez showMessage } if (self.tutorialText) { self.tutorialText.x = 2048 / 2; self.tutorialText.y = 1200; // self.tutorialText.alpha jest zarządzane przez showTutorial } break; case "game": // Tryb walki z bossem (tutorial) if (self.heartContainer) { self.heartContainer.alpha = 1; // Pozycja serc jest aktualizowana w updateHearts } if (self.timerText) { // Timer dla standardowego bossa (jeśli jest) self.timerText.alpha = 1; // Ustaw x, y, anchor, style dla tego timera self.timerText.x = 350; // Przykładowa pozycja z Twojego kodu self.timerText.y = 100; self.timerText.anchor.set(0, 0); self.timerText.style = { size: 60, fill: 0xFFFFFF }; } if (self.deathsText) { self.deathsText.alpha = 1; } if (self.bossHealthBarContainer && typeof boss !== 'undefined' && boss && !boss.dead && boss.health > 0) { self.bossHealthBarContainer.alpha = 1; } else if (self.bossHealthBarContainer) { self.bossHealthBarContainer.alpha = 0; } if (self.tutorialText) { // self.tutorialText.alpha jest zarządzane przez showTutorial/hideTutorial self.tutorialText.x = 2048 / 2; self.tutorialText.y = 200; } break; case "grillMenu": if (self.messageText) { // Dla komunikatów w Grill Menu self.messageText.x = 2048 / 2; self.messageText.y = 500; // alpha zarządzane przez showMessage } // Wszystkie inne elementy typowo rozgrywkowe powinny być ukryte przez reset na początku funkcji break; case "gameOver": // Standardowy Game Over (po Boss+ lub tutorialu, jeśli tak zdecydujesz) // Teksty i przyciski dla tego stanu są głównie zarządzane przez gameState.gameOver if (self.titleText) {// Dla "DEFEAT!" lub "TIME'S UP!" // alpha, text, x, y są ustawiane w gameState.gameOver } if (self.messageText) {// Dla dodatkowych wiadomości // alpha, text, x, y są ustawiane w gameState.gameOver } if (self.deathsText) { // Najpierw sprawdź, czy deathsText istnieje // Warunek, kiedy pokazać licznik śmierci: // Tylko jeśli gra się zakończyła (co wiemy, bo state === "gameOver"), // przyczyną była śmierć gracza, // ORAZ NIE był to tryb Boss+ (czyli isNewBossPlusMode jest false). if (gameOverReasonIsDeath && !isNewBossPlusMode) { self.deathsText.alpha = 1; self.deathsText.x = 2048 - 150; // Twoja standardowa pozycja self.deathsText.y = 50; // Twoja standardowa pozycja } else { self.deathsText.alpha = 0; // W innych przypadkach "gameOver" (np. Boss+) ukryj licznik śmierci } } break; // ... (case'y intro, fakeTutorial, realTutorial - zakładam, że są OK) ... case "intro": // case "fakeTutorial": // case "realTutorial": // // Większość elementów UI jest zazwyczaj ukryta, zarządzane przez logikę tych stanów break; // case "rollMaster": // Pokaż elementy UI specyficzne dla aktywnego trybu Roll Master if (self.timerText) { // Timer/wynik dla Roll Master self.timerText.alpha = 1; self.timerText.x = 2048 / 2; // Środek-góra self.timerText.y = 50; self.timerText.anchor.set(0.5, 0); self.timerText.style = { size: 60, fill: 0xFFFFFF }; // Tekst jest aktualizowany w ui.updateTimerDisplay } if (self.highScoreText) { // Rekord dla Roll Master (ten właściwy, nie ccHighScoreText) self.highScoreText.alpha = 1; self.highScoreText.x = 2048 - 50; // Prawy górny róg self.highScoreText.y = 50; self.highScoreText.anchor.set(1, 0); // Tekst jest aktualizowany w ui.updateHighScoreDisplay } // Jawnie ukryj elementy UI z Cursed Crystal if (self.ccScoreText) { self.ccScoreText.alpha = 0; } if (self.ccHighScoreText) { self.ccHighScoreText.alpha = 0; } // <--- BARDZO WAŻNE if (self.crystalChargeBarContainer) { self.crystalChargeBarContainer.alpha = 0; } // Ukryj inne standardowe elementy gry if (self.heartContainer) { self.heartContainer.alpha = 0; } if (self.bossHealthBarContainer) { self.bossHealthBarContainer.alpha = 0; } if (self.deathsText) { self.deathsText.alpha = 0; } if (self.tutorialText) { self.tutorialText.alpha = 0; } if (self.titleText) { self.titleText.alpha = 0; } // Główny tytuł gry break; case "rollMasterGameOver": // Jawnie ukryj elementy UI z Cursed Crystal if (self.ccScoreText) { self.ccScoreText.alpha = 0; } if (self.ccHighScoreText) { self.ccHighScoreText.alpha = 0; } if (self.crystalChargeBarContainer) { self.crystalChargeBarContainer.alpha = 0; } // Ukryj elementy aktywnej gry Roll Master (timer na żywo) if (self.timerText) { self.timerText.alpha = 0; } if (self.highScoreText) { self.highScoreText.alpha = 0; } // Ukryj inne standardowe elementy gry if (self.heartContainer) { self.heartContainer.alpha = 0; } if (self.bossHealthBarContainer) { self.bossHealthBarContainer.alpha = 0; } if (self.deathsText) { self.deathsText.alpha = 0; } if (self.tutorialText) { self.tutorialText.alpha = 0; } if (self.titleText) { self.titleText.alpha = 0; } break; case "cursedCrystal": // TYLKO JEDNO WYSTĄPIENIE TEGO CASE if (self.timerText) { self.timerText.alpha = 0; } if (self.highScoreText) { // To jest highScoreText dla Roll Master, więc ukrywamy self.highScoreText.alpha = 0; } if (self.deathsText) { // Licznik śmierci też ukrywamy w tym trybie self.deathsText.alpha = 0; } if (self.tutorialText) { // Tutorial też ukrywamy self.tutorialText.alpha = 0; } // --- Elementy specyficzne dla Cursed Crystal --- if (self.ccScoreText) { self.ccScoreText.alpha = 1; self.ccScoreText.x = 50; self.ccScoreText.y = 20; self.ccScoreText.anchor.set(0, 0); } if (self.ccHighScoreText) { self.ccHighScoreText.alpha = 1; self.ccHighScoreText.x = 2048 - 50; self.ccHighScoreText.y = 20; self.ccHighScoreText.anchor.set(1, 0); } // --- Serca Gracza --- if (self.heartContainer) { self.heartContainer.alpha = 1; // UPEWNIJ SIĘ, ŻE SERCA SĄ WIDOCZNE var ccMaxHearts = typeof gameState !== 'undefined' && gameState.cursedCrystalPlayerMaxHealth ? gameState.cursedCrystalPlayerMaxHealth : 10; var heartsWidth = ccMaxHearts * 50; self.heartContainer.x = (2048 - heartsWidth) / 2 + 25; self.heartContainer.y = 70; // Stała pozycja Y dla serc } // --- Pasek Ładowania Kryształu --- if (self.crystalChargeBarContainer && self.crystalChargeBarBg) { if (typeof gameState !== 'undefined' && gameState.isMinibossActiveCC) { self.crystalChargeBarContainer.alpha = 0; } else { self.crystalChargeBarContainer.alpha = 1; var chargeBarWidth = self.crystalChargeBarBg.width || 400; self.crystalChargeBarContainer.anchorX = 0.5; self.crystalChargeBarContainer.x = 2048 / 2; self.crystalChargeBarContainer.y = (self.heartContainer ? self.heartContainer.y + (self.heartContainer.height || 40) : 70 + 40) + 50; } } // --- Pasek HP Minibossa --- if (self.bossHealthBarContainer) { var heartsBottomY = self.heartContainer ? self.heartContainer.y + (self.heartContainer.height || 40) : 70 + 40; // Poprzednio było: heartsBottomY + 20 (ok. 130), potem heartsBottomY + 70 (ok. 180) // Aby przesunąć o kolejne 50px w dół od Y=180, potrzebujemy Y = 230 // Więc offset od heartsBottomY (ok. 110) musi być 120. var desiredOffsetYBelowHearts = 120; self.bossHealthBarContainer.y = heartsBottomY + desiredOffsetYBelowHearts; // Widoczność paska HP bossa jest zarządzana przez updateMinibossHealthCC, // więc tutaj tylko pozycjonujemy. } break; case "cursedCrystalGameOver": // Ukryj elementy rozgrywki CC (oprócz score i highscore, które mogą być częścią ekranu końca) if (self.timerText) { self.timerText.alpha = 0; } if (self.highScoreText) { self.highScoreText.alpha = 0; } // highScoreText dla RollMaster if (self.crystalChargeBarContainer) { self.crystalChargeBarContainer.alpha = 0; } if (self.heartContainer) { self.heartContainer.alpha = 0; } // Ukryj serca gracza na ekranie końca gry CC if (self.bossHealthBarContainer) { self.bossHealthBarContainer.alpha = 0; } if (self.deathsText) { self.deathsText.alpha = 0; } if (self.tutorialText) { self.tutorialText.alpha = 0; } break; } }; // Koniec positionElements return self; }); /**** * Initialize Game ****/ // Koniec var UI = Container.expand(...) var game = new LK.Game({ backgroundColor: 0x111111 // Ciemne tło domyślne }); /**** * Game Code ****/ // Jasnoniebieska elipsa dla Duszy // Placeholder, zmień ID // Dodana funkcja pomocnicza do pobierania czasu gry w milisekundach // Dodana funkcja pomocnicza do pobierania czasu gry w milisekundach // Globalny kontener na elementy scen intro/tutoriali i Grill Screena (do łatwego czyszczenia) // Komentarze o assetach pominięte dla zwięzłości // Ciemnofioletowe tło // Fioletowy klejnot // Pomarańczowy kwadrat dla Minibossa function _typeof(o) { "@babel/helpers - typeof"; return _typeof = "function" == typeof Symbol && "symbol" == typeof Symbol.iterator ? function (o) { return typeof o; } : function (o) { return o && "function" == typeof Symbol && o.constructor === Symbol && o !== Symbol.prototype ? "symbol" : typeof o; }, _typeof(o); } function _defineProperty(e, r, t) { return (r = _toPropertyKey(r)) in e ? Object.defineProperty(e, r, { value: t, enumerable: !0, configurable: !0, writable: !0 }) : e[r] = t, e; } function _toPropertyKey(t) { var i = _toPrimitive(t, "string"); return "symbol" == _typeof(i) ? i : i + ""; } function _toPrimitive(t, r) { if ("object" != _typeof(t) || !t) { return t; } var e = t[Symbol.toPrimitive]; if (void 0 !== e) { var i = e.call(t, r || "default"); if ("object" != _typeof(i)) { return i; } throw new TypeError("@@toPrimitive must return a primitive value."); } return ("string" === r ? String : Number)(t); } if (typeof LK.getGameTime === 'undefined') { LK.getGameTime = function () { return Date.now(); // Zwraca aktualny czas w milisekundach }; } if (typeof LK.getGameTime === 'undefined') { LK.getGameTime = function () { return Date.now(); // Zwraca aktualny czas w milisekundach }; } var currentSceneElements = new Container(); // Zmienne gry var player; var boss; var ui; var walls = []; // Ściany areny bossa // Zmienna do przechowywania aktywnego tła sceny var currentBackground = null; // Flaga do śledzenia, czy jesteśmy w trybie New Boss+ var isNewBossPlusMode = false; // Flaga do śledzenia przyczyny Game Over (true = gracz zginął, false = czas minął) var gameOverReasonIsDeath = false; var coffinMemeImage = null; // Funkcja do czyszczenia elementów z kontenera currentSceneElements (nie usuwa UI ani tła) function clearScene() { while (currentSceneElements.children.length > 0) { var child = currentSceneElements.children[0]; if (child && child.destroy) { child.destroy(); // Użyj destroy jeśli dostępne } else if (child && child.parent) { child.parent.removeChild(child); // Fallback } else { // Jeśli obiekt nie ma ani destroy ani parent, usuń go z tablicy (choć to nie powinno się zdarzyć) currentSceneElements.children.shift(); } } // Usuń też dzieci bezpośrednio z 'game', które mogły zostać dodane poza 'currentSceneElements' w scenach, // ale UWAŻAJ, aby nie usunąć stałych elementów jak UI, walls, currentSceneElements sam w sobie. // Lepsze podejście: zawsze dodawaj elementy specyficzne dla sceny do currentSceneElements. } // Obiekt zarządzający stanami gry var gameState = _defineProperty(_defineProperty(_defineProperty({ currentState: "title", gameDuration: 120, remainingTime: 0, crystalExploding: false, gameTimerInterval: null, fakeTutorialTimerId: null, bossSpeedIncreased: false, currentIntroMusicInstance: null, cursedCrystalMusicTracks: ['CursedCrystal', 'cursedcrystal2'], currentRollSoulsInstance: null, currentRollMasterMusicInstance: null, currentCursedCrystalMusicInstance: null, // Nowa właściwość cursedCrystalPlayerHealth: 10, // Startowe HP gracza w tym trybie cursedCrystalPlayerMaxHealth: 10, cursedCrystalChargeLevel: 0, // Aktualny poziom naładowania Klejnotu (0-100%) cursedCrystalTargetCharge: 100, // Wartość naładowania do przywołania Minibossa cursedCrystalSoulsHitCrystal: 0, // Licznik dusz, które dotarły do kryształu (do skalowania Minibossa) cursedCrystalScore: 0, cursedCrystalHighScore: 0, // Ładowane z localStorage cursedCrystalEnemies: [], // Tablica na Dusze i pociski Minibossa CC cursedCrystalEnemySpawnTimer: 0, cursedCrystalBaseSpawnInterval: 120, // Początkowy interwał spawnu (np. 2 sekundy) cursedCrystalCurrentSpawnInterval: 120, cursedCrystalMinSpawnInterval: 30, // Minimalny interwał spawnu (np. 0.5 sekundy) cursedCrystalDifficultyTimer: 0, cursedCrystalDifficultyInterval: 600, // Co ile klatek (np. 10 sekund) zwiększać trudność cursedCrystalEnemyBaseSpeed: 2, cursedCrystalEnemyCurrentMaxSpeed: 4, cursedCrystalTimeSurvived: 0, // Czas przetrwania w klatkach lub sekundach isMinibossActiveCC: false, cursedCrystalMinibossObject: null, cursedCrystalMinibossHP: 0, cursedCrystalMinibossMaxHP: 200, cursedCrystalActiveProjectiles: [], // Dla pocisków Minibossa cursedCrystalActiveExplosions: [], cursedCrystalActiveLasers: [], cursedCrystalActiveLaserWalls: [], // Dla aktywnych obszarów eksplozji // Bazowe HP Minibossa, będzie skalowane cursedCrystalMinibossAttackTimer: 0, cursedCrystalMinibossAttackInterval: 180, rollMasterTime: 0, rollMasterDifficulty: 1, rollMasterTimerInterval: null, rollMasterAttacks: [], attackSpawnTimer: 0, attackSpawnInterval: 120, explosionSpawnTimer: 0, explosionSpawnInterval: 240, rollMasterHighScore: 0, isInputActive: false, currentInputPos: { x: 0, y: 0 }, touchStart: { x: 0, y: 0 }, touchEnd: { x: 0, y: 0 }, grillMenuEffects: [], swipeStartTime: 0, init: function init() { // Zapewnienie, że LK.getGameTime jest zdefiniowane if (typeof LK.getGameTime === 'undefined') { LK.getGameTime = function () { return Date.now(); // Zwraca aktualny czas w milisekundach }; } storage.totalDeaths = 0; storage.maxHearts = 5; isNewBossPlusMode = false; gameOverReasonIsDeath = false; this.rollMasterHighScore = storage.rollMasterHighScore || 0; this.currentIntroMusicInstance = null; this.currentRollSoulsInstance = null; this.currentRollMasterMusicInstance = null; game.setBackgroundColor(0x111111); ui = game.addChild(new UI()); ui.updateDeathsCounter(); ui.updateHearts(storage.maxHearts, storage.maxHearts); ui.positionElements("title"); game.addChild(currentSceneElements); this.showTitleScreen(); }, // --- gameState.showTitleScreen --- showTitleScreen: function showTitleScreen() { isNewBossPlusMode = false; this.rollMasterTime = 0; // Normalne działanie - bez flagi testowej console.log("[showTitleScreen] State: Title Screen"); var needsIntroMusic = true; // Zatrzymywanie muzyki z innych trybów if (this.currentRollSoulsInstance && this.currentRollSoulsInstance.stop) { console.log("[showTitleScreen] Zatrzymywanie currentRollSoulsInstance."); if (typeof this.currentRollSoulsInstance.volume === 'number') { this.currentRollSoulsInstance.volume = 0; } this.currentRollSoulsInstance.stop(); this.currentRollSoulsInstance = null; } else { var musicViaLK_RS = LK.music; if (musicViaLK_RS && musicViaLK_RS.assetId === 'RollSouls' && musicViaLK_RS.playing && musicViaLK_RS.stop) { console.log("[showTitleScreen] Fallback: Zatrzymywanie RollSouls przez LK.music."); if (typeof musicViaLK_RS.volume === 'number') { musicViaLK_RS.volume = 0; } musicViaLK_RS.stop(); } } if (this.currentRollMasterMusicInstance && this.currentRollMasterMusicInstance.stop) { console.log("[showTitleScreen] Zatrzymywanie currentRollMasterMusicInstance."); if (typeof this.currentRollMasterMusicInstance.volume === 'number') { this.currentRollMasterMusicInstance.volume = 0; } this.currentRollMasterMusicInstance.stop(); this.currentRollMasterMusicInstance = null; } else { var musicViaLK_RM = LK.music; if (musicViaLK_RM && musicViaLK_RM.assetId === 'rollmaster' && musicViaLK_RM.playing && musicViaLK_RM.stop) { console.log("[showTitleScreen] Fallback: Zatrzymywanie 'rollmaster' przez LK.music."); if (typeof musicViaLK_RM.volume === 'number') { musicViaLK_RM.volume = 0; } musicViaLK_RM.stop(); } } // Logika muzyki intro if (this.currentIntroMusicInstance && this.currentIntroMusicInstance.playing) { console.log("[showTitleScreen] currentIntroMusicInstance już gra."); needsIntroMusic = false; } else { var lkMusicCheck = LK.music; if (lkMusicCheck && lkMusicCheck.assetId === 'introMusic' && lkMusicCheck.playing) { console.log("[showTitleScreen] introMusic już gra (wg LK.music). Zapisuję instancję."); this.currentIntroMusicInstance = lkMusicCheck; needsIntroMusic = false; } } if (needsIntroMusic) { console.log("[showTitleScreen] Uruchamianie introMusic..."); this.currentIntroMusicInstance = LK.playMusic('introMusic', { loop: true, volume: 0.7, fade: { start: 0, end: 0.7, duration: 1000 } }); if (!this.currentIntroMusicInstance) { console.error("[showTitleScreen] LK.playMusic('introMusic') nie zwróciło instancji!"); } } else { console.log("[showTitleScreen] introMusic nie jest ponownie uruchamiana."); } // Czyszczenie timerów if (this.fakeTutorialTimerId) { LK.clearTimeout(this.fakeTutorialTimerId); this.fakeTutorialTimerId = null; } if (this.gameTimerInterval) { LK.clearInterval(this.gameTimerInterval); this.gameTimerInterval = null; } if (this.rollMasterTimerInterval) { LK.clearInterval(this.rollMasterTimerInterval); this.rollMasterTimerInterval = null; } clearScene(); // Usuwa elementy z currentSceneElements // Ustawienie tła i cząsteczek if (currentBackground) { tween.stop(currentBackground); currentBackground.destroy(); currentBackground = null; } this.currentState = "title"; game.setBackgroundColor(0x1a1a1a); currentBackground = LK.getAsset('titleBg', { anchorX: 0, anchorY: 0, x: 0, y: 0 }); game.addChildAt(currentBackground, 0); if (typeof particleIntervalId !== 'undefined' && particleIntervalId) { LK.clearInterval(particleIntervalId); particleIntervalId = null; } if (typeof startScreenParticles !== 'undefined') { startScreenParticles.forEach(function (p) { if (p && p.destroy) { p.destroy(); } }); startScreenParticles = []; } var localStartScreenParticles = []; function localSpawnBackgroundParticle() {/* ... logika cząsteczek ... */} var localParticleIntervalId = LK.setInterval(localSpawnBackgroundParticle, 400); // Reset gracza i bossa if (player && player.destroy) { player.destroy(); } player = null; if (boss && boss.destroy) { boss.destroy(); } boss = null; walls.forEach(function (wall) { if (wall) { wall.alpha = 1; } }); // Ustawienie UI ui.positionElements("title"); ui.titleText.setText("Welcome Unchosen"); ui.showMessage("Tap to Start", 0); // Normalny tekst ui.showTutorial("Swipe to Roll - Death is Progress"); ui.updateHearts(storage.maxHearts, storage.maxHearts); ui.updateDeathsCounter(); this.touchStart = { x: 0, y: 0 }; this.touchEnd = { x: 0, y: 0 }; // Usuwamy testowy przycisk i jego logikę game.off('down'); game.on('down', function (x, y, obj) { if (gameState.currentState === 'title') { // Logika dla normalnego przejścia do intro console.log("Title screen clicked - Proceeding to Intro."); if (localParticleIntervalId) { LK.clearInterval(localParticleIntervalId); localParticleIntervalId = null; } if (localStartScreenParticles) { localStartScreenParticles.forEach(function (p) { if (p && p.destroy) { p.destroy(); } }); localStartScreenParticles = []; } // Muzyka intro powinna już grać lub zacząć grać, nie zatrzymujemy jej tutaj gameState.showIntro(); // <<< KLUCZOWA ZMIANA - PRZYWRÓCENIE NORMALNEGO PRZEJŚCIA } }); }, showIntro: function showIntro() { var skipElement = null; var skipTimerId = null; var self = this; // self tutaj to gameState if (this.fakeTutorialTimerId) { LK.clearTimeout(this.fakeTutorialTimerId); this.fakeTutorialTimerId = null; } if (typeof particleIntervalId !== 'undefined') { LK.clearInterval(particleIntervalId); particleIntervalId = null; } if (typeof startScreenParticles !== 'undefined') { startScreenParticles.forEach(function (p) { if (p && p.destroy) { p.destroy(); } }); startScreenParticles = []; } clearScene(); if (currentBackground) { tween.stop(currentBackground); currentBackground.destroy(); currentBackground = null; } this.currentState = "intro"; game.setBackgroundColor(0x111111); currentBackground = LK.getAsset('introBg', { anchorX: 0.5, anchorY: 0.4, x: 1000, y: 1000, scaleX: 1, scaleY: 1 }); game.addChildAt(currentBackground, 0); tween(currentBackground, { scaleX: 1.5, scaleY: 1.5 }, { duration: 38000, easing: tween.linear, repeat: Infinity, yoyo: true }); if (player && player.destroy) { player.destroy(); } player = null; if (boss && boss.destroy) { boss.destroy(); } boss = null; walls.forEach(function (wall) { if (wall) { wall.alpha = 0; } }); ui.positionElements("intro"); skipTimerId = LK.setTimeout(function () { if (self.currentState !== "intro") { skipTimerId = null; return; } skipElement = new Text2("Skip intro", { size: 70, fill: 0xFFFFFF }); skipElement.x = 2048 - 50; skipElement.y = 2732 - 50; skipElement.anchor.set(1, 1); skipElement.interactive = true; skipElement.cursor = "pointer"; currentSceneElements.addChild(skipElement); skipElement.down = function () { if (self.currentState !== "intro") { return; } console.log("[Skip Intro] Pominięto intro przez tekst!"); if (skipTimerId) { LK.clearTimeout(skipTimerId); skipTimerId = null; } if (currentBackground) { tween.stop(currentBackground); } clearScene(); if (self.currentIntroMusicInstance && self.currentIntroMusicInstance.stop) { console.log("[Skip Intro] Zatrzymywanie currentIntroMusicInstance."); if (typeof self.currentIntroMusicInstance.volume === 'number') { self.currentIntroMusicInstance.volume = 0; } self.currentIntroMusicInstance.stop(); self.currentIntroMusicInstance = null; } else { console.log("[Skip Intro] Brak currentIntroMusicInstance lub metody stop. Fallback na LK.music."); var musicViaLK_I = LK.music; if (musicViaLK_I && musicViaLK_I.assetId === 'introMusic' && musicViaLK_I.stop) { if (typeof musicViaLK_I.volume === 'number') { musicViaLK_I.volume = 0; } musicViaLK_I.stop(); } } if (this.currentCursedCrystalMusicInstance && this.currentCursedCrystalMusicInstance.stop) { console.log("[showTitleScreen] Zatrzymywanie currentCursedCrystalMusicInstance."); if (typeof this.currentCursedCrystalMusicInstance.volume === 'number') { this.currentCursedCrystalMusicInstance.volume = 0; } this.currentCursedCrystalMusicInstance.stop(); this.currentCursedCrystalMusicInstance = null; } else { // Opcjonalny fallback, jeśli chcesz (choć jeśli zawsze ustawiasz currentCursedCrystalMusicInstance, może nie być potrzebny) var musicViaLK_CC = LK.music; if (musicViaLK_CC && musicViaLK_CC.assetId === 'cursedcrystal' && musicViaLK_CC.playing && musicViaLK_CC.stop) { console.log("[showTitleScreen] Fallback: Zatrzymywanie muzyki Cursed Crystal przez LK.music."); if (typeof musicViaLK_CC.volume === 'number') { musicViaLK_CC.volume = 0; } musicViaLK_CC.stop(); } } if (self.currentRollMasterMusicInstance && self.currentRollMasterMusicInstance.stop) { console.log("[Skip Intro] Zatrzymywanie currentRollMasterMusicInstance."); if (typeof self.currentRollMasterMusicInstance.volume === 'number') { self.currentRollMasterMusicInstance.volume = 0; } self.currentRollMasterMusicInstance.stop(); self.currentRollMasterMusicInstance = null; } console.log("[Skip Intro] Odtwarzanie RollSouls..."); self.currentRollSoulsInstance = LK.playMusic('RollSouls', { loop: true, volume: 0.7, fade: { start: 0, end: 0.7, duration: 1000 } }); if (!self.currentRollSoulsInstance) { console.error("[Skip Intro] LK.playMusic('RollSouls') nie zwróciło instancji!"); } console.log("[Skip Intro] Przechodzenie do showRealTutorial..."); self.showRealTutorial(); }; skipTimerId = null; }, 3000); function showIntroText(text, delay, onComplete) { var introText = new Text2(text, { size: 90, fill: 0xFFFFFF, align: 'center', wordWrap: true, wordWrapWidth: 1800 }); introText.anchor.set(0.5, 0.5); introText.x = 2048 / 2; introText.y = 2232 / 2 - 400; introText.alpha = 0; currentSceneElements.addChild(introText); LK.setTimeout(function () { if (self.currentState !== "intro") { return; } tween(introText, { alpha: 1 }, { duration: 2000, easing: tween.easeInOut }); LK.setTimeout(function () { if (self.currentState !== "intro") { return; } tween(introText, { alpha: 0 }, { duration: 2000, easing: tween.easeInOut }); LK.setTimeout(function () { if (onComplete) { onComplete(); } }, 4000); }, 4000); }, delay); } showIntroText('From the authors of Dark Souls...', 3000, function () { if (self.currentState !== "intro") { return; } showIntroText('I have no information. I don’t know them.', 0, function () { if (self.currentState !== "intro") { return; } showIntroText('Silas GameStudio...', 0, function () { if (self.currentState !== "intro") { return; } showIntroText('Still looking for funding.', 0, function () { if (self.currentState !== "intro") { return; } showIntroText('Oh, and it doesn’t even exist.', 0, function () { if (self.currentState !== "intro") { return; } LK.setTimeout(function () { if (self.currentState !== "intro") { return; } if (skipTimerId) { LK.clearTimeout(skipTimerId); skipTimerId = null; } if (skipElement && skipElement.parent) { currentSceneElements.removeChild(skipElement); if (skipElement.destroy) { skipElement.destroy(); } skipElement = null; } if (typeof gameState.showFakeTutorial === 'function') { gameState.showFakeTutorial(); } else { console.error("Error: gameState.showFakeTutorial is not defined"); gameState.showTitleScreen(); } }, 1200); }); }); }); }); }); }, // Koniec funkcji showIntro // --- KONIEC SEKWENCJI INTRO --- // <--- Koniec funkcji showIntro showFakeTutorial: function showFakeTutorial() { clearScene(); if (this.fakeTutorialTimerId) { LK.clearTimeout(this.fakeTutorialTimerId); this.fakeTutorialTimerId = null; } if (this.currentIntroMusicInstance) { console.log("[FakeTutorial] Próba wyciszenia i zatrzymania currentIntroMusicInstance."); var instanceToFadeAndStop = this.currentIntroMusicInstance; LK.tween(instanceToFadeAndStop, { volume: 0 }, { duration: 3000, onFinish: function onFinish() { if (instanceToFadeAndStop.stop) { instanceToFadeAndStop.stop(); console.log("[FakeTutorial] currentIntroMusicInstance zatrzymane po tweenie."); if (gameState.currentIntroMusicInstance === instanceToFadeAndStop) { gameState.currentIntroMusicInstance = null; } } } }); } else { console.log("[FakeTutorial] Nie znaleziono currentIntroMusicInstance. Sprawdzanie LK.music..."); var musicViaLK_I = LK.music; if (musicViaLK_I && musicViaLK_I.assetId === 'introMusic') { LK.tween(musicViaLK_I, { volume: 0 }, { duration: 3000, onFinish: function onFinish() { if (musicViaLK_I.stop) { musicViaLK_I.stop(); } } }); } } if (this.currentRollMasterMusicInstance && this.currentRollMasterMusicInstance.stop) { console.log("[FakeTutorial] Zatrzymywanie currentRollMasterMusicInstance."); if (typeof this.currentRollMasterMusicInstance.volume === 'number') { this.currentRollMasterMusicInstance.volume = 0; } this.currentRollMasterMusicInstance.stop(); this.currentRollMasterMusicInstance = null; } console.log("[FakeTutorial] Odtwarzanie RollSouls..."); this.currentRollSoulsInstance = LK.playMusic('RollSouls', { loop: true, volume: 0.7, fade: { start: 0, end: 0.7, duration: 3000 } }); if (!this.currentRollSoulsInstance) { console.error("[FakeTutorial] LK.playMusic('RollSouls') nie zwróciło instancji!"); } this.currentState = "fakeTutorial"; ui.positionElements("fakeTutorial"); var instructionText = new Text2('Press LPM to block.. or don’t press.', { size: 100, fill: 0xFFFFFF, align: 'center', wordWrap: true, wordWrapWidth: 1800, dropShadow: true, dropShadowColor: 0x000000, dropShadowDistance: 3, dropShadowBlur: 4, dropShadowAngle: Math.PI / 4 }); instructionText.anchor.set(0.5, 0.5); instructionText.x = 2048 / 2; instructionText.y = 2732 / 2; currentSceneElements.addChild(instructionText); this.fakeTutorialTimerId = LK.setTimeout(function () { try { if (instructionText && instructionText.destroy) { instructionText.destroy(); } } catch (e) {} gameState.fakeTutorialTimerId = null; if (gameState && typeof gameState.showRealTutorial === 'function') { gameState.showRealTutorial(); } else { console.error("Error: gameState.showRealTutorial is not a function in fakeTutorial timeout!"); gameState.showTitleScreen(); } }, 6000); }, // *** NOWA FUNKCJA: Prawdziwy Tutorial (oddzielona od startGame) *** showRealTutorial: function showRealTutorial() { if (this.fakeTutorialTimerId) { LK.clearTimeout(this.fakeTutorialTimerId); } this.fakeTutorialTimerId = null; // Wyczyść timer fake tutorialu clearScene(); // Wyczyść elementy fake tutorialu/fake game over // Zatrzymaj animację tła intro i usuń je if (currentBackground) { tween.stop(currentBackground); currentBackground.destroy(); currentBackground = null; } this.currentState = "realTutorial"; // Ustaw tło tutoriala if (currentBackground && currentBackground.destroy) { currentBackground.destroy(); } currentBackground = LK.getAsset('realtutorialbg', {}); game.addChildAt(currentBackground, 0); // Pokaż ściany areny walls.forEach(function (wall) { if (wall) { wall.alpha = 1; } }); ui.positionElements("realTutorial"); // Ustaw UI (głównie ukrywa elementy gry) // --- NIE MA tutorialTitle ani tutorialDesc (tekst jest na grafice) --- // Przycisk "Let's Roll!" do rozpoczęcia gry var startButton = new Container(); startButton.interactive = true; startButton.x = 2048 / 2; startButton.y = 1250; // Zmniejszona pozycja Y, był 1500 currentSceneElements.addChild(startButton); // Powiększone tło przycisku var startButtonBg = LK.getAsset('button_bg', { anchorX: 0.5, anchorY: 0.5 }); startButtonBg.scale.set(1.3); // Skala tła przycisku zwiększona startButton.addChild(startButtonBg); // Powiększony i wyraźniejszy tekst przycisku var startButtonText = new Text2("Let's Roll!", { size: 80, fill: 0xFFFFFF, stroke: 0x000000, strokeThickness: 6 }); startButtonText.anchor.set(0.5, 0.5); startButton.addChild(startButtonText); // Funkcja kliknięcia przycisku startButton.down = function () { gameState.startGame(); // Rozpocznij grę }; }, // Obsługa inputu w stanie fakeTutorial (fałszywa śmierć) handleFakeTutorialInput: function handleFakeTutorialInput() { // Wyczyść timer, który miał prowadzić do prawdziwego tutorialu if (this.fakeTutorialTimerId) { LK.clearTimeout(this.fakeTutorialTimerId); this.fakeTutorialTimerId = null; // Zresetuj ID timera } clearScene(); // Wyczyść ekran fałszywego tutorialu // Ustaw stan tymczasowo (można by dodać dedykowany stan 'fakeGameOver') this.currentState = "fakeGameOver"; // Zmieniono z "gameOver" // Zatrzymaj animację tła intro, ale nie usuwaj go jeszcze if (currentBackground) { tween.stop(currentBackground); } ui.positionElements("gameOver"); // Użyj pozycjonowania gameOver dla tekstów "YOU DIED" // Wyświetl "YOU DIED" na czerwono var diedText = new Text2("YOU DIED", { size: 150, fill: 0xFF0000 }); // Czerwony kolor diedText.anchor.set(0.5, 0.5); diedText.x = 2048 / 2; diedText.y = 600; currentSceneElements.addChild(diedText); // Wyświetl wyjaśnienie var explanationText = new Text2("Did you check the title of the game?", { size: 70, fill: 0xFFFFFF, align: 'center', wordWrap: true, wordWrapWidth: 1800, dropShadow: true, dropShadowColor: 0x000000, // cień czarny dropShadowDistance: 3, // odległość cienia dropShadowBlur: 4, // lekkie rozmycie cienia dropShadowAngle: Math.PI / 4 // kąt padania cienia (45 stopni) }); explanationText.anchor.set(0.5, 0.5); explanationText.x = 2048 / 2; explanationText.y = 800; currentSceneElements.addChild(explanationText); // Dodaj przycisk "How to play again" var howToPlayButtonContainer = new Container(); howToPlayButtonContainer.interactive = true; howToPlayButtonContainer.x = 2048 / 2; howToPlayButtonContainer.y = 1300; // Pozycja przycisku currentSceneElements.addChild(howToPlayButtonContainer); var buttonBg = LK.getAsset('button_bg', { anchorX: 0.5, anchorY: 0.5 }); howToPlayButtonContainer.addChild(buttonBg); var buttonText = new Text2('How to play', { size: 50, fill: 0xFFFFFF }); // Zmieniono tekst i rozmiar buttonText.anchor.set(0.5, 0.5); howToPlayButtonContainer.addChild(buttonText); // Akcja przycisku: Przejdź do prawdziwego tutorialu howToPlayButtonContainer.down = function () { // *** POPRAWKA: Sprawdzenie przed wywołaniem *** if (gameState && typeof gameState.showRealTutorial === 'function') { gameState.showRealTutorial(); // Przejdź do prawdziwego tutorialu } else { console.error("Error: gameState.showRealTutorial is not a function in fake input handler!"); gameState.showTitleScreen(); // Awaryjny powrót do tytułu } }; }, // Przejście do stanu gry (walka z bossem) - wywoływane z Prawdziwego Tutorialu startGame: function startGame() { var _this = this; console.log("[StartGame] Rozpoczynanie standardowej gry lub NewBoss+."); if (this.currentIntroMusicInstance && this.currentIntroMusicInstance.stop) { console.log("[StartGame] Zatrzymywanie currentIntroMusicInstance."); if (typeof this.currentIntroMusicInstance.volume === 'number') { this.currentIntroMusicInstance.volume = 0; } this.currentIntroMusicInstance.stop(); this.currentIntroMusicInstance = null; } if (this.currentRollMasterMusicInstance && this.currentRollMasterMusicInstance.stop) { console.log("[StartGame] Zatrzymywanie currentRollMasterMusicInstance."); if (typeof this.currentRollMasterMusicInstance.volume === 'number') { this.currentRollMasterMusicInstance.volume = 0; } this.currentRollMasterMusicInstance.stop(); this.currentRollMasterMusicInstance = null; } if (this.currentCursedCrystalMusicInstance && this.currentCursedCrystalMusicInstance.stop) { // DODANA LOGIKA console.log("[StartGame] Zatrzymywanie currentCursedCrystalMusicInstance."); if (typeof this.currentCursedCrystalMusicInstance.volume === 'number') { this.currentCursedCrystalMusicInstance.volume = 0; } this.currentCursedCrystalMusicInstance.stop(); this.currentCursedCrystalMusicInstance = null; } if (this.currentRollSoulsInstance && this.currentRollSoulsInstance.stop) { console.log("[StartGame] Zatrzymywanie poprzedniej currentRollSoulsInstance."); if (typeof this.currentRollSoulsInstance.volume === 'number') { this.currentRollSoulsInstance.volume = 0; } this.currentRollSoulsInstance.stop(); this.currentRollSoulsInstance = null; // Upewnij się, że zerujesz po zatrzymaniu, aby odtworzyć nową } console.log("[StartGame] Uruchamianie RollSouls."); this.currentRollSoulsInstance = LK.playMusic('RollSouls', { // Zakładam, że 'RollSouls' to muzyka dla tego trybu loop: true, volume: 0.7 }); if (!this.currentRollSoulsInstance) { console.error("[StartGame] LK.playMusic('RollSouls') nie zwróciło instancji!"); } if (this.fakeTutorialTimerId) { LK.clearTimeout(this.fakeTutorialTimerId); this.fakeTutorialTimerId = null; } if (this.gameTimerInterval) { LK.clearInterval(this.gameTimerInterval); this.gameTimerInterval = null; } clearScene(); if (currentBackground) { tween.stop(currentBackground); currentBackground.destroy(); currentBackground = null; } this.currentState = "game"; if (gameState.grillMenuEffects && gameState.grillMenuEffects.length > 0) { gameState.grillMenuEffects.forEach(function (effect) { if (effect && effect.parent) { tween.stop(effect); effect.parent.removeChild(effect); if (effect.destroy && !effect.destroyed) { effect.destroy(); } } else if (effect && effect.destroy && !effect.destroyed) { console.warn("Sparkle/Star object found without parent but has destroy method:", effect); effect.destroy(); } else { console.warn("Sparkle/Star object found without parent or destroy method:", effect); } }); gameState.grillMenuEffects = []; } game.setBackgroundColor(0x111111); var arenaBg = LK.getAsset('arena', { anchorX: 0.5, anchorY: 0.5, x: 2048 / 2, y: 2732 / 2 }); game.addChildAt(arenaBg, 0); if (player && player.destroy) { player.destroy(); } player = game.addChild(new Player()); if (player) { player.health = parseInt(storage.maxHearts, 10) || 5; console.log("DEBUG: Inicjalizacja zdrowia gracza. player.health:", player.health, "storage.maxHearts:", storage.maxHearts); } player.rolling = false; player.invulnerable = false; player.invulnerabilityFrames = 0; player.rollCooldown = 0; if (player && typeof player.clearRollTimeouts === 'function') { player.clearRollTimeouts(); } player.hasRolledThroughBossThisRoll = false; player.x = 2048 / 2; player.y = 2732 / 2 + 400; player.alpha = 1; player.dead = false; if (boss && boss.destroy) { boss.destroy(); } boss = game.addChild(new Boss()); boss.x = 2048 / 2; boss.y = 2732 / 2 - 400; boss.alpha = 1; boss.attackCooldown = 90; boss.attacks = []; boss.attackSpeedMultiplier = 1; boss.repositioning = false; boss.dead = false; boss.phase = 1; boss.tint = 0xFFFFFF; boss.scale.set(1, 1); if (isNewBossPlusMode) { boss.maxHealth = 600; boss.health = boss.maxHealth; this.gameDuration = 600; boss.attackSpeedMultiplier = 0.6; boss.speed = 7; boss.ultimateAttackCooldownTimer = 0; boss.nextUltimateAttackAvailableTime = boss.ultimateAttackMinInterval; console.log("Starting NEW BOSS+ mode. HP:", boss.maxHealth, "Time:", this.gameDuration); } else { boss.maxHealth = 160; boss.health = boss.maxHealth; this.gameDuration = 120; boss.speed = 5; boss.attackSpeedMultiplier = 1; console.log("Starting STANDARD mode. HP:", boss.maxHealth, "Time:", this.gameDuration); } walls.forEach(function (wall) { if (wall) { wall.alpha = 1; } }); ui.positionElements("game"); if (ui && player) { ui.updateHearts(player.health, parseInt(storage.maxHearts, 10) || 5); } else if (ui) { ui.updateHearts(0, parseInt(storage.maxHearts, 10) || 5); } ui.showTutorial("Swipe to Roll!"); ui.updateBossHealth(boss.health, boss.maxHealth); if (coffinMemeImage && !coffinMemeImage.destroyed) { coffinMemeImage.destroy(); coffinMemeImage = null; } try { var assetMeta = null; try { assetMeta = LK.getAssetMeta('coffinDanceMeme'); } catch (metaError) { console.warn("LK.getAssetMeta nie działa lub brak assetu 'coffinDanceMeme'. Używam domyślnej wysokości 200."); } var memeHeight = assetMeta ? assetMeta.height : 200; coffinMemeImage = LK.getAsset('coffinDanceMeme', { anchorX: 0.5, anchorY: 1.0, x: 2048 / 2, y: 2732 + memeHeight, alpha: 1 }); var uiIndex = game.getChildIndex(ui); if (uiIndex > -1) { game.addChildAt(coffinMemeImage, uiIndex); } else { game.addChild(coffinMemeImage); } console.log("Coffin dance meme załadowany i schowany, y start:", coffinMemeImage.y, "Height:", memeHeight); } catch (e) { console.error("Nie udało się stworzyć/załadować assetu coffinDanceMeme:", e); coffinMemeImage = null; } this.remainingTime = this.gameDuration; ui.updateTimerDisplay(this.remainingTime); if (this.gameTimerInterval) { LK.clearInterval(this.gameTimerInterval); } this.bossSpeedIncreased = false; this.gameTimerInterval = LK.setInterval(function () { if (_this.currentState === "game") { _this.remainingTime--; ui.updateTimerDisplay(_this.remainingTime); if (!isNewBossPlusMode) { var accelerationThreshold = _this.gameDuration - 60; if (_this.remainingTime <= accelerationThreshold && !_this.bossSpeedIncreased) { _this.bossSpeedIncreased = true; if (boss && !boss.dead) { boss.attackSpeedMultiplier *= 0.7; boss.speed += 1; ui.showMessage("Boss attacks faster!", 2000); } } } if (_this.remainingTime <= 0) { LK.clearInterval(_this.gameTimerInterval); _this.gameTimerInterval = null; if (isNewBossPlusMode) { gameOverReasonIsDeath = false; gameState.gameOver(false); } else { gameState.showGrillScreen(); } } } else { if (_this.gameTimerInterval) { LK.clearInterval(_this.gameTimerInterval); } _this.gameTimerInterval = null; } }, 1000); LK.setTimeout(function () { if (_this.currentState === "game") { ui.hideTutorial(); } }, 3000); }, // Koniec funkcji startGame // victory() - nieużywane, logika w timerze i boss.die() showGrillScreen: function showGrillScreen() { this.rollMasterTime = 0; // <-- DODAJ TĘ LINIĘ isNewBossPlusMode = false; console.log("State: Grill Menu (isNewBossPlusMode reset to false)"); // Wyczyść timery (pozostaw ten fragment) if (this.gameTimerInterval) { LK.clearInterval(this.gameTimerInterval); } this.gameTimerInterval = null; if (this.fakeTutorialTimerId) { LK.clearTimeout(this.fakeTutorialTimerId); } this.fakeTutorialTimerId = null; this.bossSpeedIncreased = false; // Reset flagi // --- DODANY KOD: Tablica do przechowywania efektów z menu grilla --- gameState.grillMenuEffects = []; // Tworzymy nową tablicę przy wejściu do menu // --- KONIEC DODANEGO KODU --- // --- Rozpoczęcie agresywnego czyszczenia sceny --- // Stwórz listę obiektów (kontenerów, UI, ścian), które powinny pozostać w 'game'. // Upewnij się, że 'ui', 'currentSceneElements' i wszystkie obiekty w tablicy 'walls' są tutaj. var childrenToKeep = [ui, currentSceneElements].concat(walls); // Przejdź przez wszystkie dzieci głównego obiektu 'game' od końca do początku for (var i = game.children.length - 1; i >= 0; i--) { var child = game.children[i]; // Sprawdź, czy obecne dziecko NIE jest na liście obiektów do zachowania if (childrenToKeep.indexOf(child) === -1) { // console.log("Usuwam obiekt:", child); // Możesz tymczasowo odkomentować do debugowania // Usuń i zniszcz obiekt if (child && child.destroy) { child.destroy(); // Użyj destroy jeśli dostępne (zalecane w LK) } else if (child && child.parent) { child.parent.removeChild(child); // Fallback - usuń z rodzica } } } // Po agresywnym czyszczeniu, upewnij się, że globalne zmienne gracza, bossa i tła są null // Te obiekty powinny zostać usunięte przez powyższą pętlę, ale warto zresetować zmienne. currentBackground = null; player = null; boss = null; // --- Koniec agresywnego czyszczenia sceny --- this.currentState = "grillMenu"; game.setBackgroundColor(0x333333); // Tło grilla // Teraz dodaj nowe tło dla ekranu Grilla (ten fragment zostaje taki, jak go poprawiliśmy ostatnio - JEDNO dodanie grillMenu) currentBackground = LK.getAsset('grillMenu', { anchorX: 0.5, anchorY: 0.5, x: 2048 / 2, y: 2732 / 2 }); // Dodaj nowe tło na spód if (currentBackground) { // Sprawdź czy asset został poprawnie załadowany game.addChildAt(currentBackground, 0); } var confirmRestButton = null; // ✨ DODAJEMY 5 SPARKLINGÓW ✨ // Sparkle 1 var sparkle1 = game.addChild(LK.getAsset('sparkle', { anchorX: 0.5, anchorY: 0.5, x: 2048 / 2 - 460, y: 2732 / 2 + 690 })); gameState.grillMenuEffects.push(sparkle1); // <--- DODANA LINIJA // Sparkle 2 var sparkle2 = game.addChild(LK.getAsset('sparkle', { anchorX: 0.5, anchorY: 0.5, x: 2048 / 2 - 520, y: 2732 / 2 + 580 })); gameState.grillMenuEffects.push(sparkle2); // <--- DODANA LINIJA // Sparkle 3 var sparkle3 = game.addChild(LK.getAsset('sparkle', { anchorX: 0.5, anchorY: 0.5, x: 2048 / 2 - 660, y: 2732 / 2 + 550 })); gameState.grillMenuEffects.push(sparkle3); // <--- DODANA LINIJA // Sparkle 4 var sparkle4 = game.addChild(LK.getAsset('sparkle', { anchorX: 0.5, anchorY: 0.5, x: 2048 / 2 - 500, y: 2732 / 2 + 680 })); gameState.grillMenuEffects.push(sparkle4); // <--- DODANA LINIJA // Sparkle 5 var sparkle5 = game.addChild(LK.getAsset('sparkle', { anchorX: 0.5, anchorY: 0.5, x: 2048 / 2 - 620, y: 2732 / 2 + 720 })); gameState.grillMenuEffects.push(sparkle5); // <--- DODANA LINIJA // Funkcja animacji sparkle function animateSparkle(s) { // Sprawdzenie stanu (pozostaje bez zmian) if (gameState.currentState !== "grillMenu" || !s || s.destroyed) { return; } // ZAPISZ pozycję Y na początku TEGO cyklu animacji var initialYThisCycle = s.y; s.alpha = 0; // Rozpocznij od przezroczystości 0 // Pozycję Y ustawimy za chwilę, nie ma potrzeby resetowania jej tutaj od razu // Animacja pojawienia się i ruchu w górę tween(s, { alpha: 1, y: initialYThisCycle - 40 // Przesuń w górę WZGLĘDEM ZAPISANEJ pozycji }, { duration: 800, easing: tween.easeOut, onFinish: function onFinish() { // Po zakończeniu ruchu w górę if (gameState.currentState !== "grillMenu" || !s || s.destroyed) { return; } // Animacja zanikania (na tej wyższej pozycji) tween(s, { alpha: 0 }, { duration: 300, easing: tween.easeIn, onFinish: function onFinish() { // Po zakończeniu zanikania if (gameState.currentState !== "grillMenu" || !s || s.destroyed) { return; } // *** KLUCZOWA ZMIANA: Resetuj pozycję Y PRZED następnym cyklem *** s.y = initialYThisCycle; // <-- WRÓĆ do pozycji Y z początku tego cyklu // Zaplanuj kolejny cykl animacji LK.setTimeout(function () { animateSparkle(s); // Wywołaj kolejny cykl (teraz zacznie z poprawnej pozycji Y) }, Math.random() * 500 + 300); } }); } }); } // Start animacji sparklingów // Upewnij się, że animacje startują z pewnym opóźnieniem po dodaniu do tablicy LK.setTimeout(function () { if (gameState.currentState === "grillMenu") { animateSparkle(sparkle1); } }, Math.random() * 500); LK.setTimeout(function () { if (gameState.currentState === "grillMenu") { animateSparkle(sparkle2); } }, Math.random() * 500 + 500); LK.setTimeout(function () { if (gameState.currentState === "grillMenu") { animateSparkle(sparkle3); } }, Math.random() * 500 + 1000); LK.setTimeout(function () { if (gameState.currentState === "grillMenu") { animateSparkle(sparkle4); } }, Math.random() * 500 + 1500); LK.setTimeout(function () { if (gameState.currentState === "grillMenu") { animateSparkle(sparkle5); } }, Math.random() * 500 + 2000); // 🌟 DODAJEMY 3 GWIAZDKI NA NIEBIE 🌟 // Star 1 var star1 = game.addChild(LK.getAsset('star', { anchorX: 0.5, anchorY: 0.5, x: 2048 / 2 - 800, y: 2732 / 2 - 1000 })); gameState.grillMenuEffects.push(star1); // <--- DODANA LINIJA // Star 2 var star2 = game.addChild(LK.getAsset('star', { anchorX: 0.5, anchorY: 0.5, x: 2048 / 2 - 300, y: 2732 / 2 - 1150 })); gameState.grillMenuEffects.push(star2); // <--- DODANA LINIJA // Star 3 var star3 = game.addChild(LK.getAsset('star', { anchorX: 0.5, anchorY: 0.5, x: 2048 / 2 - 200, y: 2732 / 2 - 900 })); gameState.grillMenuEffects.push(star3); // <--- DODANA LINIJA // Funkcja animacji gwiazdek function animateStar(star) { // --- DODANE SPRAWDZENIE STANU --- if (gameState.currentState !== "grillMenu" || !star || star.destroyed) { return; } // --- KONIEC DODANEGO SPRAWDZENIA --- star.alpha = 0; tween(star, { alpha: 1 }, { duration: 2000, easing: tween.easeIn, onFinish: function onFinish() { // --- DODANE SPRAWDZENIE STANU W CALLBACKU --- if (gameState.currentState !== "grillMenu" || !star || star.destroyed) { return; } // --- KONIEC DODANEGO SPRAWDZENIA --- tween(star, { alpha: 0 }, { duration: 2000, easing: tween.easeOut, onFinish: function onFinish() { // --- DODANE SPRAWDZENIE STANU W WEWNĘTRZNYM CALLBACKU --- if (gameState.currentState !== "grillMenu" || !star || star.destroyed) { return; } // --- KONIEC DODANEGO SPRAWDZENIA --- // Po całym cyklu czekamy losowo 2-5 sekund przed kolejnym pojawieniem się LK.setTimeout(function () { animateStar(star); }, Math.random() * 3000 + 2000); } }); } }); } // Start animacji gwiazdek // Upewnij się, że animacje startują z pewnym opóźnieniem po dodaniu do tablicy LK.setTimeout(function () { if (gameState.currentState === "grillMenu") { animateStar(star1); } }, Math.random() * 1000); LK.setTimeout(function () { if (gameState.currentState === "grillMenu") { animateStar(star2); } }, Math.random() * 1000 + 500); LK.setTimeout(function () { if (gameState.currentState === "grillMenu") { animateStar(star3); } }, Math.random() * 1000 + 1000); // --- KONIEC gwiazdek --- // --- KONIEC sparklingów --- // Ukryj ściany (jeśli są widoczne w menu) walls.forEach(function (wall) { if (wall) { wall.alpha = 0; } }); // Ustaw UI dla Grill Menu ui.positionElements("grillMenu"); ui.updateBossHealth(0, 1); // Ukryj pasek HP bossa ui.updateHearts(0, storage.maxHearts); // Ukryj serca gracza // --- Przyciski na ekranie Grilla --- var buttonYStart = 1000; var buttonYOffset = 150; // Przycisk "Rest" var restButton = new Container(); restButton.interactive = true; restButton.cursor = "pointer"; restButton.x = 600; restButton.y = 650; currentSceneElements.addChild(restButton); var restButtonBg = LK.getAsset('buttonRest', { anchorX: 0.5, anchorY: 0.5 }); restButton.addChild(restButtonBg); restButton.down = function () { if (!confirmRestButton || confirmRestButton && confirmRestButton.destroyed) { confirmRestButton = new Container(); confirmRestButton.interactive = true; confirmRestButton.cursor = "pointer"; // Zamieniamy tekst i tło na konkretny asset var confirmButtonGraphic = LK.getAsset('confirmRestButton', { anchorX: 0.5, anchorY: 0.5 }); confirmRestButton.addChild(confirmButtonGraphic); var restButtonActualWidth = restButtonBg.width || 500; var confirmButtonActualWidth = confirmButtonGraphic.width || 550; var paddingBetweenButtons = 30; confirmRestButton.x = restButton.x + restButtonActualWidth / 2 + confirmButtonActualWidth / 2 + paddingBetweenButtons; confirmRestButton.y = restButton.y; currentSceneElements.addChild(confirmRestButton); confirmRestButton.down = function () { var farewellGraphic = null; try { farewellGraphic = LK.getAsset('grillMenuFarewellGraphic', { anchorX: 0.5, anchorY: 0.5, x: 2048 / 2, y: 2732 / 2, alpha: 1 // od razu widoczna }); currentSceneElements.addChild(farewellGraphic); LK.setTimeout(function () { if (gameState.currentState === "grillMenu" && farewellGraphic && !farewellGraphic.destroyed) { tween(farewellGraphic, { alpha: 0 }, { duration: 500, onFinish: function onFinish() { if (farewellGraphic && !farewellGraphic.destroyed) { currentSceneElements.removeChild(farewellGraphic); farewellGraphic.destroy(); } if (confirmRestButton && !confirmRestButton.destroyed) { currentSceneElements.removeChild(confirmRestButton); confirmRestButton.destroy(); confirmRestButton = null; } } }); } else if (farewellGraphic && !farewellGraphic.destroyed) { currentSceneElements.removeChild(farewellGraphic); farewellGraphic.destroy(); } }, 6000); } catch (e) { console.error("Błąd grillMenuFarewellGraphic:", e); farewellGraphic = new Shape({ width: 2048, height: 2732, color: 0x101030 }); farewellGraphic.x = 2048 / 2; farewellGraphic.y = 2732 / 2; farewellGraphic.alpha = 0; currentSceneElements.addChild(farewellGraphic); tween(farewellGraphic, { alpha: 0.8 }, { duration: 500 }); LK.setTimeout(function () { if (farewellGraphic && !farewellGraphic.destroyed) { tween(farewellGraphic, { alpha: 0 }, { duration: 500, onFinish: function onFinish() { if (farewellGraphic && !farewellGraphic.destroyed) { currentSceneElements.removeChild(farewellGraphic); farewellGraphic.destroy(); } if (confirmRestButton && !confirmRestButton.destroyed) { currentSceneElements.removeChild(confirmRestButton); confirmRestButton.destroy(); confirmRestButton = null; } } }); } }, 6000); if (ui && ui.showMessage) { ui.showMessage("Błąd: Brak assetu grafiki. Placeholder.", 3000); } } }; } else { console.log("Przycisk 'Potwierdź' już istnieje."); } }; // Przycisk "Upgrade Roll" (TEN I KOLEJNE PRZYCISKI POZOSTAJĄ BEZ ZMIAN) var upgradeButton = new Container(); upgradeButton.interactive = true; upgradeButton.x = 600; upgradeButton.y = 850; currentSceneElements.addChild(upgradeButton); var upgradeButtonBg = LK.getAsset('buttonUpgrade', { anchorX: 0.5, anchorY: 0.5 }); upgradeButton.addChild(upgradeButtonBg); upgradeButton.down = function () { if (currentBackground) { tween(currentBackground, { alpha: 0 }, { duration: 600, easing: tween.easeIn, onFinish: function onFinish() { currentBackground.destroy(); currentBackground = LK.getAsset('upgradebg', { anchorX: 0.5, anchorY: 0.5, x: 2048 / 2, y: 2732 / 2, alpha: 0 }); game.addChildAt(currentBackground, 0); tween(currentBackground, { alpha: 1 }, { duration: 1200, easing: tween.easeOut }); } }); } }; // Przycisk "New Boss+" var newBossButton = new Container(); newBossButton.interactive = true; newBossButton.cursor = "pointer"; newBossButton.x = 600; newBossButton.y = 1050; // jeszcze niżej currentSceneElements.addChild(newBossButton); // Dodaj do kontenera sceny var newBossButtonBg = LK.getAsset('buttonBoss', { anchorX: 0.5, anchorY: 0.5 }); newBossButton.addChild(newBossButtonBg); newBossButton.down = function () { isNewBossPlusMode = true; // Poprzedni kod usuwający sparkle tutaj jest już niepotrzebny, // ponieważ obsługa usuwania jest teraz w startGame z wykorzystaniem gameState.grillMenuEffects gameState.startGame(); }; // Przycisk "Roll Master" var rollMasterButton = new Container(); // Tworzymy kontener na przycisk rollMasterButton.interactive = true; // Ustawiamy interaktywność rollMasterButton.cursor = "pointer"; // Zmieniamy kursor po najechaniu rollMasterButton.x = 600; // Ta sama pozycja X co inne przyciski rollMasterButton.y = 2500; // Pozycja Y pod przyciskiem "New Boss+" currentSceneElements.addChild(rollMasterButton); // Dodajemy kontener do sceny // --- NOWY KOD TŁA --- // Pobieramy asset obrazka dla przycisku Roll Master var rollMasterButtonImage = LK.getAsset('buttonRollMaster', { anchorX: 0.5, // Ustawiamy punkt zaczepienia na środek obrazka anchorY: 0.5 }); rollMasterButton.addChild(rollMasterButtonImage); // Dodajemy obrazek do kontenera przycisku // --- KONIEC NOWEGO KODU TŁA --- // Usunęliśmy kod tworzący i dodający obiekt Text2 // Akcja przycisku (kliknięcie) pozostaje bez zmian rollMasterButton.down = function () { gameState.startRollMasterMode(); // Uruchom tryb Roll Master }; // Przycisk "Cursed Crystal" var cursedCrystalButton = new Container(); cursedCrystalButton.interactive = true; cursedCrystalButton.cursor = "pointer"; cursedCrystalButton.x = 600; // Ta sama kolumna co inne główne przyciski // Ustaw pozycję Y pod przyciskiem "New Boss+" // Zakładamy, że newBossButton i newBossButtonBg istnieją i są zdefiniowane powyżej var newBossButtonHeight = newBossButtonBg && newBossButtonBg.height ? newBossButtonBg.height : 200; // Domyślna wysokość, jeśli nieznana var verticalSpacing = 700; // Odstęp między przyciskami // --- POCZĄTEK POPRAWKI dla LK.getAssetMeta --- var cursedCrystalButtonAssetForSizing; var cursedCrystalButtonHeight = 250; // Domyślna wysokość assetu buttonCursedCrystal (zgodnie z definicją w LK.init.image) try { // Pobierz asset, aby spróbować odczytać jego rzeczywistą wysokość cursedCrystalButtonAssetForSizing = LK.getAsset('buttonCursedCrystal', {}); if (cursedCrystalButtonAssetForSizing && typeof cursedCrystalButtonAssetForSizing.height === 'number') { cursedCrystalButtonHeight = cursedCrystalButtonAssetForSizing.height; } } catch (e) { // Jeśli wystąpi błąd przy pobieraniu assetu (np. jeszcze niezaładowany lub błąd ID), // użyjemy domyślnej wysokości i wyświetlimy ostrzeżenie. console.warn("Nie można pobrać assetu 'buttonCursedCrystal' do ustalenia wysokości, używam domyślnej: " + cursedCrystalButtonHeight, e); } // Oblicz pozycję Y przycisku Cursed Crystal cursedCrystalButton.y = newBossButton.y + newBossButtonHeight / 2 + verticalSpacing + cursedCrystalButtonHeight / 2; // --- KONIEC POPRAWKI dla LK.getAssetMeta --- currentSceneElements.addChild(cursedCrystalButton); var cursedCrystalButtonImage = LK.getAsset('buttonCursedCrystal', { anchorX: 0.5, anchorY: 0.5 }); cursedCrystalButton.addChild(cursedCrystalButtonImage); cursedCrystalButton.down = function () { if (gameState.currentState === "grillMenu") { console.log("Przycisk Cursed Crystal kliknięty!"); gameState.startCursedCrystalMode(); } }; var creditsButton = new Container(); creditsButton.interactive = true; creditsButton.cursor = "pointer"; // Ustaw pozycję dla prawego dolnego rogu // Te wartości (X, Y) są odległościami od lewego górnego rogu ekranu (0,0) // Musisz je dostosować do rozmiaru swojego przycisku i ekranu. // Załóżmy, że Twój przycisk 'buttoncredits' ma np. 200px szerokości i 80px wysokości. // Aby umieścić go w prawym dolnym rogu z marginesem 50px: var creditsButtonAsset = LK.getAsset('buttoncredits', {}); // Pobierz asset, aby sprawdzić wymiary jeśli trzeba var creditsButtonWidth = creditsButtonAsset.width || 200; // Domyślna szerokość, jeśli nie ma w assecie var creditsButtonHeight = creditsButtonAsset.height || 80; // Domyślna wysokość creditsButton.x = 2048 - creditsButtonWidth / 2 - 50; // Od prawej krawędzi (2048) odejmij połowę szerokości przycisku i margines creditsButton.y = 2732 - creditsButtonHeight / 2 - 50; // Od dolnej krawędzi (2732) odejmij połowę wysokości przycisku i margines currentSceneElements.addChild(creditsButton); // Użyj swojego nowego assetu 'buttoncredits' jako tła/grafiki przycisku var creditsButtonBg = LK.getAsset('buttoncredits', { anchorX: 0.5, // Anchor na środek, bo pozycjonujemy kontener anchorY: 0.5 // Nie potrzebujesz tutaj scale, chyba że Twój asset jest za duży/mały }); creditsButton.addChild(creditsButtonBg); // Tekst na przycisku (jeśli Twój asset 'buttoncredits' nie ma już napisu) // Jeśli 'buttoncredits' to już gotowy obrazek z napisem "Credits", ten obiekt Text2 nie jest potrzebny. var creditsButtonText = new Text2("Credits", { size: 30, // Dostosuj rozmiar, aby pasował do Twojego przycisku fill: 0xFFFFFF // Kolor tekstu // Możesz dodać stroke, jeśli tekst jest na skomplikowanym tle // stroke: 0x000000, // strokeThickness: 2 }); creditsButtonText.anchor.set(0.5, 0.5); // Wyśrodkuj tekst na przycisku creditsButton.addChild(creditsButtonText); // Dodaj tekst DO kontenera przycisku creditsButton.down = function () { if (gameState.currentState === "grillMenu") { console.log("Przycisk Credits kliknięty!"); // Dodaj log dla testu gameState.showCredits(); } }; }, showCredits: function showCredits() { var self = this; var previousState = this.currentState; var wasInputActive = self.isInputActive; self.isInputActive = false; // Tymczasowo zablokuj input gry // self.creditsPlaying = true; // Opcjonalna flaga, jeśli potrzebujesz console.log("[Credits] Pokazywanie Creditsów z własnym tłem"); // 1. Stwórz tło 'creditsbg' var creditsBackground; // Zmienna na nasze tło try { creditsBackground = LK.getAsset('creditsbg', { anchorX: 0.5, // Zakładamy, że chcesz je wyśrodkować anchorY: 0.5, x: 2048 / 2, y: 2732 / 2, alpha: 0 // Zacznij od przezroczystości }); game.addChild(creditsBackground); // Dodaj do sceny } catch (e) { console.error("[Credits] Błąd podczas ładowania assetu 'creditsbg':", e); // Awaryjnie: stwórz czarne tło, jeśli asset się nie załadował creditsBackground = new Shape({ width: 2048, height: 2732, color: 0x111010 }); // Użyjmy Twojego działającego koloru creditsBackground.x = 2048 / 2; creditsBackground.y = 2732 / 2; creditsBackground.alpha = 0; game.addChild(creditsBackground); } var creditsText = null; // Zmienna na tekst creditsów var creditsTextTween = null; // Zmienna na animację tekstu // Handler do pomijania creditsów var _skipCreditsHandler = function skipCreditsHandler() { console.log("[Credits] Pominięto creditsy"); // Zatrzymaj animacje tween.stop(creditsBackground); if (creditsTextTween) { tween.stop(creditsTextTween); } // Usuń obiekty if (creditsText && !creditsText.destroyed) { creditsText.destroy(); } if (creditsBackground && !creditsBackground.destroyed) { creditsBackground.destroy(); } // Przywróć stan self.isInputActive = wasInputActive; // self.creditsPlaying = false; game.off('down', _skipCreditsHandler); // Usuń listener // Jeśli currentState było zmieniane na coś w stylu "creditsScreenActive", przywróć je teraz // self.currentState = previousState; // W tej wersji nie zmieniamy currentState, więc nie ma potrzeby przywracania }; // Animacja pojawienia się tła tween(creditsBackground, { alpha: 1 }, { duration: 500, // Krótki fade-in onFinish: function onFinish() { // Stwórz tekst "Silas" creditsText = new Text2("Silas", { size: 150, fill: 0xFFFFFF, align: 'center' }); creditsText.anchor.set(0.5, 0.5); creditsText.x = 2048 / 2; creditsText.y = 2732 + creditsText.height; game.addChild(creditsText); var textAnimationDuration = 8000; var textEndY = -creditsText.height; // Animacja tekstu w górę creditsTextTween = tween(creditsText, { y: textEndY }, { duration: textAnimationDuration, easing: tween.linear, onFinish: function onFinish() { if (creditsText && !creditsText.destroyed) { creditsText.destroy(); } // Animacja zanikania tła 'creditsbg' tween(creditsBackground, { alpha: 0 }, { duration: 500, onFinish: function onFinish() { if (creditsBackground && !creditsBackground.destroyed) { creditsBackground.destroy(); } console.log("[Credits] Koniec Creditsów, powrót do", previousState); self.isInputActive = wasInputActive; // self.creditsPlaying = false; game.off('down', _skipCreditsHandler); // Usuń listener, jeśli nie został wcześniej wywołany } }); } }); // Dodaj listener do pomijania dopiero po pojawieniu się tła i tekstu (lub od razu, jeśli wolisz) game.once('down', _skipCreditsHandler); } }); }, // Koniec funkcji showGrillScreen (przecinek oddziela ją od następnej metody gameState) // --- NOWA FUNKCJA: Start przejścia do trybu Roll Master --- startRollMasterMode: function startRollMasterMode() { console.log("Rozpoczynanie trybu Roll Master..."); this.currentState = "rollMaster"; // Ustawienie nowego stanu gry // Wyczyszczenie elementów z poprzedniej sceny (np. Grill Menu) // Upewnij się, że 'currentSceneElements' to poprawny kontener dla elementów Grill Menu // lub użyj innej metody czyszczenia specyficznej dla Twojej konfiguracji. // Poniżej przykład czyszczenia globalnego kontenera, jeśli go używasz: if (typeof clearScene === 'function') { // Sprawdź, czy funkcja clearScene istnieje clearScene(); // Użyj globalnej funkcji clearScene, jeśli jest dostępna } else if (currentSceneElements && typeof currentSceneElements.removeChildren === 'function') { // Alternatywnie, jeśli currentSceneElements zawiera tylko elementy menu: currentSceneElements.removeChildren(); } else { console.warn("Nie można automatycznie wyczyścić sceny przed Roll Master!"); } // Dodatkowo, usuń specyficzne efekty z Grill Menu, jeśli istnieją if (this.grillMenuEffects && this.grillMenuEffects.length > 0) { this.grillMenuEffects.forEach(function (effect) { if (effect && effect.destroy) { effect.destroy(); } }); this.grillMenuEffects = []; } // Usuń tło Grill Menu if (currentBackground && currentBackground.destroy) { tween.stop(currentBackground); // Zatrzymaj animacje, jeśli były currentBackground.destroy(); currentBackground = null; } // Wywołanie funkcji konfigurującej scenę tego trybu this.setupRollMasterScene(); }, // <-- WAŻNE: Przecinek tutaj, bo następuje kolejna funkcja gameState // --- NOWA FUNKCJA: Konfiguracja sceny Roll Master --- setupRollMasterScene: function setupRollMasterScene() { var _this2 = this; // _this2 dla callbacków w timerach poniżej console.log("[SetupRollMaster] Konfiguracja sceny Roll Master..."); // --- POCZĄTEK SEKCJI ZARZĄDZANIA MUZYKĄ --- if (this.currentIntroMusicInstance && this.currentIntroMusicInstance.stop) { console.log("[SetupRollMaster] Zatrzymywanie currentIntroMusicInstance."); if (typeof this.currentIntroMusicInstance.volume === 'number') { this.currentIntroMusicInstance.volume = 0; } this.currentIntroMusicInstance.stop(); this.currentIntroMusicInstance = null; } if (this.currentCursedCrystalMusicInstance && this.currentCursedCrystalMusicInstance.stop) { this.currentCursedCrystalMusicInstance.stop(); this.currentCursedCrystalMusicInstance = null; } // <<< DODAJ TUTAJ if (this.currentRollSoulsInstance && this.currentRollSoulsInstance.stop) { console.log("[SetupRollMaster] Zatrzymywanie currentRollSoulsInstance."); if (typeof this.currentRollSoulsInstance.volume === 'number') { this.currentRollSoulsInstance.volume = 0; } this.currentRollSoulsInstance.stop(); this.currentRollSoulsInstance = null; } // Zatrzymaj poprzednią instancję rollmaster music, jeśli jakimś cudem istnieje if (this.currentRollMasterMusicInstance && this.currentRollMasterMusicInstance.stop) { console.log("[SetupRollMaster] Zatrzymywanie poprzedniej currentRollMasterMusicInstance."); if (typeof this.currentRollMasterMusicInstance.volume === 'number') { this.currentRollMasterMusicInstance.volume = 0; } this.currentRollMasterMusicInstance.stop(); // this.currentRollMasterMusicInstance = null; // Zerujemy, bo zaraz przypiszemy nową } console.log("[SetupRollMaster] Odtwarzanie muzyki 'rollmaster'."); this.currentRollMasterMusicInstance = LK.playMusic('rollmaster', { loop: true, volume: 0.7, fade: { start: 0, end: 0.7, duration: 1000 } }); if (!this.currentRollMasterMusicInstance) { console.error("[SetupRollMaster] LK.playMusic('rollmaster') nie zwróciło instancji!"); } // --- KONIEC SEKCJI ZARZĄDZANIA MUZYKĄ --- try { currentBackground = LK.getAsset('rollMasterBg', { anchorX: 0.5, anchorY: 0.5, x: 2048 / 2, y: 2732 / 2 }); game.addChildAt(currentBackground, 0); } catch (e) { console.warn("Nie znaleziono assetu 'rollMasterBg', używam koloru tła."); game.setBackgroundColor(0x222233); } if (typeof Player !== 'undefined') { player = game.addChild(new Player()); player.x = 2048 / 2; player.y = 2732 / 2 + 400; player.health = 1; player.rolling = false; player.invulnerable = false; player.invulnerabilityFrames = 0; player.rollCooldown = 0; if (player && typeof player.clearRollTimeouts === 'function') { player.clearRollTimeouts(); } player.hasRolledThroughBossThisRoll = false; player.alpha = 1; player.dead = false; } else { console.error("Klasa Player nie jest zdefiniowana!"); this.showGrillScreen(); return; } this.rollMasterTime = 0; this.rollMasterDifficulty = 1; this.rollMasterAttacks = []; this.attackUnlockOrder = ['rmattack1', 'rmattack2', 'rmattack3', 'rmattack4']; this.unlockedAttacks = ['rmattack1']; this.nextUnlockTime = 15; console.log("Ataki startowe:", this.unlockedAttacks, "Następne odblokowanie w:", this.nextUnlockTime + "s"); this.attackSpawnTimer = 0; this.attackSpawnInterval = 120; this.explosionSpawnTimer = 0; this.explosionSpawnInterval = 240; this.laserSpawnTimer = 0; this.laserSpawnInterval = 300; this.laserWarningTime = 1500; this.spreaderSpawnTimer = 0; this.spreaderSpawnInterval = 600; this.spreaderSplitTime = 180; if (this.randomRmattack1Timer) { LK.clearTimeout(this.randomRmattack1Timer); this.randomRmattack1Timer = null; } this.rollMasterHighScore = storage.rollMasterHighScore || 0; if (ui && ui.updateHighScoreDisplay) { ui.updateHighScoreDisplay(this.rollMasterHighScore); } else { console.error("Nie można zaktualizować wyświetlania rekordu - brak ui.updateHighScoreDisplay"); } if (ui && ui.timerText) { ui.updateTimerDisplay(this.rollMasterTime); } else { console.error("Nie można znaleźć ui.timerText do skonfigurowania!"); } if (ui && ui.updateHearts) { ui.updateHearts(1, 1); } if (ui && ui.updateBossHealth) { ui.updateBossHealth(0, 1); } if (ui && ui.positionElements) { ui.positionElements("rollMaster"); } else { console.error("Nie można ustawić elementów UI dla Roll Master - brak ui.positionElements"); } if (this.rollMasterTimerInterval) { LK.clearInterval(this.rollMasterTimerInterval); } this.rollMasterTimerInterval = LK.setInterval(function () { if (_this2.currentState !== "rollMaster") { LK.clearInterval(_this2.rollMasterTimerInterval); _this2.rollMasterTimerInterval = null; return; } _this2.rollMasterTime++; if (ui && ui.updateTimerDisplay) { ui.updateTimerDisplay(_this2.rollMasterTime); } if (_this2.rollMasterTime > 0 && _this2.rollMasterTime % 20 === 0) { var newAttackJustUnlocked = false; var newAttackFriendlyName = ""; if (_this2.unlockedAttacks.length < _this2.attackUnlockOrder.length) { var nextAttackIndex = _this2.unlockedAttacks.length; var attackToUnlockTechnicalName = _this2.attackUnlockOrder[nextAttackIndex]; _this2.unlockedAttacks.push(attackToUnlockTechnicalName); newAttackJustUnlocked = true; newAttackFriendlyName = attackToUnlockTechnicalName.replace('rmattack1', 'Fart of the Forgotten Pyromancer').replace('rmattack2', 'Blood Ball Z').replace('rmattack3', 'Holy Wifi Beam').replace('rmattack4', 'Flame Skull.exe'); console.log("Odblokowano nowy atak:", attackToUnlockTechnicalName, "w", _this2.rollMasterTime + "s"); } _this2.increaseRollMasterDifficulty(); if (ui && ui.showMessage) { var messageToShow = ""; var currentDifficulty = _this2.rollMasterDifficulty; if (newAttackJustUnlocked) { messageToShow = "New Attack: " + newAttackFriendlyName + "! (Difficulty: " + currentDifficulty + ")"; } else { switch (currentDifficulty) { case 2: case 3: case 4: messageToShow = "Even the pause button is afraid."; break; case 5: messageToShow = "Alright, that’s probably enough difficulty... (Poziom: " + currentDifficulty + ")"; break; case 6: messageToShow = "Wait, are you still playing? I ran out of messages. (Poziom: " + currentDifficulty + ")"; break; case 7: messageToShow = "Okay seriously, stop. I didn’t test this far. (Poziom: " + currentDifficulty + ")"; break; default: messageToShow = "I stopped writing messages after level 7. You're on your own. Level: " + currentDifficulty; break; } } ui.showMessage(messageToShow, 2500); } } }, 1000); if (ui && ui.showMessage && this.unlockedAttacks.length > 0) { var firstAttackName = this.unlockedAttacks[0]; var friendlyFirstName = firstAttackName.replace('rmattack1', 'Fart of the Forgotten Pyromancer').replace('rmattack2', 'Blood Ball Z').replace('rmattack3', 'Holy Wifi Beam').replace('rmattack4', 'Flame Skull.exe'); LK.setTimeout(function () { if (gameState.currentState === "rollMaster") { ui.showMessage("First Attack: " + friendlyFirstName, 3000); } }, 1000); } console.log("Scena Roll Master skonfigurowana i muzyka 'rollmaster' powinna grać."); }, // <-- WAŻNE: Przecinek tutaj // --- NOWA FUNKCJA: Zwiększanie trudności w Roll Master --- increaseRollMasterDifficulty: function increaseRollMasterDifficulty() { this.rollMasterDifficulty++; console.log("Trudność Roll Master zwiększona do:", this.rollMasterDifficulty); var decreaseAmountProjectile = 10; var minIntervalProjectile = 30; this.attackSpawnInterval = Math.max(minIntervalProjectile, this.attackSpawnInterval - decreaseAmountProjectile); console.log("Nowy interwał projectile:", this.attackSpawnInterval); var decreaseAmountExplosion = 15; var minIntervalExplosion = 60; this.explosionSpawnInterval = Math.max(minIntervalExplosion, this.explosionSpawnInterval - decreaseAmountExplosion); console.log("Nowy interwał explosion:", this.explosionSpawnInterval); // --- NOWA LOGIKA DLA LASERA --- var decreaseAmountLaser = 20; // Jak szybko ma skracać się interwał lasera var minIntervalLaser = 120; // Minimalny interwał lasera (np. 2 sekundy) this.laserSpawnInterval = Math.max(minIntervalLaser, this.laserSpawnInterval - decreaseAmountLaser); console.log("Nowy interwał laser:", this.laserSpawnInterval); // --- NOWA LOGIKA DLA SPREADERA --- var decreaseAmountSpreader = 30; // Jak szybko ma skracać się interwał spreadera var minIntervalSpreader = 180; // Minimalny interwał spreadera (np. 3 sekundy) this.spreaderSpawnInterval = Math.max(minIntervalSpreader, this.spreaderSpawnInterval - decreaseAmountSpreader); console.log("Nowy interwał spreader:", this.spreaderSpawnInterval); }, // Przejście do stanu Game Over // isDeath: true (gracz zginął), false (czas minął w Boss+) gameOver: function gameOver(isDeathParam) { // Krok 1: Zabezpieczenie parametru isDeathParam i przechwycenie aktualnego stanu isNewBossPlusMode var localIsDeath = typeof isDeathParam === 'boolean' ? isDeathParam : true; var calledDuringBossPlusMode = isNewBossPlusMode; // Przechwyć stan na początku wywołania! console.log("gameState.gameOver CALLED. isDeathParam:", isDeathParam, "=> localIsDeath:", localIsDeath, ". Flag isNewBossPlusMode at call time (captured as calledDuringBossPlusMode):", calledDuringBossPlusMode, ". Current global isNewBossPlusMode:", isNewBossPlusMode // Dla porównania, jeśli coś by go zmieniło w międzyczasie ); this.currentState = "gameOver"; // Ustaw stan gry gameOverReasonIsDeath = localIsDeath; // Ustaw globalną flagę (jeśli nadal jej używasz) // Krok 2: Zatrzymaj timery i inne procesy specyficzne dla trybu "game" if (this.gameTimerInterval) { LK.clearInterval(this.gameTimerInterval); this.gameTimerInterval = null; } // if (this.rollMasterTimerInterval) { // To jest dla Roll Master, nie powinno być tutaj zatrzymywane // LK.clearInterval(this.rollMasterTimerInterval); // this.rollMasterTimerInterval = null; // } this.bossSpeedIncreased = false; // Reset flagi dla standardowego bossa // Krok 3: Ukryj aktywne elementy gry (gracz, boss, ściany) if (player && player.alpha !== 0) { // Nie niszczymy gracza tutaj, jego animacja śmierci powinna się zakończyć, // a obiekt player zostanie zniszczony przed pokazaniem przycisków lub restartem. player.alpha = 0; } if (boss && boss.alpha !== 0) { // Podobnie dla bossa, jego animacja śmierci (jeśli jest) powinna się zakończyć. // Zostanie zniszczony później. boss.alpha = 0; } walls.forEach(function (wall) { if (wall) { wall.alpha = 0; } }); // Krok 4: Wstępne ustawienie UI (czyszczenie tekstów) if (ui) { ui.positionElements("gameOver"); // Może ukryć niepotrzebne elementy, pokazać stałe dla gameOver ui.titleText.setText(""); ui.titleText.alpha = 0; ui.messageText.setText(""); ui.messageText.alpha = 0; ui.tutorialText.setText(""); ui.tutorialText.alpha = 0; // Ukryj tutorial, jeśli był widoczny } // Krok 5: Wyświetl mema śmierci (jeśli localIsDeath) lub komunikat "Time's Up" var memeDisplayDuration = 3000; // Czas wyświetlania mema/komunikatu przed przyciskami var deathMemeVisual = null; // Zmienna do przechowywania obiektu mema if (localIsDeath) { var memeAssets = ['deathMeme1', 'deathMeme2', 'deathMeme3', 'deathMeme4', 'deathMeme5']; var randomMemeAssetName = memeAssets[Math.floor(Math.random() * memeAssets.length)]; console.log("[GameOver] Wyświetlanie mema:", randomMemeAssetName); try { deathMemeVisual = LK.getAsset(randomMemeAssetName, { anchorX: 0.5, anchorY: 0.5, alpha: 0, x: 2048 / 2, y: 2732 / 2 - 150, // Trochę wyżej, żeby zrobić miejsce na przyciski scaleX: 0.8, scaleY: 0.8 }); currentSceneElements.addChild(deathMemeVisual); // Dodaj do kontenera, który będzie czyszczony tween(deathMemeVisual, { alpha: 1 }, { duration: 500, easing: tween.easeIn }); } catch (e) { console.error("[GameOver] Błąd ładowania assetu mema:", randomMemeAssetName, e); if (ui) { // Awaryjny tekst, jeśli mem się nie załaduje ui.titleText.setText("DEFEAT!"); // Tłumaczenie ui.titleText.style = { size: 150, fill: 0xFF0000, align: 'center' }; ui.titleText.alpha = 1; } } } else if (calledDuringBossPlusMode && !localIsDeath) { // Boss+ Time's Up if (ui) { ui.titleText.setText("TIME'S UP!"); // Tłumaczenie ui.titleText.style = { size: 150, fill: 0xFFA500, align: 'center', stroke: 0x000000, strokeThickness: 6 }; ui.titleText.alpha = 1; // Można dodać dodatkowy komunikat poniżej tytułu // ui.messageText.setText("The challenge ends."); // ui.messageText.alpha = 1; } } // Krok 6: Timeout, po którym wykonają się akcje końcowe (przyciski lub przejście) LK.setTimeout(function () { // Ten console.log był źródłem błędu, teraz używamy localIsDeath i calledDuringBossPlusMode console.log("DEBUG: Timeout #1 (po memie) w gameState.gameOver. localIsDeath:", localIsDeath, "Captured BossPlus state (calledDuringBossPlusMode):", calledDuringBossPlusMode); // Usuń mema, jeśli był wyświetlony if (deathMemeVisual && !deathMemeVisual.destroyed) { tween(deathMemeVisual, { alpha: 0 }, { duration: 300, onFinish: function onFinish() { if (deathMemeVisual && deathMemeVisual.parent) { deathMemeVisual.parent.removeChild(deathMemeVisual); } if (deathMemeVisual && deathMemeVisual.destroy) { deathMemeVisual.destroy(); } deathMemeVisual = null; } }); } // Wyczyść tytuł/wiadomość, która była z memem, aby przygotować miejsce na nowy tytuł z przyciskami if (ui) { ui.titleText.alpha = 0; ui.messageText.alpha = 0; } // Ostateczne czyszczenie obiektów gry (gracz, boss, tło areny) // clearScene() może być zbyt agresywne, jeśli currentSceneElements zawiera UI. Lepiej ręcznie. if (player && player.destroy && !player.destroyed) { player.destroy(); player = null; } if (boss && boss.destroy && !boss.destroyed) { boss.destroy(); boss = null; } if (currentBackground) { // Usuń tło areny, jeśli istnieje if (currentBackground.parent) { currentBackground.parent.removeChild(currentBackground); } if (currentBackground.destroy && !currentBackground.destroyed) { currentBackground.destroy(); } currentBackground = null; } game.setBackgroundColor(0x1a1a1a); // Ciemne tło dla ekranu z przyciskami // Usuń poprzednie aktywne przyciski (np. z Cursed Crystal) if (gameState.activeButtons && gameState.activeButtons.length > 0) { gameState.activeButtons.forEach(function (btn) { if (btn && btn.destroy && !btn.destroyed) { // Dodatkowe sprawdzenie !btn.destroyed if (btn.parent) { btn.parent.removeChild(btn); } // Usuń z rodzica przed zniszczeniem btn.destroy(); } }); } gameState.activeButtons = []; // Wyczyść tablicę na nowe przyciski // Logika przycisków lub restartu if (calledDuringBossPlusMode) { // Przypadek Boss+ (śmierć LUB koniec czasu) - wyświetl przyciski console.log("gameOver: Scenariusz Boss+. Wyświetlanie tytułu i przycisków."); var titleTextForBossPlus = localIsDeath ? "DEFEAT!" : "TIME'S UP!"; if (ui) { ui.titleText.setText(titleTextForBossPlus); ui.titleText.style = { size: 120, fill: localIsDeath ? 0xFF0000 : 0xFFA500, align: 'center', stroke: 0x000000, strokeThickness: 6 }; ui.titleText.alpha = 1; ui.titleText.x = 2048 / 2; ui.titleText.y = 700; // Przykładowa pozycja Y dla tytułu } var buttonYStart = (ui && ui.titleText ? ui.titleText.y : 700) + 200; var buttonSpacing = 220; // Przycisk "Main Menu" var menuButton = new Container(); menuButton.interactive = true; menuButton.cursor = "pointer"; try { var menuButtonAsset = LK.getAsset('mainmenu', { anchorX: 0.5, anchorY: 0.5 }); menuButton.addChild(menuButtonAsset); } catch (e) { console.error("Błąd ładowania assetu przycisku 'mainmenu':", e); // Prosty fallback - tylko kształt, bez tekstu, bo zakładamy, że go nie potrzebujemy, // skoro główny asset ma tekst. Jeśli chcesz, możesz dodać tu tekst awaryjny. var menuButtonBgFallback = new Shape({ width: 400, height: 100, color: 0x555555, shape: 'box' }); menuButton.addChild(menuButtonBgFallback); } menuButton.x = 2048 / 2; menuButton.y = buttonYStart; menuButton.down = function () { if (gameState.currentState !== "gameOver") { return; } // Dodatkowe zabezpieczenie gameState.activeButtons.forEach(function (btn) { if (btn && btn.destroy && !btn.destroyed) { if (btn.parent) { btn.parent.removeChild(btn); } btn.destroy(); } }); gameState.activeButtons = []; isNewBossPlusMode = false; gameState.showGrillScreen(); }; currentSceneElements.addChild(menuButton); // Dodajemy do currentSceneElements gameState.activeButtons.push(menuButton); // Przycisk "Restart Boss+" var restartButton = new Container(); restartButton.interactive = true; restartButton.cursor = "pointer"; try { var restartButtonAsset = LK.getAsset('restartgame', { anchorX: 0.5, anchorY: 0.5 }); restartButton.addChild(restartButtonAsset); } catch (e) { console.error("Błąd ładowania assetu przycisku 'restartgame':", e); // Prosty fallback var restartButtonBgFallback = new Shape({ width: 400, height: 100, color: 0x4477FF, shape: 'box' }); // Dostosuj wymiary i kolor restartButton.addChild(restartButtonBgFallback); } restartButton.x = 2048 / 2; restartButton.y = buttonYStart + buttonSpacing; restartButton.down = function () { if (gameState.currentState !== "gameOver") { return; } gameState.activeButtons.forEach(function (btn) { if (btn && btn.destroy && !btn.destroyed) { if (btn.parent) { btn.parent.removeChild(btn); } btn.destroy(); } }); gameState.activeButtons = []; isNewBossPlusMode = true; gameState.startGame(); }; currentSceneElements.addChild(restartButton); gameState.activeButtons.push(restartButton); } else if (localIsDeath && !calledDuringBossPlusMode) { // Śmierć w standardowym trybie (tutorial) - BEZ PRZYCISKÓW, od razu restart console.log("gameOver: Standardowa śmierć (tutorial). Restartowanie poziomu."); isNewBossPlusMode = false; gameState.startGame(); } else { // Inny, nieoczekiwany scenariusz (np. !localIsDeath i !calledDuringBossPlusMode) // To jest fallback, np. jeśli jakimś cudem czas minął w trybie non-Boss+ (co nie powinno się zdarzyć) console.warn("gameOver: Nieoczekiwany scenariusz (np. timeout w trybie non-Boss+). Przejście do Grill Menu."); isNewBossPlusMode = false; gameState.showGrillScreen(); } }, memeDisplayDuration); // Czas na wyświetlenie mema/komunikatu przed pokazaniem przycisków/przejściem }, // --- NOWA FUNKCJA: Zakończenie trybu Roll Master --- // Wewnątrz obiektu gameState: endRollMasterMode: function endRollMasterMode(finalTime) { if (this.currentState === "rollMasterGameOver") { return; } console.log("[EndRollMasterMode] Zakończono tryb Roll Master. Czas:", finalTime); this.currentState = "rollMasterGameOver"; if (this.currentRollMasterMusicInstance && this.currentRollMasterMusicInstance.stop) { this.currentRollMasterMusicInstance.stop(); this.currentRollMasterMusicInstance = null; } // ... (zatrzymanie timerów, czyszczenie ataków RM, logika rekordu - jak w poprzedniej odpowiedzi) ... if (this.rollMasterTimerInterval) { LK.clearInterval(this.rollMasterTimerInterval); this.rollMasterTimerInterval = null; } if (this.randomRmattack1Timer) { LK.clearTimeout(this.randomRmattack1Timer); this.randomRmattack1Timer = null; } if (this.rollMasterAttacks && this.rollMasterAttacks.length > 0) { this.rollMasterAttacks.forEach(function (atk) { if (atk.visual && atk.visual.destroy && !atk.visual.destroyed) { if (atk.visual.parent) { atk.visual.parent.removeChild(atk.visual); } atk.visual.destroy(); } }); this.rollMasterAttacks = []; } if (player && typeof player.clearRollTimeouts === 'function') { player.clearRollTimeouts(); } var oldHighScore = parseInt(storage.rollMasterHighScore, 10) || 0; var newRecordRM = false; if (finalTime > oldHighScore) { storage.rollMasterHighScore = finalTime; this.rollMasterHighScore = finalTime; newRecordRM = true; } if (player && player.alpha !== 0) { player.alpha = 0; // Ukryj gracza, ale nie niszcz od razu } var memeAndScoreDisplayDuration = 3000; var deathMemeVisualRM = null; var scoreTextVisualRM = null; var memeAssets = ['deathMeme1', 'deathMeme2', 'deathMeme3', 'deathMeme4', 'deathMeme5']; var randomMemeAssetName = memeAssets[Math.floor(Math.random() * memeAssets.length)]; try { deathMemeVisualRM = LK.getAsset(randomMemeAssetName, { anchorX: 0.5, anchorY: 0.5, alpha: 0, x: 2048 / 2, y: 2732 / 2 - 200, scaleX: 0.8, scaleY: 0.8 }); // Dodaj mema NAD istniejącym tłem (rollMasterBg), jeśli currentSceneElements jest nad nim // lub bezpośrednio do 'game' na odpowiedniej warstwie. // Jeśli currentSceneElements jest używane dla elementów pop-up, to jest dobre miejsce. game.addChild(deathMemeVisualRM); // Lub currentSceneElements.addChild(deathMemeVisualRM); tween(deathMemeVisualRM, { alpha: 1 }, { duration: 500, easing: tween.easeIn }); } catch (e) { console.error("[EndRollMasterMode] Błąd ładowania mema:", e); } var timeMessage = "Your Time: " + String(Math.floor(finalTime / 60)).padStart(2, '0') + ':' + String(finalTime % 60).padStart(2, '0'); if (newRecordRM) { timeMessage += "\n(NEW HIGH SCORE!)"; } else { timeMessage += "\nBest: " + String(Math.floor(this.rollMasterHighScore / 60)).padStart(2, '0') + ':' + String(this.rollMasterHighScore % 60).padStart(2, '0'); } scoreTextVisualRM = new Text2(timeMessage, { size: 70, fill: 0xFFFFFF, align: 'center', stroke: 0x000000, strokeThickness: 4, alpha: 0 }); scoreTextVisualRM.anchor.set(0.5, 0.5); scoreTextVisualRM.x = 2048 / 2; var memeHeightEstimate = deathMemeVisualRM && deathMemeVisualRM.height ? deathMemeVisualRM.height * (deathMemeVisualRM.scaleY || 1) : 560; // 700*0.8 scoreTextVisualRM.y = (deathMemeVisualRM ? deathMemeVisualRM.y + memeHeightEstimate / 2 : 2732 / 2 - 200 + 300) + 100; game.addChild(scoreTextVisualRM); // Lub currentSceneElements.addChild(scoreTextVisualRM); tween(scoreTextVisualRM, { alpha: 1 }, { duration: 500, delay: 300, easing: tween.easeIn }); if (ui && ui.positionElements) { ui.positionElements("rollMasterGameOver"); } if (ui && ui.updateHighScoreDisplay) { ui.updateHighScoreDisplay(this.rollMasterHighScore); } // Pokaż rekord w UI // Usuń stare aktywne przyciski (jeśli jakieś były) if (gameState.activeButtons && gameState.activeButtons.length > 0) { gameState.activeButtons.forEach(function (btn) { if (btn && btn.destroy && !btn.destroyed) { if (btn.parent) { btn.parent.removeChild(btn); } btn.destroy(); } }); } gameState.activeButtons = []; LK.setTimeout(function () { console.log("DEBUG: Timeout #1 (po memie/wyniku) w endRollMasterMode. Pokazywanie przycisków."); if (deathMemeVisualRM && !deathMemeVisualRM.destroyed) { tween(deathMemeVisualRM, { alpha: 0 }, { duration: 300, onFinish: function onFinish() { if (deathMemeVisualRM.parent) { deathMemeVisualRM.parent.removeChild(deathMemeVisualRM); } if (deathMemeVisualRM.destroy) { deathMemeVisualRM.destroy(); } deathMemeVisualRM = null; } }); } if (scoreTextVisualRM && !scoreTextVisualRM.destroyed) { tween(scoreTextVisualRM, { alpha: 0 }, { duration: 300, onFinish: function onFinish() { if (scoreTextVisualRM.parent) { scoreTextVisualRM.parent.removeChild(scoreTextVisualRM); } if (scoreTextVisualRM.destroy) { scoreTextVisualRM.destroy(); } scoreTextVisualRM = null; } }); } // Zniszcz obiekt gracza teraz, gdy już nie jest potrzebny if (player && player.destroy && !player.destroyed) { player.destroy(); player = null; } // TŁO: currentBackground (czyli rollMasterBg) POWINNO POZOSTAĆ. Nie niszczymy go. // NIE wywołuj game.setBackgroundColor, aby nie nadpisać rollMasterBg. // if (currentBackground) { // // Jeśli currentBackground to było coś specyficznego dla mema, a nie rollMasterBg, // // to tutaj trzeba by je usunąć i ewentualnie przywrócić rollMasterBg. // // Ale zakładamy, że currentBackground to rollMasterBg od początku tego trybu. // } // game.setBackgroundColor(0x1a1a1a); // <--- USUWAMY TĘ LINIĘ (lub podobną) var buttonYStart = 900; var buttonSpacing = 220; // Przycisk "Main Menu" var menuButton = new Container(); /* ... reszta definicji bez zmian ... */ menuButton.interactive = true; menuButton.cursor = "pointer"; var menuButtonBg = new Shape({ width: 400, height: 100, color: 0x555555, shape: 'box' }); try { var menuButtonAsset = LK.getAsset('mainmenu', { anchorX: 0.5, anchorY: 0.5 }); menuButton.addChild(menuButtonAsset); } catch (e) { console.error("Błąd ładowania assetu przycisku 'mainmenu':", e); var menuButtonBgFallback = new Shape({ width: 400, height: 100, color: 0x555555, shape: 'box' }); menuButton.addChild(menuButtonBgFallback); // Możesz dodać awaryjny Text2, jeśli chcesz, gdyby asset się nie załadował // var menuButtonTextFallback = new Text2("Main Menu", { size: 40, fill: 0xFFFFFF, anchor: { x: 0.5, y: 0.5 } }); // menuButton.addChild(menuButtonTextFallback); } menuButton.x = 2048 / 2; menuButton.y = buttonYStart; menuButton.down = function () { if (gameState.currentState !== "rollMasterGameOver") { return; } gameState.activeButtons.forEach(function (btn) { if (btn && btn.destroy && !btn.destroyed) { if (btn.parent) { btn.parent.removeChild(btn); } btn.destroy(); } }); gameState.activeButtons = []; // WAŻNE: Zanim przejdziesz do GrillMenu, zniszcz currentBackground (rollMasterBg) if (currentBackground && currentBackground.destroy && !currentBackground.destroyed) { if (currentBackground.parent) { currentBackground.parent.removeChild(currentBackground); } currentBackground.destroy(); currentBackground = null; } gameState.showGrillScreen(); }; game.addChild(menuButton); // Lub currentSceneElements.addChild(menuButton); gameState.activeButtons.push(menuButton); // Przycisk "Restart Mode" var restartButton = new Container(); /* ... reszta definicji bez zmian ... */ try { var restartButtonAsset = LK.getAsset('restartgame', { anchorX: 0.5, anchorY: 0.5 }); restartButton.addChild(restartButtonAsset); } catch (e) { console.error("Błąd ładowania assetu przycisku 'restartgame':", e); var restartButtonBgFallback = new Shape({ width: 400, height: 100, color: 0x4477FF, shape: 'box' }); restartButton.addChild(restartButtonBgFallback); // Możesz dodać awaryjny Text2 // var restartButtonTextFallback = new Text2("Restart Mode", { size: 40, fill: 0xFFFFFF, anchor: { x: 0.5, y: 0.5 } }); // restartButton.addChild(restartButtonTextFallback); } restartButton.x = 2048 / 2; restartButton.y = buttonYStart + buttonSpacing; restartButton.down = function () { if (gameState.currentState !== "rollMasterGameOver") { return; } gameState.activeButtons.forEach(function (btn) { if (btn && btn.destroy && !btn.destroyed) { if (btn.parent) { btn.parent.removeChild(btn); } btn.destroy(); } }); gameState.activeButtons = []; // WAŻNE: Zanim zrestartujesz tryb, zniszcz currentBackground (rollMasterBg) if (currentBackground && currentBackground.destroy && !currentBackground.destroyed) { if (currentBackground.parent) { currentBackground.parent.removeChild(currentBackground); } currentBackground.destroy(); currentBackground = null; } gameState.startRollMasterMode(); }; game.addChild(restartButton); // Lub currentSceneElements.addChild(restartButton); gameState.activeButtons.push(restartButton); }, memeAndScoreDisplayDuration); }, startCursedCrystalMode: function startCursedCrystalMode() { if (this.currentIntroMusicInstance && this.currentIntroMusicInstance.stop) { this.currentIntroMusicInstance.stop(); this.currentIntroMusicInstance = null; } if (this.currentRollSoulsInstance && this.currentRollSoulsInstance.stop) { this.currentRollSoulsInstance.stop(); this.currentRollSoulsInstance = null; } if (this.currentRollMasterMusicInstance && this.currentRollMasterMusicInstance.stop) { this.currentRollMasterMusicInstance.stop(); this.currentRollMasterMusicInstance = null; } if (this.currentCursedCrystalMusicInstance && this.currentCursedCrystalMusicInstance.stop) { this.currentCursedCrystalMusicInstance.stop(); this.currentCursedCrystalMusicInstance = null; } var randomIndex = Math.floor(Math.random() * this.cursedCrystalMusicTracks.length); var selectedTrackId = this.cursedCrystalMusicTracks[randomIndex]; console.log(Date.now() + " [StartCursedCrystal] Próba odtworzenia: " + selectedTrackId); this.currentCursedCrystalMusicInstance = LK.playMusic(selectedTrackId, { loop: true, volume: 0.7 }); // Logowanie wartości zwróconej przez LK.playMusic console.log(Date.now() + " [StartCursedCrystal] LK.playMusic zwróciło dla '" + selectedTrackId + "':", this.currentCursedCrystalMusicInstance); if (!this.currentCursedCrystalMusicInstance) { console.error(Date.now() + " [StartCursedCrystal] Nie udało się odtworzyć muzyki (instancja jest falsy): " + selectedTrackId); } else { console.log(Date.now() + " [StartCursedCrystal] Instancja muzyki wydaje się być utworzona dla: " + selectedTrackId); } console.log("[StartCursedCrystal] Rozpoczynanie trybu Cursed Crystal..."); console.log("[StartCursedCrystal] Rozpoczynanie trybu Cursed Crystal..."); this.currentState = "cursedCrystal"; // 1. Zatrzymaj muzykę z innych trybów if (this.currentIntroMusicInstance && this.currentIntroMusicInstance.stop) { this.currentIntroMusicInstance.stop(); this.currentIntroMusicInstance = null; } if (this.currentRollSoulsInstance && this.currentRollSoulsInstance.stop) { this.currentRollSoulsInstance.stop(); this.currentRollSoulsInstance = null; } if (this.currentRollMasterMusicInstance && this.currentRollMasterMusicInstance.stop) { this.currentRollMasterMusicInstance.stop(); this.currentRollMasterMusicInstance = null; } // TODO: Odtwórz muzykę specyficzną dla Cursed Crystal, jeśli ją masz // np. this.currentCursedCrystalMusic = LK.playMusic('cursedCrystalMusic_asset', { loop: true, volume: 0.7 }); // 2. Wyczyść scenę z elementów poprzedniego trybu if (typeof clearScene === 'function') { clearScene(); // Usuwa elementy z currentSceneElements } else { console.warn("Funkcja clearScene nie jest zdefiniowana globalnie!"); } // Dokładne czyszczenie specyficznych elementów z innych trybów: // Czyszczenie timerów i ataków z Roll Master if (this.rollMasterTimerInterval) { LK.clearInterval(this.rollMasterTimerInterval); this.rollMasterTimerInterval = null; } if (this.randomRmattack1Timer) { // Timer dla rmattack1 w Roll Master LK.clearTimeout(this.randomRmattack1Timer); this.randomRmattack1Timer = null; } if (this.rollMasterAttacks && this.rollMasterAttacks.length > 0) { console.log("[StartCursedCrystal] Czyszczenie " + this.rollMasterAttacks.length + " ataków z Roll Master."); this.rollMasterAttacks.forEach(function (atk) { if (atk.visual && atk.visual.destroy && !atk.visual.destroyed) { if (atk.visual.parent) { // Dodatkowe zabezpieczenie atk.visual.parent.removeChild(atk.visual); } atk.visual.destroy(); } // Jeśli atak miał inne zasoby (np. własne timery), też powinny być tu czyszczone }); this.rollMasterAttacks = []; } // Usuń specyficzne efekty z Grill Menu, jeśli istnieją if (this.grillMenuEffects && this.grillMenuEffects.length > 0) { console.log("[StartCursedCrystal] Czyszczenie " + this.grillMenuEffects.length + " efektów z Grill Menu."); this.grillMenuEffects.forEach(function (effect) { if (effect && effect.destroy && !effect.destroyed) { if (effect.parent) { // Dodatkowe zabezpieczenie effect.parent.removeChild(effect); } effect.destroy(); } }); this.grillMenuEffects = []; } // Usuń globalne obiekty gracza i bossa, jeśli istnieją if (player && player.destroy && !player.destroyed) { // Jeśli gracz ma jakieś specyficzne timery/interwały, które nie są czyszczone w jego .destroy() lub .clearRollTimeouts() // to tutaj byłoby miejsce na ich wyczyszczenie przed zniszczeniem obiektu player. // player.clearRollTimeouts(); // To powinno być wywoływane w Player.die() lub przed destroy() player.destroy(); } player = null; if (boss && boss.destroy && !boss.destroyed) { if (typeof boss.clearAllAttacks === 'function') { boss.clearAllAttacks("startCursedCrystalMode"); // Upewnij się, że ataki bossa są czyszczone } // Podobnie, jeśli boss ma jakieś globalne timery nieczyszczone w .destroy() boss.destroy(); } boss = null; // Usuń istniejące tło, jeśli jest if (currentBackground && currentBackground.destroy) { tween.stop(currentBackground); // Zatrzymaj animacje tła, jeśli były currentBackground.destroy(); currentBackground = null; } // Usuń obiekt Klejnotu, jeśli istniał z poprzedniej sesji tego trybu if (typeof crystalCoreObject !== 'undefined' && crystalCoreObject && crystalCoreObject.destroy && !crystalCoreObject.destroyed) { crystalCoreObject.destroy(); } crystalCoreObject = null; // 3. Wywołaj konfigurację sceny dla Cursed Crystal // Zamiast this.setupCursedCrystalScene_MinimalTest(); wywołaj normalną funkcję: this.setupCursedCrystalScene(); // Aby użyć minimalnej sceny testowej, odkomentuj poniższe i zakomentuj powyższe: // this.setupCursedCrystalScene_MinimalTest(); }, updateCrystalVisual: function updateCrystalVisual() { if (!crystalCoreObject || crystalCoreObject.destroyed || this.isMinibossActiveCC || this.cursedCrystalChargeLevel >= this.cursedCrystalTargetCharge || this.crystalExploding) { return; } var newAssetId = 'crystal_state_0'; var charge = this.cursedCrystalChargeLevel; var targetCharge = this.cursedCrystalTargetCharge; var thresholds = { state1: 0.15 * targetCharge, state2: 0.30 * targetCharge, state3: 0.55 * targetCharge, state4: 0.80 * targetCharge }; if (charge >= thresholds.state4) { newAssetId = 'crystal_state_4'; } else if (charge >= thresholds.state3) { newAssetId = 'crystal_state_3'; } else if (charge >= thresholds.state2) { newAssetId = 'crystal_state_2'; } else if (charge >= thresholds.state1) { newAssetId = 'crystal_state_1'; } if (crystalCoreObject.currentVisual && crystalCoreObject.currentVisual.assetId === newAssetId) { return; } if (crystalCoreObject.currentVisual) { crystalCoreObject.removeChild(crystalCoreObject.currentVisual); if (crystalCoreObject.currentVisual.destroy && !crystalCoreObject.currentVisual.destroyed) { crystalCoreObject.currentVisual.destroy(); } crystalCoreObject.currentVisual = null; } try { var newVisual = LK.getAsset(newAssetId, { anchorX: 0.5, anchorY: 0.5 }); crystalCoreObject.addChild(newVisual); crystalCoreObject.currentVisual = newVisual; crystalCoreObject.currentVisual.assetId = newAssetId; console.log("Wizualizacja kryształu zmieniona na: " + newAssetId + " (naładowanie: " + charge + ")"); } catch (e) { console.error("Błąd ładowania assetu kryształu: " + newAssetId, e); if (newAssetId !== 'crystal_state_0' && (!crystalCoreObject.currentVisual || crystalCoreObject.currentVisual.assetId !== 'crystal_state_0')) { try { var baseVisual = LK.getAsset('crystal_state_0', { anchorX: 0.5, anchorY: 0.5 }); crystalCoreObject.addChild(baseVisual); crystalCoreObject.currentVisual = baseVisual; crystalCoreObject.currentVisual.assetId = 'crystal_state_0'; } catch (e2) { console.error("Błąd ładowania awaryjnego assetu 'crystal_state_0'", e2); } } } }, playCrystalExplosionAnimation: function playCrystalExplosionAnimation() { var crystalOriginalX = 2048 / 2; // Domyślna pozycja X na środek ekranu var crystalOriginalY = 2732 / 2; // Domyślna pozycja Y na środek ekranu if (crystalCoreObject && !crystalCoreObject.destroyed) { // Jeśli crystalCoreObject istnieje, użyj jego aktualnej pozycji crystalOriginalX = crystalCoreObject.x; crystalOriginalY = crystalCoreObject.y; console.log("Odtwarzanie animacji eksplozji kryształu w pozycji: X=" + crystalOriginalX + ", Y=" + crystalOriginalY); // Zatrzymaj istniejące tweeny na kontenerze kryształu (lewitacja, pulsowanie) if (crystalCoreObject.levitationTween && crystalCoreObject.levitationTween.stop) { crystalCoreObject.levitationTween.stop(); crystalCoreObject.levitationTween = null; // Wyczyść referencję } if (crystalCoreObject.scaleTween && crystalCoreObject.scaleTween.stop) { crystalCoreObject.scaleTween.stop(); crystalCoreObject.scaleTween = null; // Wyczyść referencję } // Można dodać reset pozycji/skali, jeśli tweeny mogły je zmienić w sposób niepożądany dla animacji crystalCoreObject.scale.set(1); // Przywróć domyślną skalę kontenera // crystalCoreObject.y = crystalCoreObject.originalY; // Jeśli chcesz przywrócić oryginalną Y przed animacją // Usuń obecną statyczną grafikę kryształu (dziecko kontenera crystalCoreObject) if (crystalCoreObject.currentVisual) { crystalCoreObject.removeChild(crystalCoreObject.currentVisual); if (crystalCoreObject.currentVisual.destroy && !crystalCoreObject.currentVisual.destroyed) { crystalCoreObject.currentVisual.destroy(); } crystalCoreObject.currentVisual = null; } var explosionFramesAssets = []; for (var i = 0; i < 4; i++) { // Załóżmy 4 klatki animacji try { // Upewnij się, że anchorX i anchorY są ustawione, jeśli Twoje klatki tego wymagają // dla poprawnego pozycjonowania wewnątrz SpriteAnimation explosionFramesAssets.push(LK.getAsset('crystal_explosion_f' + i, { anchorX: 0.5, anchorY: 0.5, clone: true })); } catch (e) { console.error("Błąd ładowania klatki eksplozji: crystal_explosion_f" + i, e); // Awaryjny placeholder jeśli klatki brakuje, aby animacja nadal miała odpowiednią długość var placeholderFrame = new Shape({ width: 150, height: 150, color: 0xFF8C00, shape: 'ellipse' }); placeholderFrame.anchor.set(0.5, 0.5); // Ustaw anchor dla Shape explosionFramesAssets.push(placeholderFrame); } } var explosionAnim = new SpriteAnimation({ frames: explosionFramesAssets, frameDuration: 150, // Czas trwania klatki w ms (np. 150ms * 4 klatki = 0.6s) - dostosuj loop: false, anchorX: 0.5, // Anchor samej animacji SpriteAnimation, jeśli ma być inaczej pozycjonowana w crystalCoreObject anchorY: 0.5 // x i y będą 0,0 względem kontenera crystalCoreObject, jeśli animacja ma być na jego środku }); crystalCoreObject.addChild(explosionAnim); // Dodaj animację eksplozji do kontenera kryształu explosionAnim.play(); // TODO: Dodaj tutaj swój dźwięk eksplozji, jeśli masz // np. LK.getSound('twoj_dzwiek_eksplozji_krysztalu').play(); } else { // Ten blok jest awaryjny, jeśli crystalCoreObject nie istnieje, gdy funkcja jest wołana console.warn("Próba eksplozji, ale crystalCoreObject nie istnieje lub został zniszczony przed rozpoczęciem animacji. Bezpośrednie spawnowanie Minibossa w domyślnej pozycji."); this.spawnCursedCrystalMiniboss(crystalOriginalX, crystalOriginalY); this.crystalExploding = false; // Zresetuj flagę return; // Zakończ funkcję, jeśli nie ma kryształu do animowania } var selfGameState = this; // Zachowaj referencję 'this' (gameState) dla callbacku onComplete explosionAnim.onComplete = function () { console.log("Animacja eksplozji kryształu zakończona."); // Zniszcz kontener crystalCoreObject po zakończeniu animacji if (crystalCoreObject && !crystalCoreObject.destroyed) { if (crystalCoreObject.parent) { crystalCoreObject.parent.removeChild(crystalCoreObject); } crystalCoreObject.destroy(); } crystalCoreObject = null; // Wyczyść globalną/dostępną referencję // Wywołaj logikę spawnowania Minibossa, przekazując zapisaną pozycję selfGameState.spawnCursedCrystalMiniboss(crystalOriginalX, crystalOriginalY); selfGameState.crystalExploding = false; // Zresetuj flagę po zakończeniu całego procesu }; }, spawnCursedCrystalMiniboss: function spawnCursedCrystalMiniboss(spawnX, spawnY) { // Argumenty to spawnX i spawnY console.log("MINIBOSS SPAWNING SEQUENCE INITIATED! Pozycja: X=" + spawnX + ", Y=" + spawnY); var self = this; self.isMinibossActiveCC = true; if (ui && ui.crystalChargeBarContainer) { ui.crystalChargeBarContainer.alpha = 0; } var timeSurvivedInMinutes = Math.floor(self.cursedCrystalTimeSurvived / (60 * 60)); var baseMinibossHp = 200; var hpPerMinute = 100; var scaledMaxHp = baseMinibossHp + timeSurvivedInMinutes * hpPerMinute; scaledMaxHp = Math.max(baseMinibossHp, scaledMaxHp); self.cursedCrystalMinibossMaxHP = scaledMaxHp; self.cursedCrystalMinibossHP = scaledMaxHp; var twoMinutesInFrames = 1 * 60 * 60; // 2 minuty * 60 sekund * 60 klatek/sekundę if (self.cursedCrystalTimeSurvived >= twoMinutesInFrames) { console.log("GRACZ PRZETRWAŁ PONAD 2 MINUTY! Miniboss otrzymuje ulepszone ataki!"); self.minibossEnhancedProjectile = true; self.minibossEnhancedLaserWall = true; } else { self.minibossEnhancedProjectile = false; self.minibossEnhancedLaserWall = false; } self.cursedCrystalEnemies.forEach(function (soul) { if (soul && !soul.isDead) { if (soul.parent) { soul.parent.removeChild(soul); } if (soul.destroy && !soul.destroyed) { soul.destroy(); } } }); self.cursedCrystalEnemies = []; self.cursedCrystalChargeLevel = 0; if (ui && ui.updateCrystalCharge) { ui.updateCrystalCharge(self.cursedCrystalChargeLevel, self.cursedCrystalTargetCharge); } LK.setTimeout(function () { if (self.currentState !== "cursedCrystal" || !self.isMinibossActiveCC) { console.log("Spawn Minibossa anulowany - zmiana stanu gry lub isMinibossActiveCC jest false."); return; } console.log("FAZA SPAWNU MINIBOSSA PO OPÓŹNIENIU"); var minibossOptions = { maxHp: scaledMaxHp, x: spawnX, // ***** POPRAWKA: Używamy argumentu funkcji spawnX ***** y: spawnY // ***** POPRAWKA: Używamy argumentu funkcji spawnY ***** }; self.cursedCrystalMinibossObject = game.addChild(new MinibossCC(minibossOptions)); if (self.cursedCrystalMinibossObject) { self.cursedCrystalMinibossObject.alpha = 0; tween(self.cursedCrystalMinibossObject, { alpha: 1 }, { duration: 1500, easing: tween.easeInQuad, onFinish: function onFinish() { console.log("Miniboss w pełni widoczny."); } }); if (ui && ui.updateMinibossHealthCC) { ui.updateMinibossHealthCC(self.cursedCrystalMinibossHP, self.cursedCrystalMinibossMaxHP, true); } if (ui && ui.showMessage) { ui.showMessage("Demon Pepe is here", 3000); } } else { console.error("Nie udało się stworzyć obiektu MinibossCC!"); } }, 3000); }, setupCursedCrystalScene: function setupCursedCrystalScene() { console.log("[SetupCursedCrystal] Konfiguracja sceny Cursed Crystal..."); // 1. Ustaw tło dla areny try { // Upewnij się, że stary currentBackground jest niszczony, jeśli istnieje if (typeof currentBackground !== 'undefined' && currentBackground && currentBackground.destroy && !currentBackground.destroyed) { currentBackground.destroy(); } currentBackground = LK.getAsset('cursedCrystalArenaBg_asset', { anchorX: 0, // Zgodnie z Twoim kodem anchorY: 0, // Zgodnie z Twoim kodem x: 0, // Zgodnie z Twoim kodem y: 0 // Zgodnie z Twoim kodem }); game.addChildAt(currentBackground, 0); } catch (e) { console.warn("Nie znaleziono assetu tła Cursed Crystal, używam domyślnego koloru.", e); game.setBackgroundColor(0x100510); if (typeof currentBackground !== 'undefined' && currentBackground && currentBackground.destroy && !currentBackground.destroyed) { currentBackground.destroy(); } currentBackground = null; } // 2. Stwórz obiekt Klejnotu Dusz (CrystalCore) jako KONTENER if (typeof crystalCoreObject !== 'undefined' && crystalCoreObject) { if (crystalCoreObject.levitationTween && crystalCoreObject.levitationTween.stop) { crystalCoreObject.levitationTween.stop(); } if (crystalCoreObject.scaleTween && crystalCoreObject.scaleTween.stop) { crystalCoreObject.scaleTween.stop(); } if (crystalCoreObject.destroy && !crystalCoreObject.destroyed) { crystalCoreObject.destroy(); } } crystalCoreObject = new Container(); // *** NOWOŚĆ: crystalCoreObject jest teraz kontenerem *** crystalCoreObject.x = 2048 / 2; crystalCoreObject.y = 2732 / 2; crystalCoreObject.collisionRadius = 60; game.addChild(crystalCoreObject); // Dodaj początkową wizualizację kryształu (0% naładowania) try { var initialCrystalVisual = LK.getAsset('crystal_state_0', { anchorX: 0.5, anchorY: 0.5 }); crystalCoreObject.addChild(initialCrystalVisual); crystalCoreObject.currentVisual = initialCrystalVisual; // Referencja do aktualnej grafiki crystalCoreObject.currentVisual.assetId = 'crystal_state_0'; // Do sprawdzania, co jest wyświetlane } catch (e) { console.error("KRYTYCZNY BŁĄD: Nie można załadować 'crystal_state_0'!", e); // Awaryjny kształt, jeśli assetu nie ma var fallbackVisual = new Shape({ width: 100, height: 100, color: 0x8A2BE2, shape: 'ellipse' }); fallbackVisual.anchor.set(0.5); crystalCoreObject.addChild(fallbackVisual); crystalCoreObject.currentVisual = fallbackVisual; crystalCoreObject.currentVisual.assetId = 'fallback_crystal'; } // Animacje lewitacji i pulsowania dla kontenera crystalCoreObject if (crystalCoreObject && !crystalCoreObject.destroyed) { crystalCoreObject.originalY = crystalCoreObject.y; var levitationHeight = 30; var oneWayDuration = 1250; var levitateCrystalUp = function levitateCrystalUp(targetObject) { if (!targetObject || targetObject.destroyed) { return; } if (targetObject.levitationTween && targetObject.levitationTween.stop) { targetObject.levitationTween.stop(); } targetObject.y = targetObject.originalY; targetObject.levitationTween = tween(targetObject, { y: targetObject.originalY - levitationHeight }, { duration: oneWayDuration, easing: tween.easeInOut, onFinish: function onFinish() { if (targetObject && !targetObject.destroyed) { levitateCrystalDown(targetObject); } } }); }; var levitateCrystalDown = function levitateCrystalDown(targetObject) { if (!targetObject || targetObject.destroyed) { return; } if (targetObject.levitationTween && targetObject.levitationTween.stop) { targetObject.levitationTween.stop(); } targetObject.levitationTween = tween(targetObject, { y: targetObject.originalY }, { duration: oneWayDuration, easing: tween.easeInOut, onFinish: function onFinish() { if (targetObject && !targetObject.destroyed) { levitateCrystalUp(targetObject); } } }); }; levitateCrystalUp(crystalCoreObject); if (crystalCoreObject.scaleTween && crystalCoreObject.scaleTween.stop) { crystalCoreObject.scaleTween.stop(); } crystalCoreObject.baseScaleX = crystalCoreObject.scaleX || 1; // Kontener zwykle ma scale 1 crystalCoreObject.baseScaleY = crystalCoreObject.scaleY || 1; crystalCoreObject.scaleTween = tween(crystalCoreObject, { scaleX: crystalCoreObject.baseScaleX * 1.05, scaleY: crystalCoreObject.baseScaleY * 1.05 }, { duration: oneWayDuration * 2 * 0.75, easing: tween.easeInOut, yoyo: true, repeat: Infinity }); } // 3. Stwórz nowy obiekt gracza (bez zmian w tej sekcji) if (typeof player !== 'undefined' && player && player.destroy && !player.destroyed) { player.destroy(); } player = game.addChild(new Player()); player.health = this.cursedCrystalPlayerMaxHealth; player.x = 2048 / 2; player.y = (crystalCoreObject && crystalCoreObject.y ? crystalCoreObject.y : 2732 / 2) + (crystalCoreObject && crystalCoreObject.currentVisual && crystalCoreObject.currentVisual.height ? crystalCoreObject.currentVisual.height / 2 : 50) + player.height / 2 + 50; // Pozycjonowanie względem środka/dolnej krawędzi kryształu player.dead = false; player.rolling = false; player.invulnerable = false; player.rollCooldown = 0; if (player.clearRollTimeouts) { player.clearRollTimeouts(); } this.currentInputPos.x = player.x; this.currentInputPos.y = player.y; this.isInputActive = false; this.cursedCrystalPlayerHealth = this.cursedCrystalPlayerMaxHealth; // Zakładam, że this.cursedCrystalPlayerMaxHealth jest zdefiniowane (np. w gameState lub przekazane) this.cursedCrystalChargeLevel = 0; this.cursedCrystalSoulsHitCrystal = 0; this.cursedCrystalScore = 0; this.cursedCrystalHighScore = parseInt(storage.cursedCrystalHighScore, 10) || 0; this.cursedCrystalEnemies = []; this.cursedCrystalEnemySpawnTimer = 0; this.cursedCrystalSoulsPerGroupMin = 1; this.cursedCrystalSoulsPerGroupMax = 5; this.cursedCrystalGroupSpawnSpread = 60; this.cursedCrystalBaseSpawnInterval = 90; // Interwał między grupami (np. 3 sekundy) this.cursedCrystalCurrentSpawnInterval = this.cursedCrystalBaseSpawnInterval; this.cursedCrystalDifficultyTimer = 0; this.cursedCrystalEnemyBaseSpeed = 0.1; // Ustawiamy niższą bazową prędkość this.cursedCrystalEnemyCurrentMaxSpeed = this.cursedCrystalEnemyBaseSpeed; // Aktualna prędkość startuje od bazowej this.cursedCrystalTimeSurvived = 0; this.isMinibossActiveCC = false; this.crystalExploding = false; if (this.cursedCrystalMinibossObject && this.cursedCrystalMinibossObject.destroy && !this.cursedCrystalMinibossObject.destroyed) { this.cursedCrystalMinibossObject.destroy(); } this.cursedCrystalMinibossObject = null; this.cursedCrystalMinibossHP = 0; this.cursedCrystalActiveProjectiles = []; this.cursedCrystalActiveExplosions = []; this.cursedCrystalActiveLaserWalls = []; // 5. Konfiguracja UI (bez zmian w tej sekcji) if (ui) { ui.positionElements("cursedCrystal"); if (ui.updateScoreCC) { ui.updateScoreCC(this.cursedCrystalScore); } if (ui.updateHighScoreCC) { ui.updateHighScoreCC(this.cursedCrystalHighScore); } if (ui.updateCrystalCharge) { ui.updateCrystalCharge(this.cursedCrystalChargeLevel, this.cursedCrystalTargetCharge); } if (ui.updateHearts) { ui.updateHearts(this.cursedCrystalPlayerHealth, this.cursedCrystalPlayerMaxHealth); } if (ui.updateMinibossHealthCC) { ui.updateMinibossHealthCC(0, this.cursedCrystalMinibossMaxHP, false); } if (ui.bossHealthBarContainer) { ui.bossHealthBarContainer.alpha = 0; } if (ui.timerText) { ui.timerText.alpha = 0; } if (ui.highScoreText) { ui.highScoreText.alpha = 0; } if (ui.deathsText) { ui.deathsText.alpha = 0; } } // 6. Wyświetl startowy komunikat (bez zmian) if (ui && ui.showMessage) { ui.showMessage("Protect the Soul Gem!", 3000); } this.gameActive = true; console.log("[SetupCursedCrystal] Scena Cursed Crystal skonfigurowana. Gracz HP: " + player.health); } }, "setupCursedCrystalScene", function setupCursedCrystalScene() { // <<< NOWY KOD setupCursedCrystalScene ZACZYNA SIĘ TUTAJ >>> console.log("[SetupCursedCrystal] Konfiguracja sceny Cursed Crystal..."); // 1. Ustaw tło dla areny try { if (typeof currentBackground !== 'undefined' && currentBackground && currentBackground.destroy && !currentBackground.destroyed) { currentBackground.destroy(); } currentBackground = LK.getAsset('cursedCrystalArenaBg_asset', { // Podmień na swój asset tła anchorX: 0, anchorY: 0, x: 0, y: -100 }); game.addChildAt(currentBackground, 0); } catch (e) { console.warn("Nie znaleziono assetu tła Cursed Crystal, używam domyślnego koloru.", e); game.setBackgroundColor(0x100510); if (typeof currentBackground !== 'undefined' && currentBackground && currentBackground.destroy && !currentBackground.destroyed) { currentBackground.destroy(); } currentBackground = null; } // 2. Stwórz obiekt Klejnotu Dusz (CrystalCore) jako KONTENER if (typeof crystalCoreObject !== 'undefined' && crystalCoreObject) { if (crystalCoreObject.levitationTween && crystalCoreObject.levitationTween.stop) { crystalCoreObject.levitationTween.stop(); } if (crystalCoreObject.scaleTween && crystalCoreObject.scaleTween.stop) { crystalCoreObject.scaleTween.stop(); } if (crystalCoreObject.destroy && !crystalCoreObject.destroyed) { crystalCoreObject.destroy(); } } crystalCoreObject = new Container(); // *** NOWOŚĆ: crystalCoreObject jest teraz kontenerem *** crystalCoreObject.x = 2048 / 2; crystalCoreObject.y = 2732 / 2; game.addChild(crystalCoreObject); try { var initialCrystalVisual = LK.getAsset('crystal_state_0', { anchorX: 0.5, anchorY: 0.5 }); crystalCoreObject.addChild(initialCrystalVisual); crystalCoreObject.currentVisual = initialCrystalVisual; crystalCoreObject.currentVisual.assetId = 'crystal_state_0'; } catch (e) { console.error("KRYTYCZNY BŁĄD: Nie można załadować 'crystal_state_0'!", e); var fallbackVisual = new Shape({ width: 100, height: 100, color: 0x8A2BE2, shape: 'ellipse' }); fallbackVisual.anchor.set(0.5); crystalCoreObject.addChild(fallbackVisual); crystalCoreObject.currentVisual = fallbackVisual; crystalCoreObject.currentVisual.assetId = 'fallback_crystal'; } if (crystalCoreObject && !crystalCoreObject.destroyed) { crystalCoreObject.originalY = crystalCoreObject.y; var levitationHeight = 30; var oneWayDuration = 1250; var levitateCrystalUp = function levitateCrystalUp(targetObject) { if (!targetObject || targetObject.destroyed) { return; } if (targetObject.levitationTween && targetObject.levitationTween.stop) { targetObject.levitationTween.stop(); } targetObject.y = targetObject.originalY; targetObject.levitationTween = tween(targetObject, { y: targetObject.originalY - levitationHeight }, { duration: oneWayDuration, easing: tween.easeInOut, onFinish: function onFinish() { if (targetObject && !targetObject.destroyed) { levitateCrystalDown(targetObject); } } }); }; var levitateCrystalDown = function levitateCrystalDown(targetObject) { if (!targetObject || targetObject.destroyed) { return; } if (targetObject.levitationTween && targetObject.levitationTween.stop) { targetObject.levitationTween.stop(); } targetObject.levitationTween = tween(targetObject, { y: targetObject.originalY }, { duration: oneWayDuration, easing: tween.easeInOut, onFinish: function onFinish() { if (targetObject && !targetObject.destroyed) { levitateCrystalUp(targetObject); } } }); }; levitateCrystalUp(crystalCoreObject); if (crystalCoreObject.scaleTween && crystalCoreObject.scaleTween.stop) { crystalCoreObject.scaleTween.stop(); } crystalCoreObject.baseScaleX = crystalCoreObject.scaleX || 1; crystalCoreObject.baseScaleY = crystalCoreObject.scaleY || 1; crystalCoreObject.scaleTween = tween(crystalCoreObject, { scaleX: crystalCoreObject.baseScaleX * 1.05, scaleY: crystalCoreObject.baseScaleY * 1.05 }, { duration: oneWayDuration * 2 * 0.75, easing: tween.easeInOut, yoyo: true, repeat: Infinity }); } if (typeof player !== 'undefined' && player && player.destroy && !player.destroyed) { player.destroy(); } player = game.addChild(new Player()); player.health = this.cursedCrystalPlayerMaxHealth; // Użyj this player.x = 2048 / 2; player.y = (crystalCoreObject && crystalCoreObject.y ? crystalCoreObject.y : 2732 / 2) + (crystalCoreObject && crystalCoreObject.currentVisual && crystalCoreObject.currentVisual.height ? crystalCoreObject.currentVisual.height / 2 : 50) + player.height / 2 + 50; player.dead = false; player.rolling = false; player.invulnerable = false; player.rollCooldown = 0; if (player.clearRollTimeouts) { player.clearRollTimeouts(); } this.currentInputPos.x = player.x; // Użyj this this.currentInputPos.y = player.y; // Użyj this this.isInputActive = false; // Użyj this this.cursedCrystalPlayerHealth = this.cursedCrystalPlayerMaxHealth; // Użyj this this.cursedCrystalChargeLevel = 0; // Użyj this this.cursedCrystalSoulsHitCrystal = 0; // Użyj this this.cursedCrystalScore = 0; // Użyj this this.cursedCrystalHighScore = parseInt(storage.cursedCrystalHighScore, 10) || 0; // Użyj this this.cursedCrystalEnemies = []; // Użyj this this.cursedCrystalEnemySpawnTimer = 0; // Użyj this this.cursedCrystalCurrentSpawnInterval = this.cursedCrystalBaseSpawnInterval; // Użyj this this.cursedCrystalDifficultyTimer = 0; // Użyj this this.cursedCrystalEnemyCurrentMaxSpeed = this.cursedCrystalEnemyBaseSpeed; // Użyj this this.cursedCrystalTimeSurvived = 0; // Użyj this this.isMinibossActiveCC = false; // Użyj this this.crystalExploding = false; // *** NOWA FLAGA, użyj this *** if (this.cursedCrystalMinibossObject && this.cursedCrystalMinibossObject.destroy && !this.cursedCrystalMinibossObject.destroyed) { // Użyj this this.cursedCrystalMinibossObject.destroy(); } this.cursedCrystalMinibossObject = null; // Użyj this this.cursedCrystalMinibossHP = 0; // Użyj this this.cursedCrystalActiveProjectiles = []; // Użyj this this.cursedCrystalActiveExplosions = []; // Użyj this this.cursedCrystalActiveLaserWalls = []; // Użyj this if (ui) { ui.positionElements("cursedCrystal"); if (ui.updateScoreCC) { ui.updateScoreCC(this.cursedCrystalScore); } if (ui.updateHighScoreCC) { ui.updateHighScoreCC(this.cursedCrystalHighScore); } if (ui.updateCrystalCharge) { ui.updateCrystalCharge(this.cursedCrystalChargeLevel, this.cursedCrystalTargetCharge); } if (ui.updateHearts) { ui.updateHearts(this.cursedCrystalPlayerHealth, this.cursedCrystalPlayerMaxHealth); } if (ui.updateMinibossHealthCC) { ui.updateMinibossHealthCC(0, this.cursedCrystalMinibossMaxHP, false); } if (ui.bossHealthBarContainer) { ui.bossHealthBarContainer.alpha = 0; } if (ui.timerText) { ui.timerText.alpha = 0; } if (ui.highScoreText) { ui.highScoreText.alpha = 0; } if (ui.deathsText) { ui.deathsText.alpha = 0; } } if (ui && ui.showMessage) { ui.showMessage("Catch them all!", 3000); } console.log("[SetupCursedCrystal] Scena Cursed Crystal skonfigurowana. Gracz HP: " + player.health); // <<< NOWY KOD setupCursedCrystalScene KOŃCZY SIĘ TUTAJ >>> }), "endCursedCrystalMode", function endCursedCrystalMode(isVictory) { console.log("DEBUG: gameState.endCursedCrystalMode called. Victory: " + isVictory); this.currentState = "cursedCrystalGameOver"; // --- CZYSZCZENIE AKTYWNYCH ELEMENTÓW TRYBU CC --- // 1. Dusze (SoulEnemy) if (this.cursedCrystalEnemies && this.cursedCrystalEnemies.length > 0) { console.log("DEBUG: Clearing " + this.cursedCrystalEnemies.length + " remaining SoulEnemies."); this.cursedCrystalEnemies.forEach(function (enemy) { if (enemy && enemy.destroy && !enemy.destroyed) { if (enemy.parent) { enemy.parent.removeChild(enemy); } enemy.destroy(); } }); } this.cursedCrystalEnemies = []; // 2. Pociski Minibossa CC (jeśli gracz zginął, a boss jeszcze strzelał) if (this.cursedCrystalActiveProjectiles && this.cursedCrystalActiveProjectiles.length > 0) { console.log("DEBUG: Clearing " + this.cursedCrystalActiveProjectiles.length + " remaining projectiles."); this.cursedCrystalActiveProjectiles.forEach(function (proj) { if (proj && proj.destroy && !proj.destroyed) { if (proj.graphics && proj.graphics.parent) { proj.graphics.parent.removeChild(proj.graphics); } if (proj.graphics && proj.graphics.destroy) { proj.graphics.destroy(); } proj.destroy(); } }); } this.cursedCrystalActiveProjectiles = []; // 3. Eksplozje Minibossa CC if (this.cursedCrystalActiveExplosions && this.cursedCrystalActiveExplosions.length > 0) { console.log("DEBUG: Clearing " + this.cursedCrystalActiveExplosions.length + " active explosions."); // Wizualizacje eksplozji są zarządzane przez tweeny i powinny same zniknąć, // ale czyścimy tablicę logiczną. } this.cursedCrystalActiveExplosions = []; // 4. Ściany Laserowe Minibossa CC if (this.cursedCrystalActiveLaserWalls && this.cursedCrystalActiveLaserWalls.length > 0) { console.log("DEBUG: Clearing " + this.cursedCrystalActiveLaserWalls.length + " laser walls."); this.cursedCrystalActiveLaserWalls.forEach(function (wall) { if (wall && wall.segments) { wall.segments.forEach(function (seg) { if (seg.visual && seg.visual.destroy && !seg.visual.destroyed) { if (seg.visual.parent) { seg.visual.parent.removeChild(seg.visual); } seg.visual.destroy(); } }); } if (wall && wall.warningVisuals && wall.warningVisuals.length > 0) { wall.warningVisuals.forEach(function (warnVis) { if (warnVis && warnVis.destroy && !warnVis.destroyed) { if (warnVis.parent) { warnVis.parent.removeChild(warnVis); } warnVis.destroy(); } }); } }); } this.cursedCrystalActiveLaserWalls = []; // 5. Sam Miniboss CC (jeśli gracz zginął, a Miniboss nie został pokonany) // Metoda MinibossCC.die() czyści po sobie, gdy jest pokonany. // Jeśli gracz umiera, obiekt Minibossa może nadal istnieć. if (!isVictory && this.cursedCrystalMinibossObject && !this.cursedCrystalMinibossObject.isDead) { console.log("DEBUG: Player died, clearing active MinibossCC."); if (this.cursedCrystalMinibossObject.parent) { this.cursedCrystalMinibossObject.parent.removeChild(this.cursedCrystalMinibossObject); } if (this.cursedCrystalMinibossObject.destroy) { this.cursedCrystalMinibossObject.destroy(); } } this.cursedCrystalMinibossObject = null; this.isMinibossActiveCC = false; // 6. Obiekt Klejnotu (jeśli był i nie został zniszczony przy spawnie minibossa) if (typeof crystalCoreObject !== 'undefined' && crystalCoreObject && crystalCoreObject.destroy && !crystalCoreObject.destroyed) { if (crystalCoreObject.parent) { crystalCoreObject.parent.removeChild(crystalCoreObject); } crystalCoreObject.destroy(); } crystalCoreObject = null; // 7. Zatrzymaj muzykę Cursed Crystal, jeśli jest // if (this.currentCursedCrystalMusic && this.currentCursedCrystalMusic.stop) { // this.currentCursedCrystalMusic.stop(); // this.currentCursedCrystalMusic = null; // } // Uruchom muzykę menu/grilla (jeśli jest taka potrzeba, np. showGrillScreen to zrobi) // --- LOGIKA WYNIKU I REKORDU --- var finalScore = this.cursedCrystalScore; if (isVictory) { var minutesSurvived = Math.floor(this.cursedCrystalTimeSurvived / (60 * 60)); // Zakładając 60 FPS var bonusPoints = 1000 + minutesSurvived * 100; finalScore += bonusPoints; console.log("DEBUG: Victory! Base score: " + this.cursedCrystalScore + ", Time survived (frames): " + this.cursedCrystalTimeSurvived + " (" + minutesSurvived + " min), Bonus: " + bonusPoints + ", Final score: " + finalScore); } this.cursedCrystalScore = finalScore; // Zaktualizuj wynik w gameState var highScore = parseInt(storage.cursedCrystalHighScore, 10) || 0; var newRecord = false; if (finalScore > highScore) { storage.cursedCrystalHighScore = finalScore; highScore = finalScore; newRecord = true; console.log("DEBUG: New High Score for Cursed Crystal: " + highScore); } if (ui) { ui.titleText.setText(isVictory ? "VICTORY!" : "DEFEAT!"); // Przetłumaczone ui.titleText.style = { size: 120, fill: isVictory ? 0x00FF00 : 0xFF0000, align: 'center', stroke: 0x000000, strokeThickness: 6 }; var message = "Your Score: " + finalScore + "\n"; // Przetłumaczone message += "Best Score: " + highScore; // Przetłumaczone if (newRecord && isVictory) { message += "\n(NEW HIGH SCORE!)"; // Przetłumaczone } else if (newRecord && !isVictory) { message += "\n(New High Score... Pepe sad)"; // Przetłumaczone (z lekką adaptacją) } ui.messageText.setText(message); ui.messageText.style = { size: 70, fill: 0xFFFFFF, align: 'center', stroke: 0x000000, strokeThickness: 4 }; // Usunięcie starych przycisków, jeśli istnieją (bez zmian) if (this.activeButtons && this.activeButtons.length > 0) { this.activeButtons.forEach(function (btn) { if (btn && btn.destroy) { btn.destroy(); } }); } this.activeButtons = []; // Upewnij się, że teksty tytułu i wiadomości są od razu widoczne ui.titleText.alpha = 1; ui.messageText.alpha = 1; // Ustaw pozycje dla titleText i messageText (przeniesione z ui.positionElements dla jasności) // Zakładam, że te pozycje są poprawne dla ekranu końca gry ui.titleText.x = 2048 / 2; ui.titleText.y = 600; ui.titleText.anchor.set(0.5); ui.messageText.x = 2048 / 2; ui.messageText.y = 800; ui.messageText.anchor.set(0.5, 0.5); // Opóźnienie tworzenia i wyświetlania przycisków LK.setTimeout(function () { // Sprawdź, czy nadal jesteśmy w tym samym stanie gry, aby uniknąć dodawania przycisków, jeśli stan się zmienił if (gameState.currentState !== "cursedCrystalGameOver") { return; } // Definiujemy oczekiwane wysokości na początku, aby były dostępne dla obu przycisków var mainMenuButtonExpectedHeight = 100; var restartButtonExpectedHeight = 100; var spacingBetweenButtons = 60; var menuButton = new Container(); menuButton.interactive = true; menuButton.cursor = "pointer"; var menuButtonVisualHeight = mainMenuButtonExpectedHeight; // Użyjemy tej zmiennej try { var menuButtonAsset = LK.getAsset('mainmenu', { anchorX: 0.5, anchorY: 0.5 }); menuButton.addChild(menuButtonAsset); if (menuButtonAsset && typeof menuButtonAsset.height === 'number') { menuButtonVisualHeight = menuButtonAsset.height; // Użyj rzeczywistej wysokości assetu, jeśli dostępna } } catch (e) { console.error("Błąd ładowania assetu przycisku 'mainmenu':", e); var menuButtonBgFallback = new Shape({ width: 400, height: mainMenuButtonExpectedHeight, color: 0x555555, shape: 'box' }); menuButton.addChild(menuButtonBgFallback); } menuButton.x = 2048 / 2; // Poprawiona linia dla menuButton.y: menuButton.y = (ui.messageText.y || 800) + (ui.messageText.height || 70) / 2 + menuButtonVisualHeight / 2 + 80; menuButton.down = function () { if (gameState.currentState === "cursedCrystalGameOver") { gameState.activeButtons.forEach(function (btn) { if (btn && btn.destroy && !btn.destroyed) { if (btn.parent) { btn.parent.removeChild(btn); } btn.destroy(); } }); gameState.activeButtons = []; gameState.showGrillScreen(); } }; currentSceneElements.addChild(menuButton); gameState.activeButtons.push(menuButton); // --- Przycisk "Restart Mode" --- var restartButton = new Container(); restartButton.interactive = true; restartButton.cursor = "pointer"; var restartButtonVisualHeight = restartButtonExpectedHeight; // Użyjemy tej zmiennej try { var restartButtonAsset = LK.getAsset('restartgame', { anchorX: 0.5, anchorY: 0.5 }); restartButton.addChild(restartButtonAsset); if (restartButtonAsset && typeof restartButtonAsset.height === 'number') { restartButtonVisualHeight = restartButtonAsset.height; // Użyj rzeczywistej wysokości assetu } } catch (e) { console.error("Błąd ładowania assetu przycisku 'restartgame':", e); var restartButtonBgFallback = new Shape({ width: 400, height: restartButtonExpectedHeight, color: 0x555555, shape: 'box' }); restartButton.addChild(restartButtonBgFallback); } restartButton.x = 2048 / 2; // Poprawiona linia dla restartButton.y, z większym odstępem: restartButton.y = menuButton.y + menuButtonVisualHeight / 2 + restartButtonVisualHeight / 2 + spacingBetweenButtons; restartButton.down = function () { if (gameState.currentState === "cursedCrystalGameOver") { gameState.activeButtons.forEach(function (btn) { if (btn && btn.destroy && !btn.destroyed) { if (btn.parent) { btn.parent.removeChild(btn); } btn.destroy(); } }); gameState.activeButtons = []; gameState.startCursedCrystalMode(); } }; currentSceneElements.addChild(restartButton); gameState.activeButtons.push(restartButton); // ui.positionElements("cursedCrystalGameOver"); }, 3000); // Opóźnienie 3000 ms (3 sekundy) } }), "processTouchGesture", function processTouchGesture() { // ... (kod obsługi fake tutorial) ... var dx = this.touchEnd.x - this.touchStart.x; var dy = this.touchEnd.y - this.touchStart.y; var distance = Math.sqrt(dx * dx + dy * dy); var swipeThreshold = 50; // Minimalny dystans, by uznać za swipe if (distance < swipeThreshold) { // Logika Tapnięcia (bez zmian) // ... return; } // --- Obsługa Swipe (Turlania) --- if ((this.currentState === "game" || this.currentState === "rollMaster" || this.currentState === "cursedCrystal") && player && !player.dead) { // Normalizuj kierunek (bez zmian) var direction = { x: 0, y: 0 }; if (distance > 0) { direction.x = dx / distance; direction.y = dy / distance; } // <-- NOWA LOGIKA: Obliczanie czasu trwania na podstawie dystansu swipe --> var minSwipeDist = swipeThreshold; // Minimalny dystans swipe'a (np. 50) var maxSwipeDist = 400; // <-- ZWIĘKSZONA dla większej skali var minRollDuration = 50; // <-- LEKKO ZWIĘKSZONA dla płynności minimum var maxRollDuration = 400; // Maksymalny czas uniku w ms (np. 0.45s - dostosuj!) // Ogranicz dystans do zakresu min/max var clampedDistance = Math.max(minSwipeDist, Math.min(distance, maxSwipeDist)); // Znormalizuj dystans do zakresu 0-1 var normalizedDistance = (clampedDistance - minSwipeDist) / (maxSwipeDist - minSwipeDist); // Oblicz czas trwania uniku na podstawie znormalizowanego dystansu var currentRollDuration = minRollDuration + normalizedDistance * (maxRollDuration - minRollDuration); // <-- KONIEC NOWEJ LOGIKI --> // Wywołaj turlanie, przekazując kierunek ORAZ obliczony czas trwania player.roll(direction, currentRollDuration); // Przekazujemy nowy parametr! } }); // --- Obsługa inputu --- game.down = function (x, y, obj) { // Rejestruj początek dotyku/kliknięcia // <-- ZMIANA: Natychmiastowa obsługa kliknięcia w fakeTutorial --> if (gameState.currentState === "fakeTutorial") { // Sprawdźmy dodatkowo, czy timer jeszcze istnieje, na wszelki wypadek if (gameState.fakeTutorialTimerId) { gameState.handleFakeTutorialInput(); // Wywołaj logikę "fałszywej śmierci" return; // Zakończ, nie rób nic więcej w game.down } } // Stany, w których śledzimy gesty lub kliknięcia przycisków: var trackStates = ["title", "game", /* Usunięto "fakeTutorial" */"fakeGameOver", "realTutorial", "grillMenu", "gameOver", "rollMaster", "cursedCrystal"]; if (trackStates.indexOf(gameState.currentState) !== -1) { gameState.touchStart.x = x; gameState.touchStart.y = y; gameState.touchEnd.x = x; gameState.touchEnd.y = y; gameState.isInputActive = true; gameState.currentInputPos.x = x; gameState.currentInputPos.y = y; gameState.swipeStartTime = Date.now(); } // Obsługa kliknięć przycisków jest robiona przez ich własne handlery .down // Note: If you have complex button logic that consumes clicks, // you might need to check if 'obj' is null here before setting isInputActive. // For simple cases, setting it always on 'down' might be fine, // but make sure your player movement check in update respects the game state. }; game.up = function (x, y, obj) { // Rejestruj koniec dotyku/kliknięcia i przetwarzaj gest var trackStates = ["title", "game", "fakeTutorial", "fakeGameOver", "realTutorial", "grillMenu", "gameOver", "rollMaster", "cursedCrystal"]; if (trackStates.indexOf(gameState.currentState) !== -1) { gameState.touchEnd.x = x; gameState.touchEnd.y = y; // Zawsze ustawiaj isInputActive na false przy puszczeniu, aby zatrzymać chodzenie gameState.isInputActive = false; // --- TUTAJ JEST KLUCZOWA ZMIANA --- // Oblicz czas trwania gestu var swipeDuration = Date.now() - gameState.swipeStartTime; // Odczytujemy startTime var holdThreshold = 1000; // Próg w ms - PAMIĘTAJ O DOSTROJENIU! // Wywołaj logikę rolla tylko jeśli gest był krótszy niż próg przytrzymania if (swipeDuration < holdThreshold) { // Gest był szybki (potencjalny swipe/tap) console.log("game.up - GEST KRÓTKI (" + swipeDuration + "ms), przetwarzam..."); // Opcjonalny log gameState.processTouchGesture(); // Wywołujemy tylko tutaj! } else { console.log("game.up - GEST DŁUGI (" + swipeDuration + "ms), IGNORUJĘ rolla"); // Opcjonalny log } // Jeśli gest był długi, nic więcej nie robimy. // --- KONIEC KLUCZOWEJ ZMIANY --- } // Obsługa puszczenia przycisków (.up) może być dodana tutaj, jeśli potrzebna }; game.move = function (x, y, obj) { var trackStates = ["title", "game", "fakeTutorial", "fakeGameOver", "realTutorial", "grillMenu", "gameOver", "rollMaster", "cursedCrystal"]; if (trackStates.indexOf(gameState.currentState) !== -1) { gameState.touchEnd.x = x; gameState.touchEnd.y = y; if (gameState.isInputActive) { gameState.currentInputPos.x = x; gameState.currentInputPos.y = y; } } }; function launchRmattack1() { console.log("Launch rmattack1 (projectile)"); var projectileRadius = 45; var startX, startY; var edge = Math.floor(Math.random() * 4); var gameWidth = 2048; // Szerokość pola gry var gameHeight = 2732; // Wysokość pola gry // Pozycjonowanie startowe (bez zmian) if (edge === 0) { // top startX = Math.random() * gameWidth; startY = -projectileRadius; } else if (edge === 1) { // right startX = gameWidth + projectileRadius; startY = Math.random() * gameHeight; } else if (edge === 2) { // bottom startX = Math.random() * gameWidth; startY = gameHeight + projectileRadius; } else { // left startX = -projectileRadius; startY = Math.random() * gameHeight; } // Celowanie w środek (bez zmian) var targetX = gameWidth / 2; var targetY = gameHeight / 2; var dx = targetX - startX; var dy = targetY - startY; var angle = Math.atan2(dy, dx); // --- ZMIANA: Obliczanie prędkości (vx, vy) zamiast endX, endY --- var speed = 4 + gameState.rollMasterDifficulty * 0.65; // Prędkość pocisku (dostosuj wg potrzeb, może być zależna od trudności) var vx = Math.cos(angle) * speed; var vy = Math.sin(angle) * speed; // --- KONIEC ZMIANY --- var rotation = angle; var scaleX = 1; // Tworzenie animacji (bez zmian) var frames = [LK.getAsset('rmattack1_0', { anchorX: 0.5, anchorY: 0.5 }), LK.getAsset('rmattack1_1', { anchorX: 0.5, anchorY: 0.5 }), LK.getAsset('rmattack1_2', { anchorX: 0.5, anchorY: 0.5 }), LK.getAsset('rmattack1_3', { anchorX: 0.5, anchorY: 0.5 }), LK.getAsset('rmattack1_4', { anchorX: 0.5, anchorY: 0.5 }), LK.getAsset('rmattack1_5', { anchorX: 0.5, anchorY: 0.5 }), LK.getAsset('rmattack1_6', { anchorX: 0.5, anchorY: 0.5 })]; var sprite = new SpriteAnimation({ frames: frames, frameDuration: 120, loop: true, x: startX, y: startY, anchorX: 0.5, anchorY: 0.5 }); sprite.scaleX = scaleX; sprite.rotation = rotation; game.addChild(sprite); // --- ZMIANA: Dodanie do listy ataków zamiast tween --- gameState.rollMasterAttacks.push({ type: 'projectile', visual: sprite, vx: vx, vy: vy, radius: projectileRadius // Promień do sprawdzania kolizji }); // Usunięto tween - ruch będzie obsługiwany w game.update // --- KONIEC ZMIANY --- } // Funkcja launchRmattack2 ZE ZMIANĄ POZYCJONOWANIA function launchRmattack2() { var topSafeMargin = 700; console.log("Launch rmattack2 (explosion)"); var bombWidth = 120; var bombHeight = 120; var halfBombWidth = bombWidth / 2; var halfBombHeight = bombHeight / 2; var minX_b = halfBombWidth; var maxX_b = 2048 - halfBombWidth; var minY_b = halfBombHeight; var maxY_b = 2732 - halfBombHeight; var x = minX_b + Math.random() * (maxX_b - minX_b); var spawnHeight = maxY_b - topSafeMargin; var y = spawnHeight > 0 ? topSafeMargin + Math.random() * spawnHeight : topSafeMargin; var idleFrame = LK.getAsset('rmattack2_explode_0', { anchorX: 0.5, anchorY: 0.5 }); var bomb = new SpriteAnimation({ frames: [idleFrame], frameDuration: 500, loop: true, x: x, y: y, anchorX: 0.5, anchorY: 0.5 }); game.addChild(bomb); bomb.idleTween = tween(bomb, { y: y - 10 }, { duration: 500, yoyo: true, repeat: Infinity, easing: tween.inOutQuad }); LK.setTimeout(function () { if (bomb.destroyed) { return; } try { if (bomb.idleTween && typeof bomb.idleTween.stop === "function") { bomb.idleTween.stop(); } } catch (e) { console.warn("idleTween stop error:", e); } var explosionFrames = []; var finalScale = 0; for (var i = 0; i < 11; i++) { var scale = 1 + i * 0.4; finalScale = scale; var frame = LK.getAsset("rmattack2_explode_" + i, { anchorX: 0.5, anchorY: 0.5, scaleX: scale, scaleY: scale }); explosionFrames.push(frame); } var explosion = new SpriteAnimation({ frames: explosionFrames, frameDuration: 80, loop: false, x: bomb.x, y: bomb.y, anchorX: 0.5, anchorY: 0.5 }); // Nowe: natychmiastowe niszczenie po zakończeniu animacji explosion.onComplete = function () { var index = gameState.rollMasterAttacks.indexOf(explosionData); if (index > -1) { gameState.rollMasterAttacks.splice(index, 1); console.log("Usunięto eksplozję z listy przez onComplete."); } if (!explosion.destroyed) { explosion.destroy(); } }; bomb.destroy(); game.addChild(explosion); var explosionDurationFrames = explosionFrames.length * (80 / (1000 / 60)); var explosionMaxRadius = bombWidth * finalScale / 2 * 0.8; var explosionData = { type: 'explosion', visual: explosion, radius: 0, maxRadius: explosionMaxRadius, currentFrame: 0, totalFrames: explosionFrames.length, frameDurationTicks: 80 / (1000 / 60), timer: 0, damageDealt: false }; gameState.rollMasterAttacks.push(explosionData); console.log("Dodano eksplozję do śledzenia, maxRadius:", explosionMaxRadius); }, 2000); } // Funkcja launchRmattack3 (bez zmian, już miała bezpieczne pozycjonowanie i pulsowanie) function launchRmattack3() { var topSafeMargin = 600; console.log("Launch rmattack3 (laser)"); // Bezpieczne pozycjonowanie (bez zmian) var margin = 100; var halfLaserWidth = 128 / 2; var halfLaserHeight = 1012 / 2; // [cite: 95, 96] var minX = margin + halfLaserWidth; var maxX = 2048 - margin - halfLaserWidth; // [cite: 96, 97] var calculatedMinY = margin + halfLaserHeight; // Oryginalne obliczenie minY var minY = Math.max(calculatedMinY, topSafeMargin); // Użyj większej wartości: marginesu lub safe zone var maxY = 2732 - margin - halfLaserHeight; // Dolna granica // var laserY = minY + Math.random() * (maxY - minY); // <-- ZAKOMENTUJ LUB USUŃ STARĄ LINIĘ var spawnHeight = maxY - minY; // Oblicz dostępną wysokość var laserY = spawnHeight > 0 ? minY + Math.random() * spawnHeight : minY; // Losuj Y w bezpiecznej strefie // --- KONIEC ZMIAN --- var laserX = minX + Math.random() * (maxX - minX); // Animacja ostrzeżenia (bez zmian) var warningFrames = []; // [cite: 99] for (var i = 0; i < 5; i++) { warningFrames.push(LK.getAsset('rmattack3_warning_' + i, { anchorX: 0.5, anchorY: 0.5, width: 128, height: 1012 })); } // [cite: 100] var warningAnim = new SpriteAnimation({ frames: warningFrames, frameDuration: 100, loop: true, x: laserX, y: laserY, anchorX: 0.5, anchorY: 0.5, alpha: 1.0 }); // [cite: 101] warningAnim.stopPulsing = false; var currentPulseTween = null; // [cite: 101] game.addChild(warningAnim); // [cite: 102] var pulseDuration = 250; // [cite: 102] function pulseUp() { if (!warningAnim || warningAnim.destroyed || warningAnim.stopPulsing) { currentPulseTween = null; return; } currentPulseTween = tween(warningAnim, { alpha: 1.0 }, { duration: pulseDuration, easing: tween.inOutQuad, onFinish: function onFinish() { if (warningAnim && !warningAnim.destroyed && !warningAnim.stopPulsing) { pulseDown(); } else { currentPulseTween = null; } } }); } // [cite: 102, 103, 104] function pulseDown() { if (!warningAnim || warningAnim.destroyed || warningAnim.stopPulsing) { currentPulseTween = null; return; } currentPulseTween = tween(warningAnim, { alpha: 0.1 }, { duration: pulseDuration, easing: tween.inOutQuad, onFinish: function onFinish() { if (warningAnim && !warningAnim.destroyed && !warningAnim.stopPulsing) { pulseUp(); } else { currentPulseTween = null; } } }); } // [cite: 104, 105] pulseDown(); // [cite: 106] // Timer aktywacji lasera LK.setTimeout(function () { // [cite: 106] if (warningAnim && !warningAnim.destroyed) { warningAnim.stopPulsing = true; } // [cite: 106] if (currentPulseTween && typeof currentPulseTween.stop === "function") { try { currentPulseTween.stop(); currentPulseTween = null; } catch (e) { console.warn("Nie udało się zatrzymać tweenu pulsowania:", e); } } // [cite: 106] if (warningAnim && !warningAnim.destroyed) { // Jeśli ostrzeżenie nadal istnieje // Tworzenie klatek ataku (bez zmian) var activeFrames = []; // [cite: 106] var laserStrikeWidth = 228; // Szerokość kolizji var laserStrikeHeight = 1212; // Wysokość kolizji for (var j = 0; j < 5; j++) { activeFrames.push(LK.getAsset('rmattack3_strike_' + j, { anchorX: 0.5, anchorY: 0.5, width: laserStrikeWidth, height: laserStrikeHeight })); } // [cite: 106] // Tworzenie animacji ataku (bez zmian) var strikeAnim = new SpriteAnimation({ // [cite: 106] frames: activeFrames, frameDuration: 40, loop: true, x: laserX, y: laserY, anchorX: 0.5, anchorY: 0.5, alpha: 1 // [cite: 106] }); game.addChild(strikeAnim); // [cite: 106] var laserDuration = 4000; // Czas życia lasera w ms // --- ZMIANA: Dodanie lasera do listy śledzonych ataków --- var laserData = { type: 'laser', visual: strikeAnim, // Referencja do animacji uderzenia // Przekazujemy wymiary potrzebne do kolizji AABB w game.update width: laserStrikeWidth, height: laserStrikeHeight, lifeTime: laserDuration / (1000 / 60) // Czas życia w klatkach gry }; gameState.rollMasterAttacks.push(laserData); console.log("Dodano laser do śledzenia."); // --- KONIEC ZMIANY --- warningAnim.destroy(); // Usuń ostrzeżenie [cite: 106] // Timer usuwania lasera LK.setTimeout(function () { // [cite: 106] // Znajdź i usuń z listy, jeśli nadal tam jest var index = gameState.rollMasterAttacks.indexOf(laserData); if (index > -1) { gameState.rollMasterAttacks.splice(index, 1); console.log("Usunięto laser z listy po czasie."); } if (strikeAnim && !strikeAnim.destroyed) { strikeAnim.destroy(); } // [cite: 106] }, laserDuration); // [cite: 106] } }, 3000); // Czas trwania ostrzeżenia [cite: 107] } // Funkcja launchRmattack4 POPRAWIONA (tylko tworzenie, z bezpiecznym pozycjonowaniem 200px margin) function launchRmattack4() { // Obliczanie bezpiecznej strefy dla spreadera z marginesem 200px var spreaderParentRadius = 60 * 1.2; var margin = 200; var startX = margin + Math.random() * (2048 - 2 * margin); var startY = margin + Math.random() * (2732 - 2 * margin); // Klatki animacji dla Spreader Parent var spreaderParentFrames = [LK.getAsset('rmattack4_parent_0', { anchorX: 0.5, anchorY: 0.5 }), LK.getAsset('rmattack4_parent_1', { anchorX: 0.5, anchorY: 0.5 }), LK.getAsset('rmattack4_parent_2', { anchorX: 0.5, anchorY: 0.5 }), LK.getAsset('rmattack4_parent_3', { anchorX: 0.5, anchorY: 0.5 })]; // Stwórz obiekt SpriteAnimation dla rodzica var spreaderParentAnim = new SpriteAnimation({ frames: spreaderParentFrames, frameDuration: 250, loop: true, x: startX, y: startY, anchorX: 0.5, anchorY: 0.5 }); // Dodaj animację rodzica do sceny gry game.addChild(spreaderParentAnim); // Dodaj obiekt logiczny ataku do listy gameState gameState.rollMasterAttacks.push({ type: 'spreader_parent', visual: spreaderParentAnim, radius: spreaderParentRadius, timer: gameState.spreaderSplitTime }); } function spawnCursedSoul() { console.log("--- spawnCursedSoul (COMPACT GROUP) CALLED ---"); if (gameState.currentState !== "cursedCrystal" || typeof gameState.gameActive === 'boolean' && !gameState.gameActive) { console.log("spawnCursedSoul (COMPACT GROUP): Exiting - wrong state or game not active. State:", gameState.currentState, "GameActive:", gameState.gameActive); return; } if (typeof crystalCoreObject === 'undefined' || !crystalCoreObject || crystalCoreObject.destroyed) { console.log("spawnCursedSoul (COMPACT GROUP): Exiting - crystalCoreObject missing or destroyed."); return; } var minSouls = gameState.cursedCrystalSoulsPerGroupMin || 1; // Domyślnie 4, jeśli niezdefiniowane var maxSouls = gameState.cursedCrystalSoulsPerGroupMax || 5; // Domyślnie 5 if (maxSouls < minSouls) { maxSouls = minSouls; } var numSoulsInGroup = minSouls + Math.floor(Math.random() * (maxSouls - minSouls + 1)); console.log("numSoulsInGroup to spawn:", numSoulsInGroup); if (numSoulsInGroup <= 0) { console.warn("spawnCursedSoul (COMPACT GROUP): numSoulsInGroup is 0 or less."); return; } var soulSpeed = gameState.cursedCrystalEnemyCurrentMaxSpeed; console.log("Soul speed for group:", soulSpeed); // Powinno być niskie na początku var soulHP = 1; var soulAssetWidth = 30; var soulAssetHeight = 30; var gameWidth = 2048; var gameHeight = 2732; var edge = Math.floor(Math.random() * 4); var groupBaseSpawnX, groupBaseSpawnY; // Zmniejszamy rozrzut dla bardziej zwartej grupy var groupSpawnTightnessFactor = 400; // Mniejsza wartość = ciaśniejsza grupa wzdłuż krawędzi var depthSpreadFactor = gameState.cursedCrystalGroupSpawnSpread / 3 || 140; // Mniejszy rozrzut w głąb switch (edge) { case 0: // Góra groupBaseSpawnX = Math.random() * (gameWidth - 400) + 200; // Szerszy zakres, ale nadal unika rogów groupBaseSpawnY = -soulAssetHeight; break; case 1: // Prawo groupBaseSpawnX = gameWidth + soulAssetWidth; groupBaseSpawnY = Math.random() * (gameHeight - 400) + 200; break; case 2: // Dół groupBaseSpawnX = Math.random() * (gameWidth - 400) + 200; groupBaseSpawnY = gameHeight + soulAssetHeight; break; default: // Lewo (case 3) groupBaseSpawnX = -soulAssetWidth; groupBaseSpawnY = Math.random() * (gameHeight - 400) + 200; break; } console.log("Spawning group from edge:", edge, "BaseX:", groupBaseSpawnX.toFixed(0), "BaseY:", groupBaseSpawnY.toFixed(0)); for (var i = 0; i < numSoulsInGroup; i++) { var offsetX = 0; var offsetY = 0; // Ustawienie dusz blisko siebie, z lekkim losowym przesunięciem if (edge === 0 || edge === 2) { // Spawn z góry lub dołu - rozrzut poziomy offsetX = (Math.random() - 0.5) * groupSpawnTightnessFactor; offsetY = (Math.random() - 0.5) * depthSpreadFactor; // Mniejszy rozrzut w głąb } else { // Spawn z lewej lub prawej - rozrzut pionowy offsetY = (Math.random() - 0.5) * groupSpawnTightnessFactor; offsetX = (Math.random() - 0.5) * depthSpreadFactor; // Mniejszy rozrzut w głąb } var finalSpawnX = groupBaseSpawnX + offsetX; var finalSpawnY = groupBaseSpawnY + offsetY; // console.log("Creating soul " + (i + 1) + " at X:", finalSpawnX.toFixed(0), "Y:", finalSpawnY.toFixed(0)); var newSoul = new SoulEnemy({ x: finalSpawnX, y: finalSpawnY, hp: soulHP, speed: soulSpeed, // Wszystkie duszki w grupie mają tę samą (niską na początku) prędkość targetX: crystalCoreObject.x, targetY: crystalCoreObject.y }); game.addChild(newSoul); gameState.cursedCrystalEnemies.push(newSoul); } console.log("Finished spawning group of " + numSoulsInGroup + " souls."); } // --- Główna pętla aktualizacji gry --- game.update = function () { if (gameState.currentState === "cursedCrystal") { // console.log("Game Update Tick - CURSED CRYSTAL"); } if (ui) { if (gameState.currentState !== "cursedCrystal" && gameState.currentState !== "cursedCrystalGameOver") { ui.updateDeathsCounter(); } if (boss && (gameState.currentState === "game" || gameState.currentState === "gameOver" && !gameOverReasonIsDeath && isNewBossPlusMode)) { var maxHpBoss = boss.maxHealth > 0 ? boss.maxHealth : 1; ui.updateBossHealth(boss.health, maxHpBoss); } else if (gameState.currentState !== "cursedCrystal" && gameState.currentState !== "cursedCrystalGameOver") { ui.updateBossHealth(0, 1); } if (player && gameState.currentState !== "cursedCrystal" && gameState.currentState !== "cursedCrystalGameOver") { if (gameState.currentState === "rollMaster") { ui.updateHearts(player.health, 1); } else { ui.updateHearts(player.health, parseInt(storage.maxHearts, 10) || 5); } } else if (gameState.currentState !== "game" && gameState.currentState !== "gameOver" && gameState.currentState !== "rollMaster" && gameState.currentState !== "cursedCrystal" && gameState.currentState !== "cursedCrystalGameOver") { ui.updateHearts(parseInt(storage.maxHearts, 10) || 5, parseInt(storage.maxHearts, 10) || 5); } } if (coffinMemeImage && !coffinMemeImage.destroyed) { if (gameState.currentState === "game" && player) { if (!player.dead) { var currentHp = player.health; var maxHp = parseInt(storage.maxHearts, 10) || 5; var visibilityFactor = 1 - Math.max(0, currentHp) / maxHp; visibilityFactor = Math.max(0, Math.min(1, visibilityFactor)); var memeHeight = coffinMemeImage.height || 200; var screenBottom = 2732; var hiddenOffsetY = memeHeight; var targetY = screenBottom + hiddenOffsetY * (1 - visibilityFactor) + 300; var targetAlpha = visibilityFactor > 0.1 ? visibilityFactor * 0.8 : 0; var positionChangeSpeed = 10; var currentY = coffinMemeImage.y; if (Math.abs(currentY - targetY) > 1) { coffinMemeImage.y = currentY < targetY ? Math.min(targetY, currentY + positionChangeSpeed) : Math.max(targetY, currentY - positionChangeSpeed); } var alphaChangeSpeed = 0.05; var currentAlpha = coffinMemeImage.alpha; if (Math.abs(currentAlpha - targetAlpha) > 0.01) { coffinMemeImage.alpha = currentAlpha < targetAlpha ? Math.min(targetAlpha, currentAlpha + alphaChangeSpeed) : Math.max(targetAlpha, currentAlpha - alphaChangeSpeed); } } else { coffinMemeImage.y = 2932; coffinMemeImage.alpha = 1.0; } } else if (gameState.currentState !== "gameOver") { if (coffinMemeImage.alpha > 0) { coffinMemeImage.alpha = Math.max(0, coffinMemeImage.alpha - 0.05); } } } if (gameState.currentState === "game") { if (player) { player.update(); } if (boss) { boss.update(); } } else if (gameState.currentState === "rollMaster") { if (player) { player.update(); } if (gameState.unlockedAttacks.includes('rmattack1')) { if (!gameState.randomRmattack1Timer) { var _spawnRandomRmattack = function spawnRandomRmattack1() { if (gameState.currentState !== "rollMaster" || !gameState.unlockedAttacks.includes('rmattack1')) { gameState.randomRmattack1Timer = null; return; } launchRmattack1(); var nextDelay = 800 + Math.random() * 1500; gameState.randomRmattack1Timer = LK.setTimeout(_spawnRandomRmattack, nextDelay); }; _spawnRandomRmattack(); } } else if (gameState.randomRmattack1Timer) { LK.clearTimeout(gameState.randomRmattack1Timer); gameState.randomRmattack1Timer = null; } if (gameState.unlockedAttacks.includes('rmattack2') && gameState.explosionSpawnInterval > 0) { gameState.explosionSpawnTimer++; if (gameState.explosionSpawnTimer >= gameState.explosionSpawnInterval) { gameState.explosionSpawnTimer = 0; launchRmattack2(); } } if (gameState.unlockedAttacks.includes('rmattack3') && gameState.laserSpawnInterval > 0) { gameState.laserSpawnTimer++; if (gameState.laserSpawnTimer >= gameState.laserSpawnInterval) { gameState.laserSpawnTimer = 0; launchRmattack3(); } } if (gameState.unlockedAttacks.includes('rmattack4') && gameState.spreaderSpawnInterval > 0) { gameState.spreaderSpawnTimer++; if (gameState.spreaderSpawnTimer >= gameState.spreaderSpawnInterval) { gameState.spreaderSpawnTimer = 0; launchRmattack4(); } } for (var i = gameState.rollMasterAttacks.length - 1; i >= 0; i--) { var atk = gameState.rollMasterAttacks[i]; var shouldRemove = false; if (!atk || !atk.visual || atk.visual.destroyed) { gameState.rollMasterAttacks.splice(i, 1); continue; } if (atk.visual.update && typeof atk.visual.update === 'function') { atk.visual.update(); } switch (atk.type) { case 'projectile': atk.visual.x += atk.vx; atk.visual.y += atk.vy; if (player && !player.dead && !player.rolling && !player.invulnerable) { var dx_p = player.x - atk.visual.x; var dy_p = player.y - atk.visual.y; var dist_p = Math.sqrt(dx_p * dx_p + dy_p * dy_p); var playerRadius_p = (player.width || 150) / 2 * 0.8; var projectileRadius = atk.radius || 45; if (dist_p < playerRadius_p + projectileRadius) { player.takeDamage(1); shouldRemove = true; } } var screenMargin = 200; if (atk.visual.x < -screenMargin || atk.visual.x > 2048 + screenMargin || atk.visual.y < -screenMargin || atk.visual.y > 2732 + screenMargin) { shouldRemove = true; } break; case 'explosion': atk.timer++; if (atk.timer >= atk.frameDurationTicks) { atk.timer = 0; atk.currentFrame++; } atk.radius = atk.currentFrame < atk.totalFrames ? atk.maxRadius * (atk.currentFrame / atk.totalFrames) : atk.maxRadius; if (!atk.damageDealt && player && !player.dead && !player.rolling && !player.invulnerable) { var dx_e = player.x - atk.visual.x; var dy_e = player.y - atk.visual.y; var dist_e = Math.sqrt(dx_e * dx_e + dy_e * dy_e); var playerRadius_e = (player.width || 150) / 2 * 0.8; if (dist_e < playerRadius_e + atk.radius) { player.takeDamage(1); atk.damageDealt = true; } } break; case 'laser': if (player && !player.dead && !player.rolling && !player.invulnerable) { var laserHalfWidth = (atk.width || 100) / 2; var laserHalfHeight = (atk.height || 100) / 2; var playerHalfWidth_l = (player.width || 150) / 2 * 0.8; var playerHalfHeight_l = (player.height || 150) / 2 * 0.8; var laserLeft = atk.visual.x - laserHalfWidth; var laserRight = atk.visual.x + laserHalfWidth; var laserTop = atk.visual.y - laserHalfHeight; var laserBottom = atk.visual.y + laserHalfHeight; var playerLeft = player.x - playerHalfWidth_l; var playerRight = player.x + playerHalfWidth_l; var playerTop = player.y - playerHalfHeight_l; var playerBottom = player.y + playerHalfHeight_l; if (laserLeft < playerRight && laserRight > playerLeft && laserTop < playerBottom && laserBottom > playerTop) { player.takeDamage(1); } } if (atk.lifeTime && atk.lifeTime > 0) { atk.lifeTime--; } break; case 'spreader_parent': atk.timer--; if (atk.timer <= 0) { var originX = atk.visual.x; var originY = atk.visual.y; var projectileSpeed_s = 5 + gameState.rollMasterDifficulty * 0.5; var projectileRadius_s = 45; var numProjectiles_s = 12; for (var j = 0; j < numProjectiles_s; j++) { var angle_s = Math.PI * 2 / numProjectiles_s * j; var vx_s = Math.cos(angle_s) * projectileSpeed_s; var vy_s = Math.sin(angle_s) * projectileSpeed_s; var projectileFrames_s = []; for (var k_s = 0; k_s <= 6; k_s++) { projectileFrames_s.push(LK.getAsset('rmattack1_' + k_s, { anchorX: 0.5, anchorY: 0.5 })); } var projectileVisual_s = new SpriteAnimation({ frames: projectileFrames_s, frameDuration: 100, loop: true, x: originX, y: originY, anchorX: 0.5, anchorY: 0.5, scaleX: 1.2, scaleY: 1.2 }); projectileVisual_s.rotation = Math.atan2(vy_s, vx_s); var projectileObject_s = game.addChild(projectileVisual_s); gameState.rollMasterAttacks.push({ type: 'projectile', visual: projectileObject_s, vx: vx_s, vy: vy_s, radius: projectileRadius_s }); } shouldRemove = true; } break; default: console.warn("Nieznany typ ataku w RollMaster: ", atk.type, atk); break; } if (shouldRemove) { if (atk.visual && atk.visual.destroy && !atk.visual.destroyed) { if (atk.visual.parent) { atk.visual.parent.removeChild(atk.visual); } atk.visual.destroy(); } gameState.rollMasterAttacks.splice(i, 1); } } if (player && player.dead && gameState.currentState === "rollMaster") { if (typeof gameState.endRollMasterMode === 'function') { gameState.endRollMasterMode(gameState.rollMasterTime); } else { console.error("Cannot call endRollMasterMode, function undefined"); } } } else if (gameState.currentState === "cursedCrystal") { if (player) { player.update(); } if (!gameState.isMinibossActiveCC) { gameState.cursedCrystalTimeSurvived++; gameState.cursedCrystalEnemySpawnTimer++; if (gameState.cursedCrystalEnemySpawnTimer >= gameState.cursedCrystalCurrentSpawnInterval) { gameState.cursedCrystalEnemySpawnTimer = 0; spawnCursedSoul(); } gameState.cursedCrystalDifficultyTimer++; if (gameState.cursedCrystalDifficultyTimer >= gameState.cursedCrystalDifficultyInterval) { gameState.cursedCrystalDifficultyTimer = 0; var spawnIntervalDecrease = 5; gameState.cursedCrystalCurrentSpawnInterval = Math.max(gameState.cursedCrystalMinSpawnInterval, gameState.cursedCrystalCurrentSpawnInterval - spawnIntervalDecrease); var speedIncrease = 0.25; gameState.cursedCrystalEnemyCurrentMaxSpeed += speedIncrease; } } for (var i = gameState.cursedCrystalEnemies.length - 1; i >= 0; i--) { var enemy = gameState.cursedCrystalEnemies[i]; if (!enemy) { gameState.cursedCrystalEnemies.splice(i, 1); continue; } if (enemy.isDead) { gameState.cursedCrystalEnemies.splice(i, 1); continue; } enemy.update(); if (enemy.isDead) { gameState.cursedCrystalEnemies.splice(i, 1); continue; } if (player && player.rolling) { var dx_soul = player.x - enemy.x; var dy_soul = player.y - enemy.y; var distance_soul = Math.sqrt(dx_soul * dx_soul + dy_soul * dy_soul); var playerRollRadius_soul = (player.width || 150) / 2 * 0.7; var soulRadius_coll = (enemy.width || 30) / 2 * 0.8; if (distance_soul < playerRollRadius_soul + soulRadius_coll) { enemy.takeDamage(1); } } } if (gameState.currentState === "cursedCrystal" && !gameState.isMinibossActiveCC && gameState.cursedCrystalChargeLevel >= gameState.cursedCrystalTargetCharge && !gameState.crystalExploding) { gameState.crystalExploding = true; // Ustaw flagę, że proces eksplozji/spawnu się rozpoczął // Zdefiniuj współrzędne spawnu minibossa na podstawie crystalCoreObject lub domyślnych var tempCrystalSpawnX, tempCrystalSpawnY; if (typeof crystalCoreObject !== 'undefined' && crystalCoreObject && !crystalCoreObject.destroyed) { tempCrystalSpawnX = crystalCoreObject.x; tempCrystalSpawnY = crystalCoreObject.y; } else { tempCrystalSpawnX = 2048 / 2; // Domyślna pozycja X na środek ekranu tempCrystalSpawnY = 2732 / 2; // Domyślna pozycja Y na środek ekranu console.warn("crystalCoreObject nie istnieje podczas próby spawnu minibossa, używam domyślnych współrzędnych."); } if (typeof gameState.playCrystalExplosionAnimation === 'function') { // Ta funkcja powinna teraz poprawnie używać wewnętrznych crystalOriginalX/Y // i przekazywać je do spawnCursedCrystalMiniboss gameState.playCrystalExplosionAnimation(); } else { // Awaryjne spawnowanie, jeśli playCrystalExplosionAnimation nie istnieje console.error("gameState.playCrystalExplosionAnimation is not defined! Spawning miniboss directly."); // Przekaż zdefiniowane wyżej współrzędne gameState.spawnCursedCrystalMiniboss(tempCrystalSpawnX, tempCrystalSpawnY); gameState.crystalExploding = false; // Resetuj flagę, jeśli spawn był bezpośredni i synchroniczny } // UWAGA: Poniższy blok kodu, który oryginalnie tworzył minibossOptions i spawnował minibossa // w tym miejscu, został usunięty. Logika ta jest teraz w całości obsługiwana przez // gameState.playCrystalExplosionAnimation() -> gameState.spawnCursedCrystalMiniboss() // lub przez awaryjne, bezpośrednie wywołanie gameState.spawnCursedCrystalMiniboss(tempCrystalSpawnX, tempCrystalSpawnY) powyżej. // To eliminuje redundancję i błąd ReferenceError. } // Koniec if'a spawującego Minibossa // Aktualizacja Minibossa CC, jeśli jest aktywny if (gameState.isMinibossActiveCC && gameState.cursedCrystalMinibossObject && !gameState.cursedCrystalMinibossObject.isDead) { gameState.cursedCrystalMinibossObject.update(); } if (gameState.cursedCrystalMinibossObject && gameState.cursedCrystalMinibossObject.activeClones) { for (var cIdx = gameState.cursedCrystalMinibossObject.activeClones.length - 1; cIdx >= 0; cIdx--) { var cloneInstance = gameState.cursedCrystalMinibossObject.activeClones[cIdx]; if (cloneInstance.isDead) { // Już obsłużone w cloneInstance.die(), ale dla pewności gameState.cursedCrystalMinibossObject.activeClones.splice(cIdx, 1); continue; } cloneInstance.update(); // Kolizja gracza (roll) z klonem if (player && player.rolling && !cloneInstance.isDead) { var dx_clone_roll = player.x - cloneInstance.x; var dy_clone_roll = player.y - cloneInstance.y; var distance_clone_roll = Math.sqrt(dx_clone_roll * dx_clone_roll + dy_clone_roll * dy_clone_roll); var playerRollRadius_clone = (player.width || 150) / 2 * 0.7; var cloneRadius_coll = (cloneInstance.width || 120) / 2 * 0.9; if (distance_clone_roll < playerRollRadius_clone + cloneRadius_coll) { cloneInstance.takeDamage(1); // Klon ginie po jednym trafieniu rollem } } } } // --- Aktualizacja Pocisków Minibossa --- if (typeof gameState.cursedCrystalActiveProjectiles === 'undefined' || !gameState.cursedCrystalActiveProjectiles) { gameState.cursedCrystalActiveProjectiles = []; } for (var k = gameState.cursedCrystalActiveProjectiles.length - 1; k >= 0; k--) { var projectile = gameState.cursedCrystalActiveProjectiles[k]; if (!projectile || projectile.isDead) { if (projectile && projectile.graphics && projectile.graphics.parent) { projectile.graphics.parent.removeChild(projectile.graphics); if (projectile.graphics.destroy) { projectile.graphics.destroy(); } } gameState.cursedCrystalActiveProjectiles.splice(k, 1); continue; } projectile.update(); if (projectile.isDead) { if (projectile.graphics && projectile.graphics.parent) { projectile.graphics.parent.removeChild(projectile.graphics); if (projectile.graphics.destroy) { projectile.graphics.destroy(); } } gameState.cursedCrystalActiveProjectiles.splice(k, 1); } } // --- Sprawdzanie Obrażeń od Eksplozji Minibossa --- if (typeof gameState.cursedCrystalActiveExplosions === 'undefined' || !gameState.cursedCrystalActiveExplosions) { gameState.cursedCrystalActiveExplosions = []; } for (var expIdx = gameState.cursedCrystalActiveExplosions.length - 1; expIdx >= 0; expIdx--) { var explosion = gameState.cursedCrystalActiveExplosions[expIdx]; if (!explosion) { gameState.cursedCrystalActiveExplosions.splice(expIdx, 1); continue; } explosion.durationTimer--; if (player && !player.dead && !player.invulnerable && !explosion.hitPlayerThisFrame) { var distToExplosion = Math.sqrt(Math.pow(player.x - explosion.x, 2) + Math.pow(player.y - explosion.y, 2)); if (distToExplosion < (player.width || 150) / 2 * 0.7 + explosion.radius) { player.takeDamage(explosion.damage); explosion.hitPlayerThisFrame = true; } } if (explosion.durationTimer <= 0) { gameState.cursedCrystalActiveExplosions.splice(expIdx, 1); } } // --- LOGIKA DLA AKTYWNYCH ŚCIAN LASEROWYCH MINIBOSSA --- if (typeof gameState.cursedCrystalActiveLaserWalls === 'undefined' || !gameState.cursedCrystalActiveLaserWalls) { gameState.cursedCrystalActiveLaserWalls = []; } if (gameState.cursedCrystalActiveLaserWalls && gameState.cursedCrystalActiveLaserWalls.length > 0) { for (var wallIdx = gameState.cursedCrystalActiveLaserWalls.length - 1; wallIdx >= 0; wallIdx--) { var wall = gameState.cursedCrystalActiveLaserWalls[wallIdx]; if (!wall || wall.isDead) { if (wall && wall.segments) { wall.segments.forEach(function (seg) { if (seg.isPlaceholder) { return; } if (seg.visual && seg.visual.parent) { seg.visual.parent.removeChild(seg.visual); if (seg.visual.destroy && !seg.visual.destroyed) { seg.visual.destroy(); } } }); } gameState.cursedCrystalActiveLaserWalls.splice(wallIdx, 1); continue; } if (gameState.cursedCrystalMinibossObject && !gameState.cursedCrystalMinibossObject.isDead) { wall.pivotX = gameState.cursedCrystalMinibossObject.x; wall.pivotY = gameState.cursedCrystalMinibossObject.y; } if (wall.warningTimer > 0) { wall.warningTimer -= 1000 / 60; wall.segments.forEach(function (segment) { if (segment.isPlaceholder) { return; } var initialAngleForStaticDisplay = 0; var rotatedOffsetX = segment.initialOffsetX * Math.cos(initialAngleForStaticDisplay) - segment.initialOffsetY * Math.sin(initialAngleForStaticDisplay); var rotatedOffsetY = segment.initialOffsetX * Math.sin(initialAngleForStaticDisplay) + segment.initialOffsetY * Math.cos(initialAngleForStaticDisplay); segment.currentX = wall.pivotX + rotatedOffsetX; segment.currentY = wall.pivotY + rotatedOffsetY; if (segment.visual && !segment.visual.destroyed) { segment.visual.x = segment.currentX; segment.visual.y = segment.currentY; segment.visual.rotation = initialAngleForStaticDisplay; } }); } else { if (wall.activeTimer > 0) { wall.activeTimer -= 1000 / 60; wall.currentAngle += wall.rotationSpeed; wall.segments.forEach(function (segment) { if (segment.isPlaceholder) { return; } var rotatedOffsetX = segment.initialOffsetX * Math.cos(wall.currentAngle) - segment.initialOffsetY * Math.sin(wall.currentAngle); var rotatedOffsetY = segment.initialOffsetX * Math.sin(wall.currentAngle) + segment.initialOffsetY * Math.cos(wall.currentAngle); segment.currentX = wall.pivotX + rotatedOffsetX; segment.currentY = wall.pivotY + rotatedOffsetY; if (segment.visual && !segment.visual.destroyed) { segment.visual.x = segment.currentX; segment.visual.y = segment.currentY; var baseRotation = wall.currentAngle; if (segment.animationPhase === 'action_once' || segment.animationPhase === 'looping_cut') { if (segment.currentIndividualSpinAngle === undefined) { segment.currentIndividualSpinAngle = 0; } var RAPID_SPIN_SPEED = 0.4; segment.currentIndividualSpinAngle += RAPID_SPIN_SPEED; segment.visual.rotation = baseRotation + segment.currentIndividualSpinAngle; } else { segment.visual.rotation = baseRotation; } if (player && !player.dead && !player.invulnerable) { var playerHalfWidth_lw = (player.width || 150) / 2 * 0.7; var playerHalfHeight_lw = (player.height || 150) / 2 * 0.7; var segmentHalfWidth_lw = (segment.width || 120) / 2; var segmentHalfHeight_lw = (segment.height || 120) / 2; if (Math.abs(player.x - segment.currentX) < playerHalfWidth_lw + segmentHalfWidth_lw && Math.abs(player.y - segment.currentY) < playerHalfHeight_lw + segmentHalfHeight_lw) { player.takeDamage(1); } } } }); if (wall.activeTimer <= 0) { wall.isDead = true; } } else { wall.isDead = true; } } } } if (player && player.dead) {/* Obsługiwane przez player.die() */} } }; if (typeof gameState !== 'undefined' && typeof gameState.init === 'function') { gameState.init(); } else { console.error("Obiekt gameState lub gameState.init nie jest zdefiniowany przed wywołaniem init!"); }
/****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
var storage = LK.import("@upit/storage.v1");
/****
* Classes
****/
// Klasy z stworzylo zloto.txt (bardziej zaawansowana walka z bossem)
// Klasy z stworzylo zloto.txt (bardziej zaawansowana walka z bossem)
var Boss = Container.expand(function () {
var self = Container.call(this);
self.isCharging = false;
self.ultimateAttackCooldownTimer = 0;
self.ultimateAttackMinInterval = 30 * 60;
self.ultimateAttackMaxInterval = 40 * 60;
self.nextUltimateAttackAvailableTime = 0;
self.circleAttackCooldownDuration = 11 * 60;
self.circleAttackActiveCooldown = self.circleAttackCooldownDuration;
self.bossGraphics = self.addChild(LK.getAsset('bossIdle', {
anchorX: 0.5,
anchorY: 0.5,
x: 0,
y: 0
}));
self.bossGraphics.scaleX = 2;
self.bossGraphics.scaleY = 2;
self.createBossAttackAnim = function () {
var frames = [];
for (var i = 0; i <= 6; i++) {
frames.push(LK.getAsset('bossAttack' + i, {}));
}
for (var i = 5; i >= 1; i--) {
frames.push(LK.getAsset('bossAttack' + i, {}));
}
var bossAttackAnim = new SpriteAnimation({
frames: frames,
frameDuration: 100,
loop: false,
anchorX: 0.5,
anchorY: 0.5,
x: 0,
y: 0
});
bossAttackAnim.scaleX = 2;
bossAttackAnim.scaleY = 2;
return bossAttackAnim;
};
self.playBossAttackAnim = function (attackType) {
if (self.bossAttackAnim) {
self.bossAttackAnim.stop();
if (self.bossAttackAnim.parent) {
self.bossAttackAnim.parent.removeChild(self.bossAttackAnim);
}
self.bossAttackAnim.destroy();
self.bossAttackAnim = null;
}
if (attackType !== 2 && attackType !== 3) {
if (self.bossGraphics && self.bossGraphics.parent) {
self.bossGraphics.parent.removeChild(self.bossGraphics);
}
self.bossGraphics = null;
self.bossAttackAnim = self.addChild(self.createBossAttackAnim());
self.bossAttackAnim.update = function () {
if (self.bossAttackAnim !== this || !this.playing || !this.frames || this.frames.length === 0) {
return;
}
this.frameTimer = (this.frameTimer || 0) + 1;
if (this.frameTimer >= this.frameDuration / (1000 / 60)) {
this.frameTimer = 0;
this.removeChildren();
this.currentFrame = (this.currentFrame || 0) + 1;
if (this.currentFrame >= this.frames.length) {
self.bossGraphics = self.addChild(LK.getAsset('bossIdle', {
anchorX: 0.5,
anchorY: 0.5,
x: 0,
y: 0
}));
self.bossGraphics.scaleX = 2;
self.bossGraphics.scaleY = 2;
if (this.parent) {
this.parent.removeChild(this);
}
this.destroy();
self.bossAttackAnim = null;
} else {
if (this.frames[this.currentFrame]) {
this.addChild(this.frames[this.currentFrame]);
}
}
}
};
}
};
self.createAttack = function (x, y, duration, type) {
var framesToUse = [];
var scaleMultiplier = 1;
var attackRadius = 60;
if (type === 'circle') {
framesToUse = [LK.getAsset('fireball0', {
anchorX: 0.5,
anchorY: 0.5,
clone: true
}), LK.getAsset('fireball00', {
anchorX: 0.5,
anchorY: 0.5,
clone: true
}), LK.getAsset('fireball01', {
anchorX: 0.5,
anchorY: 0.5,
clone: true
}), LK.getAsset('fireball02', {
anchorX: 0.5,
anchorY: 0.5,
clone: true
}), LK.getAsset('fireball03', {
anchorX: 0.5,
anchorY: 0.5,
clone: true
}), LK.getAsset('fireball04', {
anchorX: 0.5,
anchorY: 0.5,
clone: true
}), LK.getAsset('fireball05', {
anchorX: 0.5,
anchorY: 0.5,
clone: true
}), LK.getAsset('fireball06', {
anchorX: 0.5,
anchorY: 0.5,
clone: true
}), LK.getAsset('fireball07', {
anchorX: 0.5,
anchorY: 0.5,
clone: true
}), LK.getAsset('fireball08', {
anchorX: 0.5,
anchorY: 0.5,
clone: true
}), LK.getAsset('fireball09', {
anchorX: 0.5,
anchorY: 0.5,
clone: true
}), LK.getAsset('fireball1', {
anchorX: 0.5,
anchorY: 0.5,
clone: true
}), LK.getAsset('fireball10', {
anchorX: 0.5,
anchorY: 0.5,
clone: true
}), LK.getAsset('fireball11', {
anchorX: 0.5,
anchorY: 0.5,
clone: true
}), LK.getAsset('fireball12', {
anchorX: 0.5,
anchorY: 0.5,
clone: true
}), LK.getAsset('fireball13', {
anchorX: 0.5,
anchorY: 0.5,
clone: true
}), LK.getAsset('fireball14', {
anchorX: 0.5,
anchorY: 0.5,
clone: true
}), LK.getAsset('fireballnew1', {
anchorX: 0.5,
anchorY: 0.5,
clone: true
}), LK.getAsset('fireballnew2', {
anchorX: 0.5,
anchorY: 0.5,
clone: true
}), LK.getAsset('fireballnew3', {
anchorX: 0.5,
anchorY: 0.5,
clone: true
}), LK.getAsset('fireballnew4', {
anchorX: 0.5,
anchorY: 0.5,
clone: true
})];
} else if (type === 'line') {
framesToUse = [LK.getAsset('fireball2', {
anchorX: 0.5,
anchorY: 0.5,
clone: true
}), LK.getAsset('fireball3', {
anchorX: 0.5,
anchorY: 0.5,
clone: true
}), LK.getAsset('fireball4', {
anchorX: 0.5,
anchorY: 0.5,
clone: true
}), LK.getAsset('fireball5', {
anchorX: 0.5,
anchorY: 0.5,
clone: true
}), LK.getAsset('fireball6', {
anchorX: 0.5,
anchorY: 0.5,
clone: true
}), LK.getAsset('fireball7', {
anchorX: 0.5,
anchorY: 0.5,
clone: true
}), LK.getAsset('fireball8', {
anchorX: 0.5,
anchorY: 0.5,
clone: true
}), LK.getAsset('fireball9', {
anchorX: 0.5,
anchorY: 0.5,
clone: true
}), LK.getAsset('fireball15', {
anchorX: 0.5,
anchorY: 0.5,
clone: true
}), LK.getAsset('fireball16', {
anchorX: 0.5,
anchorY: 0.5,
clone: true
}), LK.getAsset('linearattack1', {
anchorX: 0.5,
anchorY: 0.5,
clone: true
}), LK.getAsset('linearattack2', {
anchorX: 0.5,
anchorY: 0.5,
clone: true
}), LK.getAsset('linearattack3', {
anchorX: 0.5,
anchorY: 0.5,
clone: true
}), LK.getAsset('linearattack4', {
anchorX: 0.5,
anchorY: 0.5,
clone: true
}), LK.getAsset('linearattack5', {
anchorX: 0.5,
anchorY: 0.5,
clone: true
})];
scaleMultiplier = 1.3;
attackRadius = 60 * scaleMultiplier;
} else {
framesToUse = [LK.getAsset('fireball0', {
anchorX: 0.5,
anchorY: 0.5,
clone: true
})];
}
var currentFrameDuration = type === 'line' ? 250 : type === 'circle' ? 150 : 100;
var spriteAnim = game.addChild(new SpriteAnimation({
frames: framesToUse,
frameDuration: currentFrameDuration,
loop: false,
anchorX: 0.5,
anchorY: 0.5,
x: x,
y: y
}));
spriteAnim.scaleX = 1.6 * scaleMultiplier;
spriteAnim.scaleY = 1.6 * scaleMultiplier;
spriteAnim.play();
var attackData = {
x: x,
y: y,
radius: attackRadius,
visual: spriteAnim,
lifeTime: Math.floor(duration / (1000 / 60)),
isActive: true,
attackObjectType: type
};
self.attacks.push(attackData);
var animationTotalDurationMs = framesToUse.length * currentFrameDuration;
LK.setTimeout(function () {
var index = self.attacks.indexOf(attackData);
if (index !== -1) {
self.attacks.splice(index, 1);
}
if (spriteAnim && !spriteAnim.destroyed) {
if (spriteAnim.parent) {
spriteAnim.parent.removeChild(spriteAnim);
}
spriteAnim.destroy();
}
}, animationTotalDurationMs);
};
self.circleAttack = function () {
LK.getSound('bossAttack').play();
var count = isNewBossPlusMode ? 8 : 4;
var radius = 300;
var orbitDurationFrames = 230;
var flightDurationMs = isNewBossPlusMode ? 3500 : 4500;
var baseAngle = Math.random() * Math.PI * 2;
var angularSpeed = 0.02;
var attackOriginX = self.x;
var attackOriginY = self.y;
for (var i = 0; i < count; i++) {
var angleOffset = i / count * Math.PI * 2;
var initialAngle = baseAngle + angleOffset;
var x = attackOriginX + Math.cos(initialAngle) * radius;
var y = attackOriginY + Math.sin(initialAngle) * radius;
var orbFrames = [LK.getAsset('fireball0', {
anchorX: 0.5,
anchorY: 0.5,
clone: true
}), LK.getAsset('fireball00', {
anchorX: 0.5,
anchorY: 0.5,
clone: true
}), LK.getAsset('fireball01', {
anchorX: 0.5,
anchorY: 0.5,
clone: true
}), LK.getAsset('fireball02', {
anchorX: 0.5,
anchorY: 0.5,
clone: true
}), LK.getAsset('fireball03', {
anchorX: 0.5,
anchorY: 0.5,
clone: true
}), LK.getAsset('fireball04', {
anchorX: 0.5,
anchorY: 0.5,
clone: true
}), LK.getAsset('fireball05', {
anchorX: 0.5,
anchorY: 0.5,
clone: true
}), LK.getAsset('fireball06', {
anchorX: 0.5,
anchorY: 0.5,
clone: true
})];
var spriteAnim = new SpriteAnimation({
frames: orbFrames,
frameDuration: 100,
loop: true,
anchorX: 0.5,
anchorY: 0.5,
x: x,
y: y
});
spriteAnim.scaleX = 1.6;
spriteAnim.scaleY = 1.6;
spriteAnim.play();
spriteAnim.update = function () {
this.frameTimer = (this.frameTimer || 0) + 1;
if (this.frameTimer >= this.frameDuration / (1000 / 60)) {
this.frameTimer = 0;
this.removeChildren();
this.currentFrame = ((this.currentFrame || 0) + 1) % this.frames.length;
if (this.frames[this.currentFrame]) {
this.addChild(this.frames[this.currentFrame]);
}
}
};
game.addChild(spriteAnim);
self.attacks.push({
type: 'circle_orbiting',
angleOffset: angleOffset,
baseAngle: baseAngle,
currentAngle: initialAngle,
angularSpeed: angularSpeed,
radius: radius,
collisionRadius: 60,
centerX: attackOriginX,
centerY: attackOriginY,
detachCounter: orbitDurationFrames,
lifeTime: Math.floor(flightDurationMs / (1000 / 60)),
isActive: true,
visual: spriteAnim,
x: x,
y: y,
detached: false,
vx: 0,
vy: 0
});
}
};
self.takeDamage = function (amount) {
console.log("DEBUG: Boss.takeDamage CALLED. Amount:", amount, "Boss dead:", self.dead, "Current state:", gameState.currentState, "Boss health BEFORE:", self.health);
if (self.dead || typeof gameState !== 'undefined' && gameState.currentState !== "game") {
console.log("DEBUG: Boss.takeDamage REJECTED. Dead or wrong game state.");
return;
}
self.health -= amount;
self.health = Math.max(0, self.health);
console.log("DEBUG: Boss health AFTER:", self.health, "/", self.maxHealth);
LK.effects.flashObject(self, 0xFFFFFF, 200);
if (typeof isNewBossPlusMode !== 'undefined' && !isNewBossPlusMode) {
if (self.health <= self.maxHealth / 2 && self.phase === 1) {
console.log("DEBUG: Boss entering phase 2.");
self.phase = 2;
self.speed += 2;
self.attackSpeedMultiplier = (self.attackSpeedMultiplier || 1) * 0.8;
tween(self, {
tint: 0xFF3300
}, {
duration: 1000,
easing: tween.easeInOut
});
}
}
if (self.health <= 0) {
console.log("DEBUG: Boss health <= 0, calling self.die().");
self.die();
}
};
// NOWA METODA DO CZYSZCZENIA ATAKÓW
self.clearAllAttacks = function () {
console.log("DEBUG: Boss.clearAllAttacks CALLED. Clearing " + self.attacks.length + " attacks.");
var attacksToClear = self.attacks.slice(); // Iteruj po kopii
attacksToClear.forEach(function (attack) {
if (attack.visual && !attack.visual.destroyed) {
if (attack.visual.parent) {
attack.visual.parent.removeChild(attack.visual); // Usuń z rodzica (np. 'game')
}
attack.visual.destroy();
}
});
self.attacks = []; // Wyczyść tablicę ataków bossa
console.log("DEBUG: Boss.clearAllAttacks FINISHED. Attacks remaining: " + self.attacks.length);
};
self.die = function () {
var currentGameState = typeof gameState !== 'undefined' ? gameState.currentState : "gameState undefined";
console.log("DEBUG: Boss.die() called. Current state:", currentGameState, "Is boss already dead?", self.dead, "Is NewBossPlusMode?", isNewBossPlusMode);
if (self.dead && currentGameState !== "game") {
console.log("DEBUG: Boss.die() - Already dead and not in 'game' state. Attempting cleanup again just in case.");
self.clearAllAttacks("Die - Already dead, not in game state");
return;
}
if (self.dead) {
console.log("DEBUG: Boss.die() - Already dead. Exiting.");
return;
}
self.dead = true;
console.log("DEBUG: Boss.die() - Boss marked as dead. Clearing attacks.");
self.clearAllAttacks("Die - Normal death sequence");
// Dźwięk zwycięstwa tylko w trybie standardowym
if (typeof isNewBossPlusMode !== 'undefined' && !isNewBossPlusMode) {
LK.getSound('victory').play();
}
if (typeof gameState !== 'undefined' && gameState.currentState === "game") {
console.log("DEBUG: Boss.die - In 'game' state.");
// ZMIANA TUTAJ: Boss+ będzie miał animację śmierci i przejdzie do Grill Menu
// Niezależnie od tego, czy to Boss+ czy standardowy, wykonaj animację śmierci
console.log("DEBUG: Boss.die - Starting death animation tween for boss (standard or Boss+).");
tween(self, {
alpha: 0,
scaleX: self.scaleX * 1.2,
scaleY: self.scaleY * 1.2
}, {
duration: 2000,
easing: tween.easeOut,
onFinish: function onFinish() {
console.log("DEBUG: Boss.die tween onFinish reached.");
// Zwiększ licznik pokonanych bossów (możesz chcieć osobny licznik dla Boss+)
storage.bossesDefeated = (parseInt(storage.bossesDefeated, 10) || 0) + 1;
console.log("DEBUG: Boss.die onFinish - Bosses defeated:", storage.bossesDefeated);
if (typeof gameState !== 'undefined' && typeof gameState.showGrillScreen === 'function') {
// Dla Boss+ możesz przekazać specjalną flagę, jeśli chcesz inny komunikat na Grill Menu
var wasBossPlusDefeated = typeof isNewBossPlusMode !== 'undefined' && isNewBossPlusMode;
gameState.showGrillScreen(false, wasBossPlusDefeated); // (survivedBossPlusChallenge = false, defeatedBossPlus = wasBossPlusDefeated)
}
}
});
} else {
console.log("DEBUG: Boss.die - Not in 'game' state (e.g., during restart), skipping animations. Attacks should be cleared.");
}
};
self.update = function () {
if (typeof gameState !== 'undefined' && gameState.currentState !== "game" && gameState.currentState !== "rollMaster") {
if (self.rolling) {
self.rolling = false;
}
return;
}
if (self.dead) {
return;
}
self.ultimateAttackCooldownTimer++;
if (self.circleAttackActiveCooldown < self.circleAttackCooldownDuration) {
self.circleAttackActiveCooldown++;
}
if (self.attackCooldown > 30 && typeof player !== 'undefined' && player && !player.dead && !self.isCharging) {
var dx = player.x - self.x;
var dy = player.y - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
var moveSpeed = self.speed || 2;
if (distance > 150) {
var moveX = dx / distance * moveSpeed;
var moveY = dy / distance * moveSpeed;
var nextX = self.x + moveX;
var nextY = self.y + moveY;
var halfWidth = (self.width || 100) * (self.scaleX || 1) / 2;
var halfHeight = (self.height || 100) * (self.scaleY || 1) / 2;
var minX = 100 + halfWidth;
var maxX = 2048 - 100 - halfWidth;
var minY = 300 + halfHeight;
var maxY = 2732 - 100 - halfHeight;
self.x = Math.max(minX, Math.min(nextX, maxX));
self.y = Math.max(minY, Math.min(nextY, maxY));
}
}
for (var i = self.attacks.length - 1; i >= 0; i--) {
var attack = self.attacks[i];
var shouldRemove = false;
if (attack.type !== 'line_controller' && (!attack || !attack.visual || attack.visual.destroyed)) {
self.attacks.splice(i, 1);
continue;
}
if (attack.visual && typeof attack.visual.update === 'function') {
attack.visual.update();
}
if (attack.type === 'circle_orbiting') {
if (!attack.detached) {
attack.baseAngle += attack.angularSpeed;
var currentOrbAngle = attack.baseAngle + attack.angleOffset;
attack.x = attack.centerX + Math.cos(currentOrbAngle) * attack.radius;
attack.y = attack.centerY + Math.sin(currentOrbAngle) * attack.radius;
if (attack.visual) {
attack.visual.x = attack.x;
attack.visual.y = attack.y;
}
attack.detachCounter--;
if (attack.detachCounter <= 0) {
attack.detached = true;
var tangentialAngle = currentOrbAngle + Math.PI / 2;
var launchSpeed = 6;
attack.vx = Math.cos(tangentialAngle) * launchSpeed;
attack.vy = Math.sin(tangentialAngle) * launchSpeed;
}
} else {
attack.x += attack.vx;
attack.y += attack.vy;
if (attack.visual) {
attack.visual.x = attack.x;
attack.visual.y = attack.y;
}
attack.lifeTime--;
if (attack.x < -200 || attack.x > 2248 || attack.y < -200 || attack.y > 2932) {
shouldRemove = true;
}
if (attack.lifeTime <= 0) {
shouldRemove = true;
}
}
} else if (attack.type === 'ultimate_orb') {
attack.x += attack.vx;
attack.y += attack.vy;
if (attack.visual) {
attack.visual.x = attack.x;
attack.visual.y = attack.y;
}
attack.lifeTime--;
if (attack.x < -200 || attack.x > 2048 + 200 || attack.y < -200 || attack.y > 2732 + 200) {
shouldRemove = true;
}
if (attack.lifeTime <= 0) {
shouldRemove = true;
}
} else if (attack.type === 'line_controller') {
attack.x += attack.directionX * attack.speed;
attack.y += attack.directionY * attack.speed;
var currentTime = LK.getGameTime();
if (attack.currentSegmentIndex < attack.totalSegmentsToSpawn && currentTime - attack.lastSpawnTime >= attack.segmentDelay) {
self.createAttack(attack.x, attack.y, attack.segmentLifeTime, 'line');
attack.currentSegmentIndex++;
attack.lastSpawnTime = currentTime;
}
var controllerLifeTimeAfterSpawning = attack.segmentLifeTime;
if (attack.currentSegmentIndex >= attack.totalSegmentsToSpawn && LK.getGameTime() - attack.lastSpawnTime > controllerLifeTimeAfterSpawning) {
shouldRemove = true;
}
} else if (attack.attackObjectType && attack.type !== 'line_controller') {
attack.lifeTime--;
if (attack.lifeTime <= 0) {
shouldRemove = true;
}
}
if (!shouldRemove && attack.isActive && typeof player !== 'undefined' && player && !player.dead && !player.invulnerable) {
if (attack.type !== 'line_controller') {
var dx_p = player.x - attack.x;
var dy_p = player.y - attack.y;
var distance_p = Math.sqrt(dx_p * dx_p + dy_p * dy_p);
var playerRadius_p = player.width / 2 * 0.8;
var colRadius = attack.collisionRadius || attack.radius || 60;
if (distance_p < playerRadius_p + colRadius) {
player.takeDamage(1);
shouldRemove = true;
}
}
}
if (shouldRemove) {
if (attack.visual && !attack.visual.destroyed) {
if (attack.visual.parent) {
attack.visual.parent.removeChild(attack.visual);
}
attack.visual.destroy();
}
self.attacks.splice(i, 1);
}
}
if (self.attackCooldown > 0) {
self.attackCooldown--;
}
if (self.attackCooldown <= 0 && !self.isCharging) {
self.startAttackPattern();
}
}; // Koniec self.update dla Bossa
// Pełne implementacje funkcji lineAttack, chargeAttack, ultimateAttack, startAttackPattern
// powinny być tutaj wklejone z Twojej poprzedniej, kompletnej wersji.
// Poniżej skrócone wersje dla kompletności struktury.
self.lineAttack = function () {
LK.getSound('bossAttack').play();
if (typeof player === 'undefined' || !player) {
return;
}
var startX = self.x;
var startY = self.y;
var targetX = player.x;
var targetY = player.y;
var dx = targetX - startX;
var dy = targetY - startY;
var distance = Math.sqrt(dx * dx + dy * dy);
var normalizedDx = 0;
var normalizedDy = 0;
if (distance > 0) {
normalizedDx = dx / distance;
normalizedDy = dy / distance;
}
var wallSpeed = typeof isNewBossPlusMode !== 'undefined' && isNewBossPlusMode ? 3 : 2;
var numberOfSegments = typeof isNewBossPlusMode !== 'undefined' && isNewBossPlusMode ? 10 : 8;
var segmentSpawnDuration = typeof isNewBossPlusMode !== 'undefined' && isNewBossPlusMode ? 2500 : 3000;
var segmentLifeTime = typeof isNewBossPlusMode !== 'undefined' && isNewBossPlusMode ? 2000 : 2500;
var delayPerSegment = segmentSpawnDuration / numberOfSegments;
var lineAttackController = {
type: 'line_controller',
currentSegmentIndex: 0,
lastSpawnTime: LK.getGameTime(),
totalSegmentsToSpawn: numberOfSegments,
segmentDelay: delayPerSegment,
segmentLifeTime: segmentLifeTime,
directionX: normalizedDx,
directionY: normalizedDy,
speed: wallSpeed,
x: startX,
y: startY,
targetX: targetX,
targetY: targetY
};
self.attacks.push(lineAttackController);
};
self.chargeAttack = function () {
LK.getSound('bossAttack').play();
if (typeof player === 'undefined' || !player) {
return;
}
if (self.bossAttackAnim) {
self.bossAttackAnim.stop();
if (self.bossAttackAnim.parent) {
self.bossAttackAnim.parent.removeChild(self.bossAttackAnim);
}
self.bossAttackAnim.destroy();
self.bossAttackAnim = null;
}
if (!self.bossGraphics || self.bossGraphics && self.bossGraphics.destroyed) {
if (self.bossGraphics && self.bossGraphics.parent) {
self.bossGraphics.parent.removeChild(self.bossGraphics);
}
self.bossGraphics = self.addChild(LK.getAsset('bossIdle', {
anchorX: 0.5,
anchorY: 0.5,
x: 0,
y: 0
}));
self.bossGraphics.scaleX = 2;
self.bossGraphics.scaleY = 2;
}
self.isCharging = true;
var dx = player.x - self.x;
var dy = player.y - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance > 0) {
dx /= distance;
dy /= distance;
}
var chargeDistance = typeof isNewBossPlusMode !== 'undefined' && isNewBossPlusMode ? 700 : 500;
var chargeDuration = typeof isNewBossPlusMode !== 'undefined' && isNewBossPlusMode ? 600 : 800;
tween(self, {
x: self.x + dx * chargeDistance,
y: self.y + dy * chargeDistance
}, {
duration: chargeDuration * (self.attackSpeedMultiplier || 1),
easing: tween.easeIn,
onFinish: function onFinish() {
self.isCharging = false;
}
});
};
self.ultimateAttack = function () {
if (self.dead || typeof gameState !== 'undefined' && gameState.currentState !== "game" || typeof isNewBossPlusMode !== 'undefined' && !isNewBossPlusMode) {
return;
}
if (self.bossAttackAnim) {
self.bossAttackAnim.stop();
if (self.bossAttackAnim.parent) {
self.bossAttackAnim.parent.removeChild(self.bossAttackAnim);
}
self.bossAttackAnim.destroy();
self.bossAttackAnim = null;
}
if (!self.bossGraphics || self.bossGraphics && self.bossGraphics.destroyed) {
if (self.bossGraphics && self.bossGraphics.parent) {
self.bossGraphics.parent.removeChild(self.bossGraphics);
}
self.bossGraphics = self.addChild(LK.getAsset('bossIdle', {
anchorX: 0.5,
anchorY: 0.5,
x: 0,
y: 0
}));
self.bossGraphics.scaleX = 2;
self.bossGraphics.scaleY = 2;
}
var screenWidth = 2048;
var screenHeight = 2732;
var iconWidth = 700;
var iconHeight = 700;
var startX = -iconWidth;
var middleX = screenWidth / 2;
var endX = screenWidth + iconWidth;
var targetY = 250;
var hoverAmplitudeY = 20;
var hoverDurationY = 1000;
var rotationAmplitude = 0.05;
var rotationDuration = 1500;
var moveInDuration = 400;
var hoverDurationTotal = 2000;
var moveOutDuration = 400;
var attackIcon = game.addChild(LK.getAsset('ultimateBossAttack_icon', {
anchorX: 0.5,
anchorY: 0.5,
x: startX,
y: targetY,
alpha: 0,
scaleX: 0.8,
scaleY: 0.8,
rotation: 0
}));
tween(attackIcon, {
alpha: 1,
scaleX: 1,
scaleY: 1,
x: middleX
}, {
duration: moveInDuration,
easing: tween.easeOutQuad,
onFinish: function onFinish() {
if (attackIcon.destroyed) {
return;
}
var hoverTweenY = tween(attackIcon, {
y: attackIcon.y + hoverAmplitudeY
}, {
duration: hoverDurationY / 2,
easing: tween.easeInOut,
yoyo: true,
repeat: Infinity
});
var rotationTween = tween(attackIcon, {
rotation: attackIcon.rotation + rotationAmplitude
}, {
duration: rotationDuration / 2,
easing: tween.easeInOut,
yoyo: true,
repeat: Infinity
});
LK.setTimeout(function () {
if (attackIcon.destroyed) {
if (hoverTweenY) {
hoverTweenY.stop();
}
if (rotationTween) {
rotationTween.stop();
}
return;
}
if (hoverTweenY) {
hoverTweenY.stop();
}
if (rotationTween) {
rotationTween.stop();
}
tween(attackIcon, {
y: targetY,
rotation: 0
}, {
duration: 100,
onFinish: function onFinish() {
tween(attackIcon, {
alpha: 0,
x: endX
}, {
duration: moveOutDuration,
easing: tween.easeInQuad,
onFinish: function onFinish() {
if (attackIcon && !attackIcon.destroyed) {
if (attackIcon.parent) {
attackIcon.parent.removeChild(attackIcon);
}
attackIcon.destroy();
}
}
});
}
});
}, hoverDurationTotal);
}
});
var originalOrbWidth = 300;
var originalOrbHeight = 300;
var baseOrbRadius = Math.max(originalOrbWidth, originalOrbHeight) / 2;
var growAndFadeInDuration = 6000;
var finalScale = 2.6;
var orbSpeed = 8;
var travelDurationMs = 6000;
function createAndLaunchOrb(spawnX, spawnDelay) {
LK.setTimeout(function () {
if (self.dead || typeof gameState !== 'undefined' && gameState.currentState !== "game") {
return;
}
var orbFrames = [];
for (var i = 0; i <= 7; i++) {
orbFrames.push(LK.getAsset('ultimatebossattack_orb_' + i, {
anchorX: 0.5,
anchorY: 0.5,
clone: true
}));
}
var orbAnim = new SpriteAnimation({
frames: orbFrames,
frameDuration: 70,
loop: true,
x: spawnX,
y: 300,
anchorX: 0.5,
anchorY: 0.5,
scaleX: 0.1,
scaleY: 0.1,
alpha: 0
});
game.addChild(orbAnim);
tween(orbAnim, {
scaleX: finalScale,
scaleY: finalScale,
alpha: 1
}, {
duration: growAndFadeInDuration,
easing: tween.easeOutQuad,
onFinish: function onFinish() {
if (orbAnim.destroyed) {
return;
}
var targetXPlayer = typeof player !== 'undefined' && player ? player.x : screenWidth / 2;
var targetYPlayer = typeof player !== 'undefined' && player ? player.y : screenHeight / 2;
var actualOrbRadius = baseOrbRadius * finalScale;
var dxOrb = targetXPlayer - orbAnim.x;
var dyOrb = targetYPlayer - orbAnim.y;
var distOrb = Math.sqrt(dxOrb * dxOrb + dyOrb * dyOrb);
var vxOrb = distOrb > 0 ? dxOrb / distOrb * orbSpeed : 0;
var vyOrb = distOrb > 0 ? dyOrb / distOrb * orbSpeed : 0;
self.attacks.push({
type: 'ultimate_orb',
visual: orbAnim,
vx: vxOrb,
vy: vyOrb,
radius: actualOrbRadius,
lifeTime: travelDurationMs / (1000 / 60),
x: orbAnim.x,
y: orbAnim.y,
isActive: true
});
}
});
}, spawnDelay);
}
var spawnDelayBetweenOrbs = 1000;
createAndLaunchOrb(screenWidth / 2, 0);
createAndLaunchOrb(screenWidth / 2 - 600, spawnDelayBetweenOrbs);
createAndLaunchOrb(screenWidth / 2 + 600, spawnDelayBetweenOrbs * 2);
};
self.startAttackPattern = function () {
self.attackCooldown = typeof isNewBossPlusMode !== 'undefined' && isNewBossPlusMode ? 150 : 190;
var attackType;
var availableAttacks = [];
if (self.circleAttackActiveCooldown >= self.circleAttackCooldownDuration) {
availableAttacks.push(0);
}
availableAttacks.push(1);
availableAttacks.push(2);
var isUltimateAttackReady = self.ultimateAttackCooldownTimer >= self.nextUltimateAttackAvailableTime;
if (typeof isNewBossPlusMode !== 'undefined' && isNewBossPlusMode && isUltimateAttackReady) {
availableAttacks.push(3);
}
if (availableAttacks.length === 0) {
return;
}
attackType = availableAttacks[Math.floor(Math.random() * availableAttacks.length)];
self.playBossAttackAnim(attackType);
if (attackType === 0) {
self.circleAttack();
self.circleAttackActiveCooldown = 0;
} else if (attackType === 1) {
self.lineAttack();
} else if (attackType === 2) {
self.chargeAttack();
} else if (attackType === 3 && typeof isNewBossPlusMode !== 'undefined' && isNewBossPlusMode) {
self.ultimateAttack();
self.ultimateAttackCooldownTimer = 0;
self.nextUltimateAttackAvailableTime = self.ultimateAttackMinInterval + Math.floor(Math.random() * (self.ultimateAttackMaxInterval - self.ultimateAttackMinInterval + 1));
self.attackCooldown = 6 * 60;
}
};
// Inicjalizacja zmiennych bossa
self.nextUltimateAttackAvailableTime = self.ultimateAttackMinInterval + Math.floor(Math.random() * (self.ultimateAttackMaxInterval - self.ultimateAttackMinInterval + 1));
self.health = typeof isNewBossPlusMode !== 'undefined' && isNewBossPlusMode ? 300 : 150;
self.maxHealth = self.health;
self.speed = typeof isNewBossPlusMode !== 'undefined' && isNewBossPlusMode ? 3 : 2;
self.attackSpeedMultiplier = 1;
self.phase = 1;
self.attacks = [];
self.attackCooldown = 120; // Początkowy cooldown przed pierwszym atakiem
self.dead = false;
return self;
});
var MinibossCC = Container.expand(function (options) {
var self = Container.call(this);
options = options || {};
self.maxHp = options.maxHp || 200;
self.hp = self.maxHp;
self.speed = options.speed || 0;
self.isDead = false;
self.attackPattern = ['explosiveProjectile', 'laserWall'];
self.currentAttackIndex = 0;
self.attackCooldowns = {
'explosiveProjectile': 8 * 60,
'laserWall': 5 * 60
};
self.attackCooldown = 120;
self.currentAttackName = '';
self.isCurrentlyPlayingAttackAnim = false;
self.activeSkillAnimationInstance = null;
self.isTeleporting = false;
self.teleportCooldownTimer = 0;
self.teleportIntervalMin = 9 * 60;
self.teleportIntervalMax = 16 * 60;
self.nextTeleportTime = self.teleportIntervalMin + Math.floor(Math.random() * (self.teleportIntervalMax - self.teleportIntervalMin + 1));
self.activeClones = [];
self.maxClones = 2;
self.canTeleportWithClones = true;
self.teleportCloneCooldownDuration = 10 * 60;
self.isWaitingForClonesToDespawn = false;
self.cloneTeleportTimeoutId = null;
self.creepSpeed = 0.9;
self.creepDirectionX = 0;
self.creepDirectionY = 0;
self.creepDirectionChangeIntervalMin = 3 * 60;
self.creepDirectionChangeIntervalMax = 7 * 60;
self.creepDirectionChangeTimer = 0;
self.actualWidth = 120;
self.actualHeight = 120;
try {
self.graphics = self.attachAsset('miniboss_cc_asset', {
anchorX: 0.5,
anchorY: 0.5
});
} catch (e) {
console.warn("Nie udało się załadować 'miniboss_cc_asset', używam awaryjnego Shape:", e);
self.graphics = new Shape({
width: 120,
height: 120,
color: 0xFF8C00,
shape: 'box'
});
self.addChild(self.graphics);
}
if (self.graphics && typeof self.graphics.width !== 'undefined') {
self.width = self.graphics.width * (self.graphics.scaleX || 1);
self.height = self.graphics.height * (self.graphics.scaleY || 1);
} else {
self.width = 120;
self.height = 120;
}
if (self.graphics && typeof self.graphics.width === 'number' && typeof self.graphics.height === 'number') {
self.actualWidth = self.graphics.width * (self.graphics.scaleX || 1);
self.actualHeight = self.graphics.height * (self.graphics.scaleY || 1);
} else {
self.actualWidth = self.width;
self.actualHeight = self.height;
}
self.pickNewCreepDirection = function () {
var angle = Math.random() * 2 * Math.PI;
self.creepDirectionX = Math.cos(angle);
self.creepDirectionY = Math.sin(angle);
self.creepDirectionChangeTimer = self.creepDirectionIntervalMin + Math.floor(Math.random() * (self.creepDirectionChangeIntervalMax - self.creepDirectionIntervalMin + 1));
};
self.createMinibossSkillAnim = function () {
var frames = [];
var frameBaseName = 'bossSkillAnim0';
var totalSkillFrames = 9;
for (var i = 0; i < totalSkillFrames; i++) {
var assetName = frameBaseName + (i === 0 ? '' : String(i));
try {
frames.push(LK.getAsset(assetName, {
anchorX: 0.5,
anchorY: 0.5,
clone: true
}));
} catch (e) {
console.error("Błąd ładowania klatki animacji Klona: " + assetName, e);
if (i > 0 && frames.length > 0) {
frames.push(frames[0]);
} else {
var placeholderFrame = new Shape({
width: 120,
height: 120,
color: 0xFF00FF,
shape: 'box'
});
placeholderFrame.anchor.set(0.5, 0.5);
frames.push(placeholderFrame);
}
}
}
for (var i = totalSkillFrames - 2; i >= 1; i--) {
var assetName = frameBaseName + (i === 0 ? '' : String(i));
try {
frames.push(LK.getAsset(assetName, {
anchorX: 0.5,
anchorY: 0.5,
clone: true
}));
} catch (e) {
console.error("Błąd ładowania klatki powrotnej animacji Klona: " + assetName, e);
if (frames.length > 0) {
frames.push(frames[0]);
}
}
}
var skillAnimationObject = new SpriteAnimation({
frames: frames,
frameDuration: 120,
loop: false,
anchorX: 0.5,
anchorY: 0.5,
x: 0,
y: 0
});
return skillAnimationObject;
};
self.playMinibossSkillAnim = function (onAnimationCompleteCallback) {
if (self.isDead || self.isTeleporting) {
self.isCurrentlyPlayingAttackAnim = false;
if (typeof onAnimationCompleteCallback === 'function') {
onAnimationCompleteCallback();
}
return;
}
if (self.activeSkillAnimationInstance && self.activeSkillAnimationInstance.parent) {
self.activeSkillAnimationInstance.stop();
self.removeChild(self.activeSkillAnimationInstance);
self.activeSkillAnimationInstance.destroy();
self.activeSkillAnimationInstance = null;
}
if (self.graphics && !self.graphics.destroyed) {
self.graphics.visible = false;
}
var newAnimation = self.createMinibossSkillAnim();
self.activeSkillAnimationInstance = self.addChild(newAnimation);
self.activeSkillAnimationInstance.currentFrameIndex = 0;
self.activeSkillAnimationInstance.animationTimer = 0;
self.activeSkillAnimationInstance.framesArray = newAnimation.frames;
self.activeSkillAnimationInstance.singleFrameDuration = newAnimation.frameDuration || 120;
self.activeSkillAnimationInstance.playing = true;
self.activeSkillAnimationInstance.removeChildren();
if (self.activeSkillAnimationInstance.framesArray && self.activeSkillAnimationInstance.framesArray.length > 0) {
self.activeSkillAnimationInstance.addChild(self.activeSkillAnimationInstance.framesArray[0]);
} else {
self.isCurrentlyPlayingAttackAnim = false;
if (self.graphics && !self.graphics.destroyed) {
self.graphics.visible = true;
}
if (typeof onAnimationCompleteCallback === 'function') {
onAnimationCompleteCallback();
}
if (self.activeSkillAnimationInstance) {
if (self.activeSkillAnimationInstance.parent) {
self.removeChild(self.activeSkillAnimationInstance);
}
self.activeSkillAnimationInstance.destroy();
self.activeSkillAnimationInstance = null;
}
return;
}
self.activeSkillAnimationInstance.update = function () {
if (!this.playing || !this.framesArray || this.framesArray.length === 0) {
return;
}
this.animationTimer++;
if (this.animationTimer * (1000 / 60) >= this.singleFrameDuration) {
this.animationTimer = 0;
this.currentFrameIndex++;
if (this.currentFrameIndex >= this.framesArray.length) {
this.playing = false;
if (self.graphics && !self.graphics.destroyed) {
self.graphics.visible = true;
}
if (this.parent) {
this.parent.removeChild(this);
}
this.destroy();
if (self.activeSkillAnimationInstance === this) {
self.activeSkillAnimationInstance = null;
}
if (typeof onAnimationCompleteCallback === 'function') {
onAnimationCompleteCallback();
} else {
self.isCurrentlyPlayingAttackAnim = false;
}
} else {
this.removeChildren();
if (this.framesArray[this.currentFrameIndex]) {
this.addChild(this.framesArray[this.currentFrameIndex]);
}
}
}
};
};
self.update = function () {
if (self.isDead || gameState.currentState !== "cursedCrystal") {
return;
}
if (self.isTeleporting || self.activeSkillAnimationInstance) {} else {
self.creepDirectionChangeTimer--;
if (self.creepDirectionChangeTimer <= 0) {
self.pickNewCreepDirection();
}
var nextX = self.x + self.creepDirectionX * self.creepSpeed;
var nextY = self.y + self.creepDirectionY * self.creepSpeed;
var halfWidth = self.actualWidth / 2;
var halfHeight = self.actualHeight / 2;
var arenaMinX = 50 + halfWidth;
var arenaMaxX = 2048 - 50 - halfWidth;
var arenaMinY = 50 + halfHeight;
var arenaMaxY = 2732 - 50 - halfHeight;
if (nextX >= arenaMinX && nextX <= arenaMaxX) {
self.x = nextX;
} else {
self.creepDirectionX *= -1;
self.creepDirectionChangeTimer = self.creepDirectionIntervalMin;
}
if (nextY >= arenaMinY && nextY <= arenaMaxY) {
self.y = nextY;
} else {
self.creepDirectionY *= -1;
self.creepDirectionChangeTimer = self.creepDirectionIntervalMin;
}
}
if (self.canTeleportWithClones && !self.isTeleporting) {
self.teleportCooldownTimer++;
if (self.teleportCooldownTimer >= self.nextTeleportTime) {
self.teleport();
return;
}
}
if (!self.isTeleporting) {
if (self.attackCooldown > 0) {
self.attackCooldown--;
} else if (player && !player.dead && !self.isCurrentlyPlayingAttackAnim) {
self.isCurrentlyPlayingAttackAnim = true;
self.performAttack();
}
}
};
self.performAttack = function () {
if (!player || player.dead || self.isTeleporting) {
self.isCurrentlyPlayingAttackAnim = false;
return;
}
var executeAttackLogic = function executeAttackLogic() {
if (self.isDead) {
self.isCurrentlyPlayingAttackAnim = false;
return;
}
self.currentAttackName = self.attackPattern[self.currentAttackIndex];
if (self.currentAttackName === 'explosiveProjectile') {
var createProjectileWithDelay = function createProjectileWithDelay(delayMs) {
LK.setTimeout(function () {
if (self.isDead || !player || player.dead) {
return;
}
var projectile = new MinibossExplosiveProjectile({
x: self.x,
y: self.y,
target: player,
speed: 7,
explosionRadius: 150
});
game.addChild(projectile);
if (gameState.cursedCrystalActiveProjectiles === undefined) {
gameState.cursedCrystalActiveProjectiles = [];
}
gameState.cursedCrystalActiveProjectiles.push(projectile);
console.log("MinibossCC: Wystrzelono pocisk eksplozywny (opóźnienie: " + delayMs + "ms)");
}, delayMs);
};
createProjectileWithDelay(0); // Pierwszy pocisk natychmiast
if (gameState.minibossEnhancedProjectile) {
// Załóżmy, że ta flaga istnieje w gameState
console.log("MinibossCC (Ulepszony): Wystrzeliwuje drugi pocisk!");
createProjectileWithDelay(2000); // Drugi pocisk po 2000ms (2 sekundy)
}
} else if (self.currentAttackName === 'laserWall') {
var numTotalSegments = 7;
var middleSegmentIndex = Math.floor(numTotalSegments / 2);
var scytheWidth = 160;
var scytheHeight = 160;
var spacingBetweenSegments = 20;
var totalWallEffectiveHeight = numTotalSegments * scytheHeight + (numTotalSegments - 1) * spacingBetweenSegments;
var wallPivotX = self.x;
var wallPivotY = self.y;
var warningDuration = 1500;
var activeDurationStandard = 4000; // Dla 2 obrotów
var rotationSpeedStandard = 2 * Math.PI / 120; // 1 obrót na 120 klatek (2s)
var activeDurationEnhanced = 4500; // Czas na 3 obroty (3 * 1.5s)
var rotationSpeedEnhanced = 2 * Math.PI / 90; // 1 obrót na 90 klatek (1.5s)
var currentActiveDuration = activeDurationStandard;
var currentRotationSpeed = rotationSpeedStandard;
if (gameState.minibossEnhancedLaserWall) {
// Zakładamy dostęp do gameState
console.log("Miniboss: Ulepszony LaserWall - szybszy i 3 obroty!");
currentActiveDuration = activeDurationEnhanced;
currentRotationSpeed = rotationSpeedEnhanced;
}
var activeDuration = 4000;
var rotationSpeed = 2 * Math.PI / 120;
var laserWallInstance = {
pivotX: wallPivotX,
pivotY: wallPivotY,
segments: [],
currentAngle: 0,
rotationSpeed: currentRotationSpeed,
warningTimer: warningDuration,
activeTimer: currentActiveDuration,
isDead: false
};
var initialSegmentOffsetY = -(totalWallEffectiveHeight / 2) + scytheHeight / 2;
var frameDurationAction = 100;
var frameDurationLoop = 100;
for (var i = 0; i < numTotalSegments; i++) {
var segmentOffsetY = initialSegmentOffsetY + i * (scytheHeight + spacingBetweenSegments);
var spawnPosX = wallPivotX;
var spawnPosY = wallPivotY + segmentOffsetY;
if (i === middleSegmentIndex) {
laserWallInstance.segments.push({
isPlaceholder: true,
initialOffsetY: segmentOffsetY,
width: scytheWidth,
height: scytheHeight
});
continue;
}
var scytheAppearVisual = null;
var FADE_IN_DURATION = 500; // Czas trwania fade in w milisekundach - dostosuj!
try {
scytheAppearVisual = LK.getAsset('scythe_appear_0', {
anchorX: 0.5,
anchorY: 0.5,
clone: true,
alpha: 0 // Zacznij od przezroczystości 0
});
scytheAppearVisual.x = spawnPosX;
scytheAppearVisual.y = spawnPosY;
game.addChild(scytheAppearVisual);
tween(scytheAppearVisual, {
alpha: 1
}, {
duration: FADE_IN_DURATION,
easing: tween.easeInQuad
});
} catch (e) {
console.error("Błąd ładowania assetu scythe_appear_0: ", e);
scytheAppearVisual = new Shape({
width: scytheWidth,
height: scytheHeight,
color: 0xCCCCCC,
shape: 'box',
alpha: 0 // Zacznij od przezroczystości 0
});
scytheAppearVisual.anchor.set(0.5, 0.5);
scytheAppearVisual.x = spawnPosX;
scytheAppearVisual.y = spawnPosY;
game.addChild(scytheAppearVisual);
tween(scytheAppearVisual, {
alpha: 1
}, {
duration: FADE_IN_DURATION,
easing: tween.easeInQuad
});
}
laserWallInstance.segments.push({
isPlaceholder: false,
initialOffsetX: 0,
initialOffsetY: segmentOffsetY,
width: scytheWidth,
height: scytheHeight,
visual: scytheAppearVisual,
currentX: spawnPosX,
currentY: spawnPosY,
animationPhase: 'appearing_static'
});
}
LK.setTimeout(function () {
if (laserWallInstance.isDead) {
return;
}
laserWallInstance.segments.forEach(function (segment) {
if (segment.isPlaceholder || segment.animationPhase !== 'appearing_static') {
return;
}
if (segment.visual && segment.visual.parent) {
segment.visual.parent.removeChild(segment.visual);
segment.visual.destroy();
}
var actionFrames = [];
var actionAssetNames = ['scythe_action_0', 'scythe_action_1', 'scythe_action_2', 'scythe_action_3', 'scythe_action_4', 'scythe_action_5'];
try {
actionAssetNames.forEach(function (name) {
actionFrames.push(LK.getAsset(name, {
anchorX: 0.5,
anchorY: 0.5,
clone: true
}));
});
} catch (e) {
console.error("Błąd ładowania klatek akcji kosy: ", e);
}
if (actionFrames.length === 0) {
console.error("Brak klatek dla actionScytheAnim segmentu", segment);
segment.visual = new Shape({
width: scytheWidth,
height: scytheHeight,
color: 0xFF0000,
shape: 'box'
});
segment.visual.anchor.set(0.5, 0.5);
segment.visual.x = segment.currentX;
segment.visual.y = segment.currentY;
game.addChild(segment.visual);
segment.animationPhase = 'error_action';
return;
}
var actionScytheAnim = new SpriteAnimation({
frames: actionFrames,
frameDuration: frameDurationAction,
loop: false,
x: segment.currentX,
y: segment.currentY,
anchorX: 0.5,
anchorY: 0.5
});
game.addChild(actionScytheAnim);
segment.visual = actionScytheAnim;
segment.animationPhase = 'action_once';
actionScytheAnim.play();
actionScytheAnim.onComplete = function () {
if (segment.isPlaceholder || segment.animationPhase !== 'action_once' || laserWallInstance && laserWallInstance.isDead) {
return;
}
if (segment.visual && segment.visual.parent) {
segment.visual.parent.removeChild(segment.visual);
segment.visual.destroy();
}
var loopFrames = [];
var loopAssetIndices = [2, 3, 4, 5];
var loopAssetNames = loopAssetIndices.map(function (idx) {
return idx < actionAssetNames.length ? actionAssetNames[idx] : null;
}).filter(function (name) {
return name !== null;
});
try {
loopAssetNames.forEach(function (name) {
loopFrames.push(LK.getAsset(name, {
anchorX: 0.5,
anchorY: 0.5,
clone: true
}));
});
} catch (e) {
console.error("Błąd ładowania klatek pętli kosy: ", e);
}
if (loopFrames.length === 0) {
console.error("Brak klatek dla loopingScytheAnim segmentu", segment);
segment.visual = new Shape({
width: scytheWidth,
height: scytheHeight,
color: 0xFF0000,
shape: 'box'
});
segment.visual.anchor.set(0.5, 0.5);
segment.visual.x = segment.currentX;
segment.visual.y = segment.currentY;
game.addChild(segment.visual);
segment.animationPhase = 'error_loop';
return;
}
var loopingScytheAnim = new SpriteAnimation({
frames: loopFrames,
frameDuration: frameDurationLoop,
loop: true,
x: segment.currentX,
y: segment.currentY,
anchorX: 0.5,
anchorY: 0.5
});
game.addChild(loopingScytheAnim);
segment.visual = loopingScytheAnim;
segment.animationPhase = 'looping_cut';
loopingScytheAnim.play();
};
});
}, warningDuration);
if (gameState.cursedCrystalActiveLaserWalls === undefined) {
gameState.cursedCrystalActiveLaserWalls = [];
}
gameState.cursedCrystalActiveLaserWalls.push(laserWallInstance);
}
self.currentAttackIndex = (self.currentAttackIndex + 1) % self.attackPattern.length;
self.attackCooldown = self.attackCooldowns[self.currentAttackName] || 180;
self.isCurrentlyPlayingAttackAnim = false;
};
self.playMinibossSkillAnim(executeAttackLogic);
};
self.teleport = function () {
if (self.isDead || self.isTeleporting) {
console.log("MinibossCC: Próba teleportacji, ale już martwy lub teleportuje. Dead: " + self.isDead + ", Teleporting: " + self.isTeleporting);
return;
}
if (!self.canTeleportWithClones) {
console.log("MinibossCC: Próba teleportacji z klonami, ale jest na cooldownie po klonach lub czeka na klony.");
return;
}
self.canTeleportWithClones = false;
self.isWaitingForClonesToDespawn = true;
console.log("MinibossCC: TELEPORT START. Pozycja: X=" + self.x.toFixed(0) + ", Y=" + self.y.toFixed(0) + ", Alpha: " + self.alpha);
self.isTeleporting = true;
self.attackCooldown = 120 + Math.floor(Math.random() * 60);
tween(self, {
alpha: 0
}, {
duration: 600,
easing: tween.easeInQuad,
onFinish: function onFinish() {
if (self.isDead) {
self.alpha = 1;
self.isTeleporting = false;
self.isWaitingForClonesToDespawn = false;
self.canTeleportWithClones = true;
console.log("MinibossCC: Teleport przerwany, boss martwy w trakcie fade-out.");
return;
}
var halfWidth = self.actualWidth / 2;
var halfHeight = self.actualHeight / 2;
var arenaMinX = 50 + halfWidth;
var arenaMaxX = 2048 - 50 - halfWidth;
var arenaMinY = 50 + halfHeight;
var arenaMaxY = 2732 - 50 - halfHeight;
var newX, newY, distanceToPlayer;
var attempts = 0;
var minDistanceToPlayer = 300;
do {
newX = arenaMinX + Math.random() * (arenaMaxX - arenaMinX);
newY = arenaMinY + Math.random() * (arenaMaxY - arenaMinY);
if (typeof player !== 'undefined' && player && !player.dead) {
distanceToPlayer = Math.sqrt(Math.pow(newX - player.x, 2) + Math.pow(newY - player.y, 2));
} else {
distanceToPlayer = minDistanceToPlayer + 1;
}
attempts++;
} while (distanceToPlayer < minDistanceToPlayer && attempts < 10);
if (attempts >= 10 && distanceToPlayer < minDistanceToPlayer) {
console.warn("MinibossCC: Nie udało się znaleźć pozycji wystarczająco daleko od gracza po 10 próbach.");
}
self.x = newX;
self.y = newY;
self.pickNewCreepDirection();
var clonesActuallySpawned = 0;
var minClonesToSpawn = 1;
var maxClonesToSpawn = 3; // Domyślnie 1-2 klony
// Użyj jednej flagi, np. gameState.minibossIsEnhanced, lub sprawdź jedną z istniejących
// Zakładam, że masz flagę np. gameState.minibossEnhancedProjectile lub stworzysz ogólną gameState.minibossIsEnhanced
if (gameState.minibossEnhancedProjectile) {
// Zmień ten warunek na swoją flagę ulepszenia bossa
console.log("MinibossCC (Ulepszony): Tworzy więcej klonów przy teleportacji!");
minClonesToSpawn = 3;
maxClonesToSpawn = 5;
}
var clonesToAttemptSpawn = minClonesToSpawn + Math.floor(Math.random() * (maxClonesToSpawn - minClonesToSpawn + 1));
console.log("MinibossCC będzie próbował stworzyć klonów:", clonesToAttemptSpawn);
for (var i = 0; i < clonesToAttemptSpawn; i++) {
if (self.activeClones.length < self.maxClones) {
// Nadal respektujemy self.maxClones
var cloneSpawnAttempts = 0;
var cloneX, cloneY, distToBoss, distToPlayerClone;
var minDistanceToBoss = 200;
var minDistanceToPlayerForClone = 150;
// Logika do...while do znajdowania pozycji klona POZOSTAJE BEZ ZMIAN
do {
cloneX = arenaMinX + Math.random() * (arenaMaxX - arenaMinX);
cloneY = arenaMinY + Math.random() * (arenaMaxY - arenaMinY);
distToBoss = Math.sqrt(Math.pow(cloneX - self.x, 2) + Math.pow(cloneY - self.y, 2));
if (typeof player !== 'undefined' && player && !player.dead) {
distToPlayerClone = Math.sqrt(Math.pow(cloneX - player.x, 2) + Math.pow(cloneY - player.y, 2));
} else {
distToPlayerClone = minDistanceToPlayerForClone + 1;
}
cloneSpawnAttempts++;
} while ((distToBoss < minDistanceToBoss || distToPlayerClone < minDistanceToPlayerForClone) && cloneSpawnAttempts < 10);
var clone = new MinibossCCClone({
x: cloneX,
y: cloneY,
owner: self
});
game.addChild(clone);
self.activeClones.push(clone);
clonesActuallySpawned++;
} else {
console.log("MinibossCC: Osiągnięto maksymalną liczbę klonów (" + self.maxClones + "), nie można stworzyć więcej.");
break;
}
}
if (clonesActuallySpawned === 0 && self.isWaitingForClonesToDespawn) {
console.log("MinibossCC: No clones were spawned, starting teleport cooldown immediately.");
self.startTeleportCooldownAfterClones();
}
self.teleportCooldownTimer = 0;
self.nextTeleportTime = self.teleportIntervalMin + Math.floor(Math.random() * (self.teleportIntervalMax - self.teleportIntervalMin + 1));
tween(self, {
alpha: 1
}, {
duration: 600,
easing: tween.easeOutQuad,
onFinish: function onFinishFadeIn() {
self.isTeleporting = false;
console.log("MinibossCC: TELEPORT FADE_IN_FINISH. Następna normalna teleportacja za: " + (self.nextTeleportTime / 60).toFixed(1) + "s");
}
});
}
});
};
self.takeDamage = function (amount) {
if (self.isDead) {
return;
}
self.hp -= amount;
gameState.cursedCrystalMinibossHP = self.hp;
LK.effects.flashObject(self.graphics || self, 0xFF0000, 200);
if (self.hp <= 0) {
self.hp = 0;
gameState.cursedCrystalMinibossHP = self.hp;
self.die();
}
if (ui && ui.updateMinibossHealthCC) {
ui.updateMinibossHealthCC(self.hp, self.maxHp, true);
}
};
self.die = function () {
if (self.isDead) {
return;
}
if (self.cloneTeleportTimeoutId) {
LK.clearTimeout(self.cloneTeleportTimeoutId);
self.cloneTeleportTimeoutId = null;
}
self.isWaitingForClonesToDespawn = false;
self.canTeleportWithClones = true;
if (self.activeClones && self.activeClones.length > 0) {
console.log("MinibossCC dying, clearing " + self.activeClones.length + " active clones.");
var clonesToKill = self.activeClones.slice();
clonesToKill.forEach(function (clone) {
if (clone && !clone.isDead) {
clone.die(true);
}
});
self.activeClones = [];
}
self.isDead = true;
console.log("MinibossCC has been defeated!");
if (gameState.cursedCrystalActiveProjectiles) {
gameState.cursedCrystalActiveProjectiles.forEach(function (proj) {});
gameState.cursedCrystalActiveProjectiles = [];
}
if (gameState.cursedCrystalActiveExplosions) {
gameState.cursedCrystalActiveExplosions = [];
}
if (gameState.cursedCrystalActiveLaserWalls) {
gameState.cursedCrystalActiveLaserWalls.forEach(function (wall) {});
gameState.cursedCrystalActiveLaserWalls = [];
}
if (typeof gameState.endCursedCrystalMode === 'function') {
gameState.endCursedCrystalMode(true);
}
if (self.parent) {
self.parent.removeChild(self);
}
if (self.destroy && !self.destroyed) {
self.destroy();
}
gameState.cursedCrystalMinibossObject = null;
gameState.isMinibossActiveCC = false;
};
self.startTeleportCooldownAfterClones = function () {
if (!self.isWaitingForClonesToDespawn && self.activeClones.length > 0) {
return;
}
if (!self.isWaitingForClonesToDespawn && self.activeClones.length === 0) {
return;
}
console.log("MinibossCC: All clones despawned. Starting " + self.teleportCloneCooldownDuration / 60 + "-second cooldown for clone teleport.");
self.isWaitingForClonesToDespawn = false;
if (self.cloneTeleportTimeoutId) {
LK.clearTimeout(self.cloneTeleportTimeoutId);
}
self.cloneTeleportTimeoutId = LK.setTimeout(function () {
self.cloneTeleportTimeoutId = null;
if (!self.isDead) {
self.canTeleportWithClones = true;
self.nextTeleportTime = self.teleportCooldownTimer;
console.log("MinibossCC: Clone teleport is OFF COOLDOWN. Boss can attempt teleport. nextTeleportTime now: " + self.nextTeleportTime);
}
}, self.teleportCloneCooldownDuration * (1000 / 60));
};
self.x = options.x || 2048 / 2;
self.y = options.y || 300;
self.pickNewCreepDirection();
return self;
});
// -------- KONIEC ZAKTUALIZOWANEJ KLASY MinibossCC --------
// NOWA KLASA DLA KLONA MINIBOSSA CC
var MinibossCCClone = Container.expand(function (options) {
var self = Container.call(this);
options = options || {};
self.owner = options.owner;
self.x = options.x || 2048 / 2;
self.y = options.y || 300;
self.isDead = false;
self.lifeTimer = 30 * 60;
self.attackCooldown = 4 * 60 + Math.floor(Math.random() * (2 * 60));
self.activeSkillAnimationInstance = null;
self.isCurrentlyPlayingAttackAnim = false;
self.creepSpeed = 0.9;
self.creepDirectionX = 0;
self.creepDirectionY = 0;
self.creepDirectionChangeIntervalMin = 2 * 60;
self.creepDirectionChangeIntervalMax = 5 * 60;
self.creepDirectionChangeTimer = 0;
// --- DEFINICJA METODY pickNewCreepDirection PRZED PIERWSZYM UŻYCIEM ---
self.pickNewCreepDirection = function () {
var angle = Math.random() * 2 * Math.PI;
self.creepDirectionX = Math.cos(angle);
self.creepDirectionY = Math.sin(angle);
self.creepDirectionChangeTimer = self.creepDirectionIntervalMin + Math.floor(Math.random() * (self.creepDirectionChangeIntervalMax - self.creepDirectionIntervalMin + 1));
};
// --- PIERWSZE WYWOŁANIE METODY ---
self.pickNewCreepDirection();
try {
self.graphics = self.attachAsset('miniboss_cc_asset', {
anchorX: 0.5,
anchorY: 0.5
});
} catch (e) {
console.warn("Nie udało się załadować 'miniboss_cc_asset' dla Klona, używam Shape.", e);
self.graphics = new Shape({
width: 120,
height: 120,
color: 0xFF8C00,
shape: 'box'
});
self.addChild(self.graphics);
}
self.width = self.graphics && typeof self.graphics.width !== 'undefined' ? self.graphics.width : 120;
self.height = self.graphics && typeof self.graphics.height !== 'undefined' ? self.graphics.height : 120;
self.createMinibossSkillAnim = function () {
var frames = [];
var frameBaseName = 'bossSkillAnim0';
var totalSkillFrames = 9;
for (var i = 0; i < totalSkillFrames; i++) {
var assetName = frameBaseName + (i === 0 ? '' : String(i));
try {
frames.push(LK.getAsset(assetName, {
anchorX: 0.5,
anchorY: 0.5,
clone: true
}));
} catch (e) {
console.error("Błąd ładowania klatki animacji Klona: " + assetName, e);
if (i > 0 && frames.length > 0) {
frames.push(frames[0]);
} else {
var placeholderFrame = new Shape({
width: 120,
height: 120,
color: 0xFF00FF,
shape: 'box'
});
placeholderFrame.anchor.set(0.5, 0.5);
frames.push(placeholderFrame);
}
}
}
for (var i = totalSkillFrames - 2; i >= 1; i--) {
var assetName = frameBaseName + (i === 0 ? '' : String(i));
try {
frames.push(LK.getAsset(assetName, {
anchorX: 0.5,
anchorY: 0.5,
clone: true
}));
} catch (e) {
console.error("Błąd ładowania klatki powrotnej animacji Klona: " + assetName, e);
if (frames.length > 0) {
frames.push(frames[0]);
}
}
}
var skillAnimationObject = new SpriteAnimation({
frames: frames,
frameDuration: 120,
loop: false,
anchorX: 0.5,
anchorY: 0.5,
x: 0,
y: 0
});
return skillAnimationObject;
};
self.playMinibossSkillAnim = function (onAnimationCompleteCallback) {
if (self.isDead) {
self.isCurrentlyPlayingAttackAnim = false;
if (typeof onAnimationCompleteCallback === 'function') {
onAnimationCompleteCallback();
}
return;
}
if (self.activeSkillAnimationInstance && self.activeSkillAnimationInstance.parent) {
self.activeSkillAnimationInstance.stop();
self.removeChild(self.activeSkillAnimationInstance);
self.activeSkillAnimationInstance.destroy();
self.activeSkillAnimationInstance = null;
}
if (self.graphics && !self.graphics.destroyed) {
self.graphics.visible = false;
}
var newAnimation = self.createMinibossSkillAnim();
self.activeSkillAnimationInstance = self.addChild(newAnimation);
self.activeSkillAnimationInstance.currentFrameIndex = 0;
self.activeSkillAnimationInstance.animationTimer = 0;
self.activeSkillAnimationInstance.framesArray = newAnimation.frames;
self.activeSkillAnimationInstance.singleFrameDuration = newAnimation.frameDuration || 120;
self.activeSkillAnimationInstance.playing = true;
self.activeSkillAnimationInstance.removeChildren();
if (self.activeSkillAnimationInstance.framesArray && self.activeSkillAnimationInstance.framesArray.length > 0) {
self.activeSkillAnimationInstance.addChild(self.activeSkillAnimationInstance.framesArray[0]);
} else {
self.isCurrentlyPlayingAttackAnim = false;
if (self.graphics && !self.graphics.destroyed) {
self.graphics.visible = true;
}
if (typeof onAnimationCompleteCallback === 'function') {
onAnimationCompleteCallback();
}
if (self.activeSkillAnimationInstance) {
if (self.activeSkillAnimationInstance.parent) {
self.removeChild(self.activeSkillAnimationInstance);
}
self.activeSkillAnimationInstance.destroy();
self.activeSkillAnimationInstance = null;
}
return;
}
self.activeSkillAnimationInstance.update = function () {
if (!this.playing || !this.framesArray || this.framesArray.length === 0) {
return;
}
this.animationTimer++;
if (this.animationTimer * (1000 / 60) >= this.singleFrameDuration) {
this.animationTimer = 0;
this.currentFrameIndex++;
if (this.currentFrameIndex >= this.framesArray.length) {
this.playing = false;
if (self.graphics && !self.graphics.destroyed) {
self.graphics.visible = true;
}
if (this.parent) {
this.parent.removeChild(this);
}
this.destroy();
if (self.activeSkillAnimationInstance === this) {
self.activeSkillAnimationInstance = null;
}
if (typeof onAnimationCompleteCallback === 'function') {
onAnimationCompleteCallback();
} else {
self.isCurrentlyPlayingAttackAnim = false;
}
} else {
this.removeChildren();
if (this.framesArray[this.currentFrameIndex]) {
this.addChild(this.framesArray[this.currentFrameIndex]);
}
}
}
};
};
self.takeDamage = function (amount) {
if (self.isDead) {
return;
}
LK.effects.flashObject(self.graphics || self, 0xFFFFFF, 150);
self.die();
};
self.die = function (isTimeout) {
if (self.isDead) {
return;
}
self.isDead = true;
if (!isTimeout) {
LK.effects.flashObject(self.graphics || self, 0xFFFFFF, 150);
}
if (self.owner && self.owner.activeClones) {
var index = self.owner.activeClones.indexOf(self);
if (index > -1) {
self.owner.activeClones.splice(index, 1);
if (self.owner.activeClones.length === 0 && self.owner.isWaitingForClonesToDespawn) {
self.owner.startTeleportCooldownAfterClones();
}
}
}
var deathTween = tween(self, {
alpha: 0
}, {
duration: 800,
easing: tween.easeOutQuad,
onFinish: function onFinish() {
if (self.parent) {
self.parent.removeChild(self);
}
if (self.destroy && !self.destroyed) {
self.destroy();
}
}
});
};
self.performSimpleAttack = function () {
if (self.isDead || !player || player.dead) {
self.isCurrentlyPlayingAttackAnim = false;
return;
}
var executeCloneAttackLogic = function executeCloneAttackLogic() {
if (self.isDead) {
self.isCurrentlyPlayingAttackAnim = false;
return;
}
var projectile = new MinibossExplosiveProjectile({
x: self.x,
y: self.y,
target: player,
speed: 4,
explosionRadius: 150
});
game.addChild(projectile);
if (gameState.cursedCrystalActiveProjectiles === undefined) {
gameState.cursedCrystalActiveProjectiles = [];
}
gameState.cursedCrystalActiveProjectiles.push(projectile);
self.attackCooldown = 8 * 60 + Math.floor(Math.random() * (2 * 60));
self.isCurrentlyPlayingAttackAnim = false;
};
self.playMinibossSkillAnim(executeCloneAttackLogic);
};
self.update = function () {
if (self.isDead || gameState.currentState !== "cursedCrystal") {
return;
}
self.lifeTimer--;
if (self.lifeTimer <= 0) {
self.die(true);
return;
}
if (self.activeSkillAnimationInstance) {} else {
self.creepDirectionChangeTimer--;
if (self.creepDirectionChangeTimer <= 0) {
self.pickNewCreepDirection();
}
var nextX = self.x + self.creepDirectionX * self.creepSpeed;
var nextY = self.y + self.creepDirectionY * self.creepSpeed;
var halfWidth = (self.width || 120) / 2;
var halfHeight = (self.height || 120) / 2;
var arenaMinX = 50 + halfWidth;
var arenaMaxX = 2048 - 50 - halfWidth;
var arenaMinY = 50 + halfHeight;
var arenaMaxY = 2732 - 50 - halfHeight;
if (nextX >= arenaMinX && nextX <= arenaMaxX) {
self.x = nextX;
} else {
self.creepDirectionX *= -1;
self.creepDirectionChangeTimer = self.creepDirectionIntervalMin;
}
if (nextY >= arenaMinY && nextY <= arenaMaxY) {
self.y = nextY;
} else {
self.creepDirectionY *= -1;
self.creepDirectionChangeTimer = self.creepDirectionIntervalMin;
}
}
if (self.attackCooldown > 0) {
self.attackCooldown--;
} else if (!self.isCurrentlyPlayingAttackAnim && !self.activeSkillAnimationInstance) {
self.isCurrentlyPlayingAttackAnim = true;
self.performSimpleAttack();
}
};
self.alpha = 0;
self.scaleX = 0.5;
self.scaleY = 0.5;
tween(self, {
alpha: 1,
scaleX: 1,
scaleY: 1
}, {
duration: 500,
easing: tween.easeOutQuad
});
return self;
});
// -------- KONIEC ZAKTUALIZOWANEJ KLASY MinibossCC --------
var MinibossExplosiveProjectile = Container.expand(function (options) {
var self = Container.call(this);
options = options || {};
self.x = options.x || 0;
self.y = options.y || 0;
self.speed = options.speed || 6;
self.target = options.target;
self.lifeTimer = 240;
self.isDead = false;
self.hasExploded = false;
self.explosionRadius = options.explosionRadius || 100;
self.explosionDamage = 1;
try {
self.graphics = self.attachAsset('projectile_spinning_asset', {
// UŻYJ NOWEGO ASSETU DLA POCISKU
anchorX: 0.5,
anchorY: 0.5,
width: 140,
height: 140
});
} catch (e) {
console.warn("Nie udało się załadować 'projectile_spinning_asset', używam Shape.", e);
self.graphics = new Shape({
width: 40,
height: 40,
color: 0xFF4500,
shape: 'ellipse'
});
self.addChild(self.graphics);
}
self.width = self.graphics.width;
self.height = self.graphics.height;
self.update = function () {
if (self.isDead || self.hasExploded || !self.target || self.target.dead) {
if (!self.hasExploded && !self.isDead) {
self.isDead = true;
}
return;
}
self.lifeTimer--;
var dx = self.target.x - self.x;
var dy = self.target.y - self.y;
var distanceToTarget = Math.sqrt(dx * dx + dy * dy);
if (distanceToTarget > 0) {
var moveX = dx / distanceToTarget * self.speed;
var moveY = dy / distanceToTarget * self.speed;
self.x += moveX;
self.y += moveY;
}
if (self.graphics) {
// Dodajemy rotację pocisku
self.graphics.rotation += 0.1; // Dostosuj prędkość obrotu
}
var playerRadius = (self.target.width || 150) / 2 * 0.7;
var projectileRadius = self.width / 2;
if (distanceToTarget < playerRadius + projectileRadius) {
self.explode();
return;
}
if (self.lifeTimer <= 0) {
self.explode();
return;
}
};
self.explode = function () {
if (self.hasExploded) {
return;
}
self.hasExploded = true;
self.isDead = true;
if (self.graphics && self.graphics.parent) {
self.graphics.parent.removeChild(self.graphics);
self.graphics.destroy();
self.graphics = null;
}
var explosionFramesAssets = [];
for (var i = 0; i < 7; i++) {
try {
explosionFramesAssets.push(LK.getAsset('explosion_frame_' + i, {
anchorX: 0.5,
anchorY: 0.5,
clone: true
}));
} catch (e) {
console.error("Błąd ładowania klatki eksplozji: explosion_frame_" + i, e);
var placeholderExplosionFrame = new Shape({
width: 50,
height: 50,
color: 0xFF8C00,
shape: 'ellipse'
});
placeholderExplosionFrame.anchor.set(0.5, 0.5);
explosionFramesAssets.push(placeholderExplosionFrame);
}
}
if (explosionFramesAssets.length < 7) {
// Potrzebujemy wszystkich 7 klatek do tej logiki
console.error("Nie załadowano wystarczającej liczby klatek eksplozji.");
if (gameState && gameState.cursedCrystalActiveExplosions) {
gameState.cursedCrystalActiveExplosions.push({
x: self.x,
y: self.y,
radius: self.explosionRadius,
damage: self.explosionDamage,
durationTimer: 30,
hitPlayerThisFrame: false
});
}
return;
}
var explosionDisplay = game.addChild(new Container());
explosionDisplay.x = self.x;
explosionDisplay.y = self.y;
var currentFrameGfx = null;
var INITIAL_SCALE = 0.2;
var MAX_SCALE = self.explosionRadius * 3.0 / (explosionFramesAssets[0] ? explosionFramesAssets[0].width : 50); // ZMIENIONA LINIA (1.5 -> 3.0)
var EXP_FRAME_DURATION_MS = 70;
var LOOP_FRAME_DURATION_MS = 90;
var SHRINK_FADE_DURATION_MS = 600;
var totalDurationMs = 2000;
var expansionPhaseDuration = 7 * EXP_FRAME_DURATION_MS; // 7 klatek * 70ms = 490ms
var loopPhaseDuration = totalDurationMs - expansionPhaseDuration - SHRINK_FADE_DURATION_MS;
if (loopPhaseDuration < 0) {
// Jeśli nie ma czasu na pętlę, skróć inne fazy lub ustaw min. czas pętli
loopPhaseDuration = Math.max(0, LOOP_FRAME_DURATION_MS * 4); // np. przynajmniej jedna pełna pętla 4 klatek
SHRINK_FADE_DURATION_MS = Math.max(200, totalDurationMs - expansionPhaseDuration - loopPhaseDuration);
}
var displayFrame = function displayFrame(frameAsset, scale, alpha) {
if (currentFrameGfx && currentFrameGfx.parent) {
explosionDisplay.removeChild(currentFrameGfx);
// Nie niszczymy frameAsset, bo to klon z puli LK.getAsset
}
if (frameAsset) {
currentFrameGfx = explosionDisplay.addChild(frameAsset);
currentFrameGfx.scale.set(scale);
currentFrameGfx.alpha = alpha === undefined ? 1 : alpha;
} else {
currentFrameGfx = null;
}
};
var currentExpansionFrame = 0;
function expandAnimation() {
if (currentExpansionFrame < 7) {
var progress = currentExpansionFrame / 6.0;
var scale = INITIAL_SCALE + (MAX_SCALE - INITIAL_SCALE) * progress;
displayFrame(explosionFramesAssets[currentExpansionFrame], Math.max(INITIAL_SCALE, scale));
currentExpansionFrame++;
LK.setTimeout(expandAnimation, EXP_FRAME_DURATION_MS);
} else {
startLoopingPhase();
}
}
var loopFramesIndices = [3, 4, 5, 6]; // Klatki 3, 4, 5, 6 do pętli
var currentLoopArrayIndex = 0;
var loopEndTime;
function startLoopingPhase() {
if (loopPhaseDuration <= 0) {
startShrinkingPhase();
return;
}
loopEndTime = Date.now() + loopPhaseDuration;
currentLoopArrayIndex = 0; // Zacznij od pierwszej klatki pętli (indeks 3 globalnie)
loopAnimation();
}
function loopAnimation() {
if (Date.now() < loopEndTime && explosionDisplay && !explosionDisplay.destroyed) {
displayFrame(explosionFramesAssets[loopFramesIndices[currentLoopArrayIndex % loopFramesIndices.length]], MAX_SCALE);
currentLoopArrayIndex++;
LK.setTimeout(loopAnimation, LOOP_FRAME_DURATION_MS);
} else {
startShrinkingPhase();
}
}
function startShrinkingPhase() {
if (!currentFrameGfx || !currentFrameGfx.parent || explosionDisplay && explosionDisplay.destroyed) {
if (explosionDisplay && !explosionDisplay.destroyed) {
explosionDisplay.destroy();
}
return;
}
// Aby zmniejszać ostatnio wyświetloną klatkę (lub konkretną np. klatkę 6 jako bazę)
// Dla pewności, że mamy co skalować, możemy ponownie wyświetlić ostatnią klatkę pętli (np. klatkę 6)
// jeśli currentFrameGfx mógł zostać usunięty lub jest nieoczekiwany.
// Jeśli currentFrameGfx jest już ostatnią klatką pętli, to dobrze.
// Jeśli chcemy zawsze zmniejszać np. klatkę nr 6:
// displayFrame(explosionFramesAssets[6], MAX_SCALE);
tween(currentFrameGfx, {
scaleX: 0.01,
scaleY: 0.01,
alpha: 0
}, {
duration: SHRINK_FADE_DURATION_MS,
easing: tween.easeInQuad,
onFinish: function onFinish() {
if (explosionDisplay && !explosionDisplay.destroyed) {
explosionDisplay.destroy();
}
}
});
}
expandAnimation(); // Rozpocznij pierwszą fazę
if (gameState && gameState.cursedCrystalActiveExplosions) {
gameState.cursedCrystalActiveExplosions.push({
x: self.x,
y: self.y,
radius: self.explosionRadius,
damage: self.explosionDamage,
durationTimer: Math.round(totalDurationMs / (1000 / 60)),
// Czas trwania logicznej eksplozji = ~2 sekundy
hitPlayerThisFrame: false
});
}
};
return self;
});
// Zamykająca klamra dla Container.expand klasy Boss
// --- POCZĄTEK BLOKU DO SKOPIOWANIA ---
var Player = Container.expand(function () {
var self = Container.call(this);
if (gameState.currentState === "cursedCrystal") {
console.log("Player Update - Cursed Crystal Tick, Rolling: " + this.rolling + ", Cooldown: " + this.rollCooldown);
} else if (gameState.currentState === "game") {
console.log("Player Update - Game Tick, Rolling: " + this.rolling + ", Cooldown: " + this.rollCooldown);
}
// --- Animacja Idle ---
var idleFrames = [LK.getAsset('player', {
anchorX: 0.5,
anchorY: 0.5
}), LK.getAsset('player1', {
anchorX: 0.5,
anchorY: 0.5
}), LK.getAsset('player2', {
anchorX: 0.5,
anchorY: 0.5
})];
self.idleAnimationSprite = new SpriteAnimation({
frames: idleFrames,
frameDuration: 400,
loop: true,
anchorX: 0.5,
anchorY: 0.5,
x: 0,
y: 0
});
self.addChild(self.idleAnimationSprite);
self.idleAnimationSprite.play();
// --- Właściwości Gracza ---
self.health = 5;
self.speed = 8;
self.rolling = false;
self.rollDirection = {
x: 0,
y: 0
};
self.rollSpeed = 20;
self.rollDuration = 300;
self.rollCooldown = 0;
self.invulnerable = false;
self.invulnerabilityFrames = 0;
self.defaultInvulnerabilityFrames = 30;
self.postHitInvulnerabilityTimer = null;
self.dead = false;
self.rollTimeoutId = null;
self.invulnerabilityTimeoutId = null;
self.rollAnimationInterval = null;
self.hasRolledThroughBossThisRoll = false;
// --- Funkcje Pomocnicze ---
// *** TO JEST DEFINICJA FUNKCJI, KTÓREJ BRAKUJE ***
self.clearRollTimeouts = function () {
if (self.rollTimeoutId) {
LK.clearTimeout(self.rollTimeoutId);
self.rollTimeoutId = null;
}
if (self.rollAnimationInterval) {
LK.clearInterval(self.rollAnimationInterval);
self.rollAnimationInterval = null;
}
if (self.postHitInvulnerabilityTimer) {
LK.clearTimeout(self.postHitInvulnerabilityTimer);
self.postHitInvulnerabilityTimer = null;
}
};
// *** KONIEC DEFINICJI clearRollTimeouts ***
// --- Mechanika Uniku (Roll) ---
self.roll = function (direction, duration) {
if (!self.rolling && self.rollCooldown <= 0 && !self.dead) {
var targetScaleX = direction.x >= 0 || direction.x === 0 ? 1 : -1;
var currentRollDuration = duration || self.rollDuration;
self.rolling = true;
self.hasRolledThroughMinibossCCThisRoll = false;
self.rollDirection = direction;
self.rollCooldown = 45;
var durationRatio = currentRollDuration / self.rollDuration;
self.invulnerabilityFrames = Math.round(self.defaultInvulnerabilityFrames * durationRatio);
self.hasRolledThroughBossThisRoll = false;
if (self.idleAnimationSprite && !self.idleAnimationSprite.destroyed) {
self.idleAnimationSprite.stop();
self.idleAnimationSprite.visible = false;
}
var rollFrames = ['roll', 'roll0', 'roll1', 'roll2'];
var currentFrame = 0;
if (self.rollAnimationInterval) {
LK.clearInterval(self.rollAnimationInterval);
}
var rollAnimationSprite = null;
if (self && !self.destroyed) {
rollAnimationSprite = self.addChild(LK.getAsset(rollFrames[currentFrame], {
anchorX: 0.5,
anchorY: 0.5,
x: 0,
y: 0,
scaleX: targetScaleX
}));
}
self.rollAnimationInterval = LK.setInterval(function () {
if (!self || self.destroyed) {
LK.clearInterval(self.rollAnimationInterval);
self.rollAnimationInterval = null;
return;
}
if (rollAnimationSprite && rollAnimationSprite.destroy) {
rollAnimationSprite.destroy();
rollAnimationSprite = null;
}
currentFrame = (currentFrame + 1) % rollFrames.length;
if (self && !self.destroyed) {
rollAnimationSprite = self.addChild(LK.getAsset(rollFrames[currentFrame], {
anchorX: 0.5,
anchorY: 0.5,
x: 0,
y: 0,
scaleX: targetScaleX
}));
}
}, 70);
if (self.rollTimeoutId) {
LK.clearTimeout(self.rollTimeoutId);
}
self.rollTimeoutId = LK.setTimeout(function () {
if (!self || self.destroyed) {
return;
}
self.rolling = false;
if (self.rollAnimationInterval) {
LK.clearInterval(self.rollAnimationInterval);
self.rollAnimationInterval = null;
}
if (rollAnimationSprite && rollAnimationSprite.destroy) {
rollAnimationSprite.destroy();
rollAnimationSprite = null;
}
var standUpFrames = ['roll3', 'roll4'];
var standUpFrame = 0;
var standUpSprite = null;
if (self && !self.destroyed) {
standUpSprite = self.addChild(LK.getAsset(standUpFrames[standUpFrame], {
anchorX: 0.5,
anchorY: 0.5,
x: 0,
y: 0,
scaleX: targetScaleX
}));
}
var standUpInterval = LK.setInterval(function () {
if (!self || self.destroyed) {
LK.clearInterval(standUpInterval);
return;
}
if (standUpSprite && standUpSprite.destroy) {
standUpSprite.destroy();
standUpSprite = null;
}
standUpFrame++;
if (standUpFrame < standUpFrames.length) {
if (self && !self.destroyed) {
standUpSprite = self.addChild(LK.getAsset(standUpFrames[standUpFrame], {
anchorX: 0.5,
anchorY: 0.5,
x: 0,
y: 0,
scaleX: targetScaleX
}));
}
} else {
LK.clearInterval(standUpInterval);
standUpInterval = null;
if (standUpSprite && standUpSprite.destroy) {
standUpSprite.destroy();
standUpSprite = null;
}
if (self.idleAnimationSprite && !self.idleAnimationSprite.destroyed) {
self.idleAnimationSprite.scaleX = targetScaleX;
self.idleAnimationSprite.visible = true;
self.idleAnimationSprite.play();
}
}
}, 100);
self.rollTimeoutId = null;
}, currentRollDuration);
}
};
// --- Otrzymywanie Obrażeń ---
self.takeDamage = function (amount) {
var timestamp = Date.now();
var currentHealthBeforeDamage = this.health;
console.log("[Player.takeDamage] Called at: " + timestamp + " | Amount: " + amount + " | Invulnerable: " + this.invulnerable + " | Dead: " + this.dead + " | Health BEFORE: " + currentHealthBeforeDamage + " | Current State: " + gameState.currentState);
if (!this.invulnerable && !this.dead) {
if (gameState.currentState === "cursedCrystal") {
gameState.cursedCrystalPlayerHealth -= amount;
gameState.cursedCrystalPlayerHealth = Math.max(0, gameState.cursedCrystalPlayerHealth);
this.health = gameState.cursedCrystalPlayerHealth;
console.log(" [CursedCrystal DMG] New Health (gameState & this.health): " + this.health + " at " + timestamp);
if (ui && ui.updateHearts) {
ui.updateHearts(gameState.cursedCrystalPlayerHealth, gameState.cursedCrystalPlayerMaxHealth);
}
} else {
this.health -= amount;
this.health = Math.max(0, this.health);
console.log(" [DMG in " + gameState.currentState + "] New Health (this.health): " + this.health + " at " + timestamp);
}
if (this.health < currentHealthBeforeDamage) {
LK.effects.flashObject(this, 0xFF0000, 200);
}
if (this.health <= 0) {
console.log(" Player health is <= 0 at " + timestamp + ". Calling this.die().");
this.die();
return;
}
this.invulnerable = true;
console.log(" Player.invulnerable SET TO TRUE at " + timestamp + ". IMMEDIATELY CHECKING: player.invulnerable is " + this.invulnerable);
if (this.postHitInvulnerabilityTimer) {
LK.clearTimeout(this.postHitInvulnerabilityTimer);
this.postHitInvulnerabilityTimer = null;
}
var playerInstance = this;
this.postHitInvulnerabilityTimer = LK.setTimeout(function () {
var endInvulnerableTimestamp = Date.now();
if (!playerInstance || playerInstance.destroyed) {
console.log(" Player invulnerability timeout: player destroyed or null at " + endInvulnerableTimestamp);
return;
}
playerInstance.invulnerable = false;
console.log(" Player.invulnerable SET TO FALSE at " + endInvulnerableTimestamp + " (after 2000ms)");
var currentVisualSprite = null;
if (playerInstance.rolling && playerInstance.children.length > 0 && playerInstance.children[0] !== playerInstance.idleAnimationSprite) {} else if (playerInstance.idleAnimationSprite && playerInstance.idleAnimationSprite.visible && playerInstance.idleAnimationSprite.children.length > 0) {
currentVisualSprite = playerInstance.idleAnimationSprite.children[0];
}
if (currentVisualSprite && currentVisualSprite.alpha !== 1) {
currentVisualSprite.alpha = 1;
}
playerInstance.postHitInvulnerabilityTimer = null;
}, 2000);
} else {
console.log("[Player.takeDamage] SKIPPED at: " + timestamp + " (Invulnerable: " + this.invulnerable + ", Dead: " + this.dead + ")");
}
};
// --- Śmierć (z poprawkami dla storage i onFinish) ---
self.die = function () {
console.log("!!!! Player.die() called! State:", gameState.currentState, "Current Health:", self.health);
if (self.dead) {
console.log("Player.die() exited - already dead.");
return;
}
self.dead = true;
try {
self.clearRollTimeouts();
} catch (e) {
console.error("!!! Błąd podczas wywoływania self.clearRollTimeouts w Player.die:", e);
}
if (gameState.currentState === "cursedCrystal") {
console.log("Player died in Cursed Crystal mode. Calling gameState.endCursedCrystalMode(false) via tween.");
tween(self, {
alpha: 0,
scaleX: self.scaleX * 1.2,
scaleY: self.scaleY * 1.2
}, {
duration: 1000,
easing: tween.easeOut,
onFinish: function onFinish() {
console.log("Player.die tween (Cursed Crystal) onFinish.");
if (typeof gameState.endCursedCrystalMode === 'function') {
gameState.endCursedCrystalMode(false);
} else {
console.error("gameState.endCursedCrystalMode is not defined!");
if (typeof gameState.showGrillScreen === 'function') {
gameState.showGrillScreen();
}
}
}
});
return;
}
var currentDeaths = parseInt(storage.totalDeaths, 10) || 0;
storage.totalDeaths = currentDeaths + 1;
console.log("Updated totalDeaths to:", storage.totalDeaths);
var currentMaxHearts = parseInt(storage.maxHearts, 10);
if (isNaN(currentMaxHearts) || currentMaxHearts < 5) {
storage.maxHearts = 5;
}
if (storage.maxHearts < 10) {
storage.maxHearts = (parseInt(storage.maxHearts, 10) || 5) + 1;
}
console.log("Updated maxHearts to:", storage.maxHearts);
tween(self, {
alpha: 0,
scaleX: self.scaleX * 1.2,
scaleY: self.scaleY * 1.2
}, {
duration: 1000,
easing: tween.easeOut,
onFinish: function onFinish() {
console.log("Player.die tween (Non-CC) onFinish. gameState.currentState:", gameState.currentState);
if (gameState.currentState === "game") {
console.log("Player.die (game mode) onFinish - Calling gameState.gameOver(true).");
if (typeof boss !== 'undefined' && boss && typeof boss.clearAllAttacks === 'function') {
boss.clearAllAttacks("Player.die -> tutorial gameOver");
}
if (typeof gameState.gameOver === 'function') {
gameOverReasonIsDeath = true;
gameState.gameOver(true);
} else {
console.error("gameState.gameOver() is not defined for 'game' mode death!");
if (typeof gameState.showGrillScreen === 'function') {
gameState.showGrillScreen();
}
}
} else if (gameState.currentState === "rollMaster" || gameState.currentState === "rollMasterGameOver") {
console.log("Player.die (RollMaster or rollMasterGameOver state) onFinish. No further action needed from Player.die, endRollMasterMode handles the rest.");
} else if (gameState.currentState === "gameOver") {
console.log("Player.die onFinish while gameState.currentState is already 'gameOver'. No further action needed from Player.die.");
}
}
});
};
// Koniec self.die
// --- Aktualizacja (Update) ---
self.update = function () {
if (gameState.currentState === "cursedCrystal") {
//console.log("Player Update Tick - CC - Health: " + self.health + ", RollCD: " + self.rollCooldown + ", Rolling: " + self.rolling + ", Invuln: " + self.invulnerable + ", InvulnFrames: " + self.invulnerabilityFrames);
}
if (gameState.currentState !== "game" && gameState.currentState !== "rollMaster" && gameState.currentState !== "cursedCrystal") {
if (self.rolling) {
self.rolling = false;
self.invulnerabilityFrames = 0;
if (self.idleAnimationSprite && !self.idleAnimationSprite.destroyed) {
self.idleAnimationSprite.visible = true;
self.idleAnimationSprite.play();
if (self.children.length > 1) {
for (var i = self.children.length - 1; i >= 0; i--) {
if (self.children[i] !== self.idleAnimationSprite && self.children[i].destroy) {
self.children[i].destroy();
}
}
}
}
}
self.clearRollTimeouts();
return;
}
if (self.dead) {
return;
}
if (self.rollCooldown > 0) {
self.rollCooldown--;
}
var currentVisualSprite = null;
if (self.rolling) {
for (var i = 0; i < self.children.length; i++) {
if (self.children[i] !== self.idleAnimationSprite) {
currentVisualSprite = self.children[i];
break;
}
}
} else if (self.idleAnimationSprite && self.idleAnimationSprite.visible && self.idleAnimationSprite.children.length > 0) {
currentVisualSprite = self.idleAnimationSprite.children[0];
}
if (self.rolling) {
if (self.invulnerabilityFrames > 0) {
self.invulnerabilityFrames--;
if (currentVisualSprite) {
currentVisualSprite.alpha = self.invulnerabilityFrames % 10 < 5 ? 0.3 : 1;
}
} else {
if (currentVisualSprite) {
currentVisualSprite.alpha = 1;
}
}
var rollDx = self.rollDirection.x * self.rollSpeed;
var rollDy = self.rollDirection.y * self.rollSpeed;
var nextX = self.x + rollDx;
var nextY = self.y + rollDy;
var pWidth = currentVisualSprite ? currentVisualSprite.width * Math.abs(currentVisualSprite.scaleX || 1) : self.width;
var pHeight = currentVisualSprite ? currentVisualSprite.height * (currentVisualSprite.scaleY || 1) : self.height;
var halfWidth = pWidth / 2;
var halfHeight = pHeight / 2;
var minXBoundary = halfWidth;
var maxXBoundary = 2048 - halfWidth;
var minYBoundary = halfHeight;
var maxYBoundary = 2732 - halfHeight;
nextX = Math.max(minXBoundary, Math.min(nextX, maxXBoundary));
nextY = Math.max(minYBoundary, Math.min(nextY, maxYBoundary));
if (gameState.currentState === "cursedCrystal" && typeof crystalCoreObject !== 'undefined' && crystalCoreObject && !crystalCoreObject.destroyed) {
var crystalCX = crystalCoreObject.x;
var crystalCY = crystalCoreObject.y;
var crystalRadius = crystalCoreObject.collisionRadius || 50;
var distToCrystalHorizontal = Math.abs(nextX - crystalCX);
var distToCrystalVertical = Math.abs(nextY - crystalCY);
if (distToCrystalHorizontal < halfWidth + crystalRadius && distToCrystalVertical < halfHeight + crystalRadius) {
if (self.x < crystalCX && nextX > self.x) {
nextX = crystalCX - crystalRadius - halfWidth - 1;
} else if (self.x > crystalCX && nextX < self.x) {
nextX = crystalCX + crystalRadius + halfWidth + 1;
}
if (self.y < crystalCY && nextY > self.y) {
nextY = crystalCY - crystalRadius - halfHeight - 1;
} else if (self.y > crystalCY && nextY < self.y) {
nextY = crystalCY + crystalRadius + halfHeight + 1;
}
}
}
self.x = nextX;
self.y = nextY;
if (gameState.currentState === "game" && boss && !boss.dead && !self.hasRolledThroughBossThisRoll) {
var dx_b = self.x - boss.x;
var dy_b = self.y - boss.y;
var distance_b = Math.sqrt(dx_b * dx_b + dy_b * dy_b);
var playerCollisionRadius = pWidth / 2 * 0.8;
var hitboxMultiplier = 1.2;
var bossCollisionRadius = (boss.width || 180) * (boss.scaleX || 1) / 2 * hitboxMultiplier;
if (distance_b < playerCollisionRadius + bossCollisionRadius) {
boss.takeDamage(10);
self.hasRolledThroughBossThisRoll = true;
LK.effects.flashObject(boss, 0x00FF00, 200);
}
}
if (gameState.currentState === "cursedCrystal" && gameState.isMinibossActiveCC === true && gameState.cursedCrystalMinibossObject && !gameState.cursedCrystalMinibossObject.isDead && !self.hasRolledThroughMinibossCCThisRoll) {
var minibossCC = gameState.cursedCrystalMinibossObject;
var dx_mcc = self.x - minibossCC.x;
var dy_mcc = self.y - minibossCC.y;
var distance_mcc = Math.sqrt(dx_mcc * dx_mcc + dy_mcc * dy_mcc);
var playerRollRadius = pWidth / 2 * 0.8;
var minibossCCRadius = (minibossCC.width || 120) / 2 * 0.9;
if (distance_mcc < playerRollRadius + minibossCCRadius) {
minibossCC.takeDamage(10);
self.hasRolledThroughMinibossCCThisRoll = true;
console.log("Player rolled through Miniboss CC and dealt damage!");
}
}
} else if (self.invulnerable && !self.rolling) {
if (currentVisualSprite) {
currentVisualSprite.alpha = Math.floor(Date.now() / 100) % 4 > 1 ? 0.3 : 1;
}
} else {
if (currentVisualSprite && currentVisualSprite.alpha !== 1) {
currentVisualSprite.alpha = 1;
}
if (gameState.isInputActive && (gameState.currentState === "game" || gameState.currentState === "rollMaster" || gameState.currentState === "cursedCrystal") && player && !player.dead) {
var targetX = gameState.currentInputPos.x;
var targetY = gameState.currentInputPos.y;
var dx_m = targetX - self.x;
var dy_m = targetY - self.y;
var distance_m = Math.sqrt(dx_m * dx_m + dy_m * dy_m);
var moveSpeed = 3;
if (distance_m > moveSpeed) {
var normalizedX = dx_m / distance_m;
var normalizedY = dy_m / distance_m;
var nextPlayerX = self.x + normalizedX * moveSpeed;
var nextPlayerY = self.y + normalizedY * moveSpeed;
var pWidthIdle = self.idleAnimationSprite.children[0] ? self.idleAnimationSprite.children[0].width * (self.idleAnimationSprite.scaleX || 1) : self.width;
var pHeightIdle = self.idleAnimationSprite.children[0] ? self.idleAnimationSprite.children[0].height * (self.idleAnimationSprite.scaleY || 1) : self.height;
var halfWidthIdle = pWidthIdle / 2;
var halfHeightIdle = pHeightIdle / 2;
var minXBoundaryIdle = halfWidthIdle;
var maxXBoundaryIdle = 2048 - halfWidthIdle;
var minYBoundaryIdle = halfHeightIdle;
var maxYBoundaryIdle = 2732 - halfHeightIdle;
nextPlayerX = Math.max(minXBoundaryIdle, Math.min(nextPlayerX, maxXBoundaryIdle));
nextPlayerY = Math.max(minYBoundaryIdle, Math.min(nextPlayerY, maxYBoundaryIdle));
if (gameState.currentState === "cursedCrystal" && typeof crystalCoreObject !== 'undefined' && crystalCoreObject && !crystalCoreObject.destroyed) {
var crystalCXIdle = crystalCoreObject.x;
var crystalCYIdle = crystalCoreObject.y;
var crystalRadiusIdle = crystalCoreObject.collisionRadius || 50;
var distToCrystalHorizontalIdle = Math.abs(nextPlayerX - crystalCXIdle);
var distToCrystalVerticalIdle = Math.abs(nextPlayerY - crystalCYIdle);
if (distToCrystalHorizontalIdle < halfWidthIdle + crystalRadiusIdle && distToCrystalVerticalIdle < halfHeightIdle + crystalRadiusIdle) {} else {
self.x = nextPlayerX;
self.y = nextPlayerY;
}
} else {
self.x = nextPlayerX;
self.y = nextPlayerY;
}
if (self.idleAnimationSprite && !self.idleAnimationSprite.destroyed) {
if (normalizedX > 0.1) {
self.idleAnimationSprite.scaleX = 1;
} else if (normalizedX < -0.1) {
self.idleAnimationSprite.scaleX = -1;
}
}
}
}
}
if (self.idleAnimationSprite && self.idleAnimationSprite.visible && !self.idleAnimationSprite.destroyed && typeof self.idleAnimationSprite.update === 'function') {
if (gameState.currentState === "cursedCrystal") {}
self.idleAnimationSprite.update();
}
}; // Ten nawias klamrowy zamyka funkcję self.update = function () { ... };
return self;
});
// Koniec Player
// --- KONIEC BLOKU DO SKOPIOWANIA ---
// Koniec Player
// <--- To zamyka Container.expand dla gracza
var Shape = Container.expand(function (options) {
var self = Container.call(this);
// <--- Tutaj zaczyna się Shape
// ...
options = options || {};
var width = options.width || 100;
var height = options.height || 100;
var color = options.color || 0xFFFFFF;
var shape = options.shape || 'box';
// Create the shape as an asset
var asset = self.attachAsset(shape, {
anchorX: 0.5,
anchorY: 0.5,
width: width,
height: height,
tint: color
});
// Set width and height for easier access
self.width = width;
self.height = height;
// Add anchor property to Shape for positioning
self.anchor = {
set: function set(x, y) {
// This mimics the behavior of the anchor.set method
self.anchorX = x;
self.anchorY = y;
}
};
return self;
});
var SoulEnemy = Container.expand(function (options) {
var self = Container.call(this);
options = options || {};
self.hp = options.hp || 1;
self.speed = options.speed || 2;
self.isDead = false;
self.targetX = options.targetX || 2048 / 2;
self.targetY = options.targetY || 2732 / 2;
var animFrames = [];
for (var i = 0; i < 6; i++) {
// 6 klatek animacji
try {
animFrames.push(LK.getAsset('soul_anim_' + i, {
anchorX: 0.5,
anchorY: 0.5,
clone: true
}));
} catch (e) {
console.error("Błąd ładowania klatki animacji duszy: soul_anim_" + i, e);
// Awaryjny placeholder, jeśli klatka się nie załaduje
var placeholderFrame = new Shape({
width: options.width || 30,
height: options.height || 30,
color: 0xFF00FF,
shape: 'ellipse'
});
placeholderFrame.anchor.set(0.5, 0.5);
animFrames.push(placeholderFrame);
}
}
if (animFrames.length > 0) {
self.animation = new SpriteAnimation({
frames: animFrames,
frameDuration: 120,
// Czas trwania klatki w ms - dostosuj
loop: true,
anchorX: 0.5,
anchorY: 0.5
});
self.addChild(self.animation);
self.animation.play();
} else {
// Jeśli nie udało się załadować żadnej klatki, stwórz awaryjny kształt
console.error("Nie udało się załadować żadnej klatki dla SoulEnemy. Tworzenie awaryjnego kształtu.");
self.fallbackGraphics = new Shape({
width: options.width || 30,
height: options.height || 30,
color: 0xADD8E6,
shape: 'ellipse'
});
self.fallbackGraphics.anchor.set(0.5, 0.5);
self.addChild(self.fallbackGraphics);
}
// Ustawienie self.width i self.height na podstawie pierwszej klatki animacji lub opcji
if (animFrames.length > 0 && animFrames[0] && typeof animFrames[0].width !== 'undefined') {
self.width = animFrames[0].width;
self.height = animFrames[0].height;
} else {
self.width = options.width || 30;
self.height = options.height || 30;
}
self.update = function () {
if (self.isDead || gameState.currentState !== "cursedCrystal") {
return;
}
var currentTargetX = self.targetX;
var currentTargetY = self.targetY;
if (typeof crystalCoreObject !== 'undefined' && crystalCoreObject && !crystalCoreObject.destroyed) {
currentTargetX = crystalCoreObject.x;
currentTargetY = crystalCoreObject.y;
}
var dx = currentTargetX - self.x;
var dy = currentTargetY - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (self.animation) {
// Obracanie animacji w kierunku ruchu
var angle = Math.atan2(dy, dx);
self.animation.rotation = angle;
// Jeśli chcesz, aby sprite "patrzył" w prawo, gdy kąt jest 0, możesz dodać:
// self.animation.rotation = angle + Math.PI / 2; // Jeśli sprite jest domyślnie skierowany w górę
} else if (self.fallbackGraphics) {
// Obracanie awaryjnej grafiki
var angle = Math.atan2(dy, dx);
self.fallbackGraphics.rotation = angle;
}
if (distance > self.speed) {
var moveX = dx / distance * self.speed;
var moveY = dy / distance * self.speed;
self.x += moveX;
self.y += moveY;
} else {
self.x = currentTargetX;
self.y = currentTargetY;
if (typeof crystalCoreObject !== 'undefined' && crystalCoreObject && !crystalCoreObject.destroyed && self.x === crystalCoreObject.x && self.y === crystalCoreObject.y) {
self.onHitCrystal();
} else if (!crystalCoreObject || crystalCoreObject.destroyed) {
self.isDead = true;
if (self.parent) {
self.parent.removeChild(self);
}
self.destroy();
}
}
// SpriteAnimation ma własną metodę update, która jest wywoływana automatycznie, jeśli jest dzieckiem
// Nie ma potrzeby wywoływania self.animation.update() ręcznie tutaj.
};
self.takeDamage = function (amount) {
if (self.isDead) {
return;
}
self.hp -= amount;
// Efekt flash na całym obiekcie SoulEnemy (który zawiera animację)
LK.effects.flashObject(self, 0xFFFFFF, 150);
if (self.hp <= 0) {
self.die();
}
};
self.die = function () {
if (self.isDead) {
return;
}
self.isDead = true;
gameState.cursedCrystalScore += 10;
if (ui && ui.updateScoreCC) {
ui.updateScoreCC(gameState.cursedCrystalScore);
}
if (self.parent) {
self.parent.removeChild(self);
}
self.destroy(); // To powinno zniszczyć również animację jako dziecko
};
self.onHitCrystal = function () {
if (self.isDead) {
return;
}
self.isDead = true; // Oznacz jako martwą, aby zatrzymać inne logiki (np. update)
gameState.cursedCrystalSoulsHitCrystal++;
gameState.cursedCrystalChargeLevel = Math.min(gameState.cursedCrystalTargetCharge, gameState.cursedCrystalChargeLevel + 3);
if (ui && ui.updateCrystalCharge) {
ui.updateCrystalCharge(gameState.cursedCrystalChargeLevel, gameState.cursedCrystalTargetCharge);
}
if (typeof gameState.updateCrystalVisual === 'function') {
gameState.updateCrystalVisual();
}
var FADE_OUT_DURATION = 300; // Czas trwania fade out w ms - dostosuj!
// Zatrzymaj animację SpriteAnimation, jeśli istnieje i gra
if (self.animation && typeof self.animation.stop === 'function') {
self.animation.stop();
}
tween(self, {
alpha: 0
}, {
duration: FADE_OUT_DURATION,
easing: tween.easeOutQuad,
// Możesz wybrać inną funkcję easing
onFinish: function onFinish() {
if (self.parent) {
self.parent.removeChild(self);
}
if (self.destroy && !self.destroyed) {
// Dodatkowe sprawdzenie !self.destroyed
self.destroy();
}
}
});
};
self.x = options.x || 0;
self.y = options.y || 0;
return self;
});
var SpriteAnimation = Container.expand(function (options) {
var self = Container.call(this);
// Initialize with default values
options = options || {};
self.frames = options.frames || [];
self.frameDuration = options.frameDuration || 120; // Default 100ms per frame
self.loop = options.loop !== undefined ? options.loop : true;
self.currentFrame = 0;
self.frameTimer = 0;
self.playing = true;
// Dodaj tę linię do konstruktora, aby SpriteAnimation pamiętało własne alpha
// i mogło przekazywać je do klatek.
self.alpha = options.alpha !== undefined ? options.alpha : 1;
// Set initial position and anchor if provided
if (options.x !== undefined) {
self.x = options.x;
}
if (options.y !== undefined) {
self.y = options.y;
}
// Handle anchor options
if (options.anchorX !== undefined || options.anchorY !== undefined) {
var anchorX = options.anchorX !== undefined ? options.anchorX : 0;
var anchorY = options.anchorY !== undefined ? options.anchorY : 0;
// Apply anchor to all frames
self.frames.forEach(function (frame) {
if (frame && frame.anchor) {
frame.anchor.set(anchorX, anchorY);
}
});
}
// Add the first frame to display initially
if (self.frames.length > 0) {
self.removeChildren();
var firstFrame = self.frames[self.currentFrame];
if (firstFrame && firstFrame.anchor) {
firstFrame.anchor.set(options.anchorX || 0, options.anchorY || 0);
}
// TUTAJ JEST KLUCZOWA ZMIANA W KONSTRUKTORZE:
// Ustaw alpha dla pierwszej klatki na podstawie alpha kontenera SpriteAnimation.
if (firstFrame) {
firstFrame.alpha = self.alpha;
}
self.addChild(firstFrame);
}
// Animation update method
self.update = function () {
if (!self.playing || self.frames.length === 0) {
return;
}
self.frameTimer++;
if (self.frameTimer >= self.frameDuration / (1000 / 60)) {
self.frameTimer = 0;
if (self.children.length > 0) {
self.removeChild(self.children[0]);
}
self.currentFrame++;
if (self.currentFrame >= self.frames.length) {
if (self.loop) {
self.currentFrame = 0;
// TUTAJ JEST KLUCZOWA ZMIANA W UPDATE DLA PĘTLI:
var nextFrame = self.frames[self.currentFrame];
if (nextFrame) {
nextFrame.alpha = self.alpha; // Zastosuj alpha kontenera
}
self.addChild(nextFrame);
} else {
self.playing = false;
if (typeof self.onComplete === 'function') {
self.onComplete();
}
return;
}
} else {
// Dodajemy nową klatkę tylko wtedy, gdy animacja jeszcze trwa
// TUTAJ JEST KLUCZOWA ZMIANA W UPDATE DLA DALSZYCH KLATEK:
var nextFrame = self.frames[self.currentFrame];
if (nextFrame) {
nextFrame.alpha = self.alpha; // Zastosuj alpha kontenera
}
self.addChild(nextFrame);
}
}
};
// Method to stop animation
self.stop = function () {
self.playing = false;
};
// Method to start/resume animation
self.play = function () {
self.playing = true;
};
// Method to set a specific frame
self.gotoFrame = function (frameIndex) {
if (frameIndex >= 0 && frameIndex < self.frames.length) {
self.removeChildren();
self.currentFrame = frameIndex;
self.addChild(self.frames[self.currentFrame]);
}
};
return self;
});
var UI = Container.expand(function () {
var self = Container.call(this);
// Create heart containers (wizualizacja zdrowia)
self.hearts = [];
self.heartContainer = new Container(); // Kontener na ikony serc
self.addChild(self.heartContainer);
// Title and messages (teksty na ekranach)
self.titleText = new Text2("ROLL SOULS", {
size: 150,
fill: 0xFFFFFF
});
self.titleText.anchor.set(0.5, 0.5);
self.addChild(self.titleText);
self.messageText = new Text2("", {
size: 60,
fill: 0xFFFFFF
});
self.messageText.anchor.set(0.5, 0.5);
self.addChild(self.messageText);
// Deaths counter (licznik śmierci)
self.deathsText = new Text2("Deaths: 0", {
size: 40,
fill: 0xFFFFFF
});
self.deathsText.anchor.set(1, 0); // Wyrównanie do prawego górnego rogu
self.deathsText.x = 2048 - 50; // Pozycja X od prawej krawędzi
self.deathsText.y = 50; // Pozycja Y od góry
self.addChild(self.deathsText);
// Tutorial text (teksty tutoriali)
self.tutorialText = new Text2("", {
size: 50,
fill: 0xFFFFFF
});
self.tutorialText.anchor.set(0.5, 0); // Wyrównanie do środka na górze
self.tutorialText.x = 2048 / 2; // Pozycja X na środku
self.tutorialText.y = 200; // Pozycja Y (może być różna w zależności od stanu)
self.addChild(self.tutorialText);
// --- Timer Text ---
self.timerText = new Text2("00:00", {
// Zmieniono tekst początkowy
size: 60,
fill: 0xFFFFFF // Biały kolor
});
self.timerText.x = 2048 / 2; // Wyśrodkuj w poziomie
self.timerText.y = 50; // Przysuń do góry
self.timerText.anchor.set(0.5, 0); // Pozycja Y od górnej krawędzi
self.timerText.alpha = 0; // Domyślnie ukryty
self.addChild(self.timerText);
// --- DODANY NOWY ELEMENT TEKSTOWY DLA REKORDU ---
self.highScoreText = new Text2("Best: 00:00", {
// Tekst początkowy
size: 50,
// Mniejszy rozmiar niż aktualny czas
fill: 0xFFFF00 // Żółty kolor dla odróżnienia
});
self.highScoreText.x = 2048 - 50; // Przysuń do prawej krawędzi (z marginesem 50)
self.highScoreText.y = 50; // Przysuń do góry (ta sama wysokość co timer)
self.highScoreText.anchor.set(1, 0); // Pozycja Y (poniżej timerText)
self.highScoreText.alpha = 0; // Domyślnie ukryty
self.addChild(self.highScoreText); // Dodaj do kontenera UI
// --- KONIEC DODAWANIA NOWEGO ELEMENTU ---
// --- Boss Health Bar (wizualizacja zdrowia bossa) ---
self.bossHealthBarContainer = new Container(); // Kontener na pasek zdrowia bossa
self.bossHealthBarContainer.x = 2048 / 2; // Wyśrodkuj poziomo
self.bossHealthBarContainer.y = 150; // Pozycja Y (poniżej timera)
self.bossHealthBarContainer.alpha = 0; // Domyślnie ukryty
self.addChild(self.bossHealthBarContainer);
var barWidth = 800; // Szerokość paska zdrowia (musi być taka sama jak szerokość assetu bossHpbar)
var barHeight = 30; // Wysokość paska zdrowia (musi być taka sama jak wysokość assetu bossHpbar)
// Właściwy pasek zdrowia bossa (czerwony) - UŻYWAMY TERAZ NOWEGO ASSETU bossHpbar
self.bossHealthBar = self.attachAsset('bossHpbar', {
// Użyj assetu 'bossHpbar'
anchorX: 0,
// Ustaw punkt odniesienia na lewą krawędź (0), środek pionowo (0.5)
anchorY: 0.5,
x: -barWidth / 2 // Przesuń w lewo o połowę szerokości tła
});
self.bossHealthBarContainer.addChild(self.bossHealthBar);
// --- METODY AKTUALIZACJI UI ---
// Aktualizuje wizualizację serc na UI
self.updateHearts = function (current, max) {
current = Math.max(0, current || 0);
max = Math.max(1, max || 1); // Max musi być co najmniej 1
if (self.hearts.length !== max) {
while (self.hearts.length > 0) {
var oldHeart = self.hearts.pop();
if (oldHeart && oldHeart.destroy && !oldHeart.destroyed) {
oldHeart.destroy();
}
}
self.heartContainer.removeChildren(); // Usuń wizualne obiekty serc
for (var i = 0; i < max; i++) {
var heart = LK.getAsset('heart', {
anchorX: 0.5,
anchorY: 0.5,
x: i * 50,
y: 0,
tint: i < current ? 0xFF0000 : 0x555555
});
self.hearts.push(heart);
self.heartContainer.addChild(heart);
}
self.heartContainer.x = (2048 - max * 50) / 2 + 25;
self.heartContainer.y = 100; // Zmieniono pozycję serc, aby zrobić miejsce
} else {
for (var j = 0; j < self.hearts.length; j++) {
if (self.hearts[j]) {
self.hearts[j].tint = j < current ? 0xFF0000 : 0x555555;
}
}
}
};
// Aktualizuje wizualizację paska zdrowia bossa
self.updateBossHealth = function (current, max) {
current = Math.max(0, current || 0);
max = Math.max(1, max || 1);
var barWidth = 800;
var currentWidth = current / max * barWidth;
if (self.bossHealthBar) {
self.bossHealthBar.width = currentWidth;
var shouldBeVisible = (gameState.currentState === "game" || gameState.currentState === "gameOver") && (boss && !boss.dead && current > 0 || gameState.currentState === "gameOver" && !gameOverReasonIsDeath && isNewBossPlusMode);
self.bossHealthBarContainer.alpha = shouldBeVisible ? 1 : 0;
}
if (self.bossHealthBarBg) {
// Jeśli używasz tła paska HP
self.bossHealthBarBg.alpha = self.bossHealthBarContainer.alpha;
}
};
// Wyświetla komunikat na środku ekranu
self.showMessage = function (message, duration) {
self.messageText.setText(message);
self.messageText.alpha = 1;
if (self.messageTimeout) {
LK.clearTimeout(self.messageTimeout);
self.messageTimeout = null;
}
if (duration && duration > 0) {
self.messageTimeout = LK.setTimeout(function () {
tween(self.messageText, {
alpha: 0
}, {
duration: 500,
onFinish: function onFinish() {
self.messageTimeout = null;
}
});
}, duration);
} else {
self.messageText.alpha = 1;
}
};
// Wyświetla tekst tutorialu
self.showTutorial = function (text) {
self.tutorialText.setText(text);
self.tutorialText.alpha = 1;
};
// Ukrywa tekst tutorialu (zanikając)
self.hideTutorial = function () {
tween(self.tutorialText, {
alpha: 0
}, {
duration: 500
});
};
// Aktualizuje licznik śmierci
self.updateDeathsCounter = function () {
var deaths = parseInt(storage.totalDeaths, 10) || 0;
self.deathsText.setText("Deaths: " + deaths);
self.deathsText.visible = gameState.currentState === "game";
};
// Aktualizuje wyświetlanie czasu timera
self.updateTimerDisplay = function (seconds) {
seconds = Math.max(0, seconds || 0);
var minutes = Math.floor(seconds / 60);
var remainingSeconds = seconds % 60;
var formattedSeconds = remainingSeconds < 10 ? '0' + remainingSeconds : remainingSeconds;
var formattedTime = String(minutes).padStart(2, '0') + ':' + formattedSeconds;
if (self.timerText) {
// W Roll Master wyświetlamy "Time:", w innych trybach tylko czas
var prefix = gameState.currentState === "rollMaster" ? "Time: " : "";
self.timerText.setText(prefix + formattedTime);
}
};
self.ccScoreText = new Text2("Score: 0", {
size: 50,
fill: 0xFFFF00,
// Żółty, dla odróżnienia
align: 'left'
});
self.ccScoreText.anchor.set(0, 0); // Lewy górny róg tego tekstu
self.addChild(self.ccScoreText);
// Rekord dla Cursed Crystal
self.ccHighScoreText = new Text2("Best: 0", {
size: 40,
fill: 0xFFFFFF,
// Biały
align: 'left'
});
self.ccHighScoreText.anchor.set(0, 0); // Lewy górny róg tego tekstu
self.addChild(self.ccHighScoreText);
// Pasek Naładowania Klejnotu
self.crystalChargeBarContainer = new Container();
self.addChild(self.crystalChargeBarContainer);
var baseAssetID = 'cursedenergybar'; // Nazwa Twojego assetu
var chargeBarAssetFullWidth; // Zmienna na pełną szerokość assetu
var chargeBarAssetHeight; // Zmienna na wysokość assetu
// Tło paska (ten sam asset, ale z większą przezroczystością)
try {
self.crystalChargeBarBg = self.attachAsset(baseAssetID, {
anchorX: 0.5,
anchorY: 0.5,
alpha: 0.3,
// Ustaw przezroczystość tła (np. 30%)
clone: true
});
chargeBarAssetFullWidth = self.crystalChargeBarBg.width; // Pobierz szerokość z assetu
chargeBarAssetHeight = self.crystalChargeBarBg.height; // Pobierz wysokość z assetu
self.crystalChargeBarContainer.addChild(self.crystalChargeBarBg);
} catch (e) {
console.error("Błąd ładowania assetu tła paska energii ('" + baseAssetID + "'): ", e);
chargeBarAssetFullWidth = 400; // Awaryjna szerokość
chargeBarAssetHeight = 25; // Awaryjna wysokość
self.crystalChargeBarBg = new Shape({
width: chargeBarAssetFullWidth,
height: chargeBarAssetHeight,
color: 0xCCCCCC,
alpha: 0.3
});
self.crystalChargeBarBg.anchor.set(0, 0.5);
self.crystalChargeBarContainer.addChild(self.crystalChargeBarBg);
}
// Wypełnienie paska (ten sam asset, w pełni widoczny)
try {
self.crystalChargeBarFill = self.attachAsset(baseAssetID, {
anchorX: 0.5,
anchorY: 0.5,
alpha: 1,
// Pełna widoczność
clone: true
});
self.crystalChargeBarFill.width = 0; // Zacznij od zerowej szerokości
// Jeśli Twoje tło i wypełnienie mają być idealnie na sobie, a oba mają anchorX:0, anchorY:0.5,
// to ich .x i .y względem kontenera powinny być takie same (np. 0,0).
// self.crystalChargeBarFill.x = 0;
// self.crystalChargeBarFill.y = 0;
self.crystalChargeBarContainer.addChild(self.crystalChargeBarFill);
} catch (e) {
console.error("Błąd ładowania assetu wypełnienia paska energii ('" + baseAssetID + "'): ", e);
self.crystalChargeBarFill = new Shape({
width: 0,
height: chargeBarAssetHeight ? chargeBarAssetHeight : 25,
color: 0x8A2BE2
});
self.crystalChargeBarFill.anchor.set(0, 0.5);
self.crystalChargeBarContainer.addChild(self.crystalChargeBarFill);
}
// --- DODANA NOWA FUNKCJA DO AKTUALIZACJI TEKSTU REKORDU ---
self.updateHighScoreDisplay = function (seconds) {
// 'seconds' to rekord czasu dla Roll Master
seconds = Math.max(0, seconds || 0);
var minutes = Math.floor(seconds / 60);
var remainingSeconds = seconds % 60;
var formattedSeconds = String(remainingSeconds).padStart(2, '0'); // Zawsze dwie cyfry dla sekund
var formattedTime = String(minutes).padStart(2, '0') + ':' + formattedSeconds;
if (this.highScoreText) {
// this.highScoreText to element UI dla rekordu Roll Master
// Wyświetlaj rekord Roll Master tylko, gdy jesteśmy w tym trybie lub na jego ekranie końca
if (gameState.currentState === "rollMaster" || gameState.currentState === "rollMasterGameOver") {
this.highScoreText.setText("Best Time: " + formattedTime);
// Pozycja i widoczność (alpha) tego elementu są zarządzane przez UI.prototype.positionElements
}
// W innych stanach gry nie modyfikujemy tekstu; positionElements powinno go ukryć.
}
};
self.updateScoreCC = function (currentScore) {
if (this.ccScoreText) {
// this.ccScoreText to element dla "Score: X" w Cursed Crystal
if (gameState.currentState === "cursedCrystal") {
this.ccScoreText.setText("Score: " + (currentScore || 0));
// Pozycja i widoczność (alpha) są zarządzane przez UI.prototype.positionElements("cursedCrystal")
}
}
};
self.updateHighScoreCC = function (highScore) {
// 'highScore' to rekord dla Cursed Crystal
if (this.ccHighScoreText) {
// this.ccHighScoreText to element dla "Best: Y" w Cursed Crystal
if (gameState.currentState === "cursedCrystal") {
this.ccHighScoreText.setText("Best: " + (highScore || 0));
// Pozycja i widoczność (alpha) są zarządzane przez UI.prototype.positionElements("cursedCrystal")
}
}
};
// Aktualizuje wizualizację paska naładowania Klejnotu
self.updateCrystalCharge = function (currentCharge, maxCharge) {
if (self.crystalChargeBarFill && self.crystalChargeBarBg) {
maxCharge = Math.max(1, maxCharge || 1); // Max musi być co najmniej 1
currentCharge = Math.max(0, Math.min(currentCharge, maxCharge));
var chargeBarBaseWidth = self.crystalChargeBarBg.width; // Szerokość tła paska
var fillWidth = currentCharge / maxCharge * chargeBarBaseWidth;
self.crystalChargeBarFill.width = fillWidth;
}
};
// Funkcja do pokazywania/ukrywania i aktualizacji paska HP Minibossa CC
// Wykorzysta istniejący self.bossHealthBar i self.bossHealthBarContainer
self.updateMinibossHealthCC = function (currentHP, maxHP, isActive) {
if (self.bossHealthBarContainer && self.bossHealthBar) {
if (isActive && currentHP > 0) {
self.bossHealthBarContainer.alpha = 1;
maxHP = Math.max(1, maxHP || 1);
currentHP = Math.max(0, Math.min(currentHP, maxHP));
var barWidth = 800; // Upewnij się, że to ta sama szerokość co w updateBossHealth
// lub pobierz z self.bossHealthBarBg.width jeśli masz tło
var currentBarWidth = currentHP / maxHP * barWidth;
self.bossHealthBar.width = currentBarWidth;
} else {
self.bossHealthBarContainer.alpha = 0;
}
}
};
// --- KONIEC DODAWANIA NOWEJ FUNKCJI ---
// Pozycjonuje elementy UI w zależności od stanu gry
self.positionElements = function (state) {
// Pozycje stałe dla niektórych elementów (mogą być nadpisywane w case'ach)
if (self.deathsText) {
// Sprawdzenie, czy element istnieje
self.deathsText.x = 2048 - 150;
self.deathsText.y = 50;
}
if (self.heartContainer) {
self.heartContainer.y = 80;
}
if (self.bossHealthBarContainer) {
self.bossHealthBarContainer.x = 2048 / 2;
self.bossHealthBarContainer.y = 160;
}
// Resetuj widoczność wszystkich głównych elementów UI przed ustawieniem dla danego stanu
if (self.titleText) {
self.titleText.alpha = 0;
}
if (self.messageText) {
self.messageText.alpha = 0;
}
if (self.tutorialText) {
self.tutorialText.alpha = 0;
}
if (self.timerText) {
self.timerText.alpha = 0;
}
if (self.heartContainer) {
self.heartContainer.alpha = 0;
}
if (self.bossHealthBarContainer) {
self.bossHealthBarContainer.alpha = 0;
}
if (self.deathsText) {
self.deathsText.alpha = 0;
}
// Ten highScoreText jest specyficzny dla Roll Master, więc domyślnie go ukrywamy
// chyba że jesteśmy w stanie rollMaster lub rollMasterGameOver (obsłużone w case)
if (self.highScoreText) {
self.highScoreText.alpha = 0;
}
// NOWE ELEMENTY UI DLA CURSED CRYSTAL - też domyślnie ukryte
if (self.ccScoreText) {
self.ccScoreText.alpha = 0;
}
if (self.ccHighScoreText) {
self.ccHighScoreText.alpha = 0;
}
if (self.crystalChargeBarContainer) {
self.crystalChargeBarContainer.alpha = 0;
}
// Przywróć domyślną pozycję i styl timerText (może być nadpisane w case "rollMaster")
if (self.timerText) {
self.timerText.x = 350; // Domyślna pozycja X dla trybu "game"
self.timerText.y = 100; // Domyślna pozycja Y dla trybu "game"
self.timerText.anchor.set(0, 0);
self.timerText.style = {
size: 60,
fill: 0xFFFFFF
};
self.timerText.setText("00:00"); // Resetuj tekst na wszelki wypadek
}
// Logika dla highScoreText (Roll Master) - resetowanie tekstu poza trybem RM
if (self.highScoreText && state !== "rollMaster" && state !== "rollMasterGameOver") {
// self.highScoreText.setText("Best RM: 00:00"); // Możesz zresetować tekst lub zostawić ostatni znany
}
switch (state) {
case "title":
if (self.titleText) {
self.titleText.x = 2048 / 2;
self.titleText.y = 800;
self.titleText.alpha = 1;
}
if (self.messageText) {
self.messageText.x = 2048 / 2;
self.messageText.y = 1000;
// self.messageText.alpha jest zarządzane przez showMessage
}
if (self.tutorialText) {
self.tutorialText.x = 2048 / 2;
self.tutorialText.y = 1200;
// self.tutorialText.alpha jest zarządzane przez showTutorial
}
break;
case "game":
// Tryb walki z bossem (tutorial)
if (self.heartContainer) {
self.heartContainer.alpha = 1;
// Pozycja serc jest aktualizowana w updateHearts
}
if (self.timerText) {
// Timer dla standardowego bossa (jeśli jest)
self.timerText.alpha = 1;
// Ustaw x, y, anchor, style dla tego timera
self.timerText.x = 350; // Przykładowa pozycja z Twojego kodu
self.timerText.y = 100;
self.timerText.anchor.set(0, 0);
self.timerText.style = {
size: 60,
fill: 0xFFFFFF
};
}
if (self.deathsText) {
self.deathsText.alpha = 1;
}
if (self.bossHealthBarContainer && typeof boss !== 'undefined' && boss && !boss.dead && boss.health > 0) {
self.bossHealthBarContainer.alpha = 1;
} else if (self.bossHealthBarContainer) {
self.bossHealthBarContainer.alpha = 0;
}
if (self.tutorialText) {
// self.tutorialText.alpha jest zarządzane przez showTutorial/hideTutorial
self.tutorialText.x = 2048 / 2;
self.tutorialText.y = 200;
}
break;
case "grillMenu":
if (self.messageText) {
// Dla komunikatów w Grill Menu
self.messageText.x = 2048 / 2;
self.messageText.y = 500;
// alpha zarządzane przez showMessage
}
// Wszystkie inne elementy typowo rozgrywkowe powinny być ukryte przez reset na początku funkcji
break;
case "gameOver":
// Standardowy Game Over (po Boss+ lub tutorialu, jeśli tak zdecydujesz)
// Teksty i przyciski dla tego stanu są głównie zarządzane przez gameState.gameOver
if (self.titleText) {// Dla "DEFEAT!" lub "TIME'S UP!"
// alpha, text, x, y są ustawiane w gameState.gameOver
}
if (self.messageText) {// Dla dodatkowych wiadomości
// alpha, text, x, y są ustawiane w gameState.gameOver
}
if (self.deathsText) {
// Najpierw sprawdź, czy deathsText istnieje
// Warunek, kiedy pokazać licznik śmierci:
// Tylko jeśli gra się zakończyła (co wiemy, bo state === "gameOver"),
// przyczyną była śmierć gracza,
// ORAZ NIE był to tryb Boss+ (czyli isNewBossPlusMode jest false).
if (gameOverReasonIsDeath && !isNewBossPlusMode) {
self.deathsText.alpha = 1;
self.deathsText.x = 2048 - 150; // Twoja standardowa pozycja
self.deathsText.y = 50; // Twoja standardowa pozycja
} else {
self.deathsText.alpha = 0; // W innych przypadkach "gameOver" (np. Boss+) ukryj licznik śmierci
}
}
break;
// ... (case'y intro, fakeTutorial, realTutorial - zakładam, że są OK) ...
case "intro": //
case "fakeTutorial": //
case "realTutorial":
//
// Większość elementów UI jest zazwyczaj ukryta, zarządzane przez logikę tych stanów
break;
//
case "rollMaster":
// Pokaż elementy UI specyficzne dla aktywnego trybu Roll Master
if (self.timerText) {
// Timer/wynik dla Roll Master
self.timerText.alpha = 1;
self.timerText.x = 2048 / 2; // Środek-góra
self.timerText.y = 50;
self.timerText.anchor.set(0.5, 0);
self.timerText.style = {
size: 60,
fill: 0xFFFFFF
};
// Tekst jest aktualizowany w ui.updateTimerDisplay
}
if (self.highScoreText) {
// Rekord dla Roll Master (ten właściwy, nie ccHighScoreText)
self.highScoreText.alpha = 1;
self.highScoreText.x = 2048 - 50; // Prawy górny róg
self.highScoreText.y = 50;
self.highScoreText.anchor.set(1, 0);
// Tekst jest aktualizowany w ui.updateHighScoreDisplay
}
// Jawnie ukryj elementy UI z Cursed Crystal
if (self.ccScoreText) {
self.ccScoreText.alpha = 0;
}
if (self.ccHighScoreText) {
self.ccHighScoreText.alpha = 0;
} // <--- BARDZO WAŻNE
if (self.crystalChargeBarContainer) {
self.crystalChargeBarContainer.alpha = 0;
}
// Ukryj inne standardowe elementy gry
if (self.heartContainer) {
self.heartContainer.alpha = 0;
}
if (self.bossHealthBarContainer) {
self.bossHealthBarContainer.alpha = 0;
}
if (self.deathsText) {
self.deathsText.alpha = 0;
}
if (self.tutorialText) {
self.tutorialText.alpha = 0;
}
if (self.titleText) {
self.titleText.alpha = 0;
} // Główny tytuł gry
break;
case "rollMasterGameOver":
// Jawnie ukryj elementy UI z Cursed Crystal
if (self.ccScoreText) {
self.ccScoreText.alpha = 0;
}
if (self.ccHighScoreText) {
self.ccHighScoreText.alpha = 0;
}
if (self.crystalChargeBarContainer) {
self.crystalChargeBarContainer.alpha = 0;
}
// Ukryj elementy aktywnej gry Roll Master (timer na żywo)
if (self.timerText) {
self.timerText.alpha = 0;
}
if (self.highScoreText) {
self.highScoreText.alpha = 0;
}
// Ukryj inne standardowe elementy gry
if (self.heartContainer) {
self.heartContainer.alpha = 0;
}
if (self.bossHealthBarContainer) {
self.bossHealthBarContainer.alpha = 0;
}
if (self.deathsText) {
self.deathsText.alpha = 0;
}
if (self.tutorialText) {
self.tutorialText.alpha = 0;
}
if (self.titleText) {
self.titleText.alpha = 0;
}
break;
case "cursedCrystal":
// TYLKO JEDNO WYSTĄPIENIE TEGO CASE
if (self.timerText) {
self.timerText.alpha = 0;
}
if (self.highScoreText) {
// To jest highScoreText dla Roll Master, więc ukrywamy
self.highScoreText.alpha = 0;
}
if (self.deathsText) {
// Licznik śmierci też ukrywamy w tym trybie
self.deathsText.alpha = 0;
}
if (self.tutorialText) {
// Tutorial też ukrywamy
self.tutorialText.alpha = 0;
}
// --- Elementy specyficzne dla Cursed Crystal ---
if (self.ccScoreText) {
self.ccScoreText.alpha = 1;
self.ccScoreText.x = 50;
self.ccScoreText.y = 20;
self.ccScoreText.anchor.set(0, 0);
}
if (self.ccHighScoreText) {
self.ccHighScoreText.alpha = 1;
self.ccHighScoreText.x = 2048 - 50;
self.ccHighScoreText.y = 20;
self.ccHighScoreText.anchor.set(1, 0);
}
// --- Serca Gracza ---
if (self.heartContainer) {
self.heartContainer.alpha = 1; // UPEWNIJ SIĘ, ŻE SERCA SĄ WIDOCZNE
var ccMaxHearts = typeof gameState !== 'undefined' && gameState.cursedCrystalPlayerMaxHealth ? gameState.cursedCrystalPlayerMaxHealth : 10;
var heartsWidth = ccMaxHearts * 50;
self.heartContainer.x = (2048 - heartsWidth) / 2 + 25;
self.heartContainer.y = 70; // Stała pozycja Y dla serc
}
// --- Pasek Ładowania Kryształu ---
if (self.crystalChargeBarContainer && self.crystalChargeBarBg) {
if (typeof gameState !== 'undefined' && gameState.isMinibossActiveCC) {
self.crystalChargeBarContainer.alpha = 0;
} else {
self.crystalChargeBarContainer.alpha = 1;
var chargeBarWidth = self.crystalChargeBarBg.width || 400;
self.crystalChargeBarContainer.anchorX = 0.5;
self.crystalChargeBarContainer.x = 2048 / 2;
self.crystalChargeBarContainer.y = (self.heartContainer ? self.heartContainer.y + (self.heartContainer.height || 40) : 70 + 40) + 50;
}
}
// --- Pasek HP Minibossa ---
if (self.bossHealthBarContainer) {
var heartsBottomY = self.heartContainer ? self.heartContainer.y + (self.heartContainer.height || 40) : 70 + 40;
// Poprzednio było: heartsBottomY + 20 (ok. 130), potem heartsBottomY + 70 (ok. 180)
// Aby przesunąć o kolejne 50px w dół od Y=180, potrzebujemy Y = 230
// Więc offset od heartsBottomY (ok. 110) musi być 120.
var desiredOffsetYBelowHearts = 120;
self.bossHealthBarContainer.y = heartsBottomY + desiredOffsetYBelowHearts;
// Widoczność paska HP bossa jest zarządzana przez updateMinibossHealthCC,
// więc tutaj tylko pozycjonujemy.
}
break;
case "cursedCrystalGameOver":
// Ukryj elementy rozgrywki CC (oprócz score i highscore, które mogą być częścią ekranu końca)
if (self.timerText) {
self.timerText.alpha = 0;
}
if (self.highScoreText) {
self.highScoreText.alpha = 0;
} // highScoreText dla RollMaster
if (self.crystalChargeBarContainer) {
self.crystalChargeBarContainer.alpha = 0;
}
if (self.heartContainer) {
self.heartContainer.alpha = 0;
} // Ukryj serca gracza na ekranie końca gry CC
if (self.bossHealthBarContainer) {
self.bossHealthBarContainer.alpha = 0;
}
if (self.deathsText) {
self.deathsText.alpha = 0;
}
if (self.tutorialText) {
self.tutorialText.alpha = 0;
}
break;
}
}; // Koniec positionElements
return self;
});
/****
* Initialize Game
****/
// Koniec var UI = Container.expand(...)
var game = new LK.Game({
backgroundColor: 0x111111 // Ciemne tło domyślne
});
/****
* Game Code
****/
// Jasnoniebieska elipsa dla Duszy
// Placeholder, zmień ID
// Dodana funkcja pomocnicza do pobierania czasu gry w milisekundach
// Dodana funkcja pomocnicza do pobierania czasu gry w milisekundach
// Globalny kontener na elementy scen intro/tutoriali i Grill Screena (do łatwego czyszczenia)
// Komentarze o assetach pominięte dla zwięzłości
// Ciemnofioletowe tło
// Fioletowy klejnot
// Pomarańczowy kwadrat dla Minibossa
function _typeof(o) {
"@babel/helpers - typeof";
return _typeof = "function" == typeof Symbol && "symbol" == typeof Symbol.iterator ? function (o) {
return typeof o;
} : function (o) {
return o && "function" == typeof Symbol && o.constructor === Symbol && o !== Symbol.prototype ? "symbol" : typeof o;
}, _typeof(o);
}
function _defineProperty(e, r, t) {
return (r = _toPropertyKey(r)) in e ? Object.defineProperty(e, r, {
value: t,
enumerable: !0,
configurable: !0,
writable: !0
}) : e[r] = t, e;
}
function _toPropertyKey(t) {
var i = _toPrimitive(t, "string");
return "symbol" == _typeof(i) ? i : i + "";
}
function _toPrimitive(t, r) {
if ("object" != _typeof(t) || !t) {
return t;
}
var e = t[Symbol.toPrimitive];
if (void 0 !== e) {
var i = e.call(t, r || "default");
if ("object" != _typeof(i)) {
return i;
}
throw new TypeError("@@toPrimitive must return a primitive value.");
}
return ("string" === r ? String : Number)(t);
}
if (typeof LK.getGameTime === 'undefined') {
LK.getGameTime = function () {
return Date.now(); // Zwraca aktualny czas w milisekundach
};
}
if (typeof LK.getGameTime === 'undefined') {
LK.getGameTime = function () {
return Date.now(); // Zwraca aktualny czas w milisekundach
};
}
var currentSceneElements = new Container();
// Zmienne gry
var player;
var boss;
var ui;
var walls = []; // Ściany areny bossa
// Zmienna do przechowywania aktywnego tła sceny
var currentBackground = null;
// Flaga do śledzenia, czy jesteśmy w trybie New Boss+
var isNewBossPlusMode = false;
// Flaga do śledzenia przyczyny Game Over (true = gracz zginął, false = czas minął)
var gameOverReasonIsDeath = false;
var coffinMemeImage = null;
// Funkcja do czyszczenia elementów z kontenera currentSceneElements (nie usuwa UI ani tła)
function clearScene() {
while (currentSceneElements.children.length > 0) {
var child = currentSceneElements.children[0];
if (child && child.destroy) {
child.destroy(); // Użyj destroy jeśli dostępne
} else if (child && child.parent) {
child.parent.removeChild(child); // Fallback
} else {
// Jeśli obiekt nie ma ani destroy ani parent, usuń go z tablicy (choć to nie powinno się zdarzyć)
currentSceneElements.children.shift();
}
}
// Usuń też dzieci bezpośrednio z 'game', które mogły zostać dodane poza 'currentSceneElements' w scenach,
// ale UWAŻAJ, aby nie usunąć stałych elementów jak UI, walls, currentSceneElements sam w sobie.
// Lepsze podejście: zawsze dodawaj elementy specyficzne dla sceny do currentSceneElements.
}
// Obiekt zarządzający stanami gry
var gameState = _defineProperty(_defineProperty(_defineProperty({
currentState: "title",
gameDuration: 120,
remainingTime: 0,
crystalExploding: false,
gameTimerInterval: null,
fakeTutorialTimerId: null,
bossSpeedIncreased: false,
currentIntroMusicInstance: null,
cursedCrystalMusicTracks: ['CursedCrystal', 'cursedcrystal2'],
currentRollSoulsInstance: null,
currentRollMasterMusicInstance: null,
currentCursedCrystalMusicInstance: null,
// Nowa właściwość
cursedCrystalPlayerHealth: 10,
// Startowe HP gracza w tym trybie
cursedCrystalPlayerMaxHealth: 10,
cursedCrystalChargeLevel: 0,
// Aktualny poziom naładowania Klejnotu (0-100%)
cursedCrystalTargetCharge: 100,
// Wartość naładowania do przywołania Minibossa
cursedCrystalSoulsHitCrystal: 0,
// Licznik dusz, które dotarły do kryształu (do skalowania Minibossa)
cursedCrystalScore: 0,
cursedCrystalHighScore: 0,
// Ładowane z localStorage
cursedCrystalEnemies: [],
// Tablica na Dusze i pociski Minibossa CC
cursedCrystalEnemySpawnTimer: 0,
cursedCrystalBaseSpawnInterval: 120,
// Początkowy interwał spawnu (np. 2 sekundy)
cursedCrystalCurrentSpawnInterval: 120,
cursedCrystalMinSpawnInterval: 30,
// Minimalny interwał spawnu (np. 0.5 sekundy)
cursedCrystalDifficultyTimer: 0,
cursedCrystalDifficultyInterval: 600,
// Co ile klatek (np. 10 sekund) zwiększać trudność
cursedCrystalEnemyBaseSpeed: 2,
cursedCrystalEnemyCurrentMaxSpeed: 4,
cursedCrystalTimeSurvived: 0,
// Czas przetrwania w klatkach lub sekundach
isMinibossActiveCC: false,
cursedCrystalMinibossObject: null,
cursedCrystalMinibossHP: 0,
cursedCrystalMinibossMaxHP: 200,
cursedCrystalActiveProjectiles: [],
// Dla pocisków Minibossa
cursedCrystalActiveExplosions: [],
cursedCrystalActiveLasers: [],
cursedCrystalActiveLaserWalls: [],
// Dla aktywnych obszarów eksplozji
// Bazowe HP Minibossa, będzie skalowane
cursedCrystalMinibossAttackTimer: 0,
cursedCrystalMinibossAttackInterval: 180,
rollMasterTime: 0,
rollMasterDifficulty: 1,
rollMasterTimerInterval: null,
rollMasterAttacks: [],
attackSpawnTimer: 0,
attackSpawnInterval: 120,
explosionSpawnTimer: 0,
explosionSpawnInterval: 240,
rollMasterHighScore: 0,
isInputActive: false,
currentInputPos: {
x: 0,
y: 0
},
touchStart: {
x: 0,
y: 0
},
touchEnd: {
x: 0,
y: 0
},
grillMenuEffects: [],
swipeStartTime: 0,
init: function init() {
// Zapewnienie, że LK.getGameTime jest zdefiniowane
if (typeof LK.getGameTime === 'undefined') {
LK.getGameTime = function () {
return Date.now(); // Zwraca aktualny czas w milisekundach
};
}
storage.totalDeaths = 0;
storage.maxHearts = 5;
isNewBossPlusMode = false;
gameOverReasonIsDeath = false;
this.rollMasterHighScore = storage.rollMasterHighScore || 0;
this.currentIntroMusicInstance = null;
this.currentRollSoulsInstance = null;
this.currentRollMasterMusicInstance = null;
game.setBackgroundColor(0x111111);
ui = game.addChild(new UI());
ui.updateDeathsCounter();
ui.updateHearts(storage.maxHearts, storage.maxHearts);
ui.positionElements("title");
game.addChild(currentSceneElements);
this.showTitleScreen();
},
// --- gameState.showTitleScreen ---
showTitleScreen: function showTitleScreen() {
isNewBossPlusMode = false;
this.rollMasterTime = 0;
// Normalne działanie - bez flagi testowej
console.log("[showTitleScreen] State: Title Screen");
var needsIntroMusic = true;
// Zatrzymywanie muzyki z innych trybów
if (this.currentRollSoulsInstance && this.currentRollSoulsInstance.stop) {
console.log("[showTitleScreen] Zatrzymywanie currentRollSoulsInstance.");
if (typeof this.currentRollSoulsInstance.volume === 'number') {
this.currentRollSoulsInstance.volume = 0;
}
this.currentRollSoulsInstance.stop();
this.currentRollSoulsInstance = null;
} else {
var musicViaLK_RS = LK.music;
if (musicViaLK_RS && musicViaLK_RS.assetId === 'RollSouls' && musicViaLK_RS.playing && musicViaLK_RS.stop) {
console.log("[showTitleScreen] Fallback: Zatrzymywanie RollSouls przez LK.music.");
if (typeof musicViaLK_RS.volume === 'number') {
musicViaLK_RS.volume = 0;
}
musicViaLK_RS.stop();
}
}
if (this.currentRollMasterMusicInstance && this.currentRollMasterMusicInstance.stop) {
console.log("[showTitleScreen] Zatrzymywanie currentRollMasterMusicInstance.");
if (typeof this.currentRollMasterMusicInstance.volume === 'number') {
this.currentRollMasterMusicInstance.volume = 0;
}
this.currentRollMasterMusicInstance.stop();
this.currentRollMasterMusicInstance = null;
} else {
var musicViaLK_RM = LK.music;
if (musicViaLK_RM && musicViaLK_RM.assetId === 'rollmaster' && musicViaLK_RM.playing && musicViaLK_RM.stop) {
console.log("[showTitleScreen] Fallback: Zatrzymywanie 'rollmaster' przez LK.music.");
if (typeof musicViaLK_RM.volume === 'number') {
musicViaLK_RM.volume = 0;
}
musicViaLK_RM.stop();
}
}
// Logika muzyki intro
if (this.currentIntroMusicInstance && this.currentIntroMusicInstance.playing) {
console.log("[showTitleScreen] currentIntroMusicInstance już gra.");
needsIntroMusic = false;
} else {
var lkMusicCheck = LK.music;
if (lkMusicCheck && lkMusicCheck.assetId === 'introMusic' && lkMusicCheck.playing) {
console.log("[showTitleScreen] introMusic już gra (wg LK.music). Zapisuję instancję.");
this.currentIntroMusicInstance = lkMusicCheck;
needsIntroMusic = false;
}
}
if (needsIntroMusic) {
console.log("[showTitleScreen] Uruchamianie introMusic...");
this.currentIntroMusicInstance = LK.playMusic('introMusic', {
loop: true,
volume: 0.7,
fade: {
start: 0,
end: 0.7,
duration: 1000
}
});
if (!this.currentIntroMusicInstance) {
console.error("[showTitleScreen] LK.playMusic('introMusic') nie zwróciło instancji!");
}
} else {
console.log("[showTitleScreen] introMusic nie jest ponownie uruchamiana.");
}
// Czyszczenie timerów
if (this.fakeTutorialTimerId) {
LK.clearTimeout(this.fakeTutorialTimerId);
this.fakeTutorialTimerId = null;
}
if (this.gameTimerInterval) {
LK.clearInterval(this.gameTimerInterval);
this.gameTimerInterval = null;
}
if (this.rollMasterTimerInterval) {
LK.clearInterval(this.rollMasterTimerInterval);
this.rollMasterTimerInterval = null;
}
clearScene(); // Usuwa elementy z currentSceneElements
// Ustawienie tła i cząsteczek
if (currentBackground) {
tween.stop(currentBackground);
currentBackground.destroy();
currentBackground = null;
}
this.currentState = "title";
game.setBackgroundColor(0x1a1a1a);
currentBackground = LK.getAsset('titleBg', {
anchorX: 0,
anchorY: 0,
x: 0,
y: 0
});
game.addChildAt(currentBackground, 0);
if (typeof particleIntervalId !== 'undefined' && particleIntervalId) {
LK.clearInterval(particleIntervalId);
particleIntervalId = null;
}
if (typeof startScreenParticles !== 'undefined') {
startScreenParticles.forEach(function (p) {
if (p && p.destroy) {
p.destroy();
}
});
startScreenParticles = [];
}
var localStartScreenParticles = [];
function localSpawnBackgroundParticle() {/* ... logika cząsteczek ... */}
var localParticleIntervalId = LK.setInterval(localSpawnBackgroundParticle, 400);
// Reset gracza i bossa
if (player && player.destroy) {
player.destroy();
}
player = null;
if (boss && boss.destroy) {
boss.destroy();
}
boss = null;
walls.forEach(function (wall) {
if (wall) {
wall.alpha = 1;
}
});
// Ustawienie UI
ui.positionElements("title");
ui.titleText.setText("Welcome Unchosen");
ui.showMessage("Tap to Start", 0); // Normalny tekst
ui.showTutorial("Swipe to Roll - Death is Progress");
ui.updateHearts(storage.maxHearts, storage.maxHearts);
ui.updateDeathsCounter();
this.touchStart = {
x: 0,
y: 0
};
this.touchEnd = {
x: 0,
y: 0
};
// Usuwamy testowy przycisk i jego logikę
game.off('down');
game.on('down', function (x, y, obj) {
if (gameState.currentState === 'title') {
// Logika dla normalnego przejścia do intro
console.log("Title screen clicked - Proceeding to Intro.");
if (localParticleIntervalId) {
LK.clearInterval(localParticleIntervalId);
localParticleIntervalId = null;
}
if (localStartScreenParticles) {
localStartScreenParticles.forEach(function (p) {
if (p && p.destroy) {
p.destroy();
}
});
localStartScreenParticles = [];
}
// Muzyka intro powinna już grać lub zacząć grać, nie zatrzymujemy jej tutaj
gameState.showIntro(); // <<< KLUCZOWA ZMIANA - PRZYWRÓCENIE NORMALNEGO PRZEJŚCIA
}
});
},
showIntro: function showIntro() {
var skipElement = null;
var skipTimerId = null;
var self = this; // self tutaj to gameState
if (this.fakeTutorialTimerId) {
LK.clearTimeout(this.fakeTutorialTimerId);
this.fakeTutorialTimerId = null;
}
if (typeof particleIntervalId !== 'undefined') {
LK.clearInterval(particleIntervalId);
particleIntervalId = null;
}
if (typeof startScreenParticles !== 'undefined') {
startScreenParticles.forEach(function (p) {
if (p && p.destroy) {
p.destroy();
}
});
startScreenParticles = [];
}
clearScene();
if (currentBackground) {
tween.stop(currentBackground);
currentBackground.destroy();
currentBackground = null;
}
this.currentState = "intro";
game.setBackgroundColor(0x111111);
currentBackground = LK.getAsset('introBg', {
anchorX: 0.5,
anchorY: 0.4,
x: 1000,
y: 1000,
scaleX: 1,
scaleY: 1
});
game.addChildAt(currentBackground, 0);
tween(currentBackground, {
scaleX: 1.5,
scaleY: 1.5
}, {
duration: 38000,
easing: tween.linear,
repeat: Infinity,
yoyo: true
});
if (player && player.destroy) {
player.destroy();
}
player = null;
if (boss && boss.destroy) {
boss.destroy();
}
boss = null;
walls.forEach(function (wall) {
if (wall) {
wall.alpha = 0;
}
});
ui.positionElements("intro");
skipTimerId = LK.setTimeout(function () {
if (self.currentState !== "intro") {
skipTimerId = null;
return;
}
skipElement = new Text2("Skip intro", {
size: 70,
fill: 0xFFFFFF
});
skipElement.x = 2048 - 50;
skipElement.y = 2732 - 50;
skipElement.anchor.set(1, 1);
skipElement.interactive = true;
skipElement.cursor = "pointer";
currentSceneElements.addChild(skipElement);
skipElement.down = function () {
if (self.currentState !== "intro") {
return;
}
console.log("[Skip Intro] Pominięto intro przez tekst!");
if (skipTimerId) {
LK.clearTimeout(skipTimerId);
skipTimerId = null;
}
if (currentBackground) {
tween.stop(currentBackground);
}
clearScene();
if (self.currentIntroMusicInstance && self.currentIntroMusicInstance.stop) {
console.log("[Skip Intro] Zatrzymywanie currentIntroMusicInstance.");
if (typeof self.currentIntroMusicInstance.volume === 'number') {
self.currentIntroMusicInstance.volume = 0;
}
self.currentIntroMusicInstance.stop();
self.currentIntroMusicInstance = null;
} else {
console.log("[Skip Intro] Brak currentIntroMusicInstance lub metody stop. Fallback na LK.music.");
var musicViaLK_I = LK.music;
if (musicViaLK_I && musicViaLK_I.assetId === 'introMusic' && musicViaLK_I.stop) {
if (typeof musicViaLK_I.volume === 'number') {
musicViaLK_I.volume = 0;
}
musicViaLK_I.stop();
}
}
if (this.currentCursedCrystalMusicInstance && this.currentCursedCrystalMusicInstance.stop) {
console.log("[showTitleScreen] Zatrzymywanie currentCursedCrystalMusicInstance.");
if (typeof this.currentCursedCrystalMusicInstance.volume === 'number') {
this.currentCursedCrystalMusicInstance.volume = 0;
}
this.currentCursedCrystalMusicInstance.stop();
this.currentCursedCrystalMusicInstance = null;
} else {
// Opcjonalny fallback, jeśli chcesz (choć jeśli zawsze ustawiasz currentCursedCrystalMusicInstance, może nie być potrzebny)
var musicViaLK_CC = LK.music;
if (musicViaLK_CC && musicViaLK_CC.assetId === 'cursedcrystal' && musicViaLK_CC.playing && musicViaLK_CC.stop) {
console.log("[showTitleScreen] Fallback: Zatrzymywanie muzyki Cursed Crystal przez LK.music.");
if (typeof musicViaLK_CC.volume === 'number') {
musicViaLK_CC.volume = 0;
}
musicViaLK_CC.stop();
}
}
if (self.currentRollMasterMusicInstance && self.currentRollMasterMusicInstance.stop) {
console.log("[Skip Intro] Zatrzymywanie currentRollMasterMusicInstance.");
if (typeof self.currentRollMasterMusicInstance.volume === 'number') {
self.currentRollMasterMusicInstance.volume = 0;
}
self.currentRollMasterMusicInstance.stop();
self.currentRollMasterMusicInstance = null;
}
console.log("[Skip Intro] Odtwarzanie RollSouls...");
self.currentRollSoulsInstance = LK.playMusic('RollSouls', {
loop: true,
volume: 0.7,
fade: {
start: 0,
end: 0.7,
duration: 1000
}
});
if (!self.currentRollSoulsInstance) {
console.error("[Skip Intro] LK.playMusic('RollSouls') nie zwróciło instancji!");
}
console.log("[Skip Intro] Przechodzenie do showRealTutorial...");
self.showRealTutorial();
};
skipTimerId = null;
}, 3000);
function showIntroText(text, delay, onComplete) {
var introText = new Text2(text, {
size: 90,
fill: 0xFFFFFF,
align: 'center',
wordWrap: true,
wordWrapWidth: 1800
});
introText.anchor.set(0.5, 0.5);
introText.x = 2048 / 2;
introText.y = 2232 / 2 - 400;
introText.alpha = 0;
currentSceneElements.addChild(introText);
LK.setTimeout(function () {
if (self.currentState !== "intro") {
return;
}
tween(introText, {
alpha: 1
}, {
duration: 2000,
easing: tween.easeInOut
});
LK.setTimeout(function () {
if (self.currentState !== "intro") {
return;
}
tween(introText, {
alpha: 0
}, {
duration: 2000,
easing: tween.easeInOut
});
LK.setTimeout(function () {
if (onComplete) {
onComplete();
}
}, 4000);
}, 4000);
}, delay);
}
showIntroText('From the authors of Dark Souls...', 3000, function () {
if (self.currentState !== "intro") {
return;
}
showIntroText('I have no information. I don’t know them.', 0, function () {
if (self.currentState !== "intro") {
return;
}
showIntroText('Silas GameStudio...', 0, function () {
if (self.currentState !== "intro") {
return;
}
showIntroText('Still looking for funding.', 0, function () {
if (self.currentState !== "intro") {
return;
}
showIntroText('Oh, and it doesn’t even exist.', 0, function () {
if (self.currentState !== "intro") {
return;
}
LK.setTimeout(function () {
if (self.currentState !== "intro") {
return;
}
if (skipTimerId) {
LK.clearTimeout(skipTimerId);
skipTimerId = null;
}
if (skipElement && skipElement.parent) {
currentSceneElements.removeChild(skipElement);
if (skipElement.destroy) {
skipElement.destroy();
}
skipElement = null;
}
if (typeof gameState.showFakeTutorial === 'function') {
gameState.showFakeTutorial();
} else {
console.error("Error: gameState.showFakeTutorial is not defined");
gameState.showTitleScreen();
}
}, 1200);
});
});
});
});
});
},
// Koniec funkcji showIntro
// --- KONIEC SEKWENCJI INTRO ---
// <--- Koniec funkcji showIntro
showFakeTutorial: function showFakeTutorial() {
clearScene();
if (this.fakeTutorialTimerId) {
LK.clearTimeout(this.fakeTutorialTimerId);
this.fakeTutorialTimerId = null;
}
if (this.currentIntroMusicInstance) {
console.log("[FakeTutorial] Próba wyciszenia i zatrzymania currentIntroMusicInstance.");
var instanceToFadeAndStop = this.currentIntroMusicInstance;
LK.tween(instanceToFadeAndStop, {
volume: 0
}, {
duration: 3000,
onFinish: function onFinish() {
if (instanceToFadeAndStop.stop) {
instanceToFadeAndStop.stop();
console.log("[FakeTutorial] currentIntroMusicInstance zatrzymane po tweenie.");
if (gameState.currentIntroMusicInstance === instanceToFadeAndStop) {
gameState.currentIntroMusicInstance = null;
}
}
}
});
} else {
console.log("[FakeTutorial] Nie znaleziono currentIntroMusicInstance. Sprawdzanie LK.music...");
var musicViaLK_I = LK.music;
if (musicViaLK_I && musicViaLK_I.assetId === 'introMusic') {
LK.tween(musicViaLK_I, {
volume: 0
}, {
duration: 3000,
onFinish: function onFinish() {
if (musicViaLK_I.stop) {
musicViaLK_I.stop();
}
}
});
}
}
if (this.currentRollMasterMusicInstance && this.currentRollMasterMusicInstance.stop) {
console.log("[FakeTutorial] Zatrzymywanie currentRollMasterMusicInstance.");
if (typeof this.currentRollMasterMusicInstance.volume === 'number') {
this.currentRollMasterMusicInstance.volume = 0;
}
this.currentRollMasterMusicInstance.stop();
this.currentRollMasterMusicInstance = null;
}
console.log("[FakeTutorial] Odtwarzanie RollSouls...");
this.currentRollSoulsInstance = LK.playMusic('RollSouls', {
loop: true,
volume: 0.7,
fade: {
start: 0,
end: 0.7,
duration: 3000
}
});
if (!this.currentRollSoulsInstance) {
console.error("[FakeTutorial] LK.playMusic('RollSouls') nie zwróciło instancji!");
}
this.currentState = "fakeTutorial";
ui.positionElements("fakeTutorial");
var instructionText = new Text2('Press LPM to block.. or don’t press.', {
size: 100,
fill: 0xFFFFFF,
align: 'center',
wordWrap: true,
wordWrapWidth: 1800,
dropShadow: true,
dropShadowColor: 0x000000,
dropShadowDistance: 3,
dropShadowBlur: 4,
dropShadowAngle: Math.PI / 4
});
instructionText.anchor.set(0.5, 0.5);
instructionText.x = 2048 / 2;
instructionText.y = 2732 / 2;
currentSceneElements.addChild(instructionText);
this.fakeTutorialTimerId = LK.setTimeout(function () {
try {
if (instructionText && instructionText.destroy) {
instructionText.destroy();
}
} catch (e) {}
gameState.fakeTutorialTimerId = null;
if (gameState && typeof gameState.showRealTutorial === 'function') {
gameState.showRealTutorial();
} else {
console.error("Error: gameState.showRealTutorial is not a function in fakeTutorial timeout!");
gameState.showTitleScreen();
}
}, 6000);
},
// *** NOWA FUNKCJA: Prawdziwy Tutorial (oddzielona od startGame) ***
showRealTutorial: function showRealTutorial() {
if (this.fakeTutorialTimerId) {
LK.clearTimeout(this.fakeTutorialTimerId);
}
this.fakeTutorialTimerId = null; // Wyczyść timer fake tutorialu
clearScene(); // Wyczyść elementy fake tutorialu/fake game over
// Zatrzymaj animację tła intro i usuń je
if (currentBackground) {
tween.stop(currentBackground);
currentBackground.destroy();
currentBackground = null;
}
this.currentState = "realTutorial";
// Ustaw tło tutoriala
if (currentBackground && currentBackground.destroy) {
currentBackground.destroy();
}
currentBackground = LK.getAsset('realtutorialbg', {});
game.addChildAt(currentBackground, 0);
// Pokaż ściany areny
walls.forEach(function (wall) {
if (wall) {
wall.alpha = 1;
}
});
ui.positionElements("realTutorial"); // Ustaw UI (głównie ukrywa elementy gry)
// --- NIE MA tutorialTitle ani tutorialDesc (tekst jest na grafice) ---
// Przycisk "Let's Roll!" do rozpoczęcia gry
var startButton = new Container();
startButton.interactive = true;
startButton.x = 2048 / 2;
startButton.y = 1250; // Zmniejszona pozycja Y, był 1500
currentSceneElements.addChild(startButton);
// Powiększone tło przycisku
var startButtonBg = LK.getAsset('button_bg', {
anchorX: 0.5,
anchorY: 0.5
});
startButtonBg.scale.set(1.3); // Skala tła przycisku zwiększona
startButton.addChild(startButtonBg);
// Powiększony i wyraźniejszy tekst przycisku
var startButtonText = new Text2("Let's Roll!", {
size: 80,
fill: 0xFFFFFF,
stroke: 0x000000,
strokeThickness: 6
});
startButtonText.anchor.set(0.5, 0.5);
startButton.addChild(startButtonText);
// Funkcja kliknięcia przycisku
startButton.down = function () {
gameState.startGame(); // Rozpocznij grę
};
},
// Obsługa inputu w stanie fakeTutorial (fałszywa śmierć)
handleFakeTutorialInput: function handleFakeTutorialInput() {
// Wyczyść timer, który miał prowadzić do prawdziwego tutorialu
if (this.fakeTutorialTimerId) {
LK.clearTimeout(this.fakeTutorialTimerId);
this.fakeTutorialTimerId = null; // Zresetuj ID timera
}
clearScene(); // Wyczyść ekran fałszywego tutorialu
// Ustaw stan tymczasowo (można by dodać dedykowany stan 'fakeGameOver')
this.currentState = "fakeGameOver"; // Zmieniono z "gameOver"
// Zatrzymaj animację tła intro, ale nie usuwaj go jeszcze
if (currentBackground) {
tween.stop(currentBackground);
}
ui.positionElements("gameOver"); // Użyj pozycjonowania gameOver dla tekstów "YOU DIED"
// Wyświetl "YOU DIED" na czerwono
var diedText = new Text2("YOU DIED", {
size: 150,
fill: 0xFF0000
}); // Czerwony kolor
diedText.anchor.set(0.5, 0.5);
diedText.x = 2048 / 2;
diedText.y = 600;
currentSceneElements.addChild(diedText);
// Wyświetl wyjaśnienie
var explanationText = new Text2("Did you check the title of the game?", {
size: 70,
fill: 0xFFFFFF,
align: 'center',
wordWrap: true,
wordWrapWidth: 1800,
dropShadow: true,
dropShadowColor: 0x000000,
// cień czarny
dropShadowDistance: 3,
// odległość cienia
dropShadowBlur: 4,
// lekkie rozmycie cienia
dropShadowAngle: Math.PI / 4 // kąt padania cienia (45 stopni)
});
explanationText.anchor.set(0.5, 0.5);
explanationText.x = 2048 / 2;
explanationText.y = 800;
currentSceneElements.addChild(explanationText);
// Dodaj przycisk "How to play again"
var howToPlayButtonContainer = new Container();
howToPlayButtonContainer.interactive = true;
howToPlayButtonContainer.x = 2048 / 2;
howToPlayButtonContainer.y = 1300; // Pozycja przycisku
currentSceneElements.addChild(howToPlayButtonContainer);
var buttonBg = LK.getAsset('button_bg', {
anchorX: 0.5,
anchorY: 0.5
});
howToPlayButtonContainer.addChild(buttonBg);
var buttonText = new Text2('How to play', {
size: 50,
fill: 0xFFFFFF
}); // Zmieniono tekst i rozmiar
buttonText.anchor.set(0.5, 0.5);
howToPlayButtonContainer.addChild(buttonText);
// Akcja przycisku: Przejdź do prawdziwego tutorialu
howToPlayButtonContainer.down = function () {
// *** POPRAWKA: Sprawdzenie przed wywołaniem ***
if (gameState && typeof gameState.showRealTutorial === 'function') {
gameState.showRealTutorial(); // Przejdź do prawdziwego tutorialu
} else {
console.error("Error: gameState.showRealTutorial is not a function in fake input handler!");
gameState.showTitleScreen(); // Awaryjny powrót do tytułu
}
};
},
// Przejście do stanu gry (walka z bossem) - wywoływane z Prawdziwego Tutorialu
startGame: function startGame() {
var _this = this;
console.log("[StartGame] Rozpoczynanie standardowej gry lub NewBoss+.");
if (this.currentIntroMusicInstance && this.currentIntroMusicInstance.stop) {
console.log("[StartGame] Zatrzymywanie currentIntroMusicInstance.");
if (typeof this.currentIntroMusicInstance.volume === 'number') {
this.currentIntroMusicInstance.volume = 0;
}
this.currentIntroMusicInstance.stop();
this.currentIntroMusicInstance = null;
}
if (this.currentRollMasterMusicInstance && this.currentRollMasterMusicInstance.stop) {
console.log("[StartGame] Zatrzymywanie currentRollMasterMusicInstance.");
if (typeof this.currentRollMasterMusicInstance.volume === 'number') {
this.currentRollMasterMusicInstance.volume = 0;
}
this.currentRollMasterMusicInstance.stop();
this.currentRollMasterMusicInstance = null;
}
if (this.currentCursedCrystalMusicInstance && this.currentCursedCrystalMusicInstance.stop) {
// DODANA LOGIKA
console.log("[StartGame] Zatrzymywanie currentCursedCrystalMusicInstance.");
if (typeof this.currentCursedCrystalMusicInstance.volume === 'number') {
this.currentCursedCrystalMusicInstance.volume = 0;
}
this.currentCursedCrystalMusicInstance.stop();
this.currentCursedCrystalMusicInstance = null;
}
if (this.currentRollSoulsInstance && this.currentRollSoulsInstance.stop) {
console.log("[StartGame] Zatrzymywanie poprzedniej currentRollSoulsInstance.");
if (typeof this.currentRollSoulsInstance.volume === 'number') {
this.currentRollSoulsInstance.volume = 0;
}
this.currentRollSoulsInstance.stop();
this.currentRollSoulsInstance = null; // Upewnij się, że zerujesz po zatrzymaniu, aby odtworzyć nową
}
console.log("[StartGame] Uruchamianie RollSouls.");
this.currentRollSoulsInstance = LK.playMusic('RollSouls', {
// Zakładam, że 'RollSouls' to muzyka dla tego trybu
loop: true,
volume: 0.7
});
if (!this.currentRollSoulsInstance) {
console.error("[StartGame] LK.playMusic('RollSouls') nie zwróciło instancji!");
}
if (this.fakeTutorialTimerId) {
LK.clearTimeout(this.fakeTutorialTimerId);
this.fakeTutorialTimerId = null;
}
if (this.gameTimerInterval) {
LK.clearInterval(this.gameTimerInterval);
this.gameTimerInterval = null;
}
clearScene();
if (currentBackground) {
tween.stop(currentBackground);
currentBackground.destroy();
currentBackground = null;
}
this.currentState = "game";
if (gameState.grillMenuEffects && gameState.grillMenuEffects.length > 0) {
gameState.grillMenuEffects.forEach(function (effect) {
if (effect && effect.parent) {
tween.stop(effect);
effect.parent.removeChild(effect);
if (effect.destroy && !effect.destroyed) {
effect.destroy();
}
} else if (effect && effect.destroy && !effect.destroyed) {
console.warn("Sparkle/Star object found without parent but has destroy method:", effect);
effect.destroy();
} else {
console.warn("Sparkle/Star object found without parent or destroy method:", effect);
}
});
gameState.grillMenuEffects = [];
}
game.setBackgroundColor(0x111111);
var arenaBg = LK.getAsset('arena', {
anchorX: 0.5,
anchorY: 0.5,
x: 2048 / 2,
y: 2732 / 2
});
game.addChildAt(arenaBg, 0);
if (player && player.destroy) {
player.destroy();
}
player = game.addChild(new Player());
if (player) {
player.health = parseInt(storage.maxHearts, 10) || 5;
console.log("DEBUG: Inicjalizacja zdrowia gracza. player.health:", player.health, "storage.maxHearts:", storage.maxHearts);
}
player.rolling = false;
player.invulnerable = false;
player.invulnerabilityFrames = 0;
player.rollCooldown = 0;
if (player && typeof player.clearRollTimeouts === 'function') {
player.clearRollTimeouts();
}
player.hasRolledThroughBossThisRoll = false;
player.x = 2048 / 2;
player.y = 2732 / 2 + 400;
player.alpha = 1;
player.dead = false;
if (boss && boss.destroy) {
boss.destroy();
}
boss = game.addChild(new Boss());
boss.x = 2048 / 2;
boss.y = 2732 / 2 - 400;
boss.alpha = 1;
boss.attackCooldown = 90;
boss.attacks = [];
boss.attackSpeedMultiplier = 1;
boss.repositioning = false;
boss.dead = false;
boss.phase = 1;
boss.tint = 0xFFFFFF;
boss.scale.set(1, 1);
if (isNewBossPlusMode) {
boss.maxHealth = 600;
boss.health = boss.maxHealth;
this.gameDuration = 600;
boss.attackSpeedMultiplier = 0.6;
boss.speed = 7;
boss.ultimateAttackCooldownTimer = 0;
boss.nextUltimateAttackAvailableTime = boss.ultimateAttackMinInterval;
console.log("Starting NEW BOSS+ mode. HP:", boss.maxHealth, "Time:", this.gameDuration);
} else {
boss.maxHealth = 160;
boss.health = boss.maxHealth;
this.gameDuration = 120;
boss.speed = 5;
boss.attackSpeedMultiplier = 1;
console.log("Starting STANDARD mode. HP:", boss.maxHealth, "Time:", this.gameDuration);
}
walls.forEach(function (wall) {
if (wall) {
wall.alpha = 1;
}
});
ui.positionElements("game");
if (ui && player) {
ui.updateHearts(player.health, parseInt(storage.maxHearts, 10) || 5);
} else if (ui) {
ui.updateHearts(0, parseInt(storage.maxHearts, 10) || 5);
}
ui.showTutorial("Swipe to Roll!");
ui.updateBossHealth(boss.health, boss.maxHealth);
if (coffinMemeImage && !coffinMemeImage.destroyed) {
coffinMemeImage.destroy();
coffinMemeImage = null;
}
try {
var assetMeta = null;
try {
assetMeta = LK.getAssetMeta('coffinDanceMeme');
} catch (metaError) {
console.warn("LK.getAssetMeta nie działa lub brak assetu 'coffinDanceMeme'. Używam domyślnej wysokości 200.");
}
var memeHeight = assetMeta ? assetMeta.height : 200;
coffinMemeImage = LK.getAsset('coffinDanceMeme', {
anchorX: 0.5,
anchorY: 1.0,
x: 2048 / 2,
y: 2732 + memeHeight,
alpha: 1
});
var uiIndex = game.getChildIndex(ui);
if (uiIndex > -1) {
game.addChildAt(coffinMemeImage, uiIndex);
} else {
game.addChild(coffinMemeImage);
}
console.log("Coffin dance meme załadowany i schowany, y start:", coffinMemeImage.y, "Height:", memeHeight);
} catch (e) {
console.error("Nie udało się stworzyć/załadować assetu coffinDanceMeme:", e);
coffinMemeImage = null;
}
this.remainingTime = this.gameDuration;
ui.updateTimerDisplay(this.remainingTime);
if (this.gameTimerInterval) {
LK.clearInterval(this.gameTimerInterval);
}
this.bossSpeedIncreased = false;
this.gameTimerInterval = LK.setInterval(function () {
if (_this.currentState === "game") {
_this.remainingTime--;
ui.updateTimerDisplay(_this.remainingTime);
if (!isNewBossPlusMode) {
var accelerationThreshold = _this.gameDuration - 60;
if (_this.remainingTime <= accelerationThreshold && !_this.bossSpeedIncreased) {
_this.bossSpeedIncreased = true;
if (boss && !boss.dead) {
boss.attackSpeedMultiplier *= 0.7;
boss.speed += 1;
ui.showMessage("Boss attacks faster!", 2000);
}
}
}
if (_this.remainingTime <= 0) {
LK.clearInterval(_this.gameTimerInterval);
_this.gameTimerInterval = null;
if (isNewBossPlusMode) {
gameOverReasonIsDeath = false;
gameState.gameOver(false);
} else {
gameState.showGrillScreen();
}
}
} else {
if (_this.gameTimerInterval) {
LK.clearInterval(_this.gameTimerInterval);
}
_this.gameTimerInterval = null;
}
}, 1000);
LK.setTimeout(function () {
if (_this.currentState === "game") {
ui.hideTutorial();
}
}, 3000);
},
// Koniec funkcji startGame
// victory() - nieużywane, logika w timerze i boss.die()
showGrillScreen: function showGrillScreen() {
this.rollMasterTime = 0; // <-- DODAJ TĘ LINIĘ
isNewBossPlusMode = false;
console.log("State: Grill Menu (isNewBossPlusMode reset to false)");
// Wyczyść timery (pozostaw ten fragment)
if (this.gameTimerInterval) {
LK.clearInterval(this.gameTimerInterval);
}
this.gameTimerInterval = null;
if (this.fakeTutorialTimerId) {
LK.clearTimeout(this.fakeTutorialTimerId);
}
this.fakeTutorialTimerId = null;
this.bossSpeedIncreased = false; // Reset flagi
// --- DODANY KOD: Tablica do przechowywania efektów z menu grilla ---
gameState.grillMenuEffects = []; // Tworzymy nową tablicę przy wejściu do menu
// --- KONIEC DODANEGO KODU ---
// --- Rozpoczęcie agresywnego czyszczenia sceny ---
// Stwórz listę obiektów (kontenerów, UI, ścian), które powinny pozostać w 'game'.
// Upewnij się, że 'ui', 'currentSceneElements' i wszystkie obiekty w tablicy 'walls' są tutaj.
var childrenToKeep = [ui, currentSceneElements].concat(walls);
// Przejdź przez wszystkie dzieci głównego obiektu 'game' od końca do początku
for (var i = game.children.length - 1; i >= 0; i--) {
var child = game.children[i];
// Sprawdź, czy obecne dziecko NIE jest na liście obiektów do zachowania
if (childrenToKeep.indexOf(child) === -1) {
// console.log("Usuwam obiekt:", child); // Możesz tymczasowo odkomentować do debugowania
// Usuń i zniszcz obiekt
if (child && child.destroy) {
child.destroy(); // Użyj destroy jeśli dostępne (zalecane w LK)
} else if (child && child.parent) {
child.parent.removeChild(child); // Fallback - usuń z rodzica
}
}
}
// Po agresywnym czyszczeniu, upewnij się, że globalne zmienne gracza, bossa i tła są null
// Te obiekty powinny zostać usunięte przez powyższą pętlę, ale warto zresetować zmienne.
currentBackground = null;
player = null;
boss = null;
// --- Koniec agresywnego czyszczenia sceny ---
this.currentState = "grillMenu";
game.setBackgroundColor(0x333333); // Tło grilla
// Teraz dodaj nowe tło dla ekranu Grilla (ten fragment zostaje taki, jak go poprawiliśmy ostatnio - JEDNO dodanie grillMenu)
currentBackground = LK.getAsset('grillMenu', {
anchorX: 0.5,
anchorY: 0.5,
x: 2048 / 2,
y: 2732 / 2
});
// Dodaj nowe tło na spód
if (currentBackground) {
// Sprawdź czy asset został poprawnie załadowany
game.addChildAt(currentBackground, 0);
}
var confirmRestButton = null;
// ✨ DODAJEMY 5 SPARKLINGÓW ✨
// Sparkle 1
var sparkle1 = game.addChild(LK.getAsset('sparkle', {
anchorX: 0.5,
anchorY: 0.5,
x: 2048 / 2 - 460,
y: 2732 / 2 + 690
}));
gameState.grillMenuEffects.push(sparkle1); // <--- DODANA LINIJA
// Sparkle 2
var sparkle2 = game.addChild(LK.getAsset('sparkle', {
anchorX: 0.5,
anchorY: 0.5,
x: 2048 / 2 - 520,
y: 2732 / 2 + 580
}));
gameState.grillMenuEffects.push(sparkle2); // <--- DODANA LINIJA
// Sparkle 3
var sparkle3 = game.addChild(LK.getAsset('sparkle', {
anchorX: 0.5,
anchorY: 0.5,
x: 2048 / 2 - 660,
y: 2732 / 2 + 550
}));
gameState.grillMenuEffects.push(sparkle3); // <--- DODANA LINIJA
// Sparkle 4
var sparkle4 = game.addChild(LK.getAsset('sparkle', {
anchorX: 0.5,
anchorY: 0.5,
x: 2048 / 2 - 500,
y: 2732 / 2 + 680
}));
gameState.grillMenuEffects.push(sparkle4); // <--- DODANA LINIJA
// Sparkle 5
var sparkle5 = game.addChild(LK.getAsset('sparkle', {
anchorX: 0.5,
anchorY: 0.5,
x: 2048 / 2 - 620,
y: 2732 / 2 + 720
}));
gameState.grillMenuEffects.push(sparkle5); // <--- DODANA LINIJA
// Funkcja animacji sparkle
function animateSparkle(s) {
// Sprawdzenie stanu (pozostaje bez zmian)
if (gameState.currentState !== "grillMenu" || !s || s.destroyed) {
return;
}
// ZAPISZ pozycję Y na początku TEGO cyklu animacji
var initialYThisCycle = s.y;
s.alpha = 0; // Rozpocznij od przezroczystości 0
// Pozycję Y ustawimy za chwilę, nie ma potrzeby resetowania jej tutaj od razu
// Animacja pojawienia się i ruchu w górę
tween(s, {
alpha: 1,
y: initialYThisCycle - 40 // Przesuń w górę WZGLĘDEM ZAPISANEJ pozycji
}, {
duration: 800,
easing: tween.easeOut,
onFinish: function onFinish() {
// Po zakończeniu ruchu w górę
if (gameState.currentState !== "grillMenu" || !s || s.destroyed) {
return;
}
// Animacja zanikania (na tej wyższej pozycji)
tween(s, {
alpha: 0
}, {
duration: 300,
easing: tween.easeIn,
onFinish: function onFinish() {
// Po zakończeniu zanikania
if (gameState.currentState !== "grillMenu" || !s || s.destroyed) {
return;
}
// *** KLUCZOWA ZMIANA: Resetuj pozycję Y PRZED następnym cyklem ***
s.y = initialYThisCycle; // <-- WRÓĆ do pozycji Y z początku tego cyklu
// Zaplanuj kolejny cykl animacji
LK.setTimeout(function () {
animateSparkle(s); // Wywołaj kolejny cykl (teraz zacznie z poprawnej pozycji Y)
}, Math.random() * 500 + 300);
}
});
}
});
}
// Start animacji sparklingów
// Upewnij się, że animacje startują z pewnym opóźnieniem po dodaniu do tablicy
LK.setTimeout(function () {
if (gameState.currentState === "grillMenu") {
animateSparkle(sparkle1);
}
}, Math.random() * 500);
LK.setTimeout(function () {
if (gameState.currentState === "grillMenu") {
animateSparkle(sparkle2);
}
}, Math.random() * 500 + 500);
LK.setTimeout(function () {
if (gameState.currentState === "grillMenu") {
animateSparkle(sparkle3);
}
}, Math.random() * 500 + 1000);
LK.setTimeout(function () {
if (gameState.currentState === "grillMenu") {
animateSparkle(sparkle4);
}
}, Math.random() * 500 + 1500);
LK.setTimeout(function () {
if (gameState.currentState === "grillMenu") {
animateSparkle(sparkle5);
}
}, Math.random() * 500 + 2000);
// 🌟 DODAJEMY 3 GWIAZDKI NA NIEBIE 🌟
// Star 1
var star1 = game.addChild(LK.getAsset('star', {
anchorX: 0.5,
anchorY: 0.5,
x: 2048 / 2 - 800,
y: 2732 / 2 - 1000
}));
gameState.grillMenuEffects.push(star1); // <--- DODANA LINIJA
// Star 2
var star2 = game.addChild(LK.getAsset('star', {
anchorX: 0.5,
anchorY: 0.5,
x: 2048 / 2 - 300,
y: 2732 / 2 - 1150
}));
gameState.grillMenuEffects.push(star2); // <--- DODANA LINIJA
// Star 3
var star3 = game.addChild(LK.getAsset('star', {
anchorX: 0.5,
anchorY: 0.5,
x: 2048 / 2 - 200,
y: 2732 / 2 - 900
}));
gameState.grillMenuEffects.push(star3); // <--- DODANA LINIJA
// Funkcja animacji gwiazdek
function animateStar(star) {
// --- DODANE SPRAWDZENIE STANU ---
if (gameState.currentState !== "grillMenu" || !star || star.destroyed) {
return;
}
// --- KONIEC DODANEGO SPRAWDZENIA ---
star.alpha = 0;
tween(star, {
alpha: 1
}, {
duration: 2000,
easing: tween.easeIn,
onFinish: function onFinish() {
// --- DODANE SPRAWDZENIE STANU W CALLBACKU ---
if (gameState.currentState !== "grillMenu" || !star || star.destroyed) {
return;
}
// --- KONIEC DODANEGO SPRAWDZENIA ---
tween(star, {
alpha: 0
}, {
duration: 2000,
easing: tween.easeOut,
onFinish: function onFinish() {
// --- DODANE SPRAWDZENIE STANU W WEWNĘTRZNYM CALLBACKU ---
if (gameState.currentState !== "grillMenu" || !star || star.destroyed) {
return;
}
// --- KONIEC DODANEGO SPRAWDZENIA ---
// Po całym cyklu czekamy losowo 2-5 sekund przed kolejnym pojawieniem się
LK.setTimeout(function () {
animateStar(star);
}, Math.random() * 3000 + 2000);
}
});
}
});
}
// Start animacji gwiazdek
// Upewnij się, że animacje startują z pewnym opóźnieniem po dodaniu do tablicy
LK.setTimeout(function () {
if (gameState.currentState === "grillMenu") {
animateStar(star1);
}
}, Math.random() * 1000);
LK.setTimeout(function () {
if (gameState.currentState === "grillMenu") {
animateStar(star2);
}
}, Math.random() * 1000 + 500);
LK.setTimeout(function () {
if (gameState.currentState === "grillMenu") {
animateStar(star3);
}
}, Math.random() * 1000 + 1000);
// --- KONIEC gwiazdek ---
// --- KONIEC sparklingów ---
// Ukryj ściany (jeśli są widoczne w menu)
walls.forEach(function (wall) {
if (wall) {
wall.alpha = 0;
}
});
// Ustaw UI dla Grill Menu
ui.positionElements("grillMenu");
ui.updateBossHealth(0, 1); // Ukryj pasek HP bossa
ui.updateHearts(0, storage.maxHearts); // Ukryj serca gracza
// --- Przyciski na ekranie Grilla ---
var buttonYStart = 1000;
var buttonYOffset = 150;
// Przycisk "Rest"
var restButton = new Container();
restButton.interactive = true;
restButton.cursor = "pointer";
restButton.x = 600;
restButton.y = 650;
currentSceneElements.addChild(restButton);
var restButtonBg = LK.getAsset('buttonRest', {
anchorX: 0.5,
anchorY: 0.5
});
restButton.addChild(restButtonBg);
restButton.down = function () {
if (!confirmRestButton || confirmRestButton && confirmRestButton.destroyed) {
confirmRestButton = new Container();
confirmRestButton.interactive = true;
confirmRestButton.cursor = "pointer";
// Zamieniamy tekst i tło na konkretny asset
var confirmButtonGraphic = LK.getAsset('confirmRestButton', {
anchorX: 0.5,
anchorY: 0.5
});
confirmRestButton.addChild(confirmButtonGraphic);
var restButtonActualWidth = restButtonBg.width || 500;
var confirmButtonActualWidth = confirmButtonGraphic.width || 550;
var paddingBetweenButtons = 30;
confirmRestButton.x = restButton.x + restButtonActualWidth / 2 + confirmButtonActualWidth / 2 + paddingBetweenButtons;
confirmRestButton.y = restButton.y;
currentSceneElements.addChild(confirmRestButton);
confirmRestButton.down = function () {
var farewellGraphic = null;
try {
farewellGraphic = LK.getAsset('grillMenuFarewellGraphic', {
anchorX: 0.5,
anchorY: 0.5,
x: 2048 / 2,
y: 2732 / 2,
alpha: 1 // od razu widoczna
});
currentSceneElements.addChild(farewellGraphic);
LK.setTimeout(function () {
if (gameState.currentState === "grillMenu" && farewellGraphic && !farewellGraphic.destroyed) {
tween(farewellGraphic, {
alpha: 0
}, {
duration: 500,
onFinish: function onFinish() {
if (farewellGraphic && !farewellGraphic.destroyed) {
currentSceneElements.removeChild(farewellGraphic);
farewellGraphic.destroy();
}
if (confirmRestButton && !confirmRestButton.destroyed) {
currentSceneElements.removeChild(confirmRestButton);
confirmRestButton.destroy();
confirmRestButton = null;
}
}
});
} else if (farewellGraphic && !farewellGraphic.destroyed) {
currentSceneElements.removeChild(farewellGraphic);
farewellGraphic.destroy();
}
}, 6000);
} catch (e) {
console.error("Błąd grillMenuFarewellGraphic:", e);
farewellGraphic = new Shape({
width: 2048,
height: 2732,
color: 0x101030
});
farewellGraphic.x = 2048 / 2;
farewellGraphic.y = 2732 / 2;
farewellGraphic.alpha = 0;
currentSceneElements.addChild(farewellGraphic);
tween(farewellGraphic, {
alpha: 0.8
}, {
duration: 500
});
LK.setTimeout(function () {
if (farewellGraphic && !farewellGraphic.destroyed) {
tween(farewellGraphic, {
alpha: 0
}, {
duration: 500,
onFinish: function onFinish() {
if (farewellGraphic && !farewellGraphic.destroyed) {
currentSceneElements.removeChild(farewellGraphic);
farewellGraphic.destroy();
}
if (confirmRestButton && !confirmRestButton.destroyed) {
currentSceneElements.removeChild(confirmRestButton);
confirmRestButton.destroy();
confirmRestButton = null;
}
}
});
}
}, 6000);
if (ui && ui.showMessage) {
ui.showMessage("Błąd: Brak assetu grafiki. Placeholder.", 3000);
}
}
};
} else {
console.log("Przycisk 'Potwierdź' już istnieje.");
}
};
// Przycisk "Upgrade Roll" (TEN I KOLEJNE PRZYCISKI POZOSTAJĄ BEZ ZMIAN)
var upgradeButton = new Container();
upgradeButton.interactive = true;
upgradeButton.x = 600;
upgradeButton.y = 850;
currentSceneElements.addChild(upgradeButton);
var upgradeButtonBg = LK.getAsset('buttonUpgrade', {
anchorX: 0.5,
anchorY: 0.5
});
upgradeButton.addChild(upgradeButtonBg);
upgradeButton.down = function () {
if (currentBackground) {
tween(currentBackground, {
alpha: 0
}, {
duration: 600,
easing: tween.easeIn,
onFinish: function onFinish() {
currentBackground.destroy();
currentBackground = LK.getAsset('upgradebg', {
anchorX: 0.5,
anchorY: 0.5,
x: 2048 / 2,
y: 2732 / 2,
alpha: 0
});
game.addChildAt(currentBackground, 0);
tween(currentBackground, {
alpha: 1
}, {
duration: 1200,
easing: tween.easeOut
});
}
});
}
};
// Przycisk "New Boss+"
var newBossButton = new Container();
newBossButton.interactive = true;
newBossButton.cursor = "pointer";
newBossButton.x = 600;
newBossButton.y = 1050; // jeszcze niżej
currentSceneElements.addChild(newBossButton); // Dodaj do kontenera sceny
var newBossButtonBg = LK.getAsset('buttonBoss', {
anchorX: 0.5,
anchorY: 0.5
});
newBossButton.addChild(newBossButtonBg);
newBossButton.down = function () {
isNewBossPlusMode = true;
// Poprzedni kod usuwający sparkle tutaj jest już niepotrzebny,
// ponieważ obsługa usuwania jest teraz w startGame z wykorzystaniem gameState.grillMenuEffects
gameState.startGame();
};
// Przycisk "Roll Master"
var rollMasterButton = new Container(); // Tworzymy kontener na przycisk
rollMasterButton.interactive = true; // Ustawiamy interaktywność
rollMasterButton.cursor = "pointer"; // Zmieniamy kursor po najechaniu
rollMasterButton.x = 600; // Ta sama pozycja X co inne przyciski
rollMasterButton.y = 2500; // Pozycja Y pod przyciskiem "New Boss+"
currentSceneElements.addChild(rollMasterButton); // Dodajemy kontener do sceny
// --- NOWY KOD TŁA ---
// Pobieramy asset obrazka dla przycisku Roll Master
var rollMasterButtonImage = LK.getAsset('buttonRollMaster', {
anchorX: 0.5,
// Ustawiamy punkt zaczepienia na środek obrazka
anchorY: 0.5
});
rollMasterButton.addChild(rollMasterButtonImage); // Dodajemy obrazek do kontenera przycisku
// --- KONIEC NOWEGO KODU TŁA ---
// Usunęliśmy kod tworzący i dodający obiekt Text2
// Akcja przycisku (kliknięcie) pozostaje bez zmian
rollMasterButton.down = function () {
gameState.startRollMasterMode(); // Uruchom tryb Roll Master
};
// Przycisk "Cursed Crystal"
var cursedCrystalButton = new Container();
cursedCrystalButton.interactive = true;
cursedCrystalButton.cursor = "pointer";
cursedCrystalButton.x = 600; // Ta sama kolumna co inne główne przyciski
// Ustaw pozycję Y pod przyciskiem "New Boss+"
// Zakładamy, że newBossButton i newBossButtonBg istnieją i są zdefiniowane powyżej
var newBossButtonHeight = newBossButtonBg && newBossButtonBg.height ? newBossButtonBg.height : 200; // Domyślna wysokość, jeśli nieznana
var verticalSpacing = 700; // Odstęp między przyciskami
// --- POCZĄTEK POPRAWKI dla LK.getAssetMeta ---
var cursedCrystalButtonAssetForSizing;
var cursedCrystalButtonHeight = 250; // Domyślna wysokość assetu buttonCursedCrystal (zgodnie z definicją w LK.init.image)
try {
// Pobierz asset, aby spróbować odczytać jego rzeczywistą wysokość
cursedCrystalButtonAssetForSizing = LK.getAsset('buttonCursedCrystal', {});
if (cursedCrystalButtonAssetForSizing && typeof cursedCrystalButtonAssetForSizing.height === 'number') {
cursedCrystalButtonHeight = cursedCrystalButtonAssetForSizing.height;
}
} catch (e) {
// Jeśli wystąpi błąd przy pobieraniu assetu (np. jeszcze niezaładowany lub błąd ID),
// użyjemy domyślnej wysokości i wyświetlimy ostrzeżenie.
console.warn("Nie można pobrać assetu 'buttonCursedCrystal' do ustalenia wysokości, używam domyślnej: " + cursedCrystalButtonHeight, e);
}
// Oblicz pozycję Y przycisku Cursed Crystal
cursedCrystalButton.y = newBossButton.y + newBossButtonHeight / 2 + verticalSpacing + cursedCrystalButtonHeight / 2;
// --- KONIEC POPRAWKI dla LK.getAssetMeta ---
currentSceneElements.addChild(cursedCrystalButton);
var cursedCrystalButtonImage = LK.getAsset('buttonCursedCrystal', {
anchorX: 0.5,
anchorY: 0.5
});
cursedCrystalButton.addChild(cursedCrystalButtonImage);
cursedCrystalButton.down = function () {
if (gameState.currentState === "grillMenu") {
console.log("Przycisk Cursed Crystal kliknięty!");
gameState.startCursedCrystalMode();
}
};
var creditsButton = new Container();
creditsButton.interactive = true;
creditsButton.cursor = "pointer";
// Ustaw pozycję dla prawego dolnego rogu
// Te wartości (X, Y) są odległościami od lewego górnego rogu ekranu (0,0)
// Musisz je dostosować do rozmiaru swojego przycisku i ekranu.
// Załóżmy, że Twój przycisk 'buttoncredits' ma np. 200px szerokości i 80px wysokości.
// Aby umieścić go w prawym dolnym rogu z marginesem 50px:
var creditsButtonAsset = LK.getAsset('buttoncredits', {}); // Pobierz asset, aby sprawdzić wymiary jeśli trzeba
var creditsButtonWidth = creditsButtonAsset.width || 200; // Domyślna szerokość, jeśli nie ma w assecie
var creditsButtonHeight = creditsButtonAsset.height || 80; // Domyślna wysokość
creditsButton.x = 2048 - creditsButtonWidth / 2 - 50; // Od prawej krawędzi (2048) odejmij połowę szerokości przycisku i margines
creditsButton.y = 2732 - creditsButtonHeight / 2 - 50; // Od dolnej krawędzi (2732) odejmij połowę wysokości przycisku i margines
currentSceneElements.addChild(creditsButton);
// Użyj swojego nowego assetu 'buttoncredits' jako tła/grafiki przycisku
var creditsButtonBg = LK.getAsset('buttoncredits', {
anchorX: 0.5,
// Anchor na środek, bo pozycjonujemy kontener
anchorY: 0.5
// Nie potrzebujesz tutaj scale, chyba że Twój asset jest za duży/mały
});
creditsButton.addChild(creditsButtonBg);
// Tekst na przycisku (jeśli Twój asset 'buttoncredits' nie ma już napisu)
// Jeśli 'buttoncredits' to już gotowy obrazek z napisem "Credits", ten obiekt Text2 nie jest potrzebny.
var creditsButtonText = new Text2("Credits", {
size: 30,
// Dostosuj rozmiar, aby pasował do Twojego przycisku
fill: 0xFFFFFF // Kolor tekstu
// Możesz dodać stroke, jeśli tekst jest na skomplikowanym tle
// stroke: 0x000000,
// strokeThickness: 2
});
creditsButtonText.anchor.set(0.5, 0.5); // Wyśrodkuj tekst na przycisku
creditsButton.addChild(creditsButtonText); // Dodaj tekst DO kontenera przycisku
creditsButton.down = function () {
if (gameState.currentState === "grillMenu") {
console.log("Przycisk Credits kliknięty!"); // Dodaj log dla testu
gameState.showCredits();
}
};
},
showCredits: function showCredits() {
var self = this;
var previousState = this.currentState;
var wasInputActive = self.isInputActive;
self.isInputActive = false; // Tymczasowo zablokuj input gry
// self.creditsPlaying = true; // Opcjonalna flaga, jeśli potrzebujesz
console.log("[Credits] Pokazywanie Creditsów z własnym tłem");
// 1. Stwórz tło 'creditsbg'
var creditsBackground; // Zmienna na nasze tło
try {
creditsBackground = LK.getAsset('creditsbg', {
anchorX: 0.5,
// Zakładamy, że chcesz je wyśrodkować
anchorY: 0.5,
x: 2048 / 2,
y: 2732 / 2,
alpha: 0 // Zacznij od przezroczystości
});
game.addChild(creditsBackground); // Dodaj do sceny
} catch (e) {
console.error("[Credits] Błąd podczas ładowania assetu 'creditsbg':", e);
// Awaryjnie: stwórz czarne tło, jeśli asset się nie załadował
creditsBackground = new Shape({
width: 2048,
height: 2732,
color: 0x111010
}); // Użyjmy Twojego działającego koloru
creditsBackground.x = 2048 / 2;
creditsBackground.y = 2732 / 2;
creditsBackground.alpha = 0;
game.addChild(creditsBackground);
}
var creditsText = null; // Zmienna na tekst creditsów
var creditsTextTween = null; // Zmienna na animację tekstu
// Handler do pomijania creditsów
var _skipCreditsHandler = function skipCreditsHandler() {
console.log("[Credits] Pominięto creditsy");
// Zatrzymaj animacje
tween.stop(creditsBackground);
if (creditsTextTween) {
tween.stop(creditsTextTween);
}
// Usuń obiekty
if (creditsText && !creditsText.destroyed) {
creditsText.destroy();
}
if (creditsBackground && !creditsBackground.destroyed) {
creditsBackground.destroy();
}
// Przywróć stan
self.isInputActive = wasInputActive;
// self.creditsPlaying = false;
game.off('down', _skipCreditsHandler); // Usuń listener
// Jeśli currentState było zmieniane na coś w stylu "creditsScreenActive", przywróć je teraz
// self.currentState = previousState;
// W tej wersji nie zmieniamy currentState, więc nie ma potrzeby przywracania
};
// Animacja pojawienia się tła
tween(creditsBackground, {
alpha: 1
}, {
duration: 500,
// Krótki fade-in
onFinish: function onFinish() {
// Stwórz tekst "Silas"
creditsText = new Text2("Silas", {
size: 150,
fill: 0xFFFFFF,
align: 'center'
});
creditsText.anchor.set(0.5, 0.5);
creditsText.x = 2048 / 2;
creditsText.y = 2732 + creditsText.height;
game.addChild(creditsText);
var textAnimationDuration = 8000;
var textEndY = -creditsText.height;
// Animacja tekstu w górę
creditsTextTween = tween(creditsText, {
y: textEndY
}, {
duration: textAnimationDuration,
easing: tween.linear,
onFinish: function onFinish() {
if (creditsText && !creditsText.destroyed) {
creditsText.destroy();
}
// Animacja zanikania tła 'creditsbg'
tween(creditsBackground, {
alpha: 0
}, {
duration: 500,
onFinish: function onFinish() {
if (creditsBackground && !creditsBackground.destroyed) {
creditsBackground.destroy();
}
console.log("[Credits] Koniec Creditsów, powrót do", previousState);
self.isInputActive = wasInputActive;
// self.creditsPlaying = false;
game.off('down', _skipCreditsHandler); // Usuń listener, jeśli nie został wcześniej wywołany
}
});
}
});
// Dodaj listener do pomijania dopiero po pojawieniu się tła i tekstu (lub od razu, jeśli wolisz)
game.once('down', _skipCreditsHandler);
}
});
},
// Koniec funkcji showGrillScreen (przecinek oddziela ją od następnej metody gameState)
// --- NOWA FUNKCJA: Start przejścia do trybu Roll Master ---
startRollMasterMode: function startRollMasterMode() {
console.log("Rozpoczynanie trybu Roll Master...");
this.currentState = "rollMaster"; // Ustawienie nowego stanu gry
// Wyczyszczenie elementów z poprzedniej sceny (np. Grill Menu)
// Upewnij się, że 'currentSceneElements' to poprawny kontener dla elementów Grill Menu
// lub użyj innej metody czyszczenia specyficznej dla Twojej konfiguracji.
// Poniżej przykład czyszczenia globalnego kontenera, jeśli go używasz:
if (typeof clearScene === 'function') {
// Sprawdź, czy funkcja clearScene istnieje
clearScene(); // Użyj globalnej funkcji clearScene, jeśli jest dostępna
} else if (currentSceneElements && typeof currentSceneElements.removeChildren === 'function') {
// Alternatywnie, jeśli currentSceneElements zawiera tylko elementy menu:
currentSceneElements.removeChildren();
} else {
console.warn("Nie można automatycznie wyczyścić sceny przed Roll Master!");
}
// Dodatkowo, usuń specyficzne efekty z Grill Menu, jeśli istnieją
if (this.grillMenuEffects && this.grillMenuEffects.length > 0) {
this.grillMenuEffects.forEach(function (effect) {
if (effect && effect.destroy) {
effect.destroy();
}
});
this.grillMenuEffects = [];
}
// Usuń tło Grill Menu
if (currentBackground && currentBackground.destroy) {
tween.stop(currentBackground); // Zatrzymaj animacje, jeśli były
currentBackground.destroy();
currentBackground = null;
}
// Wywołanie funkcji konfigurującej scenę tego trybu
this.setupRollMasterScene();
},
// <-- WAŻNE: Przecinek tutaj, bo następuje kolejna funkcja gameState
// --- NOWA FUNKCJA: Konfiguracja sceny Roll Master ---
setupRollMasterScene: function setupRollMasterScene() {
var _this2 = this; // _this2 dla callbacków w timerach poniżej
console.log("[SetupRollMaster] Konfiguracja sceny Roll Master...");
// --- POCZĄTEK SEKCJI ZARZĄDZANIA MUZYKĄ ---
if (this.currentIntroMusicInstance && this.currentIntroMusicInstance.stop) {
console.log("[SetupRollMaster] Zatrzymywanie currentIntroMusicInstance.");
if (typeof this.currentIntroMusicInstance.volume === 'number') {
this.currentIntroMusicInstance.volume = 0;
}
this.currentIntroMusicInstance.stop();
this.currentIntroMusicInstance = null;
}
if (this.currentCursedCrystalMusicInstance && this.currentCursedCrystalMusicInstance.stop) {
this.currentCursedCrystalMusicInstance.stop();
this.currentCursedCrystalMusicInstance = null;
} // <<< DODAJ TUTAJ
if (this.currentRollSoulsInstance && this.currentRollSoulsInstance.stop) {
console.log("[SetupRollMaster] Zatrzymywanie currentRollSoulsInstance.");
if (typeof this.currentRollSoulsInstance.volume === 'number') {
this.currentRollSoulsInstance.volume = 0;
}
this.currentRollSoulsInstance.stop();
this.currentRollSoulsInstance = null;
}
// Zatrzymaj poprzednią instancję rollmaster music, jeśli jakimś cudem istnieje
if (this.currentRollMasterMusicInstance && this.currentRollMasterMusicInstance.stop) {
console.log("[SetupRollMaster] Zatrzymywanie poprzedniej currentRollMasterMusicInstance.");
if (typeof this.currentRollMasterMusicInstance.volume === 'number') {
this.currentRollMasterMusicInstance.volume = 0;
}
this.currentRollMasterMusicInstance.stop();
// this.currentRollMasterMusicInstance = null; // Zerujemy, bo zaraz przypiszemy nową
}
console.log("[SetupRollMaster] Odtwarzanie muzyki 'rollmaster'.");
this.currentRollMasterMusicInstance = LK.playMusic('rollmaster', {
loop: true,
volume: 0.7,
fade: {
start: 0,
end: 0.7,
duration: 1000
}
});
if (!this.currentRollMasterMusicInstance) {
console.error("[SetupRollMaster] LK.playMusic('rollmaster') nie zwróciło instancji!");
}
// --- KONIEC SEKCJI ZARZĄDZANIA MUZYKĄ ---
try {
currentBackground = LK.getAsset('rollMasterBg', {
anchorX: 0.5,
anchorY: 0.5,
x: 2048 / 2,
y: 2732 / 2
});
game.addChildAt(currentBackground, 0);
} catch (e) {
console.warn("Nie znaleziono assetu 'rollMasterBg', używam koloru tła.");
game.setBackgroundColor(0x222233);
}
if (typeof Player !== 'undefined') {
player = game.addChild(new Player());
player.x = 2048 / 2;
player.y = 2732 / 2 + 400;
player.health = 1;
player.rolling = false;
player.invulnerable = false;
player.invulnerabilityFrames = 0;
player.rollCooldown = 0;
if (player && typeof player.clearRollTimeouts === 'function') {
player.clearRollTimeouts();
}
player.hasRolledThroughBossThisRoll = false;
player.alpha = 1;
player.dead = false;
} else {
console.error("Klasa Player nie jest zdefiniowana!");
this.showGrillScreen();
return;
}
this.rollMasterTime = 0;
this.rollMasterDifficulty = 1;
this.rollMasterAttacks = [];
this.attackUnlockOrder = ['rmattack1', 'rmattack2', 'rmattack3', 'rmattack4'];
this.unlockedAttacks = ['rmattack1'];
this.nextUnlockTime = 15;
console.log("Ataki startowe:", this.unlockedAttacks, "Następne odblokowanie w:", this.nextUnlockTime + "s");
this.attackSpawnTimer = 0;
this.attackSpawnInterval = 120;
this.explosionSpawnTimer = 0;
this.explosionSpawnInterval = 240;
this.laserSpawnTimer = 0;
this.laserSpawnInterval = 300;
this.laserWarningTime = 1500;
this.spreaderSpawnTimer = 0;
this.spreaderSpawnInterval = 600;
this.spreaderSplitTime = 180;
if (this.randomRmattack1Timer) {
LK.clearTimeout(this.randomRmattack1Timer);
this.randomRmattack1Timer = null;
}
this.rollMasterHighScore = storage.rollMasterHighScore || 0;
if (ui && ui.updateHighScoreDisplay) {
ui.updateHighScoreDisplay(this.rollMasterHighScore);
} else {
console.error("Nie można zaktualizować wyświetlania rekordu - brak ui.updateHighScoreDisplay");
}
if (ui && ui.timerText) {
ui.updateTimerDisplay(this.rollMasterTime);
} else {
console.error("Nie można znaleźć ui.timerText do skonfigurowania!");
}
if (ui && ui.updateHearts) {
ui.updateHearts(1, 1);
}
if (ui && ui.updateBossHealth) {
ui.updateBossHealth(0, 1);
}
if (ui && ui.positionElements) {
ui.positionElements("rollMaster");
} else {
console.error("Nie można ustawić elementów UI dla Roll Master - brak ui.positionElements");
}
if (this.rollMasterTimerInterval) {
LK.clearInterval(this.rollMasterTimerInterval);
}
this.rollMasterTimerInterval = LK.setInterval(function () {
if (_this2.currentState !== "rollMaster") {
LK.clearInterval(_this2.rollMasterTimerInterval);
_this2.rollMasterTimerInterval = null;
return;
}
_this2.rollMasterTime++;
if (ui && ui.updateTimerDisplay) {
ui.updateTimerDisplay(_this2.rollMasterTime);
}
if (_this2.rollMasterTime > 0 && _this2.rollMasterTime % 20 === 0) {
var newAttackJustUnlocked = false;
var newAttackFriendlyName = "";
if (_this2.unlockedAttacks.length < _this2.attackUnlockOrder.length) {
var nextAttackIndex = _this2.unlockedAttacks.length;
var attackToUnlockTechnicalName = _this2.attackUnlockOrder[nextAttackIndex];
_this2.unlockedAttacks.push(attackToUnlockTechnicalName);
newAttackJustUnlocked = true;
newAttackFriendlyName = attackToUnlockTechnicalName.replace('rmattack1', 'Fart of the Forgotten Pyromancer').replace('rmattack2', 'Blood Ball Z').replace('rmattack3', 'Holy Wifi Beam').replace('rmattack4', 'Flame Skull.exe');
console.log("Odblokowano nowy atak:", attackToUnlockTechnicalName, "w", _this2.rollMasterTime + "s");
}
_this2.increaseRollMasterDifficulty();
if (ui && ui.showMessage) {
var messageToShow = "";
var currentDifficulty = _this2.rollMasterDifficulty;
if (newAttackJustUnlocked) {
messageToShow = "New Attack: " + newAttackFriendlyName + "! (Difficulty: " + currentDifficulty + ")";
} else {
switch (currentDifficulty) {
case 2:
case 3:
case 4:
messageToShow = "Even the pause button is afraid.";
break;
case 5:
messageToShow = "Alright, that’s probably enough difficulty... (Poziom: " + currentDifficulty + ")";
break;
case 6:
messageToShow = "Wait, are you still playing? I ran out of messages. (Poziom: " + currentDifficulty + ")";
break;
case 7:
messageToShow = "Okay seriously, stop. I didn’t test this far. (Poziom: " + currentDifficulty + ")";
break;
default:
messageToShow = "I stopped writing messages after level 7. You're on your own. Level: " + currentDifficulty;
break;
}
}
ui.showMessage(messageToShow, 2500);
}
}
}, 1000);
if (ui && ui.showMessage && this.unlockedAttacks.length > 0) {
var firstAttackName = this.unlockedAttacks[0];
var friendlyFirstName = firstAttackName.replace('rmattack1', 'Fart of the Forgotten Pyromancer').replace('rmattack2', 'Blood Ball Z').replace('rmattack3', 'Holy Wifi Beam').replace('rmattack4', 'Flame Skull.exe');
LK.setTimeout(function () {
if (gameState.currentState === "rollMaster") {
ui.showMessage("First Attack: " + friendlyFirstName, 3000);
}
}, 1000);
}
console.log("Scena Roll Master skonfigurowana i muzyka 'rollmaster' powinna grać.");
},
// <-- WAŻNE: Przecinek tutaj
// --- NOWA FUNKCJA: Zwiększanie trudności w Roll Master ---
increaseRollMasterDifficulty: function increaseRollMasterDifficulty() {
this.rollMasterDifficulty++;
console.log("Trudność Roll Master zwiększona do:", this.rollMasterDifficulty);
var decreaseAmountProjectile = 10;
var minIntervalProjectile = 30;
this.attackSpawnInterval = Math.max(minIntervalProjectile, this.attackSpawnInterval - decreaseAmountProjectile);
console.log("Nowy interwał projectile:", this.attackSpawnInterval);
var decreaseAmountExplosion = 15;
var minIntervalExplosion = 60;
this.explosionSpawnInterval = Math.max(minIntervalExplosion, this.explosionSpawnInterval - decreaseAmountExplosion);
console.log("Nowy interwał explosion:", this.explosionSpawnInterval);
// --- NOWA LOGIKA DLA LASERA ---
var decreaseAmountLaser = 20; // Jak szybko ma skracać się interwał lasera
var minIntervalLaser = 120; // Minimalny interwał lasera (np. 2 sekundy)
this.laserSpawnInterval = Math.max(minIntervalLaser, this.laserSpawnInterval - decreaseAmountLaser);
console.log("Nowy interwał laser:", this.laserSpawnInterval);
// --- NOWA LOGIKA DLA SPREADERA ---
var decreaseAmountSpreader = 30; // Jak szybko ma skracać się interwał spreadera
var minIntervalSpreader = 180; // Minimalny interwał spreadera (np. 3 sekundy)
this.spreaderSpawnInterval = Math.max(minIntervalSpreader, this.spreaderSpawnInterval - decreaseAmountSpreader);
console.log("Nowy interwał spreader:", this.spreaderSpawnInterval);
},
// Przejście do stanu Game Over
// isDeath: true (gracz zginął), false (czas minął w Boss+)
gameOver: function gameOver(isDeathParam) {
// Krok 1: Zabezpieczenie parametru isDeathParam i przechwycenie aktualnego stanu isNewBossPlusMode
var localIsDeath = typeof isDeathParam === 'boolean' ? isDeathParam : true;
var calledDuringBossPlusMode = isNewBossPlusMode; // Przechwyć stan na początku wywołania!
console.log("gameState.gameOver CALLED. isDeathParam:", isDeathParam, "=> localIsDeath:", localIsDeath, ". Flag isNewBossPlusMode at call time (captured as calledDuringBossPlusMode):", calledDuringBossPlusMode, ". Current global isNewBossPlusMode:", isNewBossPlusMode // Dla porównania, jeśli coś by go zmieniło w międzyczasie
);
this.currentState = "gameOver"; // Ustaw stan gry
gameOverReasonIsDeath = localIsDeath; // Ustaw globalną flagę (jeśli nadal jej używasz)
// Krok 2: Zatrzymaj timery i inne procesy specyficzne dla trybu "game"
if (this.gameTimerInterval) {
LK.clearInterval(this.gameTimerInterval);
this.gameTimerInterval = null;
}
// if (this.rollMasterTimerInterval) { // To jest dla Roll Master, nie powinno być tutaj zatrzymywane
// LK.clearInterval(this.rollMasterTimerInterval);
// this.rollMasterTimerInterval = null;
// }
this.bossSpeedIncreased = false; // Reset flagi dla standardowego bossa
// Krok 3: Ukryj aktywne elementy gry (gracz, boss, ściany)
if (player && player.alpha !== 0) {
// Nie niszczymy gracza tutaj, jego animacja śmierci powinna się zakończyć,
// a obiekt player zostanie zniszczony przed pokazaniem przycisków lub restartem.
player.alpha = 0;
}
if (boss && boss.alpha !== 0) {
// Podobnie dla bossa, jego animacja śmierci (jeśli jest) powinna się zakończyć.
// Zostanie zniszczony później.
boss.alpha = 0;
}
walls.forEach(function (wall) {
if (wall) {
wall.alpha = 0;
}
});
// Krok 4: Wstępne ustawienie UI (czyszczenie tekstów)
if (ui) {
ui.positionElements("gameOver"); // Może ukryć niepotrzebne elementy, pokazać stałe dla gameOver
ui.titleText.setText("");
ui.titleText.alpha = 0;
ui.messageText.setText("");
ui.messageText.alpha = 0;
ui.tutorialText.setText("");
ui.tutorialText.alpha = 0; // Ukryj tutorial, jeśli był widoczny
}
// Krok 5: Wyświetl mema śmierci (jeśli localIsDeath) lub komunikat "Time's Up"
var memeDisplayDuration = 3000; // Czas wyświetlania mema/komunikatu przed przyciskami
var deathMemeVisual = null; // Zmienna do przechowywania obiektu mema
if (localIsDeath) {
var memeAssets = ['deathMeme1', 'deathMeme2', 'deathMeme3', 'deathMeme4', 'deathMeme5'];
var randomMemeAssetName = memeAssets[Math.floor(Math.random() * memeAssets.length)];
console.log("[GameOver] Wyświetlanie mema:", randomMemeAssetName);
try {
deathMemeVisual = LK.getAsset(randomMemeAssetName, {
anchorX: 0.5,
anchorY: 0.5,
alpha: 0,
x: 2048 / 2,
y: 2732 / 2 - 150,
// Trochę wyżej, żeby zrobić miejsce na przyciski
scaleX: 0.8,
scaleY: 0.8
});
currentSceneElements.addChild(deathMemeVisual); // Dodaj do kontenera, który będzie czyszczony
tween(deathMemeVisual, {
alpha: 1
}, {
duration: 500,
easing: tween.easeIn
});
} catch (e) {
console.error("[GameOver] Błąd ładowania assetu mema:", randomMemeAssetName, e);
if (ui) {
// Awaryjny tekst, jeśli mem się nie załaduje
ui.titleText.setText("DEFEAT!"); // Tłumaczenie
ui.titleText.style = {
size: 150,
fill: 0xFF0000,
align: 'center'
};
ui.titleText.alpha = 1;
}
}
} else if (calledDuringBossPlusMode && !localIsDeath) {
// Boss+ Time's Up
if (ui) {
ui.titleText.setText("TIME'S UP!"); // Tłumaczenie
ui.titleText.style = {
size: 150,
fill: 0xFFA500,
align: 'center',
stroke: 0x000000,
strokeThickness: 6
};
ui.titleText.alpha = 1;
// Można dodać dodatkowy komunikat poniżej tytułu
// ui.messageText.setText("The challenge ends.");
// ui.messageText.alpha = 1;
}
}
// Krok 6: Timeout, po którym wykonają się akcje końcowe (przyciski lub przejście)
LK.setTimeout(function () {
// Ten console.log był źródłem błędu, teraz używamy localIsDeath i calledDuringBossPlusMode
console.log("DEBUG: Timeout #1 (po memie) w gameState.gameOver. localIsDeath:", localIsDeath, "Captured BossPlus state (calledDuringBossPlusMode):", calledDuringBossPlusMode);
// Usuń mema, jeśli był wyświetlony
if (deathMemeVisual && !deathMemeVisual.destroyed) {
tween(deathMemeVisual, {
alpha: 0
}, {
duration: 300,
onFinish: function onFinish() {
if (deathMemeVisual && deathMemeVisual.parent) {
deathMemeVisual.parent.removeChild(deathMemeVisual);
}
if (deathMemeVisual && deathMemeVisual.destroy) {
deathMemeVisual.destroy();
}
deathMemeVisual = null;
}
});
}
// Wyczyść tytuł/wiadomość, która była z memem, aby przygotować miejsce na nowy tytuł z przyciskami
if (ui) {
ui.titleText.alpha = 0;
ui.messageText.alpha = 0;
}
// Ostateczne czyszczenie obiektów gry (gracz, boss, tło areny)
// clearScene() może być zbyt agresywne, jeśli currentSceneElements zawiera UI. Lepiej ręcznie.
if (player && player.destroy && !player.destroyed) {
player.destroy();
player = null;
}
if (boss && boss.destroy && !boss.destroyed) {
boss.destroy();
boss = null;
}
if (currentBackground) {
// Usuń tło areny, jeśli istnieje
if (currentBackground.parent) {
currentBackground.parent.removeChild(currentBackground);
}
if (currentBackground.destroy && !currentBackground.destroyed) {
currentBackground.destroy();
}
currentBackground = null;
}
game.setBackgroundColor(0x1a1a1a); // Ciemne tło dla ekranu z przyciskami
// Usuń poprzednie aktywne przyciski (np. z Cursed Crystal)
if (gameState.activeButtons && gameState.activeButtons.length > 0) {
gameState.activeButtons.forEach(function (btn) {
if (btn && btn.destroy && !btn.destroyed) {
// Dodatkowe sprawdzenie !btn.destroyed
if (btn.parent) {
btn.parent.removeChild(btn);
} // Usuń z rodzica przed zniszczeniem
btn.destroy();
}
});
}
gameState.activeButtons = []; // Wyczyść tablicę na nowe przyciski
// Logika przycisków lub restartu
if (calledDuringBossPlusMode) {
// Przypadek Boss+ (śmierć LUB koniec czasu) - wyświetl przyciski
console.log("gameOver: Scenariusz Boss+. Wyświetlanie tytułu i przycisków.");
var titleTextForBossPlus = localIsDeath ? "DEFEAT!" : "TIME'S UP!";
if (ui) {
ui.titleText.setText(titleTextForBossPlus);
ui.titleText.style = {
size: 120,
fill: localIsDeath ? 0xFF0000 : 0xFFA500,
align: 'center',
stroke: 0x000000,
strokeThickness: 6
};
ui.titleText.alpha = 1;
ui.titleText.x = 2048 / 2;
ui.titleText.y = 700; // Przykładowa pozycja Y dla tytułu
}
var buttonYStart = (ui && ui.titleText ? ui.titleText.y : 700) + 200;
var buttonSpacing = 220;
// Przycisk "Main Menu"
var menuButton = new Container();
menuButton.interactive = true;
menuButton.cursor = "pointer";
try {
var menuButtonAsset = LK.getAsset('mainmenu', {
anchorX: 0.5,
anchorY: 0.5
});
menuButton.addChild(menuButtonAsset);
} catch (e) {
console.error("Błąd ładowania assetu przycisku 'mainmenu':", e);
// Prosty fallback - tylko kształt, bez tekstu, bo zakładamy, że go nie potrzebujemy,
// skoro główny asset ma tekst. Jeśli chcesz, możesz dodać tu tekst awaryjny.
var menuButtonBgFallback = new Shape({
width: 400,
height: 100,
color: 0x555555,
shape: 'box'
});
menuButton.addChild(menuButtonBgFallback);
}
menuButton.x = 2048 / 2;
menuButton.y = buttonYStart;
menuButton.down = function () {
if (gameState.currentState !== "gameOver") {
return;
} // Dodatkowe zabezpieczenie
gameState.activeButtons.forEach(function (btn) {
if (btn && btn.destroy && !btn.destroyed) {
if (btn.parent) {
btn.parent.removeChild(btn);
}
btn.destroy();
}
});
gameState.activeButtons = [];
isNewBossPlusMode = false;
gameState.showGrillScreen();
};
currentSceneElements.addChild(menuButton); // Dodajemy do currentSceneElements
gameState.activeButtons.push(menuButton);
// Przycisk "Restart Boss+"
var restartButton = new Container();
restartButton.interactive = true;
restartButton.cursor = "pointer";
try {
var restartButtonAsset = LK.getAsset('restartgame', {
anchorX: 0.5,
anchorY: 0.5
});
restartButton.addChild(restartButtonAsset);
} catch (e) {
console.error("Błąd ładowania assetu przycisku 'restartgame':", e);
// Prosty fallback
var restartButtonBgFallback = new Shape({
width: 400,
height: 100,
color: 0x4477FF,
shape: 'box'
}); // Dostosuj wymiary i kolor
restartButton.addChild(restartButtonBgFallback);
}
restartButton.x = 2048 / 2;
restartButton.y = buttonYStart + buttonSpacing;
restartButton.down = function () {
if (gameState.currentState !== "gameOver") {
return;
}
gameState.activeButtons.forEach(function (btn) {
if (btn && btn.destroy && !btn.destroyed) {
if (btn.parent) {
btn.parent.removeChild(btn);
}
btn.destroy();
}
});
gameState.activeButtons = [];
isNewBossPlusMode = true;
gameState.startGame();
};
currentSceneElements.addChild(restartButton);
gameState.activeButtons.push(restartButton);
} else if (localIsDeath && !calledDuringBossPlusMode) {
// Śmierć w standardowym trybie (tutorial) - BEZ PRZYCISKÓW, od razu restart
console.log("gameOver: Standardowa śmierć (tutorial). Restartowanie poziomu.");
isNewBossPlusMode = false;
gameState.startGame();
} else {
// Inny, nieoczekiwany scenariusz (np. !localIsDeath i !calledDuringBossPlusMode)
// To jest fallback, np. jeśli jakimś cudem czas minął w trybie non-Boss+ (co nie powinno się zdarzyć)
console.warn("gameOver: Nieoczekiwany scenariusz (np. timeout w trybie non-Boss+). Przejście do Grill Menu.");
isNewBossPlusMode = false;
gameState.showGrillScreen();
}
}, memeDisplayDuration); // Czas na wyświetlenie mema/komunikatu przed pokazaniem przycisków/przejściem
},
// --- NOWA FUNKCJA: Zakończenie trybu Roll Master ---
// Wewnątrz obiektu gameState:
endRollMasterMode: function endRollMasterMode(finalTime) {
if (this.currentState === "rollMasterGameOver") {
return;
}
console.log("[EndRollMasterMode] Zakończono tryb Roll Master. Czas:", finalTime);
this.currentState = "rollMasterGameOver";
if (this.currentRollMasterMusicInstance && this.currentRollMasterMusicInstance.stop) {
this.currentRollMasterMusicInstance.stop();
this.currentRollMasterMusicInstance = null;
}
// ... (zatrzymanie timerów, czyszczenie ataków RM, logika rekordu - jak w poprzedniej odpowiedzi) ...
if (this.rollMasterTimerInterval) {
LK.clearInterval(this.rollMasterTimerInterval);
this.rollMasterTimerInterval = null;
}
if (this.randomRmattack1Timer) {
LK.clearTimeout(this.randomRmattack1Timer);
this.randomRmattack1Timer = null;
}
if (this.rollMasterAttacks && this.rollMasterAttacks.length > 0) {
this.rollMasterAttacks.forEach(function (atk) {
if (atk.visual && atk.visual.destroy && !atk.visual.destroyed) {
if (atk.visual.parent) {
atk.visual.parent.removeChild(atk.visual);
}
atk.visual.destroy();
}
});
this.rollMasterAttacks = [];
}
if (player && typeof player.clearRollTimeouts === 'function') {
player.clearRollTimeouts();
}
var oldHighScore = parseInt(storage.rollMasterHighScore, 10) || 0;
var newRecordRM = false;
if (finalTime > oldHighScore) {
storage.rollMasterHighScore = finalTime;
this.rollMasterHighScore = finalTime;
newRecordRM = true;
}
if (player && player.alpha !== 0) {
player.alpha = 0; // Ukryj gracza, ale nie niszcz od razu
}
var memeAndScoreDisplayDuration = 3000;
var deathMemeVisualRM = null;
var scoreTextVisualRM = null;
var memeAssets = ['deathMeme1', 'deathMeme2', 'deathMeme3', 'deathMeme4', 'deathMeme5'];
var randomMemeAssetName = memeAssets[Math.floor(Math.random() * memeAssets.length)];
try {
deathMemeVisualRM = LK.getAsset(randomMemeAssetName, {
anchorX: 0.5,
anchorY: 0.5,
alpha: 0,
x: 2048 / 2,
y: 2732 / 2 - 200,
scaleX: 0.8,
scaleY: 0.8
});
// Dodaj mema NAD istniejącym tłem (rollMasterBg), jeśli currentSceneElements jest nad nim
// lub bezpośrednio do 'game' na odpowiedniej warstwie.
// Jeśli currentSceneElements jest używane dla elementów pop-up, to jest dobre miejsce.
game.addChild(deathMemeVisualRM); // Lub currentSceneElements.addChild(deathMemeVisualRM);
tween(deathMemeVisualRM, {
alpha: 1
}, {
duration: 500,
easing: tween.easeIn
});
} catch (e) {
console.error("[EndRollMasterMode] Błąd ładowania mema:", e);
}
var timeMessage = "Your Time: " + String(Math.floor(finalTime / 60)).padStart(2, '0') + ':' + String(finalTime % 60).padStart(2, '0');
if (newRecordRM) {
timeMessage += "\n(NEW HIGH SCORE!)";
} else {
timeMessage += "\nBest: " + String(Math.floor(this.rollMasterHighScore / 60)).padStart(2, '0') + ':' + String(this.rollMasterHighScore % 60).padStart(2, '0');
}
scoreTextVisualRM = new Text2(timeMessage, {
size: 70,
fill: 0xFFFFFF,
align: 'center',
stroke: 0x000000,
strokeThickness: 4,
alpha: 0
});
scoreTextVisualRM.anchor.set(0.5, 0.5);
scoreTextVisualRM.x = 2048 / 2;
var memeHeightEstimate = deathMemeVisualRM && deathMemeVisualRM.height ? deathMemeVisualRM.height * (deathMemeVisualRM.scaleY || 1) : 560; // 700*0.8
scoreTextVisualRM.y = (deathMemeVisualRM ? deathMemeVisualRM.y + memeHeightEstimate / 2 : 2732 / 2 - 200 + 300) + 100;
game.addChild(scoreTextVisualRM); // Lub currentSceneElements.addChild(scoreTextVisualRM);
tween(scoreTextVisualRM, {
alpha: 1
}, {
duration: 500,
delay: 300,
easing: tween.easeIn
});
if (ui && ui.positionElements) {
ui.positionElements("rollMasterGameOver");
}
if (ui && ui.updateHighScoreDisplay) {
ui.updateHighScoreDisplay(this.rollMasterHighScore);
} // Pokaż rekord w UI
// Usuń stare aktywne przyciski (jeśli jakieś były)
if (gameState.activeButtons && gameState.activeButtons.length > 0) {
gameState.activeButtons.forEach(function (btn) {
if (btn && btn.destroy && !btn.destroyed) {
if (btn.parent) {
btn.parent.removeChild(btn);
}
btn.destroy();
}
});
}
gameState.activeButtons = [];
LK.setTimeout(function () {
console.log("DEBUG: Timeout #1 (po memie/wyniku) w endRollMasterMode. Pokazywanie przycisków.");
if (deathMemeVisualRM && !deathMemeVisualRM.destroyed) {
tween(deathMemeVisualRM, {
alpha: 0
}, {
duration: 300,
onFinish: function onFinish() {
if (deathMemeVisualRM.parent) {
deathMemeVisualRM.parent.removeChild(deathMemeVisualRM);
}
if (deathMemeVisualRM.destroy) {
deathMemeVisualRM.destroy();
}
deathMemeVisualRM = null;
}
});
}
if (scoreTextVisualRM && !scoreTextVisualRM.destroyed) {
tween(scoreTextVisualRM, {
alpha: 0
}, {
duration: 300,
onFinish: function onFinish() {
if (scoreTextVisualRM.parent) {
scoreTextVisualRM.parent.removeChild(scoreTextVisualRM);
}
if (scoreTextVisualRM.destroy) {
scoreTextVisualRM.destroy();
}
scoreTextVisualRM = null;
}
});
}
// Zniszcz obiekt gracza teraz, gdy już nie jest potrzebny
if (player && player.destroy && !player.destroyed) {
player.destroy();
player = null;
}
// TŁO: currentBackground (czyli rollMasterBg) POWINNO POZOSTAĆ. Nie niszczymy go.
// NIE wywołuj game.setBackgroundColor, aby nie nadpisać rollMasterBg.
// if (currentBackground) {
// // Jeśli currentBackground to było coś specyficznego dla mema, a nie rollMasterBg,
// // to tutaj trzeba by je usunąć i ewentualnie przywrócić rollMasterBg.
// // Ale zakładamy, że currentBackground to rollMasterBg od początku tego trybu.
// }
// game.setBackgroundColor(0x1a1a1a); // <--- USUWAMY TĘ LINIĘ (lub podobną)
var buttonYStart = 900;
var buttonSpacing = 220;
// Przycisk "Main Menu"
var menuButton = new Container(); /* ... reszta definicji bez zmian ... */
menuButton.interactive = true;
menuButton.cursor = "pointer";
var menuButtonBg = new Shape({
width: 400,
height: 100,
color: 0x555555,
shape: 'box'
});
try {
var menuButtonAsset = LK.getAsset('mainmenu', {
anchorX: 0.5,
anchorY: 0.5
});
menuButton.addChild(menuButtonAsset);
} catch (e) {
console.error("Błąd ładowania assetu przycisku 'mainmenu':", e);
var menuButtonBgFallback = new Shape({
width: 400,
height: 100,
color: 0x555555,
shape: 'box'
});
menuButton.addChild(menuButtonBgFallback);
// Możesz dodać awaryjny Text2, jeśli chcesz, gdyby asset się nie załadował
// var menuButtonTextFallback = new Text2("Main Menu", { size: 40, fill: 0xFFFFFF, anchor: { x: 0.5, y: 0.5 } });
// menuButton.addChild(menuButtonTextFallback);
}
menuButton.x = 2048 / 2;
menuButton.y = buttonYStart;
menuButton.down = function () {
if (gameState.currentState !== "rollMasterGameOver") {
return;
}
gameState.activeButtons.forEach(function (btn) {
if (btn && btn.destroy && !btn.destroyed) {
if (btn.parent) {
btn.parent.removeChild(btn);
}
btn.destroy();
}
});
gameState.activeButtons = [];
// WAŻNE: Zanim przejdziesz do GrillMenu, zniszcz currentBackground (rollMasterBg)
if (currentBackground && currentBackground.destroy && !currentBackground.destroyed) {
if (currentBackground.parent) {
currentBackground.parent.removeChild(currentBackground);
}
currentBackground.destroy();
currentBackground = null;
}
gameState.showGrillScreen();
};
game.addChild(menuButton); // Lub currentSceneElements.addChild(menuButton);
gameState.activeButtons.push(menuButton);
// Przycisk "Restart Mode"
var restartButton = new Container(); /* ... reszta definicji bez zmian ... */
try {
var restartButtonAsset = LK.getAsset('restartgame', {
anchorX: 0.5,
anchorY: 0.5
});
restartButton.addChild(restartButtonAsset);
} catch (e) {
console.error("Błąd ładowania assetu przycisku 'restartgame':", e);
var restartButtonBgFallback = new Shape({
width: 400,
height: 100,
color: 0x4477FF,
shape: 'box'
});
restartButton.addChild(restartButtonBgFallback);
// Możesz dodać awaryjny Text2
// var restartButtonTextFallback = new Text2("Restart Mode", { size: 40, fill: 0xFFFFFF, anchor: { x: 0.5, y: 0.5 } });
// restartButton.addChild(restartButtonTextFallback);
}
restartButton.x = 2048 / 2;
restartButton.y = buttonYStart + buttonSpacing;
restartButton.down = function () {
if (gameState.currentState !== "rollMasterGameOver") {
return;
}
gameState.activeButtons.forEach(function (btn) {
if (btn && btn.destroy && !btn.destroyed) {
if (btn.parent) {
btn.parent.removeChild(btn);
}
btn.destroy();
}
});
gameState.activeButtons = [];
// WAŻNE: Zanim zrestartujesz tryb, zniszcz currentBackground (rollMasterBg)
if (currentBackground && currentBackground.destroy && !currentBackground.destroyed) {
if (currentBackground.parent) {
currentBackground.parent.removeChild(currentBackground);
}
currentBackground.destroy();
currentBackground = null;
}
gameState.startRollMasterMode();
};
game.addChild(restartButton); // Lub currentSceneElements.addChild(restartButton);
gameState.activeButtons.push(restartButton);
}, memeAndScoreDisplayDuration);
},
startCursedCrystalMode: function startCursedCrystalMode() {
if (this.currentIntroMusicInstance && this.currentIntroMusicInstance.stop) {
this.currentIntroMusicInstance.stop();
this.currentIntroMusicInstance = null;
}
if (this.currentRollSoulsInstance && this.currentRollSoulsInstance.stop) {
this.currentRollSoulsInstance.stop();
this.currentRollSoulsInstance = null;
}
if (this.currentRollMasterMusicInstance && this.currentRollMasterMusicInstance.stop) {
this.currentRollMasterMusicInstance.stop();
this.currentRollMasterMusicInstance = null;
}
if (this.currentCursedCrystalMusicInstance && this.currentCursedCrystalMusicInstance.stop) {
this.currentCursedCrystalMusicInstance.stop();
this.currentCursedCrystalMusicInstance = null;
}
var randomIndex = Math.floor(Math.random() * this.cursedCrystalMusicTracks.length);
var selectedTrackId = this.cursedCrystalMusicTracks[randomIndex];
console.log(Date.now() + " [StartCursedCrystal] Próba odtworzenia: " + selectedTrackId);
this.currentCursedCrystalMusicInstance = LK.playMusic(selectedTrackId, {
loop: true,
volume: 0.7
});
// Logowanie wartości zwróconej przez LK.playMusic
console.log(Date.now() + " [StartCursedCrystal] LK.playMusic zwróciło dla '" + selectedTrackId + "':", this.currentCursedCrystalMusicInstance);
if (!this.currentCursedCrystalMusicInstance) {
console.error(Date.now() + " [StartCursedCrystal] Nie udało się odtworzyć muzyki (instancja jest falsy): " + selectedTrackId);
} else {
console.log(Date.now() + " [StartCursedCrystal] Instancja muzyki wydaje się być utworzona dla: " + selectedTrackId);
}
console.log("[StartCursedCrystal] Rozpoczynanie trybu Cursed Crystal...");
console.log("[StartCursedCrystal] Rozpoczynanie trybu Cursed Crystal...");
this.currentState = "cursedCrystal";
// 1. Zatrzymaj muzykę z innych trybów
if (this.currentIntroMusicInstance && this.currentIntroMusicInstance.stop) {
this.currentIntroMusicInstance.stop();
this.currentIntroMusicInstance = null;
}
if (this.currentRollSoulsInstance && this.currentRollSoulsInstance.stop) {
this.currentRollSoulsInstance.stop();
this.currentRollSoulsInstance = null;
}
if (this.currentRollMasterMusicInstance && this.currentRollMasterMusicInstance.stop) {
this.currentRollMasterMusicInstance.stop();
this.currentRollMasterMusicInstance = null;
}
// TODO: Odtwórz muzykę specyficzną dla Cursed Crystal, jeśli ją masz
// np. this.currentCursedCrystalMusic = LK.playMusic('cursedCrystalMusic_asset', { loop: true, volume: 0.7 });
// 2. Wyczyść scenę z elementów poprzedniego trybu
if (typeof clearScene === 'function') {
clearScene(); // Usuwa elementy z currentSceneElements
} else {
console.warn("Funkcja clearScene nie jest zdefiniowana globalnie!");
}
// Dokładne czyszczenie specyficznych elementów z innych trybów:
// Czyszczenie timerów i ataków z Roll Master
if (this.rollMasterTimerInterval) {
LK.clearInterval(this.rollMasterTimerInterval);
this.rollMasterTimerInterval = null;
}
if (this.randomRmattack1Timer) {
// Timer dla rmattack1 w Roll Master
LK.clearTimeout(this.randomRmattack1Timer);
this.randomRmattack1Timer = null;
}
if (this.rollMasterAttacks && this.rollMasterAttacks.length > 0) {
console.log("[StartCursedCrystal] Czyszczenie " + this.rollMasterAttacks.length + " ataków z Roll Master.");
this.rollMasterAttacks.forEach(function (atk) {
if (atk.visual && atk.visual.destroy && !atk.visual.destroyed) {
if (atk.visual.parent) {
// Dodatkowe zabezpieczenie
atk.visual.parent.removeChild(atk.visual);
}
atk.visual.destroy();
}
// Jeśli atak miał inne zasoby (np. własne timery), też powinny być tu czyszczone
});
this.rollMasterAttacks = [];
}
// Usuń specyficzne efekty z Grill Menu, jeśli istnieją
if (this.grillMenuEffects && this.grillMenuEffects.length > 0) {
console.log("[StartCursedCrystal] Czyszczenie " + this.grillMenuEffects.length + " efektów z Grill Menu.");
this.grillMenuEffects.forEach(function (effect) {
if (effect && effect.destroy && !effect.destroyed) {
if (effect.parent) {
// Dodatkowe zabezpieczenie
effect.parent.removeChild(effect);
}
effect.destroy();
}
});
this.grillMenuEffects = [];
}
// Usuń globalne obiekty gracza i bossa, jeśli istnieją
if (player && player.destroy && !player.destroyed) {
// Jeśli gracz ma jakieś specyficzne timery/interwały, które nie są czyszczone w jego .destroy() lub .clearRollTimeouts()
// to tutaj byłoby miejsce na ich wyczyszczenie przed zniszczeniem obiektu player.
// player.clearRollTimeouts(); // To powinno być wywoływane w Player.die() lub przed destroy()
player.destroy();
}
player = null;
if (boss && boss.destroy && !boss.destroyed) {
if (typeof boss.clearAllAttacks === 'function') {
boss.clearAllAttacks("startCursedCrystalMode"); // Upewnij się, że ataki bossa są czyszczone
}
// Podobnie, jeśli boss ma jakieś globalne timery nieczyszczone w .destroy()
boss.destroy();
}
boss = null;
// Usuń istniejące tło, jeśli jest
if (currentBackground && currentBackground.destroy) {
tween.stop(currentBackground); // Zatrzymaj animacje tła, jeśli były
currentBackground.destroy();
currentBackground = null;
}
// Usuń obiekt Klejnotu, jeśli istniał z poprzedniej sesji tego trybu
if (typeof crystalCoreObject !== 'undefined' && crystalCoreObject && crystalCoreObject.destroy && !crystalCoreObject.destroyed) {
crystalCoreObject.destroy();
}
crystalCoreObject = null;
// 3. Wywołaj konfigurację sceny dla Cursed Crystal
// Zamiast this.setupCursedCrystalScene_MinimalTest(); wywołaj normalną funkcję:
this.setupCursedCrystalScene();
// Aby użyć minimalnej sceny testowej, odkomentuj poniższe i zakomentuj powyższe:
// this.setupCursedCrystalScene_MinimalTest();
},
updateCrystalVisual: function updateCrystalVisual() {
if (!crystalCoreObject || crystalCoreObject.destroyed || this.isMinibossActiveCC || this.cursedCrystalChargeLevel >= this.cursedCrystalTargetCharge || this.crystalExploding) {
return;
}
var newAssetId = 'crystal_state_0';
var charge = this.cursedCrystalChargeLevel;
var targetCharge = this.cursedCrystalTargetCharge;
var thresholds = {
state1: 0.15 * targetCharge,
state2: 0.30 * targetCharge,
state3: 0.55 * targetCharge,
state4: 0.80 * targetCharge
};
if (charge >= thresholds.state4) {
newAssetId = 'crystal_state_4';
} else if (charge >= thresholds.state3) {
newAssetId = 'crystal_state_3';
} else if (charge >= thresholds.state2) {
newAssetId = 'crystal_state_2';
} else if (charge >= thresholds.state1) {
newAssetId = 'crystal_state_1';
}
if (crystalCoreObject.currentVisual && crystalCoreObject.currentVisual.assetId === newAssetId) {
return;
}
if (crystalCoreObject.currentVisual) {
crystalCoreObject.removeChild(crystalCoreObject.currentVisual);
if (crystalCoreObject.currentVisual.destroy && !crystalCoreObject.currentVisual.destroyed) {
crystalCoreObject.currentVisual.destroy();
}
crystalCoreObject.currentVisual = null;
}
try {
var newVisual = LK.getAsset(newAssetId, {
anchorX: 0.5,
anchorY: 0.5
});
crystalCoreObject.addChild(newVisual);
crystalCoreObject.currentVisual = newVisual;
crystalCoreObject.currentVisual.assetId = newAssetId;
console.log("Wizualizacja kryształu zmieniona na: " + newAssetId + " (naładowanie: " + charge + ")");
} catch (e) {
console.error("Błąd ładowania assetu kryształu: " + newAssetId, e);
if (newAssetId !== 'crystal_state_0' && (!crystalCoreObject.currentVisual || crystalCoreObject.currentVisual.assetId !== 'crystal_state_0')) {
try {
var baseVisual = LK.getAsset('crystal_state_0', {
anchorX: 0.5,
anchorY: 0.5
});
crystalCoreObject.addChild(baseVisual);
crystalCoreObject.currentVisual = baseVisual;
crystalCoreObject.currentVisual.assetId = 'crystal_state_0';
} catch (e2) {
console.error("Błąd ładowania awaryjnego assetu 'crystal_state_0'", e2);
}
}
}
},
playCrystalExplosionAnimation: function playCrystalExplosionAnimation() {
var crystalOriginalX = 2048 / 2; // Domyślna pozycja X na środek ekranu
var crystalOriginalY = 2732 / 2; // Domyślna pozycja Y na środek ekranu
if (crystalCoreObject && !crystalCoreObject.destroyed) {
// Jeśli crystalCoreObject istnieje, użyj jego aktualnej pozycji
crystalOriginalX = crystalCoreObject.x;
crystalOriginalY = crystalCoreObject.y;
console.log("Odtwarzanie animacji eksplozji kryształu w pozycji: X=" + crystalOriginalX + ", Y=" + crystalOriginalY);
// Zatrzymaj istniejące tweeny na kontenerze kryształu (lewitacja, pulsowanie)
if (crystalCoreObject.levitationTween && crystalCoreObject.levitationTween.stop) {
crystalCoreObject.levitationTween.stop();
crystalCoreObject.levitationTween = null; // Wyczyść referencję
}
if (crystalCoreObject.scaleTween && crystalCoreObject.scaleTween.stop) {
crystalCoreObject.scaleTween.stop();
crystalCoreObject.scaleTween = null; // Wyczyść referencję
}
// Można dodać reset pozycji/skali, jeśli tweeny mogły je zmienić w sposób niepożądany dla animacji
crystalCoreObject.scale.set(1); // Przywróć domyślną skalę kontenera
// crystalCoreObject.y = crystalCoreObject.originalY; // Jeśli chcesz przywrócić oryginalną Y przed animacją
// Usuń obecną statyczną grafikę kryształu (dziecko kontenera crystalCoreObject)
if (crystalCoreObject.currentVisual) {
crystalCoreObject.removeChild(crystalCoreObject.currentVisual);
if (crystalCoreObject.currentVisual.destroy && !crystalCoreObject.currentVisual.destroyed) {
crystalCoreObject.currentVisual.destroy();
}
crystalCoreObject.currentVisual = null;
}
var explosionFramesAssets = [];
for (var i = 0; i < 4; i++) {
// Załóżmy 4 klatki animacji
try {
// Upewnij się, że anchorX i anchorY są ustawione, jeśli Twoje klatki tego wymagają
// dla poprawnego pozycjonowania wewnątrz SpriteAnimation
explosionFramesAssets.push(LK.getAsset('crystal_explosion_f' + i, {
anchorX: 0.5,
anchorY: 0.5,
clone: true
}));
} catch (e) {
console.error("Błąd ładowania klatki eksplozji: crystal_explosion_f" + i, e);
// Awaryjny placeholder jeśli klatki brakuje, aby animacja nadal miała odpowiednią długość
var placeholderFrame = new Shape({
width: 150,
height: 150,
color: 0xFF8C00,
shape: 'ellipse'
});
placeholderFrame.anchor.set(0.5, 0.5); // Ustaw anchor dla Shape
explosionFramesAssets.push(placeholderFrame);
}
}
var explosionAnim = new SpriteAnimation({
frames: explosionFramesAssets,
frameDuration: 150,
// Czas trwania klatki w ms (np. 150ms * 4 klatki = 0.6s) - dostosuj
loop: false,
anchorX: 0.5,
// Anchor samej animacji SpriteAnimation, jeśli ma być inaczej pozycjonowana w crystalCoreObject
anchorY: 0.5
// x i y będą 0,0 względem kontenera crystalCoreObject, jeśli animacja ma być na jego środku
});
crystalCoreObject.addChild(explosionAnim); // Dodaj animację eksplozji do kontenera kryształu
explosionAnim.play();
// TODO: Dodaj tutaj swój dźwięk eksplozji, jeśli masz
// np. LK.getSound('twoj_dzwiek_eksplozji_krysztalu').play();
} else {
// Ten blok jest awaryjny, jeśli crystalCoreObject nie istnieje, gdy funkcja jest wołana
console.warn("Próba eksplozji, ale crystalCoreObject nie istnieje lub został zniszczony przed rozpoczęciem animacji. Bezpośrednie spawnowanie Minibossa w domyślnej pozycji.");
this.spawnCursedCrystalMiniboss(crystalOriginalX, crystalOriginalY);
this.crystalExploding = false; // Zresetuj flagę
return; // Zakończ funkcję, jeśli nie ma kryształu do animowania
}
var selfGameState = this; // Zachowaj referencję 'this' (gameState) dla callbacku onComplete
explosionAnim.onComplete = function () {
console.log("Animacja eksplozji kryształu zakończona.");
// Zniszcz kontener crystalCoreObject po zakończeniu animacji
if (crystalCoreObject && !crystalCoreObject.destroyed) {
if (crystalCoreObject.parent) {
crystalCoreObject.parent.removeChild(crystalCoreObject);
}
crystalCoreObject.destroy();
}
crystalCoreObject = null; // Wyczyść globalną/dostępną referencję
// Wywołaj logikę spawnowania Minibossa, przekazując zapisaną pozycję
selfGameState.spawnCursedCrystalMiniboss(crystalOriginalX, crystalOriginalY);
selfGameState.crystalExploding = false; // Zresetuj flagę po zakończeniu całego procesu
};
},
spawnCursedCrystalMiniboss: function spawnCursedCrystalMiniboss(spawnX, spawnY) {
// Argumenty to spawnX i spawnY
console.log("MINIBOSS SPAWNING SEQUENCE INITIATED! Pozycja: X=" + spawnX + ", Y=" + spawnY);
var self = this;
self.isMinibossActiveCC = true;
if (ui && ui.crystalChargeBarContainer) {
ui.crystalChargeBarContainer.alpha = 0;
}
var timeSurvivedInMinutes = Math.floor(self.cursedCrystalTimeSurvived / (60 * 60));
var baseMinibossHp = 200;
var hpPerMinute = 100;
var scaledMaxHp = baseMinibossHp + timeSurvivedInMinutes * hpPerMinute;
scaledMaxHp = Math.max(baseMinibossHp, scaledMaxHp);
self.cursedCrystalMinibossMaxHP = scaledMaxHp;
self.cursedCrystalMinibossHP = scaledMaxHp;
var twoMinutesInFrames = 1 * 60 * 60; // 2 minuty * 60 sekund * 60 klatek/sekundę
if (self.cursedCrystalTimeSurvived >= twoMinutesInFrames) {
console.log("GRACZ PRZETRWAŁ PONAD 2 MINUTY! Miniboss otrzymuje ulepszone ataki!");
self.minibossEnhancedProjectile = true;
self.minibossEnhancedLaserWall = true;
} else {
self.minibossEnhancedProjectile = false;
self.minibossEnhancedLaserWall = false;
}
self.cursedCrystalEnemies.forEach(function (soul) {
if (soul && !soul.isDead) {
if (soul.parent) {
soul.parent.removeChild(soul);
}
if (soul.destroy && !soul.destroyed) {
soul.destroy();
}
}
});
self.cursedCrystalEnemies = [];
self.cursedCrystalChargeLevel = 0;
if (ui && ui.updateCrystalCharge) {
ui.updateCrystalCharge(self.cursedCrystalChargeLevel, self.cursedCrystalTargetCharge);
}
LK.setTimeout(function () {
if (self.currentState !== "cursedCrystal" || !self.isMinibossActiveCC) {
console.log("Spawn Minibossa anulowany - zmiana stanu gry lub isMinibossActiveCC jest false.");
return;
}
console.log("FAZA SPAWNU MINIBOSSA PO OPÓŹNIENIU");
var minibossOptions = {
maxHp: scaledMaxHp,
x: spawnX,
// ***** POPRAWKA: Używamy argumentu funkcji spawnX *****
y: spawnY // ***** POPRAWKA: Używamy argumentu funkcji spawnY *****
};
self.cursedCrystalMinibossObject = game.addChild(new MinibossCC(minibossOptions));
if (self.cursedCrystalMinibossObject) {
self.cursedCrystalMinibossObject.alpha = 0;
tween(self.cursedCrystalMinibossObject, {
alpha: 1
}, {
duration: 1500,
easing: tween.easeInQuad,
onFinish: function onFinish() {
console.log("Miniboss w pełni widoczny.");
}
});
if (ui && ui.updateMinibossHealthCC) {
ui.updateMinibossHealthCC(self.cursedCrystalMinibossHP, self.cursedCrystalMinibossMaxHP, true);
}
if (ui && ui.showMessage) {
ui.showMessage("Demon Pepe is here", 3000);
}
} else {
console.error("Nie udało się stworzyć obiektu MinibossCC!");
}
}, 3000);
},
setupCursedCrystalScene: function setupCursedCrystalScene() {
console.log("[SetupCursedCrystal] Konfiguracja sceny Cursed Crystal...");
// 1. Ustaw tło dla areny
try {
// Upewnij się, że stary currentBackground jest niszczony, jeśli istnieje
if (typeof currentBackground !== 'undefined' && currentBackground && currentBackground.destroy && !currentBackground.destroyed) {
currentBackground.destroy();
}
currentBackground = LK.getAsset('cursedCrystalArenaBg_asset', {
anchorX: 0,
// Zgodnie z Twoim kodem
anchorY: 0,
// Zgodnie z Twoim kodem
x: 0,
// Zgodnie z Twoim kodem
y: 0 // Zgodnie z Twoim kodem
});
game.addChildAt(currentBackground, 0);
} catch (e) {
console.warn("Nie znaleziono assetu tła Cursed Crystal, używam domyślnego koloru.", e);
game.setBackgroundColor(0x100510);
if (typeof currentBackground !== 'undefined' && currentBackground && currentBackground.destroy && !currentBackground.destroyed) {
currentBackground.destroy();
}
currentBackground = null;
}
// 2. Stwórz obiekt Klejnotu Dusz (CrystalCore) jako KONTENER
if (typeof crystalCoreObject !== 'undefined' && crystalCoreObject) {
if (crystalCoreObject.levitationTween && crystalCoreObject.levitationTween.stop) {
crystalCoreObject.levitationTween.stop();
}
if (crystalCoreObject.scaleTween && crystalCoreObject.scaleTween.stop) {
crystalCoreObject.scaleTween.stop();
}
if (crystalCoreObject.destroy && !crystalCoreObject.destroyed) {
crystalCoreObject.destroy();
}
}
crystalCoreObject = new Container(); // *** NOWOŚĆ: crystalCoreObject jest teraz kontenerem ***
crystalCoreObject.x = 2048 / 2;
crystalCoreObject.y = 2732 / 2;
crystalCoreObject.collisionRadius = 60;
game.addChild(crystalCoreObject);
// Dodaj początkową wizualizację kryształu (0% naładowania)
try {
var initialCrystalVisual = LK.getAsset('crystal_state_0', {
anchorX: 0.5,
anchorY: 0.5
});
crystalCoreObject.addChild(initialCrystalVisual);
crystalCoreObject.currentVisual = initialCrystalVisual; // Referencja do aktualnej grafiki
crystalCoreObject.currentVisual.assetId = 'crystal_state_0'; // Do sprawdzania, co jest wyświetlane
} catch (e) {
console.error("KRYTYCZNY BŁĄD: Nie można załadować 'crystal_state_0'!", e);
// Awaryjny kształt, jeśli assetu nie ma
var fallbackVisual = new Shape({
width: 100,
height: 100,
color: 0x8A2BE2,
shape: 'ellipse'
});
fallbackVisual.anchor.set(0.5);
crystalCoreObject.addChild(fallbackVisual);
crystalCoreObject.currentVisual = fallbackVisual;
crystalCoreObject.currentVisual.assetId = 'fallback_crystal';
}
// Animacje lewitacji i pulsowania dla kontenera crystalCoreObject
if (crystalCoreObject && !crystalCoreObject.destroyed) {
crystalCoreObject.originalY = crystalCoreObject.y;
var levitationHeight = 30;
var oneWayDuration = 1250;
var levitateCrystalUp = function levitateCrystalUp(targetObject) {
if (!targetObject || targetObject.destroyed) {
return;
}
if (targetObject.levitationTween && targetObject.levitationTween.stop) {
targetObject.levitationTween.stop();
}
targetObject.y = targetObject.originalY;
targetObject.levitationTween = tween(targetObject, {
y: targetObject.originalY - levitationHeight
}, {
duration: oneWayDuration,
easing: tween.easeInOut,
onFinish: function onFinish() {
if (targetObject && !targetObject.destroyed) {
levitateCrystalDown(targetObject);
}
}
});
};
var levitateCrystalDown = function levitateCrystalDown(targetObject) {
if (!targetObject || targetObject.destroyed) {
return;
}
if (targetObject.levitationTween && targetObject.levitationTween.stop) {
targetObject.levitationTween.stop();
}
targetObject.levitationTween = tween(targetObject, {
y: targetObject.originalY
}, {
duration: oneWayDuration,
easing: tween.easeInOut,
onFinish: function onFinish() {
if (targetObject && !targetObject.destroyed) {
levitateCrystalUp(targetObject);
}
}
});
};
levitateCrystalUp(crystalCoreObject);
if (crystalCoreObject.scaleTween && crystalCoreObject.scaleTween.stop) {
crystalCoreObject.scaleTween.stop();
}
crystalCoreObject.baseScaleX = crystalCoreObject.scaleX || 1; // Kontener zwykle ma scale 1
crystalCoreObject.baseScaleY = crystalCoreObject.scaleY || 1;
crystalCoreObject.scaleTween = tween(crystalCoreObject, {
scaleX: crystalCoreObject.baseScaleX * 1.05,
scaleY: crystalCoreObject.baseScaleY * 1.05
}, {
duration: oneWayDuration * 2 * 0.75,
easing: tween.easeInOut,
yoyo: true,
repeat: Infinity
});
}
// 3. Stwórz nowy obiekt gracza (bez zmian w tej sekcji)
if (typeof player !== 'undefined' && player && player.destroy && !player.destroyed) {
player.destroy();
}
player = game.addChild(new Player());
player.health = this.cursedCrystalPlayerMaxHealth;
player.x = 2048 / 2;
player.y = (crystalCoreObject && crystalCoreObject.y ? crystalCoreObject.y : 2732 / 2) + (crystalCoreObject && crystalCoreObject.currentVisual && crystalCoreObject.currentVisual.height ? crystalCoreObject.currentVisual.height / 2 : 50) + player.height / 2 + 50; // Pozycjonowanie względem środka/dolnej krawędzi kryształu
player.dead = false;
player.rolling = false;
player.invulnerable = false;
player.rollCooldown = 0;
if (player.clearRollTimeouts) {
player.clearRollTimeouts();
}
this.currentInputPos.x = player.x;
this.currentInputPos.y = player.y;
this.isInputActive = false;
this.cursedCrystalPlayerHealth = this.cursedCrystalPlayerMaxHealth; // Zakładam, że this.cursedCrystalPlayerMaxHealth jest zdefiniowane (np. w gameState lub przekazane)
this.cursedCrystalChargeLevel = 0;
this.cursedCrystalSoulsHitCrystal = 0;
this.cursedCrystalScore = 0;
this.cursedCrystalHighScore = parseInt(storage.cursedCrystalHighScore, 10) || 0;
this.cursedCrystalEnemies = [];
this.cursedCrystalEnemySpawnTimer = 0;
this.cursedCrystalSoulsPerGroupMin = 1;
this.cursedCrystalSoulsPerGroupMax = 5;
this.cursedCrystalGroupSpawnSpread = 60;
this.cursedCrystalBaseSpawnInterval = 90; // Interwał między grupami (np. 3 sekundy)
this.cursedCrystalCurrentSpawnInterval = this.cursedCrystalBaseSpawnInterval;
this.cursedCrystalDifficultyTimer = 0;
this.cursedCrystalEnemyBaseSpeed = 0.1; // Ustawiamy niższą bazową prędkość
this.cursedCrystalEnemyCurrentMaxSpeed = this.cursedCrystalEnemyBaseSpeed; // Aktualna prędkość startuje od bazowej
this.cursedCrystalTimeSurvived = 0;
this.isMinibossActiveCC = false;
this.crystalExploding = false;
if (this.cursedCrystalMinibossObject && this.cursedCrystalMinibossObject.destroy && !this.cursedCrystalMinibossObject.destroyed) {
this.cursedCrystalMinibossObject.destroy();
}
this.cursedCrystalMinibossObject = null;
this.cursedCrystalMinibossHP = 0;
this.cursedCrystalActiveProjectiles = [];
this.cursedCrystalActiveExplosions = [];
this.cursedCrystalActiveLaserWalls = [];
// 5. Konfiguracja UI (bez zmian w tej sekcji)
if (ui) {
ui.positionElements("cursedCrystal");
if (ui.updateScoreCC) {
ui.updateScoreCC(this.cursedCrystalScore);
}
if (ui.updateHighScoreCC) {
ui.updateHighScoreCC(this.cursedCrystalHighScore);
}
if (ui.updateCrystalCharge) {
ui.updateCrystalCharge(this.cursedCrystalChargeLevel, this.cursedCrystalTargetCharge);
}
if (ui.updateHearts) {
ui.updateHearts(this.cursedCrystalPlayerHealth, this.cursedCrystalPlayerMaxHealth);
}
if (ui.updateMinibossHealthCC) {
ui.updateMinibossHealthCC(0, this.cursedCrystalMinibossMaxHP, false);
}
if (ui.bossHealthBarContainer) {
ui.bossHealthBarContainer.alpha = 0;
}
if (ui.timerText) {
ui.timerText.alpha = 0;
}
if (ui.highScoreText) {
ui.highScoreText.alpha = 0;
}
if (ui.deathsText) {
ui.deathsText.alpha = 0;
}
}
// 6. Wyświetl startowy komunikat (bez zmian)
if (ui && ui.showMessage) {
ui.showMessage("Protect the Soul Gem!", 3000);
}
this.gameActive = true;
console.log("[SetupCursedCrystal] Scena Cursed Crystal skonfigurowana. Gracz HP: " + player.health);
}
}, "setupCursedCrystalScene", function setupCursedCrystalScene() {
// <<< NOWY KOD setupCursedCrystalScene ZACZYNA SIĘ TUTAJ >>>
console.log("[SetupCursedCrystal] Konfiguracja sceny Cursed Crystal...");
// 1. Ustaw tło dla areny
try {
if (typeof currentBackground !== 'undefined' && currentBackground && currentBackground.destroy && !currentBackground.destroyed) {
currentBackground.destroy();
}
currentBackground = LK.getAsset('cursedCrystalArenaBg_asset', {
// Podmień na swój asset tła
anchorX: 0,
anchorY: 0,
x: 0,
y: -100
});
game.addChildAt(currentBackground, 0);
} catch (e) {
console.warn("Nie znaleziono assetu tła Cursed Crystal, używam domyślnego koloru.", e);
game.setBackgroundColor(0x100510);
if (typeof currentBackground !== 'undefined' && currentBackground && currentBackground.destroy && !currentBackground.destroyed) {
currentBackground.destroy();
}
currentBackground = null;
}
// 2. Stwórz obiekt Klejnotu Dusz (CrystalCore) jako KONTENER
if (typeof crystalCoreObject !== 'undefined' && crystalCoreObject) {
if (crystalCoreObject.levitationTween && crystalCoreObject.levitationTween.stop) {
crystalCoreObject.levitationTween.stop();
}
if (crystalCoreObject.scaleTween && crystalCoreObject.scaleTween.stop) {
crystalCoreObject.scaleTween.stop();
}
if (crystalCoreObject.destroy && !crystalCoreObject.destroyed) {
crystalCoreObject.destroy();
}
}
crystalCoreObject = new Container(); // *** NOWOŚĆ: crystalCoreObject jest teraz kontenerem ***
crystalCoreObject.x = 2048 / 2;
crystalCoreObject.y = 2732 / 2;
game.addChild(crystalCoreObject);
try {
var initialCrystalVisual = LK.getAsset('crystal_state_0', {
anchorX: 0.5,
anchorY: 0.5
});
crystalCoreObject.addChild(initialCrystalVisual);
crystalCoreObject.currentVisual = initialCrystalVisual;
crystalCoreObject.currentVisual.assetId = 'crystal_state_0';
} catch (e) {
console.error("KRYTYCZNY BŁĄD: Nie można załadować 'crystal_state_0'!", e);
var fallbackVisual = new Shape({
width: 100,
height: 100,
color: 0x8A2BE2,
shape: 'ellipse'
});
fallbackVisual.anchor.set(0.5);
crystalCoreObject.addChild(fallbackVisual);
crystalCoreObject.currentVisual = fallbackVisual;
crystalCoreObject.currentVisual.assetId = 'fallback_crystal';
}
if (crystalCoreObject && !crystalCoreObject.destroyed) {
crystalCoreObject.originalY = crystalCoreObject.y;
var levitationHeight = 30;
var oneWayDuration = 1250;
var levitateCrystalUp = function levitateCrystalUp(targetObject) {
if (!targetObject || targetObject.destroyed) {
return;
}
if (targetObject.levitationTween && targetObject.levitationTween.stop) {
targetObject.levitationTween.stop();
}
targetObject.y = targetObject.originalY;
targetObject.levitationTween = tween(targetObject, {
y: targetObject.originalY - levitationHeight
}, {
duration: oneWayDuration,
easing: tween.easeInOut,
onFinish: function onFinish() {
if (targetObject && !targetObject.destroyed) {
levitateCrystalDown(targetObject);
}
}
});
};
var levitateCrystalDown = function levitateCrystalDown(targetObject) {
if (!targetObject || targetObject.destroyed) {
return;
}
if (targetObject.levitationTween && targetObject.levitationTween.stop) {
targetObject.levitationTween.stop();
}
targetObject.levitationTween = tween(targetObject, {
y: targetObject.originalY
}, {
duration: oneWayDuration,
easing: tween.easeInOut,
onFinish: function onFinish() {
if (targetObject && !targetObject.destroyed) {
levitateCrystalUp(targetObject);
}
}
});
};
levitateCrystalUp(crystalCoreObject);
if (crystalCoreObject.scaleTween && crystalCoreObject.scaleTween.stop) {
crystalCoreObject.scaleTween.stop();
}
crystalCoreObject.baseScaleX = crystalCoreObject.scaleX || 1;
crystalCoreObject.baseScaleY = crystalCoreObject.scaleY || 1;
crystalCoreObject.scaleTween = tween(crystalCoreObject, {
scaleX: crystalCoreObject.baseScaleX * 1.05,
scaleY: crystalCoreObject.baseScaleY * 1.05
}, {
duration: oneWayDuration * 2 * 0.75,
easing: tween.easeInOut,
yoyo: true,
repeat: Infinity
});
}
if (typeof player !== 'undefined' && player && player.destroy && !player.destroyed) {
player.destroy();
}
player = game.addChild(new Player());
player.health = this.cursedCrystalPlayerMaxHealth; // Użyj this
player.x = 2048 / 2;
player.y = (crystalCoreObject && crystalCoreObject.y ? crystalCoreObject.y : 2732 / 2) + (crystalCoreObject && crystalCoreObject.currentVisual && crystalCoreObject.currentVisual.height ? crystalCoreObject.currentVisual.height / 2 : 50) + player.height / 2 + 50;
player.dead = false;
player.rolling = false;
player.invulnerable = false;
player.rollCooldown = 0;
if (player.clearRollTimeouts) {
player.clearRollTimeouts();
}
this.currentInputPos.x = player.x; // Użyj this
this.currentInputPos.y = player.y; // Użyj this
this.isInputActive = false; // Użyj this
this.cursedCrystalPlayerHealth = this.cursedCrystalPlayerMaxHealth; // Użyj this
this.cursedCrystalChargeLevel = 0; // Użyj this
this.cursedCrystalSoulsHitCrystal = 0; // Użyj this
this.cursedCrystalScore = 0; // Użyj this
this.cursedCrystalHighScore = parseInt(storage.cursedCrystalHighScore, 10) || 0; // Użyj this
this.cursedCrystalEnemies = []; // Użyj this
this.cursedCrystalEnemySpawnTimer = 0; // Użyj this
this.cursedCrystalCurrentSpawnInterval = this.cursedCrystalBaseSpawnInterval; // Użyj this
this.cursedCrystalDifficultyTimer = 0; // Użyj this
this.cursedCrystalEnemyCurrentMaxSpeed = this.cursedCrystalEnemyBaseSpeed; // Użyj this
this.cursedCrystalTimeSurvived = 0; // Użyj this
this.isMinibossActiveCC = false; // Użyj this
this.crystalExploding = false; // *** NOWA FLAGA, użyj this ***
if (this.cursedCrystalMinibossObject && this.cursedCrystalMinibossObject.destroy && !this.cursedCrystalMinibossObject.destroyed) {
// Użyj this
this.cursedCrystalMinibossObject.destroy();
}
this.cursedCrystalMinibossObject = null; // Użyj this
this.cursedCrystalMinibossHP = 0; // Użyj this
this.cursedCrystalActiveProjectiles = []; // Użyj this
this.cursedCrystalActiveExplosions = []; // Użyj this
this.cursedCrystalActiveLaserWalls = []; // Użyj this
if (ui) {
ui.positionElements("cursedCrystal");
if (ui.updateScoreCC) {
ui.updateScoreCC(this.cursedCrystalScore);
}
if (ui.updateHighScoreCC) {
ui.updateHighScoreCC(this.cursedCrystalHighScore);
}
if (ui.updateCrystalCharge) {
ui.updateCrystalCharge(this.cursedCrystalChargeLevel, this.cursedCrystalTargetCharge);
}
if (ui.updateHearts) {
ui.updateHearts(this.cursedCrystalPlayerHealth, this.cursedCrystalPlayerMaxHealth);
}
if (ui.updateMinibossHealthCC) {
ui.updateMinibossHealthCC(0, this.cursedCrystalMinibossMaxHP, false);
}
if (ui.bossHealthBarContainer) {
ui.bossHealthBarContainer.alpha = 0;
}
if (ui.timerText) {
ui.timerText.alpha = 0;
}
if (ui.highScoreText) {
ui.highScoreText.alpha = 0;
}
if (ui.deathsText) {
ui.deathsText.alpha = 0;
}
}
if (ui && ui.showMessage) {
ui.showMessage("Catch them all!", 3000);
}
console.log("[SetupCursedCrystal] Scena Cursed Crystal skonfigurowana. Gracz HP: " + player.health);
// <<< NOWY KOD setupCursedCrystalScene KOŃCZY SIĘ TUTAJ >>>
}), "endCursedCrystalMode", function endCursedCrystalMode(isVictory) {
console.log("DEBUG: gameState.endCursedCrystalMode called. Victory: " + isVictory);
this.currentState = "cursedCrystalGameOver";
// --- CZYSZCZENIE AKTYWNYCH ELEMENTÓW TRYBU CC ---
// 1. Dusze (SoulEnemy)
if (this.cursedCrystalEnemies && this.cursedCrystalEnemies.length > 0) {
console.log("DEBUG: Clearing " + this.cursedCrystalEnemies.length + " remaining SoulEnemies.");
this.cursedCrystalEnemies.forEach(function (enemy) {
if (enemy && enemy.destroy && !enemy.destroyed) {
if (enemy.parent) {
enemy.parent.removeChild(enemy);
}
enemy.destroy();
}
});
}
this.cursedCrystalEnemies = [];
// 2. Pociski Minibossa CC (jeśli gracz zginął, a boss jeszcze strzelał)
if (this.cursedCrystalActiveProjectiles && this.cursedCrystalActiveProjectiles.length > 0) {
console.log("DEBUG: Clearing " + this.cursedCrystalActiveProjectiles.length + " remaining projectiles.");
this.cursedCrystalActiveProjectiles.forEach(function (proj) {
if (proj && proj.destroy && !proj.destroyed) {
if (proj.graphics && proj.graphics.parent) {
proj.graphics.parent.removeChild(proj.graphics);
}
if (proj.graphics && proj.graphics.destroy) {
proj.graphics.destroy();
}
proj.destroy();
}
});
}
this.cursedCrystalActiveProjectiles = [];
// 3. Eksplozje Minibossa CC
if (this.cursedCrystalActiveExplosions && this.cursedCrystalActiveExplosions.length > 0) {
console.log("DEBUG: Clearing " + this.cursedCrystalActiveExplosions.length + " active explosions.");
// Wizualizacje eksplozji są zarządzane przez tweeny i powinny same zniknąć,
// ale czyścimy tablicę logiczną.
}
this.cursedCrystalActiveExplosions = [];
// 4. Ściany Laserowe Minibossa CC
if (this.cursedCrystalActiveLaserWalls && this.cursedCrystalActiveLaserWalls.length > 0) {
console.log("DEBUG: Clearing " + this.cursedCrystalActiveLaserWalls.length + " laser walls.");
this.cursedCrystalActiveLaserWalls.forEach(function (wall) {
if (wall && wall.segments) {
wall.segments.forEach(function (seg) {
if (seg.visual && seg.visual.destroy && !seg.visual.destroyed) {
if (seg.visual.parent) {
seg.visual.parent.removeChild(seg.visual);
}
seg.visual.destroy();
}
});
}
if (wall && wall.warningVisuals && wall.warningVisuals.length > 0) {
wall.warningVisuals.forEach(function (warnVis) {
if (warnVis && warnVis.destroy && !warnVis.destroyed) {
if (warnVis.parent) {
warnVis.parent.removeChild(warnVis);
}
warnVis.destroy();
}
});
}
});
}
this.cursedCrystalActiveLaserWalls = [];
// 5. Sam Miniboss CC (jeśli gracz zginął, a Miniboss nie został pokonany)
// Metoda MinibossCC.die() czyści po sobie, gdy jest pokonany.
// Jeśli gracz umiera, obiekt Minibossa może nadal istnieć.
if (!isVictory && this.cursedCrystalMinibossObject && !this.cursedCrystalMinibossObject.isDead) {
console.log("DEBUG: Player died, clearing active MinibossCC.");
if (this.cursedCrystalMinibossObject.parent) {
this.cursedCrystalMinibossObject.parent.removeChild(this.cursedCrystalMinibossObject);
}
if (this.cursedCrystalMinibossObject.destroy) {
this.cursedCrystalMinibossObject.destroy();
}
}
this.cursedCrystalMinibossObject = null;
this.isMinibossActiveCC = false;
// 6. Obiekt Klejnotu (jeśli był i nie został zniszczony przy spawnie minibossa)
if (typeof crystalCoreObject !== 'undefined' && crystalCoreObject && crystalCoreObject.destroy && !crystalCoreObject.destroyed) {
if (crystalCoreObject.parent) {
crystalCoreObject.parent.removeChild(crystalCoreObject);
}
crystalCoreObject.destroy();
}
crystalCoreObject = null;
// 7. Zatrzymaj muzykę Cursed Crystal, jeśli jest
// if (this.currentCursedCrystalMusic && this.currentCursedCrystalMusic.stop) {
// this.currentCursedCrystalMusic.stop();
// this.currentCursedCrystalMusic = null;
// }
// Uruchom muzykę menu/grilla (jeśli jest taka potrzeba, np. showGrillScreen to zrobi)
// --- LOGIKA WYNIKU I REKORDU ---
var finalScore = this.cursedCrystalScore;
if (isVictory) {
var minutesSurvived = Math.floor(this.cursedCrystalTimeSurvived / (60 * 60)); // Zakładając 60 FPS
var bonusPoints = 1000 + minutesSurvived * 100;
finalScore += bonusPoints;
console.log("DEBUG: Victory! Base score: " + this.cursedCrystalScore + ", Time survived (frames): " + this.cursedCrystalTimeSurvived + " (" + minutesSurvived + " min), Bonus: " + bonusPoints + ", Final score: " + finalScore);
}
this.cursedCrystalScore = finalScore; // Zaktualizuj wynik w gameState
var highScore = parseInt(storage.cursedCrystalHighScore, 10) || 0;
var newRecord = false;
if (finalScore > highScore) {
storage.cursedCrystalHighScore = finalScore;
highScore = finalScore;
newRecord = true;
console.log("DEBUG: New High Score for Cursed Crystal: " + highScore);
}
if (ui) {
ui.titleText.setText(isVictory ? "VICTORY!" : "DEFEAT!"); // Przetłumaczone
ui.titleText.style = {
size: 120,
fill: isVictory ? 0x00FF00 : 0xFF0000,
align: 'center',
stroke: 0x000000,
strokeThickness: 6
};
var message = "Your Score: " + finalScore + "\n"; // Przetłumaczone
message += "Best Score: " + highScore; // Przetłumaczone
if (newRecord && isVictory) {
message += "\n(NEW HIGH SCORE!)"; // Przetłumaczone
} else if (newRecord && !isVictory) {
message += "\n(New High Score... Pepe sad)"; // Przetłumaczone (z lekką adaptacją)
}
ui.messageText.setText(message);
ui.messageText.style = {
size: 70,
fill: 0xFFFFFF,
align: 'center',
stroke: 0x000000,
strokeThickness: 4
};
// Usunięcie starych przycisków, jeśli istnieją (bez zmian)
if (this.activeButtons && this.activeButtons.length > 0) {
this.activeButtons.forEach(function (btn) {
if (btn && btn.destroy) {
btn.destroy();
}
});
}
this.activeButtons = [];
// Upewnij się, że teksty tytułu i wiadomości są od razu widoczne
ui.titleText.alpha = 1;
ui.messageText.alpha = 1;
// Ustaw pozycje dla titleText i messageText (przeniesione z ui.positionElements dla jasności)
// Zakładam, że te pozycje są poprawne dla ekranu końca gry
ui.titleText.x = 2048 / 2;
ui.titleText.y = 600;
ui.titleText.anchor.set(0.5);
ui.messageText.x = 2048 / 2;
ui.messageText.y = 800;
ui.messageText.anchor.set(0.5, 0.5);
// Opóźnienie tworzenia i wyświetlania przycisków
LK.setTimeout(function () {
// Sprawdź, czy nadal jesteśmy w tym samym stanie gry, aby uniknąć dodawania przycisków, jeśli stan się zmienił
if (gameState.currentState !== "cursedCrystalGameOver") {
return;
}
// Definiujemy oczekiwane wysokości na początku, aby były dostępne dla obu przycisków
var mainMenuButtonExpectedHeight = 100;
var restartButtonExpectedHeight = 100;
var spacingBetweenButtons = 60;
var menuButton = new Container();
menuButton.interactive = true;
menuButton.cursor = "pointer";
var menuButtonVisualHeight = mainMenuButtonExpectedHeight; // Użyjemy tej zmiennej
try {
var menuButtonAsset = LK.getAsset('mainmenu', {
anchorX: 0.5,
anchorY: 0.5
});
menuButton.addChild(menuButtonAsset);
if (menuButtonAsset && typeof menuButtonAsset.height === 'number') {
menuButtonVisualHeight = menuButtonAsset.height; // Użyj rzeczywistej wysokości assetu, jeśli dostępna
}
} catch (e) {
console.error("Błąd ładowania assetu przycisku 'mainmenu':", e);
var menuButtonBgFallback = new Shape({
width: 400,
height: mainMenuButtonExpectedHeight,
color: 0x555555,
shape: 'box'
});
menuButton.addChild(menuButtonBgFallback);
}
menuButton.x = 2048 / 2;
// Poprawiona linia dla menuButton.y:
menuButton.y = (ui.messageText.y || 800) + (ui.messageText.height || 70) / 2 + menuButtonVisualHeight / 2 + 80;
menuButton.down = function () {
if (gameState.currentState === "cursedCrystalGameOver") {
gameState.activeButtons.forEach(function (btn) {
if (btn && btn.destroy && !btn.destroyed) {
if (btn.parent) {
btn.parent.removeChild(btn);
}
btn.destroy();
}
});
gameState.activeButtons = [];
gameState.showGrillScreen();
}
};
currentSceneElements.addChild(menuButton);
gameState.activeButtons.push(menuButton);
// --- Przycisk "Restart Mode" ---
var restartButton = new Container();
restartButton.interactive = true;
restartButton.cursor = "pointer";
var restartButtonVisualHeight = restartButtonExpectedHeight; // Użyjemy tej zmiennej
try {
var restartButtonAsset = LK.getAsset('restartgame', {
anchorX: 0.5,
anchorY: 0.5
});
restartButton.addChild(restartButtonAsset);
if (restartButtonAsset && typeof restartButtonAsset.height === 'number') {
restartButtonVisualHeight = restartButtonAsset.height; // Użyj rzeczywistej wysokości assetu
}
} catch (e) {
console.error("Błąd ładowania assetu przycisku 'restartgame':", e);
var restartButtonBgFallback = new Shape({
width: 400,
height: restartButtonExpectedHeight,
color: 0x555555,
shape: 'box'
});
restartButton.addChild(restartButtonBgFallback);
}
restartButton.x = 2048 / 2;
// Poprawiona linia dla restartButton.y, z większym odstępem:
restartButton.y = menuButton.y + menuButtonVisualHeight / 2 + restartButtonVisualHeight / 2 + spacingBetweenButtons;
restartButton.down = function () {
if (gameState.currentState === "cursedCrystalGameOver") {
gameState.activeButtons.forEach(function (btn) {
if (btn && btn.destroy && !btn.destroyed) {
if (btn.parent) {
btn.parent.removeChild(btn);
}
btn.destroy();
}
});
gameState.activeButtons = [];
gameState.startCursedCrystalMode();
}
};
currentSceneElements.addChild(restartButton);
gameState.activeButtons.push(restartButton);
// ui.positionElements("cursedCrystalGameOver");
}, 3000); // Opóźnienie 3000 ms (3 sekundy)
}
}), "processTouchGesture", function processTouchGesture() {
// ... (kod obsługi fake tutorial) ...
var dx = this.touchEnd.x - this.touchStart.x;
var dy = this.touchEnd.y - this.touchStart.y;
var distance = Math.sqrt(dx * dx + dy * dy);
var swipeThreshold = 50; // Minimalny dystans, by uznać za swipe
if (distance < swipeThreshold) {
// Logika Tapnięcia (bez zmian)
// ...
return;
}
// --- Obsługa Swipe (Turlania) ---
if ((this.currentState === "game" || this.currentState === "rollMaster" || this.currentState === "cursedCrystal") && player && !player.dead) {
// Normalizuj kierunek (bez zmian)
var direction = {
x: 0,
y: 0
};
if (distance > 0) {
direction.x = dx / distance;
direction.y = dy / distance;
}
// <-- NOWA LOGIKA: Obliczanie czasu trwania na podstawie dystansu swipe -->
var minSwipeDist = swipeThreshold; // Minimalny dystans swipe'a (np. 50)
var maxSwipeDist = 400; // <-- ZWIĘKSZONA dla większej skali
var minRollDuration = 50; // <-- LEKKO ZWIĘKSZONA dla płynności minimum
var maxRollDuration = 400; // Maksymalny czas uniku w ms (np. 0.45s - dostosuj!)
// Ogranicz dystans do zakresu min/max
var clampedDistance = Math.max(minSwipeDist, Math.min(distance, maxSwipeDist));
// Znormalizuj dystans do zakresu 0-1
var normalizedDistance = (clampedDistance - minSwipeDist) / (maxSwipeDist - minSwipeDist);
// Oblicz czas trwania uniku na podstawie znormalizowanego dystansu
var currentRollDuration = minRollDuration + normalizedDistance * (maxRollDuration - minRollDuration);
// <-- KONIEC NOWEJ LOGIKI -->
// Wywołaj turlanie, przekazując kierunek ORAZ obliczony czas trwania
player.roll(direction, currentRollDuration); // Przekazujemy nowy parametr!
}
});
// --- Obsługa inputu ---
game.down = function (x, y, obj) {
// Rejestruj początek dotyku/kliknięcia
// <-- ZMIANA: Natychmiastowa obsługa kliknięcia w fakeTutorial -->
if (gameState.currentState === "fakeTutorial") {
// Sprawdźmy dodatkowo, czy timer jeszcze istnieje, na wszelki wypadek
if (gameState.fakeTutorialTimerId) {
gameState.handleFakeTutorialInput(); // Wywołaj logikę "fałszywej śmierci"
return; // Zakończ, nie rób nic więcej w game.down
}
}
// Stany, w których śledzimy gesty lub kliknięcia przycisków:
var trackStates = ["title", "game", /* Usunięto "fakeTutorial" */"fakeGameOver", "realTutorial", "grillMenu", "gameOver", "rollMaster", "cursedCrystal"];
if (trackStates.indexOf(gameState.currentState) !== -1) {
gameState.touchStart.x = x;
gameState.touchStart.y = y;
gameState.touchEnd.x = x;
gameState.touchEnd.y = y;
gameState.isInputActive = true;
gameState.currentInputPos.x = x;
gameState.currentInputPos.y = y;
gameState.swipeStartTime = Date.now();
}
// Obsługa kliknięć przycisków jest robiona przez ich własne handlery .down
// Note: If you have complex button logic that consumes clicks,
// you might need to check if 'obj' is null here before setting isInputActive.
// For simple cases, setting it always on 'down' might be fine,
// but make sure your player movement check in update respects the game state.
};
game.up = function (x, y, obj) {
// Rejestruj koniec dotyku/kliknięcia i przetwarzaj gest
var trackStates = ["title", "game", "fakeTutorial", "fakeGameOver", "realTutorial", "grillMenu", "gameOver", "rollMaster", "cursedCrystal"];
if (trackStates.indexOf(gameState.currentState) !== -1) {
gameState.touchEnd.x = x;
gameState.touchEnd.y = y;
// Zawsze ustawiaj isInputActive na false przy puszczeniu, aby zatrzymać chodzenie
gameState.isInputActive = false;
// --- TUTAJ JEST KLUCZOWA ZMIANA ---
// Oblicz czas trwania gestu
var swipeDuration = Date.now() - gameState.swipeStartTime; // Odczytujemy startTime
var holdThreshold = 1000; // Próg w ms - PAMIĘTAJ O DOSTROJENIU!
// Wywołaj logikę rolla tylko jeśli gest był krótszy niż próg przytrzymania
if (swipeDuration < holdThreshold) {
// Gest był szybki (potencjalny swipe/tap)
console.log("game.up - GEST KRÓTKI (" + swipeDuration + "ms), przetwarzam..."); // Opcjonalny log
gameState.processTouchGesture(); // Wywołujemy tylko tutaj!
} else {
console.log("game.up - GEST DŁUGI (" + swipeDuration + "ms), IGNORUJĘ rolla"); // Opcjonalny log
}
// Jeśli gest był długi, nic więcej nie robimy.
// --- KONIEC KLUCZOWEJ ZMIANY ---
}
// Obsługa puszczenia przycisków (.up) może być dodana tutaj, jeśli potrzebna
};
game.move = function (x, y, obj) {
var trackStates = ["title", "game", "fakeTutorial", "fakeGameOver", "realTutorial", "grillMenu", "gameOver", "rollMaster", "cursedCrystal"];
if (trackStates.indexOf(gameState.currentState) !== -1) {
gameState.touchEnd.x = x;
gameState.touchEnd.y = y;
if (gameState.isInputActive) {
gameState.currentInputPos.x = x;
gameState.currentInputPos.y = y;
}
}
};
function launchRmattack1() {
console.log("Launch rmattack1 (projectile)");
var projectileRadius = 45;
var startX, startY;
var edge = Math.floor(Math.random() * 4);
var gameWidth = 2048; // Szerokość pola gry
var gameHeight = 2732; // Wysokość pola gry
// Pozycjonowanie startowe (bez zmian)
if (edge === 0) {
// top
startX = Math.random() * gameWidth;
startY = -projectileRadius;
} else if (edge === 1) {
// right
startX = gameWidth + projectileRadius;
startY = Math.random() * gameHeight;
} else if (edge === 2) {
// bottom
startX = Math.random() * gameWidth;
startY = gameHeight + projectileRadius;
} else {
// left
startX = -projectileRadius;
startY = Math.random() * gameHeight;
}
// Celowanie w środek (bez zmian)
var targetX = gameWidth / 2;
var targetY = gameHeight / 2;
var dx = targetX - startX;
var dy = targetY - startY;
var angle = Math.atan2(dy, dx);
// --- ZMIANA: Obliczanie prędkości (vx, vy) zamiast endX, endY ---
var speed = 4 + gameState.rollMasterDifficulty * 0.65; // Prędkość pocisku (dostosuj wg potrzeb, może być zależna od trudności)
var vx = Math.cos(angle) * speed;
var vy = Math.sin(angle) * speed;
// --- KONIEC ZMIANY ---
var rotation = angle;
var scaleX = 1;
// Tworzenie animacji (bez zmian)
var frames = [LK.getAsset('rmattack1_0', {
anchorX: 0.5,
anchorY: 0.5
}), LK.getAsset('rmattack1_1', {
anchorX: 0.5,
anchorY: 0.5
}), LK.getAsset('rmattack1_2', {
anchorX: 0.5,
anchorY: 0.5
}), LK.getAsset('rmattack1_3', {
anchorX: 0.5,
anchorY: 0.5
}), LK.getAsset('rmattack1_4', {
anchorX: 0.5,
anchorY: 0.5
}), LK.getAsset('rmattack1_5', {
anchorX: 0.5,
anchorY: 0.5
}), LK.getAsset('rmattack1_6', {
anchorX: 0.5,
anchorY: 0.5
})];
var sprite = new SpriteAnimation({
frames: frames,
frameDuration: 120,
loop: true,
x: startX,
y: startY,
anchorX: 0.5,
anchorY: 0.5
});
sprite.scaleX = scaleX;
sprite.rotation = rotation;
game.addChild(sprite);
// --- ZMIANA: Dodanie do listy ataków zamiast tween ---
gameState.rollMasterAttacks.push({
type: 'projectile',
visual: sprite,
vx: vx,
vy: vy,
radius: projectileRadius // Promień do sprawdzania kolizji
});
// Usunięto tween - ruch będzie obsługiwany w game.update
// --- KONIEC ZMIANY ---
}
// Funkcja launchRmattack2 ZE ZMIANĄ POZYCJONOWANIA
function launchRmattack2() {
var topSafeMargin = 700;
console.log("Launch rmattack2 (explosion)");
var bombWidth = 120;
var bombHeight = 120;
var halfBombWidth = bombWidth / 2;
var halfBombHeight = bombHeight / 2;
var minX_b = halfBombWidth;
var maxX_b = 2048 - halfBombWidth;
var minY_b = halfBombHeight;
var maxY_b = 2732 - halfBombHeight;
var x = minX_b + Math.random() * (maxX_b - minX_b);
var spawnHeight = maxY_b - topSafeMargin;
var y = spawnHeight > 0 ? topSafeMargin + Math.random() * spawnHeight : topSafeMargin;
var idleFrame = LK.getAsset('rmattack2_explode_0', {
anchorX: 0.5,
anchorY: 0.5
});
var bomb = new SpriteAnimation({
frames: [idleFrame],
frameDuration: 500,
loop: true,
x: x,
y: y,
anchorX: 0.5,
anchorY: 0.5
});
game.addChild(bomb);
bomb.idleTween = tween(bomb, {
y: y - 10
}, {
duration: 500,
yoyo: true,
repeat: Infinity,
easing: tween.inOutQuad
});
LK.setTimeout(function () {
if (bomb.destroyed) {
return;
}
try {
if (bomb.idleTween && typeof bomb.idleTween.stop === "function") {
bomb.idleTween.stop();
}
} catch (e) {
console.warn("idleTween stop error:", e);
}
var explosionFrames = [];
var finalScale = 0;
for (var i = 0; i < 11; i++) {
var scale = 1 + i * 0.4;
finalScale = scale;
var frame = LK.getAsset("rmattack2_explode_" + i, {
anchorX: 0.5,
anchorY: 0.5,
scaleX: scale,
scaleY: scale
});
explosionFrames.push(frame);
}
var explosion = new SpriteAnimation({
frames: explosionFrames,
frameDuration: 80,
loop: false,
x: bomb.x,
y: bomb.y,
anchorX: 0.5,
anchorY: 0.5
});
// Nowe: natychmiastowe niszczenie po zakończeniu animacji
explosion.onComplete = function () {
var index = gameState.rollMasterAttacks.indexOf(explosionData);
if (index > -1) {
gameState.rollMasterAttacks.splice(index, 1);
console.log("Usunięto eksplozję z listy przez onComplete.");
}
if (!explosion.destroyed) {
explosion.destroy();
}
};
bomb.destroy();
game.addChild(explosion);
var explosionDurationFrames = explosionFrames.length * (80 / (1000 / 60));
var explosionMaxRadius = bombWidth * finalScale / 2 * 0.8;
var explosionData = {
type: 'explosion',
visual: explosion,
radius: 0,
maxRadius: explosionMaxRadius,
currentFrame: 0,
totalFrames: explosionFrames.length,
frameDurationTicks: 80 / (1000 / 60),
timer: 0,
damageDealt: false
};
gameState.rollMasterAttacks.push(explosionData);
console.log("Dodano eksplozję do śledzenia, maxRadius:", explosionMaxRadius);
}, 2000);
}
// Funkcja launchRmattack3 (bez zmian, już miała bezpieczne pozycjonowanie i pulsowanie)
function launchRmattack3() {
var topSafeMargin = 600;
console.log("Launch rmattack3 (laser)");
// Bezpieczne pozycjonowanie (bez zmian)
var margin = 100;
var halfLaserWidth = 128 / 2;
var halfLaserHeight = 1012 / 2; // [cite: 95, 96]
var minX = margin + halfLaserWidth;
var maxX = 2048 - margin - halfLaserWidth; // [cite: 96, 97]
var calculatedMinY = margin + halfLaserHeight; // Oryginalne obliczenie minY
var minY = Math.max(calculatedMinY, topSafeMargin); // Użyj większej wartości: marginesu lub safe zone
var maxY = 2732 - margin - halfLaserHeight; // Dolna granica
// var laserY = minY + Math.random() * (maxY - minY); // <-- ZAKOMENTUJ LUB USUŃ STARĄ LINIĘ
var spawnHeight = maxY - minY; // Oblicz dostępną wysokość
var laserY = spawnHeight > 0 ? minY + Math.random() * spawnHeight : minY; // Losuj Y w bezpiecznej strefie
// --- KONIEC ZMIAN ---
var laserX = minX + Math.random() * (maxX - minX);
// Animacja ostrzeżenia (bez zmian)
var warningFrames = []; // [cite: 99]
for (var i = 0; i < 5; i++) {
warningFrames.push(LK.getAsset('rmattack3_warning_' + i, {
anchorX: 0.5,
anchorY: 0.5,
width: 128,
height: 1012
}));
} // [cite: 100]
var warningAnim = new SpriteAnimation({
frames: warningFrames,
frameDuration: 100,
loop: true,
x: laserX,
y: laserY,
anchorX: 0.5,
anchorY: 0.5,
alpha: 1.0
}); // [cite: 101]
warningAnim.stopPulsing = false;
var currentPulseTween = null; // [cite: 101]
game.addChild(warningAnim); // [cite: 102]
var pulseDuration = 250; // [cite: 102]
function pulseUp() {
if (!warningAnim || warningAnim.destroyed || warningAnim.stopPulsing) {
currentPulseTween = null;
return;
}
currentPulseTween = tween(warningAnim, {
alpha: 1.0
}, {
duration: pulseDuration,
easing: tween.inOutQuad,
onFinish: function onFinish() {
if (warningAnim && !warningAnim.destroyed && !warningAnim.stopPulsing) {
pulseDown();
} else {
currentPulseTween = null;
}
}
});
} // [cite: 102, 103, 104]
function pulseDown() {
if (!warningAnim || warningAnim.destroyed || warningAnim.stopPulsing) {
currentPulseTween = null;
return;
}
currentPulseTween = tween(warningAnim, {
alpha: 0.1
}, {
duration: pulseDuration,
easing: tween.inOutQuad,
onFinish: function onFinish() {
if (warningAnim && !warningAnim.destroyed && !warningAnim.stopPulsing) {
pulseUp();
} else {
currentPulseTween = null;
}
}
});
} // [cite: 104, 105]
pulseDown(); // [cite: 106]
// Timer aktywacji lasera
LK.setTimeout(function () {
// [cite: 106]
if (warningAnim && !warningAnim.destroyed) {
warningAnim.stopPulsing = true;
} // [cite: 106]
if (currentPulseTween && typeof currentPulseTween.stop === "function") {
try {
currentPulseTween.stop();
currentPulseTween = null;
} catch (e) {
console.warn("Nie udało się zatrzymać tweenu pulsowania:", e);
}
} // [cite: 106]
if (warningAnim && !warningAnim.destroyed) {
// Jeśli ostrzeżenie nadal istnieje
// Tworzenie klatek ataku (bez zmian)
var activeFrames = []; // [cite: 106]
var laserStrikeWidth = 228; // Szerokość kolizji
var laserStrikeHeight = 1212; // Wysokość kolizji
for (var j = 0; j < 5; j++) {
activeFrames.push(LK.getAsset('rmattack3_strike_' + j, {
anchorX: 0.5,
anchorY: 0.5,
width: laserStrikeWidth,
height: laserStrikeHeight
}));
} // [cite: 106]
// Tworzenie animacji ataku (bez zmian)
var strikeAnim = new SpriteAnimation({
// [cite: 106]
frames: activeFrames,
frameDuration: 40,
loop: true,
x: laserX,
y: laserY,
anchorX: 0.5,
anchorY: 0.5,
alpha: 1 // [cite: 106]
});
game.addChild(strikeAnim); // [cite: 106]
var laserDuration = 4000; // Czas życia lasera w ms
// --- ZMIANA: Dodanie lasera do listy śledzonych ataków ---
var laserData = {
type: 'laser',
visual: strikeAnim,
// Referencja do animacji uderzenia
// Przekazujemy wymiary potrzebne do kolizji AABB w game.update
width: laserStrikeWidth,
height: laserStrikeHeight,
lifeTime: laserDuration / (1000 / 60) // Czas życia w klatkach gry
};
gameState.rollMasterAttacks.push(laserData);
console.log("Dodano laser do śledzenia.");
// --- KONIEC ZMIANY ---
warningAnim.destroy(); // Usuń ostrzeżenie [cite: 106]
// Timer usuwania lasera
LK.setTimeout(function () {
// [cite: 106]
// Znajdź i usuń z listy, jeśli nadal tam jest
var index = gameState.rollMasterAttacks.indexOf(laserData);
if (index > -1) {
gameState.rollMasterAttacks.splice(index, 1);
console.log("Usunięto laser z listy po czasie.");
}
if (strikeAnim && !strikeAnim.destroyed) {
strikeAnim.destroy();
} // [cite: 106]
}, laserDuration); // [cite: 106]
}
}, 3000); // Czas trwania ostrzeżenia [cite: 107]
}
// Funkcja launchRmattack4 POPRAWIONA (tylko tworzenie, z bezpiecznym pozycjonowaniem 200px margin)
function launchRmattack4() {
// Obliczanie bezpiecznej strefy dla spreadera z marginesem 200px
var spreaderParentRadius = 60 * 1.2;
var margin = 200;
var startX = margin + Math.random() * (2048 - 2 * margin);
var startY = margin + Math.random() * (2732 - 2 * margin);
// Klatki animacji dla Spreader Parent
var spreaderParentFrames = [LK.getAsset('rmattack4_parent_0', {
anchorX: 0.5,
anchorY: 0.5
}), LK.getAsset('rmattack4_parent_1', {
anchorX: 0.5,
anchorY: 0.5
}), LK.getAsset('rmattack4_parent_2', {
anchorX: 0.5,
anchorY: 0.5
}), LK.getAsset('rmattack4_parent_3', {
anchorX: 0.5,
anchorY: 0.5
})];
// Stwórz obiekt SpriteAnimation dla rodzica
var spreaderParentAnim = new SpriteAnimation({
frames: spreaderParentFrames,
frameDuration: 250,
loop: true,
x: startX,
y: startY,
anchorX: 0.5,
anchorY: 0.5
});
// Dodaj animację rodzica do sceny gry
game.addChild(spreaderParentAnim);
// Dodaj obiekt logiczny ataku do listy gameState
gameState.rollMasterAttacks.push({
type: 'spreader_parent',
visual: spreaderParentAnim,
radius: spreaderParentRadius,
timer: gameState.spreaderSplitTime
});
}
function spawnCursedSoul() {
console.log("--- spawnCursedSoul (COMPACT GROUP) CALLED ---");
if (gameState.currentState !== "cursedCrystal" || typeof gameState.gameActive === 'boolean' && !gameState.gameActive) {
console.log("spawnCursedSoul (COMPACT GROUP): Exiting - wrong state or game not active. State:", gameState.currentState, "GameActive:", gameState.gameActive);
return;
}
if (typeof crystalCoreObject === 'undefined' || !crystalCoreObject || crystalCoreObject.destroyed) {
console.log("spawnCursedSoul (COMPACT GROUP): Exiting - crystalCoreObject missing or destroyed.");
return;
}
var minSouls = gameState.cursedCrystalSoulsPerGroupMin || 1; // Domyślnie 4, jeśli niezdefiniowane
var maxSouls = gameState.cursedCrystalSoulsPerGroupMax || 5; // Domyślnie 5
if (maxSouls < minSouls) {
maxSouls = minSouls;
}
var numSoulsInGroup = minSouls + Math.floor(Math.random() * (maxSouls - minSouls + 1));
console.log("numSoulsInGroup to spawn:", numSoulsInGroup);
if (numSoulsInGroup <= 0) {
console.warn("spawnCursedSoul (COMPACT GROUP): numSoulsInGroup is 0 or less.");
return;
}
var soulSpeed = gameState.cursedCrystalEnemyCurrentMaxSpeed;
console.log("Soul speed for group:", soulSpeed); // Powinno być niskie na początku
var soulHP = 1;
var soulAssetWidth = 30;
var soulAssetHeight = 30;
var gameWidth = 2048;
var gameHeight = 2732;
var edge = Math.floor(Math.random() * 4);
var groupBaseSpawnX, groupBaseSpawnY;
// Zmniejszamy rozrzut dla bardziej zwartej grupy
var groupSpawnTightnessFactor = 400; // Mniejsza wartość = ciaśniejsza grupa wzdłuż krawędzi
var depthSpreadFactor = gameState.cursedCrystalGroupSpawnSpread / 3 || 140; // Mniejszy rozrzut w głąb
switch (edge) {
case 0:
// Góra
groupBaseSpawnX = Math.random() * (gameWidth - 400) + 200; // Szerszy zakres, ale nadal unika rogów
groupBaseSpawnY = -soulAssetHeight;
break;
case 1:
// Prawo
groupBaseSpawnX = gameWidth + soulAssetWidth;
groupBaseSpawnY = Math.random() * (gameHeight - 400) + 200;
break;
case 2:
// Dół
groupBaseSpawnX = Math.random() * (gameWidth - 400) + 200;
groupBaseSpawnY = gameHeight + soulAssetHeight;
break;
default:
// Lewo (case 3)
groupBaseSpawnX = -soulAssetWidth;
groupBaseSpawnY = Math.random() * (gameHeight - 400) + 200;
break;
}
console.log("Spawning group from edge:", edge, "BaseX:", groupBaseSpawnX.toFixed(0), "BaseY:", groupBaseSpawnY.toFixed(0));
for (var i = 0; i < numSoulsInGroup; i++) {
var offsetX = 0;
var offsetY = 0;
// Ustawienie dusz blisko siebie, z lekkim losowym przesunięciem
if (edge === 0 || edge === 2) {
// Spawn z góry lub dołu - rozrzut poziomy
offsetX = (Math.random() - 0.5) * groupSpawnTightnessFactor;
offsetY = (Math.random() - 0.5) * depthSpreadFactor; // Mniejszy rozrzut w głąb
} else {
// Spawn z lewej lub prawej - rozrzut pionowy
offsetY = (Math.random() - 0.5) * groupSpawnTightnessFactor;
offsetX = (Math.random() - 0.5) * depthSpreadFactor; // Mniejszy rozrzut w głąb
}
var finalSpawnX = groupBaseSpawnX + offsetX;
var finalSpawnY = groupBaseSpawnY + offsetY;
// console.log("Creating soul " + (i + 1) + " at X:", finalSpawnX.toFixed(0), "Y:", finalSpawnY.toFixed(0));
var newSoul = new SoulEnemy({
x: finalSpawnX,
y: finalSpawnY,
hp: soulHP,
speed: soulSpeed,
// Wszystkie duszki w grupie mają tę samą (niską na początku) prędkość
targetX: crystalCoreObject.x,
targetY: crystalCoreObject.y
});
game.addChild(newSoul);
gameState.cursedCrystalEnemies.push(newSoul);
}
console.log("Finished spawning group of " + numSoulsInGroup + " souls.");
}
// --- Główna pętla aktualizacji gry ---
game.update = function () {
if (gameState.currentState === "cursedCrystal") {
// console.log("Game Update Tick - CURSED CRYSTAL");
}
if (ui) {
if (gameState.currentState !== "cursedCrystal" && gameState.currentState !== "cursedCrystalGameOver") {
ui.updateDeathsCounter();
}
if (boss && (gameState.currentState === "game" || gameState.currentState === "gameOver" && !gameOverReasonIsDeath && isNewBossPlusMode)) {
var maxHpBoss = boss.maxHealth > 0 ? boss.maxHealth : 1;
ui.updateBossHealth(boss.health, maxHpBoss);
} else if (gameState.currentState !== "cursedCrystal" && gameState.currentState !== "cursedCrystalGameOver") {
ui.updateBossHealth(0, 1);
}
if (player && gameState.currentState !== "cursedCrystal" && gameState.currentState !== "cursedCrystalGameOver") {
if (gameState.currentState === "rollMaster") {
ui.updateHearts(player.health, 1);
} else {
ui.updateHearts(player.health, parseInt(storage.maxHearts, 10) || 5);
}
} else if (gameState.currentState !== "game" && gameState.currentState !== "gameOver" && gameState.currentState !== "rollMaster" && gameState.currentState !== "cursedCrystal" && gameState.currentState !== "cursedCrystalGameOver") {
ui.updateHearts(parseInt(storage.maxHearts, 10) || 5, parseInt(storage.maxHearts, 10) || 5);
}
}
if (coffinMemeImage && !coffinMemeImage.destroyed) {
if (gameState.currentState === "game" && player) {
if (!player.dead) {
var currentHp = player.health;
var maxHp = parseInt(storage.maxHearts, 10) || 5;
var visibilityFactor = 1 - Math.max(0, currentHp) / maxHp;
visibilityFactor = Math.max(0, Math.min(1, visibilityFactor));
var memeHeight = coffinMemeImage.height || 200;
var screenBottom = 2732;
var hiddenOffsetY = memeHeight;
var targetY = screenBottom + hiddenOffsetY * (1 - visibilityFactor) + 300;
var targetAlpha = visibilityFactor > 0.1 ? visibilityFactor * 0.8 : 0;
var positionChangeSpeed = 10;
var currentY = coffinMemeImage.y;
if (Math.abs(currentY - targetY) > 1) {
coffinMemeImage.y = currentY < targetY ? Math.min(targetY, currentY + positionChangeSpeed) : Math.max(targetY, currentY - positionChangeSpeed);
}
var alphaChangeSpeed = 0.05;
var currentAlpha = coffinMemeImage.alpha;
if (Math.abs(currentAlpha - targetAlpha) > 0.01) {
coffinMemeImage.alpha = currentAlpha < targetAlpha ? Math.min(targetAlpha, currentAlpha + alphaChangeSpeed) : Math.max(targetAlpha, currentAlpha - alphaChangeSpeed);
}
} else {
coffinMemeImage.y = 2932;
coffinMemeImage.alpha = 1.0;
}
} else if (gameState.currentState !== "gameOver") {
if (coffinMemeImage.alpha > 0) {
coffinMemeImage.alpha = Math.max(0, coffinMemeImage.alpha - 0.05);
}
}
}
if (gameState.currentState === "game") {
if (player) {
player.update();
}
if (boss) {
boss.update();
}
} else if (gameState.currentState === "rollMaster") {
if (player) {
player.update();
}
if (gameState.unlockedAttacks.includes('rmattack1')) {
if (!gameState.randomRmattack1Timer) {
var _spawnRandomRmattack = function spawnRandomRmattack1() {
if (gameState.currentState !== "rollMaster" || !gameState.unlockedAttacks.includes('rmattack1')) {
gameState.randomRmattack1Timer = null;
return;
}
launchRmattack1();
var nextDelay = 800 + Math.random() * 1500;
gameState.randomRmattack1Timer = LK.setTimeout(_spawnRandomRmattack, nextDelay);
};
_spawnRandomRmattack();
}
} else if (gameState.randomRmattack1Timer) {
LK.clearTimeout(gameState.randomRmattack1Timer);
gameState.randomRmattack1Timer = null;
}
if (gameState.unlockedAttacks.includes('rmattack2') && gameState.explosionSpawnInterval > 0) {
gameState.explosionSpawnTimer++;
if (gameState.explosionSpawnTimer >= gameState.explosionSpawnInterval) {
gameState.explosionSpawnTimer = 0;
launchRmattack2();
}
}
if (gameState.unlockedAttacks.includes('rmattack3') && gameState.laserSpawnInterval > 0) {
gameState.laserSpawnTimer++;
if (gameState.laserSpawnTimer >= gameState.laserSpawnInterval) {
gameState.laserSpawnTimer = 0;
launchRmattack3();
}
}
if (gameState.unlockedAttacks.includes('rmattack4') && gameState.spreaderSpawnInterval > 0) {
gameState.spreaderSpawnTimer++;
if (gameState.spreaderSpawnTimer >= gameState.spreaderSpawnInterval) {
gameState.spreaderSpawnTimer = 0;
launchRmattack4();
}
}
for (var i = gameState.rollMasterAttacks.length - 1; i >= 0; i--) {
var atk = gameState.rollMasterAttacks[i];
var shouldRemove = false;
if (!atk || !atk.visual || atk.visual.destroyed) {
gameState.rollMasterAttacks.splice(i, 1);
continue;
}
if (atk.visual.update && typeof atk.visual.update === 'function') {
atk.visual.update();
}
switch (atk.type) {
case 'projectile':
atk.visual.x += atk.vx;
atk.visual.y += atk.vy;
if (player && !player.dead && !player.rolling && !player.invulnerable) {
var dx_p = player.x - atk.visual.x;
var dy_p = player.y - atk.visual.y;
var dist_p = Math.sqrt(dx_p * dx_p + dy_p * dy_p);
var playerRadius_p = (player.width || 150) / 2 * 0.8;
var projectileRadius = atk.radius || 45;
if (dist_p < playerRadius_p + projectileRadius) {
player.takeDamage(1);
shouldRemove = true;
}
}
var screenMargin = 200;
if (atk.visual.x < -screenMargin || atk.visual.x > 2048 + screenMargin || atk.visual.y < -screenMargin || atk.visual.y > 2732 + screenMargin) {
shouldRemove = true;
}
break;
case 'explosion':
atk.timer++;
if (atk.timer >= atk.frameDurationTicks) {
atk.timer = 0;
atk.currentFrame++;
}
atk.radius = atk.currentFrame < atk.totalFrames ? atk.maxRadius * (atk.currentFrame / atk.totalFrames) : atk.maxRadius;
if (!atk.damageDealt && player && !player.dead && !player.rolling && !player.invulnerable) {
var dx_e = player.x - atk.visual.x;
var dy_e = player.y - atk.visual.y;
var dist_e = Math.sqrt(dx_e * dx_e + dy_e * dy_e);
var playerRadius_e = (player.width || 150) / 2 * 0.8;
if (dist_e < playerRadius_e + atk.radius) {
player.takeDamage(1);
atk.damageDealt = true;
}
}
break;
case 'laser':
if (player && !player.dead && !player.rolling && !player.invulnerable) {
var laserHalfWidth = (atk.width || 100) / 2;
var laserHalfHeight = (atk.height || 100) / 2;
var playerHalfWidth_l = (player.width || 150) / 2 * 0.8;
var playerHalfHeight_l = (player.height || 150) / 2 * 0.8;
var laserLeft = atk.visual.x - laserHalfWidth;
var laserRight = atk.visual.x + laserHalfWidth;
var laserTop = atk.visual.y - laserHalfHeight;
var laserBottom = atk.visual.y + laserHalfHeight;
var playerLeft = player.x - playerHalfWidth_l;
var playerRight = player.x + playerHalfWidth_l;
var playerTop = player.y - playerHalfHeight_l;
var playerBottom = player.y + playerHalfHeight_l;
if (laserLeft < playerRight && laserRight > playerLeft && laserTop < playerBottom && laserBottom > playerTop) {
player.takeDamage(1);
}
}
if (atk.lifeTime && atk.lifeTime > 0) {
atk.lifeTime--;
}
break;
case 'spreader_parent':
atk.timer--;
if (atk.timer <= 0) {
var originX = atk.visual.x;
var originY = atk.visual.y;
var projectileSpeed_s = 5 + gameState.rollMasterDifficulty * 0.5;
var projectileRadius_s = 45;
var numProjectiles_s = 12;
for (var j = 0; j < numProjectiles_s; j++) {
var angle_s = Math.PI * 2 / numProjectiles_s * j;
var vx_s = Math.cos(angle_s) * projectileSpeed_s;
var vy_s = Math.sin(angle_s) * projectileSpeed_s;
var projectileFrames_s = [];
for (var k_s = 0; k_s <= 6; k_s++) {
projectileFrames_s.push(LK.getAsset('rmattack1_' + k_s, {
anchorX: 0.5,
anchorY: 0.5
}));
}
var projectileVisual_s = new SpriteAnimation({
frames: projectileFrames_s,
frameDuration: 100,
loop: true,
x: originX,
y: originY,
anchorX: 0.5,
anchorY: 0.5,
scaleX: 1.2,
scaleY: 1.2
});
projectileVisual_s.rotation = Math.atan2(vy_s, vx_s);
var projectileObject_s = game.addChild(projectileVisual_s);
gameState.rollMasterAttacks.push({
type: 'projectile',
visual: projectileObject_s,
vx: vx_s,
vy: vy_s,
radius: projectileRadius_s
});
}
shouldRemove = true;
}
break;
default:
console.warn("Nieznany typ ataku w RollMaster: ", atk.type, atk);
break;
}
if (shouldRemove) {
if (atk.visual && atk.visual.destroy && !atk.visual.destroyed) {
if (atk.visual.parent) {
atk.visual.parent.removeChild(atk.visual);
}
atk.visual.destroy();
}
gameState.rollMasterAttacks.splice(i, 1);
}
}
if (player && player.dead && gameState.currentState === "rollMaster") {
if (typeof gameState.endRollMasterMode === 'function') {
gameState.endRollMasterMode(gameState.rollMasterTime);
} else {
console.error("Cannot call endRollMasterMode, function undefined");
}
}
} else if (gameState.currentState === "cursedCrystal") {
if (player) {
player.update();
}
if (!gameState.isMinibossActiveCC) {
gameState.cursedCrystalTimeSurvived++;
gameState.cursedCrystalEnemySpawnTimer++;
if (gameState.cursedCrystalEnemySpawnTimer >= gameState.cursedCrystalCurrentSpawnInterval) {
gameState.cursedCrystalEnemySpawnTimer = 0;
spawnCursedSoul();
}
gameState.cursedCrystalDifficultyTimer++;
if (gameState.cursedCrystalDifficultyTimer >= gameState.cursedCrystalDifficultyInterval) {
gameState.cursedCrystalDifficultyTimer = 0;
var spawnIntervalDecrease = 5;
gameState.cursedCrystalCurrentSpawnInterval = Math.max(gameState.cursedCrystalMinSpawnInterval, gameState.cursedCrystalCurrentSpawnInterval - spawnIntervalDecrease);
var speedIncrease = 0.25;
gameState.cursedCrystalEnemyCurrentMaxSpeed += speedIncrease;
}
}
for (var i = gameState.cursedCrystalEnemies.length - 1; i >= 0; i--) {
var enemy = gameState.cursedCrystalEnemies[i];
if (!enemy) {
gameState.cursedCrystalEnemies.splice(i, 1);
continue;
}
if (enemy.isDead) {
gameState.cursedCrystalEnemies.splice(i, 1);
continue;
}
enemy.update();
if (enemy.isDead) {
gameState.cursedCrystalEnemies.splice(i, 1);
continue;
}
if (player && player.rolling) {
var dx_soul = player.x - enemy.x;
var dy_soul = player.y - enemy.y;
var distance_soul = Math.sqrt(dx_soul * dx_soul + dy_soul * dy_soul);
var playerRollRadius_soul = (player.width || 150) / 2 * 0.7;
var soulRadius_coll = (enemy.width || 30) / 2 * 0.8;
if (distance_soul < playerRollRadius_soul + soulRadius_coll) {
enemy.takeDamage(1);
}
}
}
if (gameState.currentState === "cursedCrystal" && !gameState.isMinibossActiveCC && gameState.cursedCrystalChargeLevel >= gameState.cursedCrystalTargetCharge && !gameState.crystalExploding) {
gameState.crystalExploding = true; // Ustaw flagę, że proces eksplozji/spawnu się rozpoczął
// Zdefiniuj współrzędne spawnu minibossa na podstawie crystalCoreObject lub domyślnych
var tempCrystalSpawnX, tempCrystalSpawnY;
if (typeof crystalCoreObject !== 'undefined' && crystalCoreObject && !crystalCoreObject.destroyed) {
tempCrystalSpawnX = crystalCoreObject.x;
tempCrystalSpawnY = crystalCoreObject.y;
} else {
tempCrystalSpawnX = 2048 / 2; // Domyślna pozycja X na środek ekranu
tempCrystalSpawnY = 2732 / 2; // Domyślna pozycja Y na środek ekranu
console.warn("crystalCoreObject nie istnieje podczas próby spawnu minibossa, używam domyślnych współrzędnych.");
}
if (typeof gameState.playCrystalExplosionAnimation === 'function') {
// Ta funkcja powinna teraz poprawnie używać wewnętrznych crystalOriginalX/Y
// i przekazywać je do spawnCursedCrystalMiniboss
gameState.playCrystalExplosionAnimation();
} else {
// Awaryjne spawnowanie, jeśli playCrystalExplosionAnimation nie istnieje
console.error("gameState.playCrystalExplosionAnimation is not defined! Spawning miniboss directly.");
// Przekaż zdefiniowane wyżej współrzędne
gameState.spawnCursedCrystalMiniboss(tempCrystalSpawnX, tempCrystalSpawnY);
gameState.crystalExploding = false; // Resetuj flagę, jeśli spawn był bezpośredni i synchroniczny
}
// UWAGA: Poniższy blok kodu, który oryginalnie tworzył minibossOptions i spawnował minibossa
// w tym miejscu, został usunięty. Logika ta jest teraz w całości obsługiwana przez
// gameState.playCrystalExplosionAnimation() -> gameState.spawnCursedCrystalMiniboss()
// lub przez awaryjne, bezpośrednie wywołanie gameState.spawnCursedCrystalMiniboss(tempCrystalSpawnX, tempCrystalSpawnY) powyżej.
// To eliminuje redundancję i błąd ReferenceError.
} // Koniec if'a spawującego Minibossa
// Aktualizacja Minibossa CC, jeśli jest aktywny
if (gameState.isMinibossActiveCC && gameState.cursedCrystalMinibossObject && !gameState.cursedCrystalMinibossObject.isDead) {
gameState.cursedCrystalMinibossObject.update();
}
if (gameState.cursedCrystalMinibossObject && gameState.cursedCrystalMinibossObject.activeClones) {
for (var cIdx = gameState.cursedCrystalMinibossObject.activeClones.length - 1; cIdx >= 0; cIdx--) {
var cloneInstance = gameState.cursedCrystalMinibossObject.activeClones[cIdx];
if (cloneInstance.isDead) {
// Już obsłużone w cloneInstance.die(), ale dla pewności
gameState.cursedCrystalMinibossObject.activeClones.splice(cIdx, 1);
continue;
}
cloneInstance.update();
// Kolizja gracza (roll) z klonem
if (player && player.rolling && !cloneInstance.isDead) {
var dx_clone_roll = player.x - cloneInstance.x;
var dy_clone_roll = player.y - cloneInstance.y;
var distance_clone_roll = Math.sqrt(dx_clone_roll * dx_clone_roll + dy_clone_roll * dy_clone_roll);
var playerRollRadius_clone = (player.width || 150) / 2 * 0.7;
var cloneRadius_coll = (cloneInstance.width || 120) / 2 * 0.9;
if (distance_clone_roll < playerRollRadius_clone + cloneRadius_coll) {
cloneInstance.takeDamage(1); // Klon ginie po jednym trafieniu rollem
}
}
}
}
// --- Aktualizacja Pocisków Minibossa ---
if (typeof gameState.cursedCrystalActiveProjectiles === 'undefined' || !gameState.cursedCrystalActiveProjectiles) {
gameState.cursedCrystalActiveProjectiles = [];
}
for (var k = gameState.cursedCrystalActiveProjectiles.length - 1; k >= 0; k--) {
var projectile = gameState.cursedCrystalActiveProjectiles[k];
if (!projectile || projectile.isDead) {
if (projectile && projectile.graphics && projectile.graphics.parent) {
projectile.graphics.parent.removeChild(projectile.graphics);
if (projectile.graphics.destroy) {
projectile.graphics.destroy();
}
}
gameState.cursedCrystalActiveProjectiles.splice(k, 1);
continue;
}
projectile.update();
if (projectile.isDead) {
if (projectile.graphics && projectile.graphics.parent) {
projectile.graphics.parent.removeChild(projectile.graphics);
if (projectile.graphics.destroy) {
projectile.graphics.destroy();
}
}
gameState.cursedCrystalActiveProjectiles.splice(k, 1);
}
}
// --- Sprawdzanie Obrażeń od Eksplozji Minibossa ---
if (typeof gameState.cursedCrystalActiveExplosions === 'undefined' || !gameState.cursedCrystalActiveExplosions) {
gameState.cursedCrystalActiveExplosions = [];
}
for (var expIdx = gameState.cursedCrystalActiveExplosions.length - 1; expIdx >= 0; expIdx--) {
var explosion = gameState.cursedCrystalActiveExplosions[expIdx];
if (!explosion) {
gameState.cursedCrystalActiveExplosions.splice(expIdx, 1);
continue;
}
explosion.durationTimer--;
if (player && !player.dead && !player.invulnerable && !explosion.hitPlayerThisFrame) {
var distToExplosion = Math.sqrt(Math.pow(player.x - explosion.x, 2) + Math.pow(player.y - explosion.y, 2));
if (distToExplosion < (player.width || 150) / 2 * 0.7 + explosion.radius) {
player.takeDamage(explosion.damage);
explosion.hitPlayerThisFrame = true;
}
}
if (explosion.durationTimer <= 0) {
gameState.cursedCrystalActiveExplosions.splice(expIdx, 1);
}
}
// --- LOGIKA DLA AKTYWNYCH ŚCIAN LASEROWYCH MINIBOSSA ---
if (typeof gameState.cursedCrystalActiveLaserWalls === 'undefined' || !gameState.cursedCrystalActiveLaserWalls) {
gameState.cursedCrystalActiveLaserWalls = [];
}
if (gameState.cursedCrystalActiveLaserWalls && gameState.cursedCrystalActiveLaserWalls.length > 0) {
for (var wallIdx = gameState.cursedCrystalActiveLaserWalls.length - 1; wallIdx >= 0; wallIdx--) {
var wall = gameState.cursedCrystalActiveLaserWalls[wallIdx];
if (!wall || wall.isDead) {
if (wall && wall.segments) {
wall.segments.forEach(function (seg) {
if (seg.isPlaceholder) {
return;
}
if (seg.visual && seg.visual.parent) {
seg.visual.parent.removeChild(seg.visual);
if (seg.visual.destroy && !seg.visual.destroyed) {
seg.visual.destroy();
}
}
});
}
gameState.cursedCrystalActiveLaserWalls.splice(wallIdx, 1);
continue;
}
if (gameState.cursedCrystalMinibossObject && !gameState.cursedCrystalMinibossObject.isDead) {
wall.pivotX = gameState.cursedCrystalMinibossObject.x;
wall.pivotY = gameState.cursedCrystalMinibossObject.y;
}
if (wall.warningTimer > 0) {
wall.warningTimer -= 1000 / 60;
wall.segments.forEach(function (segment) {
if (segment.isPlaceholder) {
return;
}
var initialAngleForStaticDisplay = 0;
var rotatedOffsetX = segment.initialOffsetX * Math.cos(initialAngleForStaticDisplay) - segment.initialOffsetY * Math.sin(initialAngleForStaticDisplay);
var rotatedOffsetY = segment.initialOffsetX * Math.sin(initialAngleForStaticDisplay) + segment.initialOffsetY * Math.cos(initialAngleForStaticDisplay);
segment.currentX = wall.pivotX + rotatedOffsetX;
segment.currentY = wall.pivotY + rotatedOffsetY;
if (segment.visual && !segment.visual.destroyed) {
segment.visual.x = segment.currentX;
segment.visual.y = segment.currentY;
segment.visual.rotation = initialAngleForStaticDisplay;
}
});
} else {
if (wall.activeTimer > 0) {
wall.activeTimer -= 1000 / 60;
wall.currentAngle += wall.rotationSpeed;
wall.segments.forEach(function (segment) {
if (segment.isPlaceholder) {
return;
}
var rotatedOffsetX = segment.initialOffsetX * Math.cos(wall.currentAngle) - segment.initialOffsetY * Math.sin(wall.currentAngle);
var rotatedOffsetY = segment.initialOffsetX * Math.sin(wall.currentAngle) + segment.initialOffsetY * Math.cos(wall.currentAngle);
segment.currentX = wall.pivotX + rotatedOffsetX;
segment.currentY = wall.pivotY + rotatedOffsetY;
if (segment.visual && !segment.visual.destroyed) {
segment.visual.x = segment.currentX;
segment.visual.y = segment.currentY;
var baseRotation = wall.currentAngle;
if (segment.animationPhase === 'action_once' || segment.animationPhase === 'looping_cut') {
if (segment.currentIndividualSpinAngle === undefined) {
segment.currentIndividualSpinAngle = 0;
}
var RAPID_SPIN_SPEED = 0.4;
segment.currentIndividualSpinAngle += RAPID_SPIN_SPEED;
segment.visual.rotation = baseRotation + segment.currentIndividualSpinAngle;
} else {
segment.visual.rotation = baseRotation;
}
if (player && !player.dead && !player.invulnerable) {
var playerHalfWidth_lw = (player.width || 150) / 2 * 0.7;
var playerHalfHeight_lw = (player.height || 150) / 2 * 0.7;
var segmentHalfWidth_lw = (segment.width || 120) / 2;
var segmentHalfHeight_lw = (segment.height || 120) / 2;
if (Math.abs(player.x - segment.currentX) < playerHalfWidth_lw + segmentHalfWidth_lw && Math.abs(player.y - segment.currentY) < playerHalfHeight_lw + segmentHalfHeight_lw) {
player.takeDamage(1);
}
}
}
});
if (wall.activeTimer <= 0) {
wall.isDead = true;
}
} else {
wall.isDead = true;
}
}
}
}
if (player && player.dead) {/* Obsługiwane przez player.die() */}
}
};
if (typeof gameState !== 'undefined' && typeof gameState.init === 'function') {
gameState.init();
} else {
console.error("Obiekt gameState lub gameState.init nie jest zdefiniowany przed wywołaniem init!");
}