Code edit (1 edits merged)
Please save this source code
Code edit (1 edits merged)
Please save this source code
Code edit (1 edits merged)
Please save this source code
Code edit (1 edits merged)
Please save this source code
Code edit (1 edits merged)
Please save this source code
Code edit (1 edits merged)
Please save this source code
Code edit (1 edits merged)
Please save this source code
Code edit (1 edits merged)
Please save this source code
Code edit (1 edits merged)
Please save this source code
Code edit (1 edits merged)
Please save this source code
Code edit (1 edits merged)
Please save this source code
Code edit (1 edits merged)
Please save this source code
Code edit (1 edits merged)
Please save this source code
Code edit (1 edits merged)
Please save this source code
Code edit (1 edits merged)
Please save this source code
Code edit (1 edits merged)
Please save this source code
Code edit (1 edits merged)
Please save this source code
Code edit (1 edits merged)
Please save this source code
Code edit (1 edits merged)
Please save this source code
Code edit (1 edits merged)
Please save this source code
Code edit (1 edits merged)
Please save this source code
Code edit (1 edits merged)
Please save this source code
Code edit (1 edits merged)
Please save this source code
Code edit (1 edits merged)
Please save this source code
Code edit (1 edits merged)
Please save this source code
/**** * Plugins ****/ var tween = LK.import("@upit/tween.v1"); /**** * Classes ****/ var Building = Container.expand(function (id, size) { var self = Container.call(this); self.id = id || 'default'; self.size = size || { w: 2, h: 2 }; var assetId = 'building_' + self.id; var baseGraphics = self.attachAsset(assetId, { anchorX: 0, anchorY: 0 }); baseGraphics.width = CELL_SIZE * self.size.w; baseGraphics.height = CELL_SIZE * self.size.h; baseGraphics.tint = 0xFFFFFF; baseGraphics.alpha = 1; self.down = function () { if (isMenuTransitioning) { return; } game.children.forEach(function (child) { if (child instanceof BuildingInfoMenu) { hideBuildingInfoMenu(child); } }); if (selectedBuilding === self) { selectedBuilding = null; return; } selectedBuilding = self; var menu = game.addChild(new BuildingInfoMenu(self)); menu.x = 2048 / 2; isMenuTransitioning = true; tween(menu, { y: 2732 - 250 }, { duration: 200, easing: tween.backOut, onFinish: function onFinish() { isMenuTransitioning = false; } }); }; return self; }); var BuildingInfoMenu = Container.expand(function (building) { var self = Container.call(this); self.building = building; self.y = 2732 + 225; var menuBackground = self.attachAsset('panel_popup_background', { anchorX: 0.5, anchorY: 0.5 }); menuBackground.width = 2048; menuBackground.height = 500; var closeButton = new Container(); var closeBackground = closeButton.attachAsset('notification', { anchorX: 0.5, anchorY: 0.5, width: 90, height: 90, tint: 0xAA0000 }); var closeText = new Text2('X', { size: 68, fill: 0xFFFFFF, weight: 800 }); closeText.anchor.set(0.5, 0.5); closeButton.addChild(closeText); closeButton.x = menuBackground.width / 2 - 57; closeButton.y = -menuBackground.height / 2 + 57; self.addChild(closeButton); closeButton.down = function () { hideBuildingInfoMenu(self); }; var originalDestroy = self.destroy; self.destroy = function () { if (selectedBuilding === self.building) { selectedBuilding = null; } originalDestroy.call(self); }; return self; }); /**** * Classes ****/ var Bullet = Container.expand(function (startX, startY, targetEnemy, props) { var self = Container.call(this); self.targetEnemy = targetEnemy; self.props = props; self.x = startX; self.y = startY; self.isAnimated = Array.isArray(self.props.graphic); self.allFrames = []; if (self.isAnimated) { var frameNames = self.props.graphic; for (var i = 0; i < frameNames.length; i++) { var frameSprite = self.attachAsset(frameNames[i], { anchorX: 0.5, anchorY: 0.5 }); frameSprite.visible = false; self.allFrames.push(frameSprite); } self.currentFrame = 0; self.frameDuration = 4; self.frameTicker = 0; if (self.allFrames.length > 0) { self.allFrames[self.currentFrame].visible = true; } } else { var bulletGraphics = self.attachAsset(self.props.graphic || 'bullet', { anchorX: 0.5, anchorY: 0.5 }); if (self.props.bulletType === 'chain') { bulletGraphics.visible = false; } self.allFrames.push(bulletGraphics); } if (self.props.bulletType === 'splash') { self.allFrames[0].tint = 0xFF8C00; } if (self.props.bulletType === 'slow') { self.allFrames.forEach(function (frame) { frame.tint = 0xADD8E6; }); } self.update = function () { if (!self.targetEnemy || !self.targetEnemy.parent || self.targetEnemy.health <= 0) { self.destroy(); return; } var dx = self.targetEnemy.x - self.x; var dy = self.targetEnemy.y - self.y; var angle = Math.atan2(dy, dx); var distance = Math.sqrt(dx * dx + dy * dy); if (self.isAnimated) { self.frameTicker++; if (self.frameTicker >= self.frameDuration) { self.frameTicker = 0; self.allFrames[self.currentFrame].visible = false; self.currentFrame = (self.currentFrame + 1) % self.allFrames.length; self.allFrames[self.currentFrame].visible = true; } } self.allFrames.forEach(function (frame) { if (self.props.graphic === 'projectile_fireball') { frame.rotation += 0.2; } else { frame.rotation = angle; } }); if (distance < self.props.bulletSpeed) { self.targetEnemy.takeDamage(self.props.damage); switch (self.props.bulletType) { case 'slow': if (Math.random() < self.props.slowChance) { if (self.targetEnemy.slowDuration <= 0) { self.targetEnemy.originalTint = self.targetEnemy.children[0].tint; } self.targetEnemy.children[0].tint = 0xADD8E6; self.targetEnemy.slowDuration = self.props.slowDuration; self.targetEnemy.slowFactor = self.props.slowAmount; } break; case 'splash': spellEffectLayer.addChild(new Explosion(self.x, self.y, self.props.splashScale)); enemies.forEach(function (enemy) { if (enemy !== self.targetEnemy) { var distToSplash = Math.sqrt(Math.pow(self.x - enemy.x, 2) + Math.pow(self.y - enemy.y, 2)); if (distToSplash < self.props.splashRadius) { enemy.takeDamage(self.props.splashDamage); } } }); break; case 'chain': spellEffectLayer.addChild(new LightningStrikeEffect(self.x, self.y)); if (Math.random() < self.props.chainChance) { var chainedTargets = [self.targetEnemy]; var lastTarget = self.targetEnemy; var damage = self.props.damage; for (var i = 0; i < self.props.chainTargets; i++) { damage -= self.props.chainDamageFalloff; if (damage <= 0) { break; } var nextTarget = null; var minChainDist = Infinity; enemies.forEach(function (enemy) { if (!chainedTargets.includes(enemy)) { var distToChain = Math.sqrt(Math.pow(lastTarget.x - enemy.x, 2) + Math.pow(lastTarget.y - enemy.y, 2)); if (distToChain < self.props.range / 2 && distToChain < minChainDist) { minChainDist = distToChain; nextTarget = enemy; } } }); if (nextTarget) { nextTarget.takeDamage(damage); spellEffectLayer.addChild(new ChainLightningVisual(lastTarget, nextTarget)); chainedTargets.push(nextTarget); lastTarget = nextTarget; } else { break; } } } break; } self.destroy(); } else { self.x += Math.cos(angle) * self.props.bulletSpeed; self.y += Math.sin(angle) * self.props.bulletSpeed; } }; return self; }); var ChainLightningVisual = Container.expand(function (sourceEnemy, targetEnemy) { var self = Container.call(this); var dx = targetEnemy.x - sourceEnemy.x; var dy = targetEnemy.y - sourceEnemy.y; var distance = Math.sqrt(dx * dx + dy * dy); var angle = Math.atan2(dy, dx); self.x = sourceEnemy.x; self.y = sourceEnemy.y; self.rotation = angle; var frameNames = []; for (var i = 1; i <= 4; i++) { frameNames.push('chain_lightning_' + i); } var allFrames = []; for (var i = 0; i < frameNames.length; i++) { var frameSprite = self.attachAsset(frameNames[i], { anchorX: 0, anchorY: 0.5, width: distance, height: 80 }); frameSprite.blendMode = 1; frameSprite.visible = false; allFrames.push(frameSprite); } var currentFrame = 0; var frameDuration = 6; var frameTicker = 0; if (allFrames.length > 0) { allFrames[currentFrame].visible = true; } self.update = function () { frameTicker++; if (frameTicker >= frameDuration) { frameTicker = 0; if (allFrames[currentFrame]) { allFrames[currentFrame].visible = false; } currentFrame++; if (currentFrame >= allFrames.length) { self.destroy(); } else { if (allFrames[currentFrame]) { allFrames[currentFrame].visible = true; } } } }; return self; }); var CurseEffect = Container.expand(function (x, y, spellData) { var self = Container.call(this); self.x = x; self.y = y; var radius = spellData.radius; var radiusSq = radius * radius; var duration = spellData.duration; var isFadingOut = false; var effectGraphics = self.attachAsset('curse_effect_circle', { anchorX: 0.5, anchorY: 0.5, width: radius * 2, height: radius * 2 }); effectGraphics.alpha = 0; tween(effectGraphics, { alpha: 0.8 }, { duration: 500 }); self.update = function () { if (isFadingOut) { return; } duration--; if (duration <= 0) { isFadingOut = true; tween(self, { alpha: 0 }, { duration: 500, onFinish: function onFinish() { self.destroy(); } }); return; } effectGraphics.rotation -= 0.005; enemies.forEach(function (enemy) { var dx = enemy.x - self.x; var dy = enemy.y - self.y; if (dx * dx + dy * dy < radiusSq) { enemy.isCursed = true; enemy.curseTimer = 120; enemy.damageTakenMultiplier = spellData.damageMultiplier; enemy.slowFactor = Math.min(enemy.slowFactor, spellData.slowFactor); } }); }; return self; }); var DebugCell = Container.expand(function () { var self = Container.call(this); var cellGraphics = self.attachAsset('cell', { anchorX: 0.5, anchorY: 0.5 }); cellGraphics.tint = Math.random() * 0xffffff; var debugArrows = []; var numberLabel = new Text2('0', { size: 30, fill: 0xFFFFFF, weight: 800 }); numberLabel.anchor.set(.5, .5); self.addChild(numberLabel); self.update = function () {}; self.down = function () { return; if (self.cell.type == 0 || self.cell.type == 1) { self.cell.type = self.cell.type == 1 ? 0 : 1; if (grid.pathFind()) { self.cell.type = self.cell.type == 1 ? 0 : 1; grid.pathFind(); var notification = game.addChild(new Notification("Path is blocked!")); notification.x = 2048 / 2; notification.y = grid.height - 50; } grid.renderDebug(); } }; self.removeArrows = function () { while (debugArrows.length) { self.removeChild(debugArrows.pop()); } }; self.render = function (data) { switch (data.type) { case 0: case 2: { if (data.pathId != pathId) { self.removeArrows(); numberLabel.setText("-"); cellGraphics.tint = 0x880000; return; } numberLabel.visible = true; var tint = Math.floor(data.score / maxScore * 0x88); var towerInRangeHighlight = false; if (selectedTower && data.towersInRange && data.towersInRange.indexOf(selectedTower) !== -1) { towerInRangeHighlight = true; cellGraphics.tint = 0x0088ff; } else { cellGraphics.tint = 0x88 - tint << 8 | tint; } while (debugArrows.length > data.targets.length) { self.removeChild(debugArrows.pop()); } for (var a = 0; a < data.targets.length; a++) { var destination = data.targets[a]; var ox = destination.x - data.x; var oy = destination.y - data.y; var angle = Math.atan2(oy, ox); if (!debugArrows[a]) { debugArrows[a] = LK.getAsset('arrow', { anchorX: -.5, anchorY: 0.5 }); debugArrows[a].alpha = .5; self.addChildAt(debugArrows[a], 1); } debugArrows[a].rotation = angle; } break; } case 1: { self.removeArrows(); cellGraphics.tint = 0xaaaaaa; numberLabel.visible = false; break; } case 3: { self.removeArrows(); cellGraphics.tint = 0x008800; numberLabel.visible = false; break; } } numberLabel.setText(Math.floor(data.score / 1000) / 10); }; }); var EffectIndicator = Container.expand(function (x, y, type) { var self = Container.call(this); self.x = x; self.y = y; var effectGraphics = self.attachAsset('rangeCircle', { anchorX: 0.5, anchorY: 0.5 }); effectGraphics.blendMode = 1; switch (type) { case 'splash': effectGraphics.tint = 0x33CC00; effectGraphics.width = effectGraphics.height = CELL_SIZE * 1.5; break; case 'slow': effectGraphics.tint = 0x9900FF; effectGraphics.width = effectGraphics.height = CELL_SIZE; break; case 'poison': effectGraphics.tint = 0x00FFAA; effectGraphics.width = effectGraphics.height = CELL_SIZE; break; case 'sniper': effectGraphics.tint = 0xFF5500; effectGraphics.width = effectGraphics.height = CELL_SIZE; break; } effectGraphics.alpha = 0.7; self.alpha = 0; tween(self, { alpha: 0.8, scaleX: 1.5, scaleY: 1.5 }, { duration: 200, easing: tween.easeOut, onFinish: function onFinish() { tween(self, { alpha: 0, scaleX: 2, scaleY: 2 }, { duration: 300, easing: tween.easeIn, onFinish: function onFinish() { self.destroy(); } }); } }); return self; }); var Enemy = Container.expand(function (type, spawnerId, laneId) { var self = Container.call(this); self.type = type || 'normal'; self.spawnerId = spawnerId; self.laneId = laneId; self.laneOffset = 0; // NOWA WŁAŚCIWOŚĆ self.speed = 0.01; self.originalSpeed = self.speed; self.slowDuration = 0; self.slowFactor = 1; self.isCursed = false; self.curseTimer = 0; self.damageTakenMultiplier = 1; self.isBurning = false; self.burnDuration = 0; self.burnDamage = 0; self.lastBurnTick = 0; self.isStunned = false; self.stunTimer = 0; self.originalTint = 0xFFFFFF; self.cellX = 0; self.cellY = 0; self.currentTarget = undefined; self.maxHealth = 78; self.health = self.maxHealth; self.waveNumber = currentWave; self.isFlying = false; self.isBoss = false; self.isRanged = false; self.manaCategory = 'strong'; self.damage = 10; self.attackSpeed = 120; self.lastAttack = 0; self.range = 1.5 * CELL_SIZE; self.targetUnit = null; self.goldValue = 10; switch (self.type) { case 'shield': self.maxHealth = 94; self.damage = 3; self.manaCategory = 'strong'; self.goldValue = 36; break; case 'rider': self.maxHealth = 78; self.damage = 6; self.speed *= 1.5; self.manaCategory = 'strong'; self.goldValue = 59; break; case 'chieftain': self.maxHealth = 524; self.damage = 10; self.isBoss = true; self.manaCategory = 'boss'; self.goldValue = 304; break; case 'archer': self.maxHealth = 31; self.damage = 7; self.attackSpeed = 48; self.isRanged = true; self.range = 6 * CELL_SIZE; self.manaCategory = 'strong'; self.goldValue = 34; break; default: self.maxHealth = 62; self.damage = 4; self.manaCategory = 'weak'; self.goldValue = 19; break; } self.originalSpeed = self.speed; self.health = self.maxHealth; var enemyGraphics = self.attachAsset('orc_' + self.type, { anchorX: 0.5, anchorY: 0.5 }); var healthBarOutline = self.attachAsset('healthBarOutline', { anchorX: 0, anchorY: 0.5 }); var healthBarBG = self.attachAsset('healthBar', { anchorX: 0, anchorY: 0.5 }); var healthBar = self.attachAsset('healthBar', { anchorX: 0, anchorY: 0.5 }); healthBarBG.y = healthBarOutline.y = healthBar.y = -enemyGraphics.height / 2 - 10; healthBarOutline.x = -healthBarOutline.width / 2; healthBarBG.x = healthBar.x = -healthBar.width / 2 - .5; healthBar.tint = 0x00ff00; healthBarBG.tint = 0xff0000; self.healthBar = healthBar; self.takeDamage = function (amount) { self.health -= amount * self.damageTakenMultiplier; if (self.health < 0) { self.health = 0; } self.healthBar.width = self.health / self.maxHealth * 70; }; self.findTarget = function () { if (self.isFlying) { return null; } var closestUnit = null; var minDistance = self.range * self.range; for (var i = 0; i < friendlyUnits.length; i++) { var unit = friendlyUnits[i]; var dx = unit.x - self.x; var dy = unit.y - self.y; var distanceSq = dx * dx + dy * dy; if (distanceSq < minDistance) { minDistance = distanceSq; closestUnit = unit; } } return closestUnit; }; self.attack = function () { if (!self.targetUnit || self.targetUnit.health <= 0) { return; } if (self.isRanged) { self.fireProjectile(); return; } self.targetUnit.takeDamage(self.damage); var angle = Math.atan2(self.targetUnit.y - self.y, self.targetUnit.x - self.x); var recoilDistance = 5; var originalX = self.targetUnit.x; var originalY = self.targetUnit.y; tween(self.targetUnit, { x: originalX + Math.cos(angle) * recoilDistance, y: originalY + Math.sin(angle) * recoilDistance }, { duration: 80, easing: tween.easeOut, onFinish: function onFinish() { if (self.targetUnit) { tween(self.targetUnit, { x: originalX, y: originalY }, { duration: 120, easing: tween.easeIn }); } } }); var unitGraphics = self.targetUnit.children[0]; if (unitGraphics) { var originalTint = unitGraphics.tint; unitGraphics.tint = 0xFF0000; LK.setTimeout(function () { if (unitGraphics && !unitGraphics.destroyed) { unitGraphics.tint = originalTint; } }, 100); } createHitSparks(self.targetUnit.x, self.targetUnit.y); }; self.update = function () { if (self.isStunned) { self.stunTimer--; if (self.stunTimer <= 0) { self.isStunned = false; } return false; } self.speed = self.originalSpeed; if (self.slowDuration > 0) { self.slowDuration--; self.speed *= self.slowFactor; if (self.slowDuration <= 0) { self.slowFactor = 1; enemyGraphics.tint = self.originalTint; } } if (self.isCursed) { self.curseTimer--; if (self.curseTimer <= 0) { self.isCursed = false; self.damageTakenMultiplier = 1; } else { self.speed *= self.slowFactor; } } if (self.isBurning) { if (LK.ticks - self.lastBurnTick >= 60) { self.takeDamage(self.burnDamage); self.lastBurnTick = LK.ticks; } self.burnDuration--; if (self.burnDuration <= 0) { self.isBurning = false; } } if (self.health <= 0) { self.health = 0; self.healthBar.width = 0; return false; } self.targetUnit = self.findTarget(); var combatSpeedInPixels = self.speed * CELL_SIZE * 1.2; if (self.targetUnit && self.targetUnit.health > 0) { var dx = self.targetUnit.x - self.x; var dy = self.targetUnit.y - self.y; if (Math.sqrt(dx * dx + dy * dy) < self.range) { if (LK.ticks - self.lastAttack >= self.attackSpeed) { self.attack(); self.lastAttack = LK.ticks; } } else { var angle = Math.atan2(dy, dx); self.x += Math.cos(angle) * combatSpeedInPixels; self.y += Math.sin(angle) * combatSpeedInPixels; } } else { if (grid.updateEnemy(self)) { return true; } if (self.currentTarget) { var targetPixelX = grid.x + (self.currentTarget.x + self.laneOffset) * CELL_SIZE + CELL_SIZE / 2; var targetPixelY = grid.y + self.currentTarget.y * CELL_SIZE + CELL_SIZE / 2; var dx = targetPixelX - self.x; var dy = targetPixelY - self.y; var dist = Math.sqrt(dx * dx + dy * dy); if (dist < combatSpeedInPixels) { // NOWA, BARDZIEJ NIEZAWODNA LOGIKA if (self.currentTarget.type === 4) { return true; // NATYCHMIAST URUCHOM UTRATĘ ŻYCIA } self.x = targetPixelX; self.y = targetPixelY; self.cellX = self.currentTarget.x; self.cellY = self.currentTarget.y; self.currentTarget = null; } else { var angle = Math.atan2(dy, dx); self.x += Math.cos(angle) * combatSpeedInPixels; self.y += Math.sin(angle) * combatSpeedInPixels; } } } healthBarOutline.y = healthBarBG.y = healthBar.y = -enemyGraphics.height / 2 - 10; return false; }; return self; }); var Explosion = Container.expand(function (x, y, scale) { var self = Container.call(this); self.x = x; self.y = y; self.scale.set(scale || 1); // Ustawiamy skalę var allFrames = []; for (var i = 1; i <= 8; i++) { var frameSprite = self.attachAsset('explosion_fire_' + i, { anchorX: 0.5, anchorY: 0.5 }); frameSprite.blendMode = 1; frameSprite.visible = false; allFrames.push(frameSprite); } var currentFrame = 0; var frameDuration = 5; var frameTicker = 0; if (allFrames.length > 0) { allFrames[currentFrame].visible = true; } self.update = function () { if (allFrames.length === 0) { self.destroy(); return; } frameTicker++; if (frameTicker >= frameDuration) { frameTicker = 0; allFrames[currentFrame].visible = false; currentFrame++; if (currentFrame >= allFrames.length) { self.destroy(); } else { allFrames[currentFrame].visible = true; } } }; return self; }); var FireWall = Container.expand(function (x, y, spellData) { var self = Container.call(this); self.x = x; self.y = y; self.spellData = spellData; self.duration = self.spellData.duration; self.radiusSq = self.spellData.radius * self.spellData.radius; var isFadingOut = false; self.update = function () { if (isFadingOut) { return; } self.duration--; if (self.duration <= 0) { isFadingOut = true; var spellIndex = activeSpells.indexOf(self); if (spellIndex !== -1) { activeSpells.splice(spellIndex, 1); } self.destroy(); return; } enemies.forEach(function (enemy) { if (enemy.isBurning) { return; } var dx = enemy.x - self.x; var dy = enemy.y - self.y; if (dx * dx + dy * dy < self.radiusSq) { enemy.isBurning = true; enemy.burnDuration = self.spellData.dotDuration; enemy.burnDamage = self.spellData.dotDamage; } }); }; return self; }); var FireWallVisual = Container.expand(function (x, y, size) { var self = Container.call(this); self.x = x; self.y = y; var frameNames = []; for (var i = 1; i <= 13; i++) { frameNames.push('fire_wall_' + i); } var allFrames = []; for (var i = 0; i < frameNames.length; i++) { var frameSprite = self.attachAsset(frameNames[i], { anchorX: 0.5, anchorY: 0.5, width: size, height: size }); frameSprite.blendMode = 1; frameSprite.visible = false; allFrames.push(frameSprite); } var currentFrame = 0; var frameDuration = 7; var frameTicker = 0; var lifetime = 460; if (allFrames.length > 0) { allFrames[currentFrame].visible = true; } self.update = function () { lifetime--; if (lifetime <= 0) { tween(self, { alpha: 0 }, { duration: 500, onFinish: function onFinish() { self.destroy(); } }); return; } frameTicker++; if (frameTicker >= frameDuration) { frameTicker = 0; if (allFrames[currentFrame]) { allFrames[currentFrame].visible = false; } currentFrame = (currentFrame + 1) % allFrames.length; if (allFrames[currentFrame]) { allFrames[currentFrame].visible = true; } } }; return self; }); var FogWisp = Container.expand(function () { var self = Container.call(this); var lifetime = 240 + Math.random() * 60; // Ok. 4-5 sekund życia var isFadingOut = false; // Losowa pozycja startowa na ekranie self.x = Math.random() * 2048; self.y = Math.random() * 2732; // Losowy kierunek i prędkość var angle = Math.random() * Math.PI * 2; var speed = 0.3 + Math.random() * 0.3; var dx = Math.cos(angle) * speed; var dy = Math.sin(angle) * speed; // Losowa grafika mgły var fogAssetNames = ['fog_1', 'fog_2', 'fog_3', 'fog_4']; var randomAssetName = fogAssetNames[Math.floor(Math.random() * fogAssetNames.length)]; var sprite = self.attachAsset(randomAssetName, { anchorX: 0.5, anchorY: 0.5 }); sprite.scale.set(1.5 + Math.random() * 1.0); // Fade-in na starcie self.alpha = 0; tween(self, { alpha: 0.5 + Math.random() * 0.2 }, { duration: 1000 }); self.update = function () { if (isFadingOut) { return; } // Ruch self.x += dx; self.y += dy; // Cykl życia lifetime--; if (lifetime <= 0) { isFadingOut = true; tween(self, { alpha: 0 }, { duration: 1000, onFinish: function onFinish() { var index = activeFogWisps.indexOf(self); if (index !== -1) { activeFogWisps.splice(index, 1); } self.destroy(); } }); } }; return self; }); var FriendlyUnit = Container.expand(function (startX, startY, unitStats) { var self = Container.call(this); self.unitType = unitStats.id || 'swordsman'; Object.assign(self, unitStats); self.x = startX; self.y = startY; self.health = self.maxHealth; self.lastAttack = 0; self.targetEnemy = null; var assetId = 'unit_' + self.unitType; var unitGraphics = self.attachAsset(assetId, { anchorX: 0.5, anchorY: 0.5 }); var healthBarOutline = self.attachAsset('healthBarOutline', { anchorX: 0, anchorY: 0.5 }); var healthBarBG = self.attachAsset('healthBar', { anchorX: 0, anchorY: 0.5 }); var healthBar = self.attachAsset('healthBar', { anchorX: 0, anchorY: 0.5 }); healthBarBG.y = healthBarOutline.y = healthBar.y = -unitGraphics.height / 2 - 10; healthBarOutline.x = -healthBarOutline.width / 2; healthBarBG.x = healthBar.x = -healthBar.width / 2 - .5; healthBar.tint = 0x00ff00; healthBarBG.tint = 0xff0000; self.healthBar = healthBar; self.takeDamage = function (amount) { self.health -= amount; if (self.health < 0) { self.health = 0; } self.healthBar.width = self.health / self.maxHealth * 70; }; self.findClosestEnemy = function () { var closestEnemy = null; var minDistanceSq = self.detectionRange * self.detectionRange; for (var i = 0; i < enemies.length; i++) { var enemy = enemies[i]; if (enemy.isFlying && !self.isRanged) { continue; } var dx = enemy.x - self.x; var dy = enemy.y - self.y; var distanceSq = dx * dx + dy * dy; if (distanceSq < minDistanceSq) { minDistanceSq = distanceSq; closestEnemy = enemy; } } return closestEnemy; }; self.fireProjectile = function () { var bulletProps = { damage: self.damage, bulletSpeed: 12, graphic: 'arrow_unit_archer' }; var bullet = new Bullet(self.x, self.y, self.targetEnemy, bulletProps); gateContainer.addChild(bullet); bullets.push(bullet); }; self.attack = function () { if (!self.targetEnemy || self.targetEnemy.health <= 0) { return; } if (self.isRanged) { self.fireProjectile(); return; } self.targetEnemy.takeDamage(self.damage); var angle = Math.atan2(self.targetEnemy.y - self.y, self.targetEnemy.x - self.x); var recoilDistance = 5; var originalX = self.targetEnemy.x; var originalY = self.targetEnemy.y; tween(self.targetEnemy, { x: originalX + Math.cos(angle) * recoilDistance, y: originalY + Math.sin(angle) * recoilDistance }, { duration: 80, easing: tween.easeOut, onFinish: function onFinish() { if (self.targetEnemy) { tween(self.targetEnemy, { x: originalX, y: originalY }, { duration: 120, easing: tween.easeIn }); } } }); var enemyGraphics = self.targetEnemy.children[0]; if (enemyGraphics) { var originalTint = enemyGraphics.tint; enemyGraphics.tint = 0xFF0000; LK.setTimeout(function () { if (enemyGraphics && !enemyGraphics.destroyed) { enemyGraphics.tint = originalTint; } }, 100); } createHitSparks(self.targetEnemy.x, self.targetEnemy.y); }; self.die = function () { peopleRegenQueue.push(LK.ticks); var unitIndex = friendlyUnits.indexOf(self); if (unitIndex !== -1) { friendlyUnits.splice(unitIndex, 1); } self.destroy(); }; self.update = function () { if (self.health <= 0) { self.die(); return; } self.healthBar.width = self.health / self.maxHealth * 70; if (!self.targetEnemy || self.targetEnemy.health <= 0 || !self.targetEnemy.parent) { self.targetEnemy = self.findClosestEnemy(); } if (self.targetEnemy) { var dx = self.targetEnemy.x - self.x; var dy = self.targetEnemy.y - self.y; var distance = Math.sqrt(dx * dx + dy * dy); if (distance < self.range) { if (LK.ticks - self.lastAttack >= self.attackSpeed) { self.attack(); self.lastAttack = LK.ticks; } return; } else if (distance < self.detectionRange) { var angle = Math.atan2(dy, dx); self.x += Math.cos(angle) * self.speed; self.y += Math.sin(angle) * self.speed; return; } else { self.targetEnemy = null; } } // SPRAWDZENIE, CZY NA MAPIE SĄ WROGOWIE if (enemies.length === 0) { return; // Jeśli nie ma wrogów, stój w miejscu } // PONIŻEJ LOGIKA RUCHU, GDY SĄ WROGOWIE, ALE ŻADEN NIE JEST W ZASIĘGU var moveX = 0; var moveY = 0; // Idź w górę moveY -= 1; var separationX = 0; var separationY = 0; var separationRadius = 40; for (var i = 0; i < friendlyUnits.length; i++) { var otherUnit = friendlyUnits[i]; if (otherUnit === self) { continue; } var otherDx = self.x - otherUnit.x; var otherDy = self.y - otherUnit.y; var otherDist = Math.sqrt(otherDx * otherDx + otherDy * otherDy); if (otherDist > 0 && otherDist < separationRadius) { separationX += otherDx / (otherDist * otherDist); separationY += otherDy / (otherDist * otherDist); } } var separationWeight = 15; moveX += separationX * separationWeight; moveY += separationY * separationWeight; var moveMagnitude = Math.sqrt(moveX * moveX + moveY * moveY); if (moveMagnitude > 0) { var finalMoveX = moveX / moveMagnitude * self.speed; var finalMoveY = moveY / moveMagnitude * self.speed; self.x += finalMoveX; self.y += finalMoveY; } if (self.y < grid.y) { self.die(); } }; return self; }); var GoldIndicator = Container.expand(function (value, x, y) { var self = Container.call(this); var shadowText = new Text2("+" + value, { size: 45, fill: 0x000000, weight: 800 }); shadowText.anchor.set(0.5, 0.5); shadowText.x = 2; shadowText.y = 2; self.addChild(shadowText); var goldText = new Text2("+" + value, { size: 45, fill: 0xFFD700, weight: 800 }); goldText.anchor.set(0.5, 0.5); self.addChild(goldText); self.x = x; self.y = y; self.alpha = 0; self.scaleX = 0.5; self.scaleY = 0.5; tween(self, { alpha: 1, scaleX: 1.2, scaleY: 1.2, y: y - 40 }, { duration: 50, easing: tween.easeOut, onFinish: function onFinish() { tween(self, { alpha: 0, scaleX: 1.5, scaleY: 1.5, y: y - 80 }, { duration: 600, easing: tween.easeIn, delay: 800, onFinish: function onFinish() { self.destroy(); } }); } }); return self; }); var Golem = Container.expand(function (x, y) { var self = Container.call(this); self.x = x; self.y = y; self.maxHealth = 130; self.health = self.maxHealth; self.damage = 2; self.attackSpeed = 100; self.range = 1.2 * CELL_SIZE; self.lastAttack = 0; self.targetEnemy = null; self.lifetime = 1200; self.isFadingOut = false; var golemGraphics = self.attachAsset('unit_golem', { anchorX: 0.5, anchorY: 0.5 }); var healthBarOutline = self.attachAsset('healthBarOutline', { anchorX: 0, anchorY: 0.5 }); var healthBarBG = self.attachAsset('healthBar', { anchorX: 0, anchorY: 0.5 }); var healthBar = self.attachAsset('healthBar', { anchorX: 0, anchorY: 0.5 }); healthBarBG.y = healthBarOutline.y = healthBar.y = -golemGraphics.height / 2 - 10; healthBarOutline.x = -healthBarOutline.width / 2; healthBarBG.x = healthBar.x = -healthBar.width / 2 - .5; healthBar.tint = 0x00ff00; healthBarBG.tint = 0xff0000; self.healthBar = healthBar; self.takeDamage = function (amount) { self.health -= amount; if (self.health < 0) { self.health = 0; } self.healthBar.width = self.health / self.maxHealth * 70; }; self.findClosestEnemy = function () { var closestEnemy = null; var minDistanceSq = self.range * self.range; for (var i = 0; i < enemies.length; i++) { var enemy = enemies[i]; if (enemy.isFlying) { continue; } var dx = enemy.x - self.x; var dy = enemy.y - self.y; var distanceSq = dx * dx + dy * dy; if (distanceSq < minDistanceSq) { minDistanceSq = distanceSq; closestEnemy = enemy; } } return closestEnemy; }; self.attack = function () { if (!self.targetEnemy || self.targetEnemy.health <= 0) { return; } self.targetEnemy.takeDamage(self.damage); createHitSparks(self.targetEnemy.x, self.targetEnemy.y); }; self.die = function () { self.isFadingOut = true; tween(self, { alpha: 0 }, { duration: 500, onFinish: function onFinish() { var unitIndex = friendlyUnits.indexOf(self); if (unitIndex !== -1) { friendlyUnits.splice(unitIndex, 1); } self.destroy(); } }); }; self.update = function () { if (self.isFadingOut) { return; } self.lifetime--; if (self.lifetime <= 0 || self.health <= 0) { self.die(); return; } if (!self.targetEnemy || self.targetEnemy.health <= 0 || !self.targetEnemy.parent) { self.targetEnemy = self.findClosestEnemy(); } if (self.targetEnemy) { var dx = self.targetEnemy.x - self.x; var dy = self.targetEnemy.y - self.y; var distance = Math.sqrt(dx * dx + dy * dy); if (distance < self.range) { if (LK.ticks - self.lastAttack >= self.attackSpeed) { self.attack(); self.lastAttack = LK.ticks; } } } }; return self; }); var GolemSummonEffect = Container.expand(function (x, y, onComplete) { var self = Container.call(this); self.x = x; self.y = y; var frameNames = []; for (var i = 1; i <= 11; i++) { frameNames.push('golem_summon_' + i); } var allFrames = []; var animationSize = 250; for (var i = 0; i < frameNames.length; i++) { var frameSprite = self.attachAsset(frameNames[i], { anchorX: 0.5, anchorY: 0.5, width: animationSize, height: animationSize }); frameSprite.blendMode = 1; frameSprite.visible = false; allFrames.push(frameSprite); } var currentFrame = 0; var frameDuration = 6; var frameTicker = 0; if (allFrames.length > 0) { allFrames[currentFrame].visible = true; } self.update = function () { frameTicker++; if (frameTicker >= frameDuration) { frameTicker = 0; if (allFrames[currentFrame]) { allFrames[currentFrame].visible = false; } currentFrame++; if (currentFrame >= allFrames.length) { if (onComplete) { onComplete(); } self.destroy(); } else { if (allFrames[currentFrame]) { allFrames[currentFrame].visible = true; } } } }; return self; }); var Grid = Container.expand(function (gridWidth, gridHeight) { var self = Container.call(this); self.cells = []; for (var i = 0; i < gridWidth; i++) { self.cells[i] = []; for (var j = 0; j < gridHeight; j++) { self.cells[i][j] = { targets: {}, score: {}, towersInRange: [], specialEffect: null }; } } for (var i = 0; i < gridWidth; i++) { for (var j = 0; j < gridHeight; j++) { var cell = self.cells[i][j]; cell.x = i; cell.y = j; cell.up = self.cells[i - 1] && self.cells[i - 1][j]; cell.left = self.cells[i][j - 1]; cell.right = self.cells[i][j + 1]; cell.down = self.cells[i + 1] && self.cells[i + 1][j]; } } self.getCell = function (x, y) { return self.cells[x] && self.cells[x][y]; }; self.calculatePath = function (pathId, goalCell) { var toProcess = [goalCell]; var pathCounter = Math.random(); toProcess[0].pathId = pathCounter; toProcess[0].score[pathId] = 0; function processNode(node, targetValue, targetNode) { if (node && node.type === 0) { if (!node.score[pathId] || targetValue < node.score[pathId]) { node.targets[pathId] = [targetNode]; node.score[pathId] = targetValue; if (node.pathId != pathCounter) { toProcess.push(node); node.pathId = pathCounter; } } } } while (toProcess.length) { var node = toProcess.shift(); var targetScore = node.score[pathId] + 1; processNode(node.up, targetScore, node); processNode(node.right, targetScore, node); processNode(node.down, targetScore, node); processNode(node.left, targetScore, node); } }; self.renderDebug = function () {}; self.updateEnemy = function (enemy) { var cell = grid.getCell(enemy.cellX, enemy.cellY); if (cell.type === 4) { return true; } if (!enemy.currentTarget) { var lanePathId = enemy.spawnerId + "_" + enemy.laneId; if (cell && cell.targets[lanePathId]) { enemy.currentTarget = cell.targets[lanePathId][0]; } } return false; }; }); var HealingFieldEffect = Container.expand(function (x, y, spellData) { var self = Container.call(this); self.x = x; self.y = y; var radius = spellData.radius; var radiusSq = radius * radius; var duration = 360; var healAmount = spellData.heal; var healInterval = 60; var particleInterval = 15; var healTicker = 0; var particleTicker = 0; var isFadingOut = false; var magicCircle = self.attachAsset('magic_circle_heal', { anchorX: 0.5, anchorY: 0.5, width: radius * 2, height: radius * 2 }); function pulse() { if (!self.parent || isFadingOut) { return; } tween(magicCircle, { alpha: 0.4 }, { duration: 1500, easing: tween.easeInOut, onFinish: function onFinish() { if (!self.parent || isFadingOut) { return; } tween(magicCircle, { alpha: 0.8 }, { duration: 1500, easing: tween.easeInOut, onFinish: pulse }); } }); } magicCircle.alpha = 0; tween(magicCircle, { alpha: 0.8 }, { duration: 500, onFinish: pulse }); self.update = function () { if (isFadingOut) { return; } duration--; if (duration <= 0) { isFadingOut = true; tween(self, { alpha: 0 }, { duration: 500, onFinish: function onFinish() { self.destroy(); } }); return; } healTicker++; if (healTicker >= healInterval) { healTicker = 0; friendlyUnits.forEach(function (unit) { var dx = unit.x - self.x; var dy = unit.y - self.y; if (dx * dx + dy * dy < radiusSq) { unit.health = Math.min(unit.maxHealth, unit.health + healAmount); unit.healthBar.width = unit.health / unit.maxHealth * 70; } }); } particleTicker++; if (particleTicker >= particleInterval) { particleTicker = 0; var angle = Math.random() * 2 * Math.PI; var dist = Math.sqrt(Math.random()) * radius; var pX = Math.cos(angle) * dist; var pY = Math.sin(angle) * dist; var particle = new HealingParticle(pX, pY); self.addChild(particle); } }; return self; }); var HealingParticle = Container.expand(function (x, y) { var self = Container.call(this); self.x = x; self.y = y; var particleGraphics = self.attachAsset('particle_heal_plus', { anchorX: 0.5, anchorY: 0.5 }); particleGraphics.tint = 0xFFC0CB; self.alpha = 0; tween(self, { alpha: 0.8, y: self.y - 20 }, { duration: 1500, easing: tween.easeOut, onFinish: function onFinish() { tween(self, { alpha: 0, y: self.y - 30 }, { duration: 1500, easing: tween.easeIn, onFinish: function onFinish() { self.destroy(); } }); } }); return self; }); var LightningStrike = Container.expand(function (x, y, type) { var self = Container.call(this); self.x = x; self.y = y; var frameNames = []; for (var i = 1; i <= 5; i++) { frameNames.push('lightning_' + type + '_' + i); } var allFrames = []; for (var i = 0; i < frameNames.length; i++) { var frameSprite = self.attachAsset(frameNames[i], { anchorX: 0.5, anchorY: 0.8 }); frameSprite.blendMode = 1; frameSprite.visible = false; allFrames.push(frameSprite); } var currentFrame = 0; var frameDuration = 5; var frameTicker = 0; if (allFrames.length > 0) { allFrames[currentFrame].visible = true; } self.update = function () { if (allFrames.length === 0) { self.destroy(); return; } frameTicker++; if (frameTicker >= frameDuration) { frameTicker = 0; if (allFrames[currentFrame]) { allFrames[currentFrame].visible = false; } currentFrame++; if (currentFrame >= allFrames.length) { self.destroy(); } else { if (allFrames[currentFrame]) { allFrames[currentFrame].visible = true; } } } }; return self; }); var LightningStrikeEffect = Container.expand(function (x, y) { var self = Container.call(this); self.x = x; self.y = y; var frameNames = []; for (var i = 1; i <= 7; i++) { frameNames.push('lightning_strike_' + i); } var allFrames = []; for (var i = 0; i < frameNames.length; i++) { var frameSprite = self.attachAsset(frameNames[i], { anchorX: 0.5, anchorY: 0.9 }); frameSprite.blendMode = 1; frameSprite.visible = false; allFrames.push(frameSprite); } var currentFrame = 0; var frameDuration = 3; var frameTicker = 0; if (allFrames.length > 0) { allFrames[currentFrame].visible = true; } self.update = function () { if (allFrames.length === 0) { self.destroy(); return; } frameTicker++; if (frameTicker >= frameDuration) { frameTicker = 0; if (allFrames[currentFrame]) { allFrames[currentFrame].visible = false; } currentFrame++; if (currentFrame >= allFrames.length) { self.destroy(); } else { if (allFrames[currentFrame]) { allFrames[currentFrame].visible = true; } } } }; return self; }); var NPC = Container.expand(function (spawnX, spawnY, graphicAsset) { var self = Container.call(this); self.x = spawnX; self.y = spawnY; self.speed = 0.5 + Math.random() * 0.5; self.state = 'IDLE'; self.stateTimer = Math.random() * 90 + 30; self.path = []; self.currentTargetPos = null; self.interactionPartner = null; self.clickCount = 0; var npcGraphics = self.attachAsset(graphicAsset, { anchorX: 0.5, anchorY: 0.9 }); npcGraphics.scale.set(0.6); self.down = function () { self.clickCount++; var message = ""; if (self.clickCount === 1) { message = "Don't click me!"; } else if (self.clickCount === 2) { message = "I'M SERIOUS, STOP!"; } else if (self.clickCount === 3) { message = "THAT'S IT, I'M LEAVING!"; self.state = 'LEAVING'; self.stateTimer = 180; npcGraphics.destroy(); npcGraphics = self.attachAsset('npc_suitcase', { anchorX: 0.5, anchorY: 0.9 }); npcGraphics.scale.set(0.6); } var bubble = new SpeechBubble(message, self.x, self.y - npcGraphics.height); npcLayer.addChild(bubble); }; self.findPath = function (targetCell) { var startCell = castleGrid.getCell(Math.floor((self.x - castleGrid.x) / CELL_SIZE), Math.floor((self.y - castleGrid.y) / CELL_SIZE)); if (!startCell || !targetCell || startCell === targetCell) { return []; } var queue = [[startCell]]; var visited = {}; // ZMIANA: Zamiast new Set() var startKey = startCell.x + ',' + startCell.y; visited[startKey] = true; while (queue.length > 0) { var path = queue.shift(); var lastCell = path[path.length - 1]; if (lastCell === targetCell) { return path; } var neighbors = [lastCell.up, lastCell.down, lastCell.left, lastCell.right].filter(function (n) { if (!n || n.type !== 1) { return false; } var key = n.x + ',' + n.y; return !visited[key]; // ZMIANA: Sprawdzanie w obiekcie }); for (var i = 0; i < neighbors.length; i++) { var neighbor = neighbors[i]; var key = neighbor.x + ',' + neighbor.y; visited[key] = true; // ZMIANA: Dodawanie do obiektu var newPath = path.slice(); newPath.push(neighbor); queue.push(newPath); } } return []; }; self.findWanderTarget = function () { var validCells = []; for (var i = 0; i < castleGrid.cells.length; i++) { for (var j = 0; j < castleGrid.cells[i].length; j++) { if (castleGrid.cells[i][j].type === 1) { validCells.push(castleGrid.cells[i][j]); } } } if (validCells.length > 0) { var targetCell = validCells[Math.floor(Math.random() * validCells.length)]; self.path = self.findPath(targetCell); if (self.path.length > 0) { self.state = 'WANDERING'; } } }; self.findInteractionPartner = function () { var potentialPartners = npcs.filter(function (p) { return p !== self && p.state === 'IDLE' && !p.interactionPartner; }); var closestPartner = null; var minDistanceSq = Math.pow(3 * CELL_SIZE, 2); potentialPartners.forEach(function (p) { var dx = p.x - self.x; var dy = p.y - self.y; var distSq = dx * dx + dy * dy; if (distSq < minDistanceSq) { minDistanceSq = distSq; closestPartner = p; } }); if (closestPartner) { self.interactionPartner = closestPartner; closestPartner.interactionPartner = self; var partnerCell = castleGrid.getCell(Math.floor((closestPartner.x - castleGrid.x) / CELL_SIZE), Math.floor((closestPartner.y - castleGrid.y) / CELL_SIZE)); self.path = self.findPath(partnerCell); if (self.path.length > 0) { self.state = 'WANDERING'; closestPartner.state = 'INTERRUPTED'; } else { self.interactionPartner.interactionPartner = null; self.interactionPartner = null; self.findWanderTarget(); } } else { self.findWanderTarget(); } }; self.startInteracting = function () { self.state = 'INTERACTING'; self.stateTimer = Math.random() * 240 + 120; if (self.interactionPartner) { self.interactionPartner.state = 'INTERACTING'; self.interactionPartner.stateTimer = self.stateTimer; var bubbleX = (self.x + self.interactionPartner.x) / 2; var bubbleY = Math.min(self.y, self.interactionPartner.y) - 80; var bubble = new SpeechBubble("...", bubbleX, bubbleY, true); npcLayer.addChild(bubble); } }; self.update = function () { switch (self.state) { case 'IDLE': self.stateTimer--; if (self.stateTimer <= 0) { if (Math.random() < 0.5) { self.findInteractionPartner(); } else { self.findWanderTarget(); } } break; case 'WANDERING': if (self.path.length === 0 && !self.currentTargetPos) { if (self.interactionPartner) { self.startInteracting(); } else { self.state = 'IDLE'; self.stateTimer = Math.random() * 90 + 60; } return; } if (!self.currentTargetPos && self.path.length > 0) { var nextCell = self.path.shift(); self.currentTargetPos = { x: castleGrid.x + nextCell.x * CELL_SIZE + CELL_SIZE / 2, y: castleGrid.y + nextCell.y * CELL_SIZE + CELL_SIZE / 2 }; } if (self.currentTargetPos) { var dx = self.currentTargetPos.x - self.x; var dy = self.currentTargetPos.y - self.y; var dist = Math.sqrt(dx * dx + dy * dy); if (dist < self.speed) { self.x = self.currentTargetPos.x; self.y = self.currentTargetPos.y; self.currentTargetPos = null; } else { self.x += dx / dist * self.speed; self.y += dy / dist * self.speed; } } break; case 'INTERACTING': self.stateTimer--; if (self.stateTimer <= 0) { self.state = 'IDLE'; self.stateTimer = Math.random() * 90 + 30; if (self.interactionPartner) { self.interactionPartner.interactionPartner = null; self.interactionPartner.state = 'IDLE'; self.interactionPartner.stateTimer = Math.random() * 90 + 30; self.interactionPartner = null; } } break; case 'LEAVING': self.y += self.speed * 2; self.stateTimer--; if (self.stateTimer <= 0) { tween(self, { alpha: 0 }, { duration: 1000, onFinish: function onFinish() { people--; updateUI(); self.destroy(); } }); self.state = 'GONE'; } break; case 'INTERRUPTED': case 'GONE': break; } }; return self; }); var Notification = Container.expand(function (message) { var self = Container.call(this); var notificationGraphics = self.attachAsset('panel_popup_background', { anchorX: 0.5, anchorY: 0.5 }); // Zakładamy, że panel notyfikacji ma stały, mniejszy rozmiar notificationGraphics.width = 800; notificationGraphics.height = 150; var notificationText = new Text2(message, { size: 50, fill: 0x000000, weight: 800 }); notificationText.anchor.set(0.5, 0.5); self.addChild(notificationText); self.alpha = 1; var fadeOutTime = 120; self.update = function () { if (fadeOutTime > 0) { fadeOutTime--; self.alpha = Math.min(fadeOutTime / 120 * 2, 1); } else { self.destroy(); } }; return self; }); var PlacementPreview = Container.expand(function () { var self = Container.call(this); self.itemType = 'tower'; self.subType = 'guard'; self.itemSize = { w: 2, h: 2 }; self.hasEnoughGold = true; self.canPlace = false; self.gridX = 0; self.gridY = 0; var rangeIndicator = self.attachAsset('rangeCircle', { anchorX: 0.5, anchorY: 0.5, alpha: 0.3 }); var previewHolder = new Container(); self.addChild(previewHolder); var previewGraphics = previewHolder.attachAsset('cell', { anchorX: 0.5, anchorY: 0.5 }); // NOWA LOGIKA: Cień + Tekst var textStyle = { size: 60, weight: 900, align: 'center' }; var bonusTextShadow = new Text2("", Object.assign({}, textStyle, { fill: 0x000000 })); bonusTextShadow.anchor.set(0.5, 0.5); bonusTextShadow.x = 3; bonusTextShadow.y = 3; previewHolder.addChild(bonusTextShadow); var bonusText = new Text2("", Object.assign({}, textStyle, { fill: 0xFFFFFF })); bonusText.anchor.set(0.5, 0.5); previewHolder.addChild(bonusText); self.updateAppearance = function (specialEffect) { previewGraphics.width = CELL_SIZE * self.itemSize.w; previewGraphics.height = CELL_SIZE * self.itemSize.h; previewGraphics.alpha = 0.6; // Lepsza czytelność na lekko ciemniejszym tle bonusText.setText(""); bonusTextShadow.setText(""); previewGraphics.tint = 0x00BFFF; if (specialEffect && self.canPlace && self.hasEnoughGold) { var textToShow = ""; if (specialEffect === 'amplifier') { previewGraphics.tint = 0xFFD700; textToShow = "+15% DMG"; } else if (specialEffect === 'catalyst') { previewGraphics.tint = 0x9370DB; textToShow = "+30% ATS"; } bonusText.setText(textToShow); bonusTextShadow.setText(textToShow); } rangeIndicator.visible = false; if (self.itemType === 'tower') { var stats = TOWER_DATA[self.subType] ? TOWER_DATA[self.subType][1] : null; if (stats && stats.range) { rangeIndicator.width = rangeIndicator.height = stats.range * 2; rangeIndicator.visible = true; } } if (!self.canPlace || !self.hasEnoughGold) { previewGraphics.tint = 0xFF0000; bonusText.setText(""); bonusTextShadow.setText(""); } }; self.updatePlacementStatus = function (targetGrid) { var validGridPlacement = true; var currentGrid = targetGrid || grid; var currentSpecialEffect = null; if (self.gridY <= 4 && self.itemType === 'tower') { validGridPlacement = false; } if (self.gridY + self.itemSize.h > currentGrid.cells[0].length && self.itemType === 'building') { validGridPlacement = false; } if (validGridPlacement) { for (var i = 0; i < self.itemSize.w; i++) { for (var j = 0; j < self.itemSize.h; j++) { var cell = currentGrid.getCell(self.gridX + i, self.gridY + j); if (!cell || cell.type !== 1) { validGridPlacement = false; } if (cell && cell.specialEffect) { currentSpecialEffect = cell.specialEffect; break; } } if (currentSpecialEffect) { break; } } } var cost = 0; if (self.itemType === 'tower') { cost = getTowerCost(self.subType); } else { var buildingCost = getBuildingCost(self.subType); cost = buildingCost.gold; } self.hasEnoughGold = gold >= cost; self.canPlace = validGridPlacement; self.updateAppearance(currentSpecialEffect); }; self.snapToGrid = function (x, y, targetGrid) { var currentGrid = targetGrid || grid; var gridPosX = x - currentGrid.x; var gridPosY = y - currentGrid.y; self.gridX = Math.floor(gridPosX / CELL_SIZE); self.gridY = Math.floor(gridPosY / CELL_SIZE); var itemWidth = CELL_SIZE * self.itemSize.w; var itemHeight = CELL_SIZE * self.itemSize.h; self.x = currentGrid.x + self.gridX * CELL_SIZE + itemWidth / 2; self.y = currentGrid.y + self.gridY * CELL_SIZE + itemHeight / 2; self.updatePlacementStatus(currentGrid); }; return self; }); var Raindrop = Container.expand(function () { var self = Container.call(this); var dropGraphics = self.attachAsset('raindrop', { anchorX: 0.5, anchorY: 0.5 }); dropGraphics.tint = 0xADD8E6; dropGraphics.scale.set(0.3 + Math.random() * 0.2); self.x = Math.random() * 2048; self.y = -100; var duration = 800 + Math.random() * 600; tween(self, { y: 2732 + 100 }, { duration: duration, easing: tween.linear, onFinish: function onFinish() { self.destroy(); } }); return self; }); var SourceBuilding = Container.expand(function (buildingType) { var self = Container.call(this); self.buildingType = buildingType || 'default'; var buildingData = BUILDING_DATA[self.buildingType]; var assetId = 'building_' + self.buildingType; var baseGraphics = self.attachAsset(assetId, { anchorX: 0.5, anchorY: 0.5 }); baseGraphics.width = 130; baseGraphics.height = 130; var typeLabelShadow = new Text2(buildingData.name, { size: 50, fill: 0x000000, weight: 800 }); typeLabelShadow.anchor.set(0.5, 0.5); typeLabelShadow.x = 4; typeLabelShadow.y = -20 + 4; self.addChild(typeLabelShadow); var typeLabel = new Text2(buildingData.name, { size: 50, fill: 0xFFFFFF, weight: 800 }); typeLabel.anchor.set(0.5, 0.5); typeLabel.y = -20; self.addChild(typeLabel); var costLabelShadow = new Text2(buildingData.cost, { size: 50, fill: 0x000000, weight: 800 }); costLabelShadow.anchor.set(0.5, 0.5); costLabelShadow.x = 4; costLabelShadow.y = 24 + 12; self.addChild(costLabelShadow); var costLabel = new Text2(buildingData.cost, { size: 50, fill: 0xFFD700, weight: 800 }); costLabel.anchor.set(0.5, 0.5); costLabel.y = 20 + 12; self.addChild(costLabel); self.update = function () { var currentCost = getBuildingCost(self.buildingType); if (costLabel.text !== currentCost.gold) { costLabel.setText(currentCost.gold); costLabelShadow.setText(currentCost.gold); } var canAfford = gold >= currentCost.gold; self.alpha = canAfford ? 1 : 0.5; }; return self; }); var SourceSpell = Container.expand(function (spellId) { var self = Container.call(this); self.spellId = spellId; var spellData = SPELL_DATA[self.spellId]; var assetId = 'icon_spell_' + self.spellId; var baseGraphics = self.attachAsset(assetId, { anchorX: 0.5, anchorY: 0.5, width: 198, height: 198 }); var nameLabelShadow = new Text2(spellData.name, { size: 40, fill: 0x000000, weight: 800, align: 'center' }); nameLabelShadow.anchor.set(0.5, 0.5); nameLabelShadow.x = 2; nameLabelShadow.y = -20 + 2; self.addChild(nameLabelShadow); var nameLabel = new Text2(spellData.name, { size: 40, fill: 0xFFFFFF, weight: 800, align: 'center' }); nameLabel.anchor.set(0.5, 0.5); nameLabel.y = -20; self.addChild(nameLabel); var costLabelShadow = new Text2(spellData.cost, { size: 50, fill: 0x000000, weight: 800 }); costLabelShadow.anchor.set(0.5, 0.5); costLabelShadow.x = 4; costLabelShadow.y = 24 + 12; self.addChild(costLabelShadow); var costLabel = new Text2(spellData.cost, { size: 50, fill: 0x00BFFF, weight: 800 }); costLabel.anchor.set(0.5, 0.5); costLabel.y = 20 + 12; self.addChild(costLabel); self.update = function () { var canAfford = mana >= spellData.cost; self.alpha = canAfford ? 1 : 0.5; }; return self; }); var SourceTower = Container.expand(function (towerType) { var self = Container.call(this); self.towerType = towerType || 'default'; var assetId = 'tower_' + self.towerType; var baseGraphics = self.attachAsset(assetId, { anchorX: 0.5, anchorY: 0.5 }); baseGraphics.isTowerBase = true; baseGraphics.width = 130; baseGraphics.height = 170; var stats = TOWER_DATA[self.towerType][1]; // Pobierz dane dla poziomu 1 var towerName = stats.name; var initialCost = stats.cost; var typeLabelShadow = new Text2(towerName, { size: 40, fill: 0x000000, weight: 800 }); typeLabelShadow.anchor.set(0.5, 0.5); typeLabelShadow.x = 2; typeLabelShadow.y = -20 + 2; self.addChild(typeLabelShadow); var typeLabel = new Text2(towerName, { size: 40, fill: 0xFFFFFF, weight: 800 }); typeLabel.anchor.set(0.5, 0.5); typeLabel.y = -20; self.addChild(typeLabel); var costLabelShadow = new Text2(initialCost, { size: 50, fill: 0x000000, weight: 800 }); costLabelShadow.anchor.set(0.5, 0.5); costLabelShadow.x = 4; costLabelShadow.y = 24 + 12; self.addChild(costLabelShadow); var costLabel = new Text2(initialCost, { size: 50, fill: 0xFFD700, weight: 800 }); costLabel.anchor.set(0.5, 0.5); costLabel.y = 20 + 12; self.addChild(costLabel); self.update = function () { // Ta funkcja będzie aktualizować koszt w przyszłości, gdy wprowadzimy dynamiczne zmiany var currentCost = TOWER_DATA[self.towerType][1].cost; if (!isBuildPhase && currentWave > 0) { currentCost = Math.floor(currentCost * 1.5); } if (costLabel.text !== currentCost) { costLabel.setText(currentCost); costLabelShadow.setText(currentCost); } var canAfford = gold >= currentCost; self.alpha = canAfford ? 1 : 0.5; }; return self; }); var SourceUnit = Container.expand(function (unitId) { var self = Container.call(this); self.unitId = unitId; self.interactive = true; var unitData = UNIT_DATA[self.unitId]; var assetId = 'unit_' + unitId; var baseGraphics = self.attachAsset(assetId, { anchorX: 0.5, anchorY: 0.5 }); baseGraphics.width = 160; baseGraphics.height = 180; var nameLabelShadow = new Text2(unitData.name, { size: 40, fill: 0x000000, weight: 800, align: 'center' }); nameLabelShadow.anchor.set(0.5, 0.5); nameLabelShadow.x = 2; nameLabelShadow.y = -20 + 2; self.addChild(nameLabelShadow); var nameLabel = new Text2(unitData.name, { size: 40, fill: 0xFFFFFF, weight: 800, align: 'center' }); nameLabel.anchor.set(0.5, 0.5); nameLabel.y = -20; self.addChild(nameLabel); var costLabelShadow = new Text2(unitData.cost, { size: 50, fill: 0x000000, weight: 800 }); costLabelShadow.anchor.set(0.5, 0.5); costLabelShadow.x = 4; costLabelShadow.y = 24 + 12; self.addChild(costLabelShadow); var costLabel = new Text2(unitData.cost, { size: 50, fill: 0xFFD700, weight: 800 }); costLabel.anchor.set(0.5, 0.5); costLabel.y = 20 + 12; self.addChild(costLabel); self.update = function () { var canAfford = gold >= unitData.cost && people > 0; self.alpha = canAfford ? 1 : 0.5; }; self.down = function () { if (activeWeatherEvent === 'STORM') { var notification = game.addChild(new Notification("Recruitment disabled during a storm!")); notification.x = 2048 / 2; notification.y = grid.height - 50; return; } if (LK.ticks - lastRecruitActionTick < 10) { return; } var finalStats = JSON.parse(JSON.stringify(unitData)); // Bonusy z drzewka progresji dla jednostek if (progressionData.upgrades['army_cost_1']) { finalStats.cost = Math.floor(finalStats.cost * (1 - progressionData.upgrades['army_cost_1'] * 0.02)); } var hpUpgradeKey = 'army_' + self.unitId + '_hp'; if (progressionData.upgrades[hpUpgradeKey]) { finalStats.maxHealth = Math.round(finalStats.maxHealth * (1 + progressionData.upgrades[hpUpgradeKey] * 0.05)); } var statsUpgradeKey = 'army_' + self.unitId + '_stats'; if (progressionData.upgrades[statsUpgradeKey]) { var bonus = 1 + progressionData.upgrades[statsUpgradeKey] * 0.03; finalStats.maxHealth = Math.round(finalStats.maxHealth * bonus); finalStats.damage = Math.round(finalStats.damage * bonus); } var dmgUpgradeKey = 'army_' + self.unitId + '_dmg'; if (progressionData.upgrades[dmgUpgradeKey]) { finalStats.damage = Math.round(finalStats.damage * (1 + progressionData.upgrades[dmgUpgradeKey] * 0.05)); } // Sprawdzenie, czy stać gracza po modyfikacjach kosztu if (gold >= finalStats.cost && people > 0) { lastRecruitActionTick = LK.ticks; var freeUnitChance = (progressionData.upgrades['army_free_unit_2'] || 0) * 0.05; var freeUnitSpawned = Math.random() < freeUnitChance; setGold(gold - finalStats.cost); people--; updateUI(); panelActionTaken = true; // Bonusy z budynków var infraBonus = 1 + (progressionData.upgrades['building_infrastructure_2'] || 0) * 0.05; var trainingGroundsCount = castleBuildings.filter(function (b) { return b.id === 'training_ground'; }).length; if (trainingGroundsCount > 0) { var unitBonus = Math.pow(1.05, trainingGroundsCount) * infraBonus; finalStats.maxHealth = Math.round(finalStats.maxHealth * unitBonus); finalStats.damage = Math.round(finalStats.damage * unitBonus); } var hasSmithy = castleBuildings.some(function (b) { return b.id === 'smithy'; }); if (hasSmithy) { var smithyBonus = 1.15 * infraBonus; finalStats.maxHealth = Math.floor(finalStats.maxHealth * smithyBonus); finalStats.damage = Math.floor(finalStats.damage * smithyBonus); } var unitsToSpawn = freeUnitSpawned ? 2 : 1; for (var i = 0; i < unitsToSpawn; i++) { var spawnLaneOffset = 80; var spawnLanes = [2048 / 2 - spawnLaneOffset, 2048 / 2, 2048 / 2 + spawnLaneOffset]; var spawnX = spawnLanes[Math.floor(Math.random() * spawnLanes.length)]; spawnX += Math.random() * 40 - 20; var spawnY = grid.y + (grid.cells[0].length - 3) * CELL_SIZE; var newUnit = new FriendlyUnit(spawnX, spawnY, finalStats); friendlyUnitLayer.addChild(newUnit); friendlyUnits.push(newUnit); } } else { var message = gold < finalStats.cost ? "Not enough gold!" : "Not enough people!"; var notification = game.addChild(new Notification(message)); notification.x = 2048 / 2; notification.y = grid.height - 50; } }; return self; }); var SpeechBubble = Container.expand(function (text, x, y, isGroup) { var self = Container.call(this); self.x = x; self.y = y; var dialogues = ["The harvest should be good this year.", "A fine day for the kingdom.", "Did you know our king has one purple and one blue eye? I've heard he loves music.", "I hear that in MemeTower, a Cursed Crystal holds Demon Pepe captive.", "I used to be an adventurer like you, then I took an arrow in the knee.", "Stay a while and listen."]; var displayText = isGroup ? dialogues[Math.floor(Math.random() * dialogues.length)] : text; var bubbleGraphics = self.attachAsset('dialogue_bubble', { // Placeholder anchorX: 0.5, anchorY: 1, width: 300, height: 150, tint: 0xEFEFEF }); var bubbleText = new Text2(displayText, { size: 28, fill: 0x000000, align: 'center', wordWrap: true, wordWrapWidth: 280 }); bubbleText.anchor.set(0.5, 0.5); bubbleText.y = -85; self.addChild(bubbleText); self.alpha = 0; tween(self, { alpha: 1, y: y - 10 }, { duration: 200, easing: tween.easeOut, onFinish: function onFinish() { LK.setTimeout(function () { tween(self, { alpha: 0 }, { duration: 500, onFinish: function onFinish() { self.destroy(); } }); }, isGroup ? 4000 : 2000); } }); return self; }); var SpellPreview = Container.expand(function () { var self = Container.call(this); self.visible = false; self.spellId = null; self.rangeIndicator = self.attachAsset('rangeCircle', { anchorX: 0.5, anchorY: 0.5, alpha: 0.4 }); self.rangeIndicator.blendMode = 1; self.unitIndicator = self.attachAsset('unit_golem', { anchorX: 0.5, anchorY: 0.5, alpha: 0.5, width: 160, height: 160, visible: false }); self.getActiveGraphic = function () { if (self.spellId === 'summon_golem') { return self.unitIndicator; } return self.rangeIndicator; }; self.updateAppearance = function (spellData, canAfford) { self.spellId = spellData.id; if (spellData.id === 'summon_golem') { self.rangeIndicator.visible = false; self.unitIndicator.visible = true; } else { self.rangeIndicator.visible = true; self.unitIndicator.visible = false; self.rangeIndicator.width = self.rangeIndicator.height = spellData.radius * 2; } var activeGraphic = self.getActiveGraphic(); if (activeGraphic) { activeGraphic.tint = canAfford ? 0x00BFFF : 0xFF0000; } self.visible = true; }; self.updatePosition = function (x, y) { self.x = x; self.y = y; }; return self; }); var TalentTreeScreen = Container.expand(function () { var self = Container.call(this); self.visible = false; var currentCategory = null; var skillNodes = {}; var lineContainer = new Container(); self.addChild(lineContainer); var backgrounds = { 'Towers': self.attachAsset('bg_towers', { visible: false }), 'Army': self.attachAsset('bg_army', { visible: false }), 'Building': self.attachAsset('bg_building', { visible: false }), 'Magic': self.attachAsset('bg_magic', { visible: false }) }; self.addChildAt(lineContainer, 1); var pointsText = new Text2('Points: 0', { size: 70, fill: 0xFFD700, weight: 800 }); pointsText.anchor.set(0.5, 0.5); pointsText.x = 2048 / 2; pointsText.y = 250; self.addChild(pointsText); var backButton = new Container(); backButton.x = 200; backButton.y = 100; var backButtonBg = backButton.attachAsset('upgrade_menu_button', { anchorX: 0.5, anchorY: 0.5, tint: 0xcc0000 }); var backButtonText = new Text2('Back', { size: 60, fill: 0xffffff }); backButtonText.anchor.set(0.5, 0.5); backButton.addChild(backButtonText); self.addChild(backButton); backButton.down = function () { self.hide(); }; var descriptionPanel; var skillNameText, skillDescriptionText, skillStatsText, upgradeButtonInPanel; var currentlySelectedSkillId = null; function createDescriptionPanel() { descriptionPanel = new Container(); descriptionPanel.x = 2048 / 2; descriptionPanel.y = 2732 - 350; descriptionPanel.visible = false; self.addChild(descriptionPanel); var panelBg = descriptionPanel.attachAsset('skill_description_panel', { anchorX: 0.5, anchorY: 0.5 }); panelBg.alpha = 0.8; skillNameText = new Text2("Skill Name", { size: 60, fill: 0xFFD700, weight: 700 }); skillNameText.anchor.set(0.5, 0); skillNameText.y = -250; descriptionPanel.addChild(skillNameText); skillDescriptionText = new Text2("Description text goes here.", { size: 40, fill: 0xffffff, wordWrap: true, wordWrapWidth: 1600, align: 'center' }); skillDescriptionText.anchor.set(0.5, 0); skillDescriptionText.y = -150; descriptionPanel.addChild(skillDescriptionText); skillStatsText = new Text2("Bonus: 10% -> 15%", { size: 45, fill: 0x00ff00 }); skillStatsText.anchor.set(0.5, 0); skillStatsText.y = -50; descriptionPanel.addChild(skillStatsText); upgradeButtonInPanel = new Container(); upgradeButtonInPanel.y = 150; var upgradeBtnBg = upgradeButtonInPanel.attachAsset('upgrade_menu_button', { anchorX: 0.5, anchorY: 0.5, width: 500 }); var upgradeBtnText = new Text2("Upgrade (1 Pkt)", { size: 50, fill: 0xffffff, weight: 800 }); upgradeBtnText.anchor.set(0.5, 0.5); upgradeButtonInPanel.addChild(upgradeBtnText); upgradeButtonInPanel.text = upgradeBtnText; descriptionPanel.addChild(upgradeButtonInPanel); upgradeButtonInPanel.down = function () { if (currentlySelectedSkillId) { upgradeSkill(currentlySelectedSkillId); } }; } // ################################################################## // ### GŁÓWNA POPRAWKA BŁĘDU JEST W TEJ FUNKCJI ### // ################################################################## function renderSkills(category) { // Krok 1: Wyczyszczenie sceny for (var key in skillNodes) { if (skillNodes[key]) skillNodes[key].destroy(); } skillNodes = {}; lineContainer.removeChildren(); var skills = talentTreeConfig[category]; if (!skills) return; // Krok 2: Stworzenie WSZYSTKICH ikon umiejętności dla danej kategorii for (var i = 0; i < skills.length; i++) { var skillData = skills[i]; var node = new Container(); node.x = skillData.position.x; node.y = skillData.position.y; self.addChild(node); var icon = node.attachAsset('skill_icon', { anchorX: 0.5, anchorY: 0.5, width: 150, height: 150 }); var name = new Text2(skillData.name, { size: 28, fill: 0xffffff, align: 'center', wordWrap: true, wordWrapWidth: 160 }); name.anchor.set(0.5, 0); name.y = 85; node.addChild(name); var levelText = new Text2('0 / ' + skillData.maxLevels, { size: 32, fill: 0xffff00, weight: 700 }); levelText.anchor.set(0.5, 1); levelText.y = -85; node.addChild(levelText); node.levelText = levelText; node.interactive = true; (function (data) { node.down = function () { showDescriptionPanel(data); }; })(skillData); // Zapisanie referencji do węzła UI skillNodes[skillData.id] = node; } // Krok 3: Stworzenie WSZYSTKICH linii w osobnej pętli, gdy wszystkie węzły już istnieją for (var i = 0; i < skills.length; i++) { var skillData = skills[i]; if (skillData.dependencies && skillData.dependencies.length > 0) { for (var j = 0; j < skillData.dependencies.length; j++) { var parentId = skillData.dependencies[j]; var childNode = skillNodes[skillData.id]; var parentNode = skillNodes[parentId]; if (childNode && parentNode) { var dx = childNode.x - parentNode.x; var dy = childNode.y - parentNode.y; var distance = Math.sqrt(dx * dx + dy * dy); var angle = Math.atan2(dy, dx); var line = lineContainer.attachAsset('skill_line', { anchorX: 0, anchorY: 0.5 }); line.x = parentNode.x; line.y = parentNode.y; line.width = distance; line.rotation = angle; } } } } // Krok 4: Aktualizacja wyglądu wszystkiego updateAllSkillVisuals(); } function showDescriptionPanel(skillData) { currentlySelectedSkillId = skillData.id; descriptionPanel.visible = true; skillNameText.setText(skillData.name); skillDescriptionText.setText(skillData.description); updateDescriptionPanel(); } function updateDescriptionPanel() { if (!currentlySelectedSkillId || !descriptionPanel.visible) return; var skillData = talentTreeConfig[currentCategory].find(function (s) { return s.id === currentlySelectedSkillId; }); var currentLevel = progressionData.upgrades[currentlySelectedSkillId] || 0; skillStatsText.setText("Level: " + currentLevel + " / " + skillData.maxLevels); var isMaxed = currentLevel >= skillData.maxLevels; upgradeButtonInPanel.visible = !isMaxed; if (!isMaxed) { var canAfford = progressionData.points >= skillData.costPerLevel; upgradeButtonInPanel.alpha = canAfford ? 1.0 : 0.5; upgradeButtonInPanel.text.setText("Upgrade (" + skillData.costPerLevel + " Points)"); } } function updateSkillVisuals(node, skillId) { var skillData = talentTreeConfig[currentCategory].find(function (s) { return s.id === skillId; }); var currentLevel = progressionData.upgrades[skillId] || 0; node.levelText.setText(currentLevel + ' / ' + skillData.maxLevels); var areDepsMet = true; if (skillData.dependencies && skillData.dependencies.length > 0) { areDepsMet = skillData.dependencies.every(function (depId) { return (progressionData.upgrades[depId] || 0) > 0; }); } node.alpha = areDepsMet ? 1.0 : 0.6; var icon = node.children[0]; if (currentLevel >= skillData.maxLevels) { icon.tint = 0x00ff00; } else if (currentLevel > 0) { icon.tint = 0x00BFFF; } else { icon.tint = 0x888888; } } function upgradeSkill(skillId) { var skillData = talentTreeConfig[currentCategory].find(function (s) { return s.id === skillId; }); var currentLevel = progressionData.upgrades[skillId] || 0; var areDepsMet = true; if (skillData.dependencies && skillData.dependencies.length > 0) { areDepsMet = skillData.dependencies.every(function (depId) { return (progressionData.upgrades[depId] || 0) > 0; }); } if (areDepsMet && progressionData.points >= skillData.costPerLevel && currentLevel < skillData.maxLevels) { progressionData.points -= skillData.costPerLevel; progressionData.upgrades[skillId] = (progressionData.upgrades[skillId] || 0) + 1; pointsText.setText('Points: ' + progressionData.points); updateAllSkillVisuals(); updateDescriptionPanel(); saveProgression(); } } function updateAllSkillVisuals() { for (var skillId in skillNodes) { updateSkillVisuals(skillNodes[skillId], skillId); } } function showCategory(category) { currentCategory = category; if (descriptionPanel) descriptionPanel.visible = false; for (var key in backgrounds) { backgrounds[key].visible = key === category; } renderSkills(category); } var categories = Object.keys(talentTreeConfig); var tabSpacing = 320; var startX = (2048 - (categories.length - 1) * tabSpacing) / 2; categories.forEach(function (categoryName, index) { var tab = new Container(); tab.x = startX + index * tabSpacing; tab.y = 100; var tabBg = tab.attachAsset('tab_button_bg', { anchorX: 0.5, anchorY: 0.5 }); var tabText = new Text2(categoryName, { size: 40, fill: 0xffffff }); tabText.anchor.set(0.5, 0.5); tab.addChild(tabText); self.addChild(tab); tab.down = function () { showCategory(categoryName); }; }); createDescriptionPanel(); self.show = function () { self.visible = true; loadProgression(); pointsText.setText('Points: ' + progressionData.points); showCategory(categories[0]); }; self.hide = function () { self.visible = false; if (descriptionPanel) descriptionPanel.visible = false; if (levelSelectContainer) { levelSelectContainer.visible = true; } }; return self; }); var ThunderstormEffect = Container.expand(function (x, y, spellData) { var self = Container.call(this); self.x = x; self.y = y; var radius = spellData.radius; var radiusSq = radius * radius; var lifetime = spellData.duration || 90; var hitEnemies = []; var totalStrikes = 12; var stormDuration = 90; var strikesSpawned = 0; var spawnTicker = 0; var timeBetweenStrikes = stormDuration / totalStrikes; self.update = function () { lifetime--; if (lifetime <= 0) { self.destroy(); return; } enemies.forEach(function (enemy) { if (hitEnemies.indexOf(enemy) !== -1) { return; } var dx = enemy.x - self.x; var dy = enemy.y - self.y; if (dx * dx + dy * dy < radiusSq) { enemy.isStunned = true; enemy.stunTimer = spellData.stunDuration; if (spellData.damage) { enemy.takeDamage(spellData.damage); } hitEnemies.push(enemy); } }); spawnTicker++; if (spawnTicker >= timeBetweenStrikes) { spawnTicker = 0; strikesSpawned++; if (strikesSpawned > totalStrikes) { return; } var angle = Math.random() * 2 * Math.PI; var dist = Math.sqrt(Math.random()) * radius; var strikeX = self.x + Math.cos(angle) * dist; var strikeY = self.y + Math.sin(angle) * dist; var type = Math.random() > 0.5 ? 'white' : 'yellow'; var strike = new LightningStrike(strikeX, strikeY, type); spellEffectLayer.addChild(strike); } }; return self; }); var Tower = Container.expand(function (towerType) { var self = Container.call(this); self.towerType = towerType; self.level = 1; self.levelPath = ''; self.maxLevel = 3; self.gridX = 0; self.gridY = 0; self.targetEnemies = []; self.lastFired = 0; self.totalValue = 0; self.isConstructing = false; self.constructionContainer = null; self.timerText = null; self.auraEffectContainer = null; self.baseStats = {}; self.buffs = []; self.tileBuff = null; // NOWA WŁAŚCIWOŚĆ self.towersInAura = []; if (self.towerType === 'banner') { self.auraCheckTimer = 0; self.particleSpawnTimer = 0; self.particleSpawnRate = 190; } function setTowerSize(graphic) { var towerScaleX = 2.4; var towerScaleY = 3.9; graphic.width = CELL_SIZE * towerScaleX; graphic.height = CELL_SIZE * towerScaleY; } function applyStats(stats) { self.baseStats = Object.assign({}, stats); Object.assign(self, stats); self.recalculateStats(); } self.recalculateStats = function () { self.damage = self.baseStats.damage; self.fireRate = self.baseStats.fireRate; // Aplikuj buff z pola if (self.tileBuff) { if (self.tileBuff.damage) { self.damage *= self.tileBuff.damage; } if (self.tileBuff.attackSpeed) { self.fireRate /= self.tileBuff.attackSpeed; } } // Aplikuj buffy z innych źródeł (np. Banner) self.buffs.forEach(function (buff) { if (buff.effect.damage) { self.damage *= buff.effect.damage; } if (buff.effect.attackSpeed) { self.fireRate /= buff.effect.attackSpeed; } }); }; self.addBuff = function (buff) { if (!self.buffs.find(function (b) { return b.source === buff.source; })) { self.buffs.push(buff); self.recalculateStats(); } }; self.removeBuff = function (buffSource) { self.buffs = self.buffs.filter(function (b) { return b.source !== buffSource; }); self.recalculateStats(); }; var initialStats = TOWER_DATA[self.towerType][self.level]; self.cost = initialStats.cost; self.totalValue = self.cost; applyStats(initialStats); var assetId = 'tower_' + self.towerType; var baseGraphics = self.attachAsset(assetId, { anchorX: 0.5, anchorY: 0.5 }); baseGraphics.isTowerBase = true; setTowerSize(baseGraphics); self.getRange = function () { return self.range; }; self.getTotalValue = function () { return self.totalValue; }; self.upgrade = function (path) { if (self.isConstructing) { return; } path = path || ''; var nextLevelKey = self.level + 1 + path; var upgradeData = TOWER_DATA[self.towerType][nextLevelKey]; if (!upgradeData) { return; } self.startConstructionAnimation(5, function () { if (self.aura) { self.removeAuraFromTowers(); } self.level++; self.levelPath = path; self.totalValue += upgradeData.cost; var stats = TOWER_DATA[self.towerType][self.level + self.levelPath]; applyStats(stats); // Stosuje bazowe staty i przelicza z buffami var oldGraphics = self.children[0]; if (oldGraphics.isTowerBase) { oldGraphics.destroy(); } var newAssetId = 'tower_' + self.towerType + '_' + self.level + self.levelPath; var newGraphics = self.attachAsset(newAssetId, { anchorX: 0.5, anchorY: 0.5 }); newGraphics.isTowerBase = true; setTowerSize(newGraphics); self.addChildAt(newGraphics, 0); if (self.towerType === 'crossbow' && self.gridX < 12) { newGraphics.scale.x = -1; } if (self.aura) { self.applyAuraToTowers(); } }); }; self.destroyAura = function () { if (self.aura) { self.removeAuraFromTowers(); } }; self.placeOnGrid = function (gridX, gridY) { self.gridX = gridX; self.gridY = gridY; self.x = grid.x + (gridX + 1) * CELL_SIZE; self.y = grid.y + (gridY + 1) * CELL_SIZE - 50; if (self.towerType === 'crossbow' && self.gridX < 12) { var towerGraphic = self.children[0]; if (towerGraphic) { towerGraphic.scale.x = -1; } } // Sprawdź bonus z pola i go przypisz for (var i = 0; i < 2; i++) { for (var j = 0; j < 2; j++) { var cell = grid.getCell(gridX + i, gridY + j); if (cell && cell.specialEffect) { if (cell.specialEffect === 'amplifier') { self.tileBuff = { damage: 1.15 }; } else if (cell.specialEffect === 'catalyst') { self.tileBuff = { attackSpeed: 1.30 }; } self.recalculateStats(); break; // Znaleziono bonus, przerywamy pętlę } } if (self.tileBuff) { break; } } for (var i = 0; i < 2; i++) { for (var j = 0; j < 2; j++) { var cellToOccupy = grid.getCell(gridX + i, gridY + j); if (cellToOccupy) { cellToOccupy.type = 5; } } } if (self.aura) { self.applyAuraToTowers(); } }; self.findTargets = function () { var maxTargets = self.multiShot || 1; var potentialTargets = []; if (self.damage === 0) { return []; } if (self.bulletType === 'slow') { var unslowedTargets = []; var slowedTargets = []; enemies.forEach(function (enemy) { var dx = enemy.x - self.x; var dy = enemy.y - self.y; if (dx * dx + dy * dy < self.range * self.range) { if (!enemy.slowed) { unslowedTargets.push(enemy); } else { slowedTargets.push(enemy); } } }); potentialTargets = unslowedTargets.concat(slowedTargets); } else { enemies.forEach(function (enemy) { var dx = enemy.x - self.x; var dy = enemy.y - self.y; if (dx * dx + dy * dy < self.range * self.range) { potentialTargets.push(enemy); } }); potentialTargets.sort(function (a, b) { var distA = Math.pow(a.x - self.x, 2) + Math.pow(a.y - self.y, 2); var distB = Math.pow(b.x - self.x, 2) + Math.pow(b.y - self.y, 2); return distA - distB; }); } return potentialTargets.slice(0, maxTargets); }; self.fire = function () { self.targetEnemies.forEach(function (targetEnemy) { if (targetEnemy && targetEnemy.health > 0) { var dx = targetEnemy.x - self.x; var dy = targetEnemy.y - self.y; var angle = Math.atan2(dy, dx); var bulletX = self.x + Math.cos(angle) * 40; var bulletY = self.y + Math.sin(angle) * 40; var bullet = new Bullet(bulletX, bulletY, targetEnemy, self); if (self.towerType === 'mage') { spellEffectLayer.addChild(bullet); } else { gateContainer.addChild(bullet); } bullets.push(bullet); } }); }; self.applyAuraToTowers = function () { self.removeAuraFromTowers(); towers.forEach(function (tower) { if (tower === self) { return; } var dx = tower.x - self.x; var dy = tower.y - self.y; if (dx * dx + dy * dy < self.range * self.range) { tower.addBuff({ source: self, effect: self.aura }); self.towersInAura.push(tower); } }); }; self.removeAuraFromTowers = function () { self.towersInAura.forEach(function (tower) { tower.removeBuff(self); }); self.towersInAura = []; }; self.startConstructionAnimation = function (duration, onComplete) { self.isConstructing = true; var baseGraphics = self.children[0]; if (baseGraphics) { baseGraphics.visible = false; } if (self.constructionContainer) { self.constructionContainer.destroy(); } self.constructionContainer = new Container(); self.addChild(self.constructionContainer); var constructionAssets = []; for (var i = 1; i <= 5; i++) { var assetName = 'tower_construction_' + i; var asset = self.constructionContainer.attachAsset(assetName, { anchorX: 0.5, anchorY: 0.5 }); asset.alpha = i === 1 ? 1 : 0; constructionAssets.push(asset); } if (self.timerText) { self.timerText.destroy(); } self.timerText = new Text2(duration.toString(), { size: 80, fill: 0xFFFFFF, weight: 800, stroke: 0x000000, strokeThickness: 10 }); self.timerText.anchor.set(0.5, 0.5); self.timerText.y = -CELL_SIZE * 1.5; self.addChild(self.timerText); function animateStep(stage) { if (stage > duration) { if (baseGraphics) { baseGraphics.visible = true; } self.constructionContainer.destroy(); self.constructionContainer = null; self.timerText.destroy(); self.timerText = null; self.isConstructing = false; if (onComplete) { onComplete(); } return; } self.timerText.setText((duration - stage + 1).toString()); var currentAsset = constructionAssets[stage - 1]; var prevAsset = constructionAssets[stage - 2]; if (prevAsset) { tween(prevAsset, { alpha: 0 }, { duration: 200 }); } if (currentAsset) { tween(currentAsset, { alpha: 1 }, { duration: 300 }); } LK.setTimeout(function () { animateStep(stage + 1); }, 1000); } animateStep(1); }; self.update = function () { if (self.isConstructing) { return; } if (self.towerType === 'banner') { self.auraCheckTimer++; if (self.auraCheckTimer > 60) { self.auraCheckTimer = 0; self.applyAuraToTowers(); } self.particleSpawnTimer++; if (self.particleSpawnTimer >= self.particleSpawnRate) { self.particleSpawnTimer = 0; self.towersInAura.forEach(function (buffedTower) { if (buffedTower && buffedTower.parent) { var particle = new Container(); particle.attachAsset('aura_particle', { anchorX: 0.5, anchorY: 0.5, alpha: 0.8 }); var spread = 80; particle.x = buffedTower.x + (Math.random() * spread - spread / 2); particle.y = buffedTower.y + 100; gateContainer.addChild(particle); tween(particle, { y: buffedTower.y - 120, alpha: 0 }, { duration: 4000 + Math.random() * 1000, easing: tween.easeIn, onFinish: function onFinish() { particle.destroy(); } }); } }); } return; } self.targetEnemies = self.findTargets(); if (self.targetEnemies.length > 0) { if (self.fireRate > 0 && LK.ticks - self.lastFired >= self.fireRate) { self.fire(); self.lastFired = LK.ticks; } } }; self.down = function (x, y, obj) { if (isMenuTransitioning || self.isConstructing) { return; } if (selectedTower === self) { closeActiveUpgradeMenu(); return; } closeActiveUpgradeMenu(); selectedTower = self; var upgradeMenu = game.addChild(new UpgradeMenu(self)); upgradeMenu.x = 2048 / 2; isMenuTransitioning = true; tween(upgradeMenu, { y: 2732 - 250 }, { duration: 200, easing: tween.backOut, onFinish: function onFinish() { isMenuTransitioning = false; } }); var rangeCircle = new Container(); rangeCircle.isTowerRange = true; rangeCircle.tower = self; var graphics = rangeCircle.attachAsset('rangeCircle', { anchorX: 0.5, anchorY: 0.5 }); graphics.width = graphics.height = self.getRange() * 2; graphics.alpha = 0.2; graphics.tint = 0xFFFFFF; rangeCircle.x = self.x; rangeCircle.y = self.y; gateContainer.addChildAt(rangeCircle, gateContainer.children.indexOf(towerLayer)); }; return self; }); var UpgradeMenu = Container.expand(function (tower) { var self = Container.call(this); self.tower = tower; self.y = 2732 + 225; var menuBackground = self.attachAsset('panel_upgrade_background', { anchorX: 0.5, anchorY: 0.5 }); menuBackground.width = 1800; menuBackground.height = 550; var closeButton = new Container(); var closeBackground = closeButton.attachAsset('notification', { anchorX: 0.5, anchorY: 0.5, width: 90, height: 90, tint: 0xAA0000 }); var closeText = new Text2('X', { size: 68, fill: 0xFFFFFF, weight: 800 }); closeText.anchor.set(0.5, 0.5); closeButton.addChild(closeText); closeButton.x = menuBackground.width / 2 - 57; closeButton.y = -menuBackground.height / 2 + 57; self.addChild(closeButton); closeButton.down = function () { hideUpgradeMenu(self); }; function createStatLine(label, value, yPos, isBuffed) { var statLabel = new Text2(label + ':', { size: 55, fill: 0xFFFFFF, weight: 600, align: 'left' }); statLabel.anchor.set(0, 0.5); statLabel.x = -620; statLabel.y = yPos; self.addChild(statLabel); var statValue = new Text2(value, { size: 55, fill: isBuffed ? 0x00FF00 : 0xFFD700, // ZIELONY jeśli wzmocnione weight: 800, align: 'left' }); statValue.anchor.set(0, 0.5); statValue.x = statLabel.x + 300; statValue.y = yPos; self.addChild(statValue); } function populateStats() { var yStart = -120; var yStep = 70; var isDamageBuffed = self.tower.tileBuff && self.tower.tileBuff.damage; var isSpeedBuffed = self.tower.tileBuff && self.tower.tileBuff.attackSpeed; createStatLine('Damage', Math.round(self.tower.damage), yStart, isDamageBuffed); createStatLine('Fire Rate', Math.round(60 / self.tower.fireRate * 100) / 100 + '/s', yStart + yStep, isSpeedBuffed); createStatLine('Range', Math.round(self.tower.range / CELL_SIZE * 10) / 10, yStart + yStep * 2, false); // Zakładamy, że zasięg nie jest buffowany z pola } populateStats(); function createUpgradeButton(upgradeKey, position, totalButtons) { var upgradeData = TOWER_DATA[self.tower.towerType][upgradeKey]; if (!upgradeData) { return; } var button = new Container(); var bg = button.attachAsset('button_upgrade', { anchorX: 0.5, anchorY: 0.5 }); bg.width = 500; if (totalButtons > 1) { bg.height = (450 - 100) / totalButtons - 15; } else { bg.height = 140; } var buttonTextContent = upgradeData.name + '\n' + upgradeData.cost + 'g'; var text = new Text2(buttonTextContent, { size: 40, fill: 0xFFFFFF, weight: 800, align: 'center', wordWrap: true, wordWrapWidth: bg.width - 20 }); text.anchor.set(0.5, 0.5); text.x = 20; button.addChild(text); var canAfford = gold >= upgradeData.cost; button.alpha = canAfford ? 1 : 0.7; button.down = function () { if (gold >= upgradeData.cost) { setGold(gold - upgradeData.cost); self.tower.upgrade(upgradeKey.replace((self.tower.level + 1).toString(), '')); hideUpgradeMenu(self); } else { var notification = game.addChild(new Notification("Not enough gold!")); notification.x = 2048 / 2; notification.y = grid.height - 50; } }; button.x = position.x; button.y = position.y; self.addChild(button); } function populateUpgradeButtons() { var hasWorkshop = castleBuildings.some(function (b) { return b.id === 'workshop'; }); if (self.tower.level >= (hasWorkshop ? self.tower.maxLevel : 2)) { var maxLevelText = new Text2('Max Level', { size: 60, fill: 0xFFD700, weight: 800 }); if (self.tower.level === 2 && !hasWorkshop) { maxLevelText.setText("Requires Workshop"); maxLevelText.size = 50; } maxLevelText.anchor.set(0.5, 0.5); maxLevelText.x = 300; self.addChild(maxLevelText); return; } var nextLevel = self.tower.level + 1; var upgradeKeys = Object.keys(TOWER_DATA[self.tower.towerType]).filter(function (key) { if (!key.startsWith(nextLevel.toString())) { return false; } if (self.tower.level === 2 && self.tower.levelPath && !key.includes(self.tower.levelPath)) { return false; } if (nextLevel >= 3 && !hasWorkshop) { return false; } return true; }); var buttonX = 300; if (upgradeKeys.length === 1) { createUpgradeButton(upgradeKeys[0], { x: buttonX, y: 0 }, 1); } else if (upgradeKeys.length > 1) { var totalButtonHeight = 450 - 100; var buttonHeight = totalButtonHeight / upgradeKeys.length; var startY = -totalButtonHeight / 2 + buttonHeight / 2; for (var i = 0; i < upgradeKeys.length; i++) { createUpgradeButton(upgradeKeys[i], { x: buttonX, y: startY + i * buttonHeight }, upgradeKeys.length); } } } populateUpgradeButtons(); var originalDestroy = self.destroy; self.destroy = function () { for (var i = game.children.length - 1; i >= 0; i--) { if (game.children[i].isTowerRange && game.children[i].tower === self.tower) { gateContainer.removeChild(game.children[i]); } } if (selectedTower === self.tower) { selectedTower = null; } originalDestroy.call(self); }; return self; }); var WeatherSelection = Container.expand(function (levelNumber, onComplete) { var self = Container.call(this); self.levelNumber = levelNumber; self.onComplete = onComplete; self.cards = []; self.animationTicker = 0; self.animationDelay = 5; self.currentStep = 0; self.winningCard = null; self.winningCardIndex = -1; self.stepsToGo = -1; var bg = self.attachAsset('cell', { width: 2048, height: 2732, tint: 0x000000, alpha: 0 }); tween(bg, { alpha: 0.7 }, { duration: 300 }); var visualDeck = WEATHER_DECK_DATA[self.levelNumber] || WEATHER_DECK_DATA[1]; var cardContainer = new Container(); self.addChild(cardContainer); var cardSpacing = 240; var totalWidth = (visualDeck.length - 1) * cardSpacing; var startX = -totalWidth / 2; cardContainer.x = 2048 / 2; cardContainer.y = 2732 / 2 - 200; for (var i = 0; i < visualDeck.length; i++) { var eventId = visualDeck[i]; var cardAsset = EVENT_DATA[eventId].asset; var card = cardContainer.attachAsset(cardAsset, { anchorX: 0.5, anchorY: 0.5 }); card.x = startX + i * cardSpacing; card.y = 500; card.alpha = 0; card.tint = 0x777777; card.eventId = eventId; self.cards.push(card); var tweenOptions = { duration: 400, delay: 100 + i * 150, easing: tween.backOut }; if (i === visualDeck.length - 1) { tweenOptions.onFinish = function () { LK.setTimeout(function () { if (self.startSelection) { self.startSelection(); } }, 1000); }; } tween(card, { y: 0, alpha: 1 }, tweenOptions); } self.startSelection = function () { self.winningCardIndex = Math.floor(Math.random() * self.cards.length); self.winningCard = self.cards[self.winningCardIndex]; var fullLoops = 2; self.stepsToGo = fullLoops * self.cards.length + self.winningCardIndex; }; self.showResult = function () { activeWeatherEvent = self.winningCard.eventId; applyActiveWeatherModifiers(); self.winningCard.tint = 0xFFFFFF; var flash = self.attachAsset('cell', { width: 2048, height: 2732, tint: 0xFFFFFF, alpha: 0 }); tween(flash, { alpha: 0.7 }, { duration: 100, onFinish: function onFinish() { tween(flash, { alpha: 0 }, { duration: 150, onFinish: function onFinish() { flash.destroy(); } }); } }); shakeScreen(15, 200); for (var i = 0; i < 30; i++) { var particle = self.attachAsset('star_particle', { anchorX: 0.5, anchorY: 0.5 }); particle.x = self.winningCard.x + cardContainer.x + (Math.random() - 0.5) * 200; particle.y = self.winningCard.y + cardContainer.y + (Math.random() - 0.5) * 200; particle.scale.set(Math.random() * 0.5 + 0.3); tween(particle, { x: particle.x + (Math.random() - 0.5) * 400, y: particle.y + (Math.random() - 0.5) * 400, alpha: 0, scaleX: 0.1, scaleY: 0.1 }, { duration: 800 + Math.random() * 500, onFinish: particle.destroy.bind(particle) }); } for (var i = 0; i < self.cards.length; i++) { if (i !== self.winningCardIndex) { tween(self.cards[i], { alpha: 0, y: 50 }, { duration: 300 }); } } tween(self.winningCard, { x: 0, y: -100, scaleX: 1.5, scaleY: 1.5 }, { duration: 800, easing: tween.easeOut }); var eventInfo = EVENT_DATA[self.winningCard.eventId]; var descriptionContainer = new Container(); descriptionContainer.x = cardContainer.x; descriptionContainer.y = cardContainer.y + 400; descriptionContainer.alpha = 0; self.addChild(descriptionContainer); var nameText = new Text2(eventInfo.name, { size: 73, fill: 0xFFFFFF, weight: 800 }); nameText.anchor.set(0.5, 0.5); var descText = new Text2(eventInfo.description, { size: 56, fill: 0xDDDDDD, weight: 500, align: 'center', wordWrap: true, wordWrapWidth: 800 }); descText.anchor.set(0.5, 0.5); descText.y = 80; descriptionContainer.addChild(nameText); descriptionContainer.addChild(descText); tween(descriptionContainer, { alpha: 1 }, { duration: 400, delay: 200 }); LK.setTimeout(function () { tween(self, { alpha: 0 }, { duration: 500, onFinish: function onFinish() { if (self.onComplete) { self.onComplete(); } self.destroy(); } }); }, 4000); }; self.update = function () { if (self.stepsToGo > -1) { self.animationTicker++; if (self.animationTicker >= self.animationDelay) { self.animationTicker = 0; self.cards.forEach(function (card) { card.tint = 0x777777; }); var currentIndex = self.currentStep % self.cards.length; self.cards[currentIndex].tint = 0xFFFFFF; if (self.currentStep >= self.stepsToGo) { self.stepsToGo = -1; self.cards.forEach(function (card) { card.tint = 0x777777; }); self.cards[self.winningCardIndex].tint = 0xFFFFFF; LK.setTimeout(function () { self.showResult(); }, 1000); } else { self.currentStep++; if (self.stepsToGo - self.currentStep < 5) { self.animationDelay += 4; } else if (self.stepsToGo - self.currentStep < 10) { self.animationDelay += 1; } } } } }; return self; }); /**** * Initialize Game ****/ /**** * Initialize Game ****/ var game = new LK.Game({ backgroundColor: 0x333333 }); /**** * Game Code ****/ /**** * Game Code ****/ var progressionData = { points: 0, upgrades: {} }; function saveProgression() { // Ta funkcja w przyszłości użyje pluginu LK.storage.set console.log("Zapisano progresję:", JSON.stringify(progressionData)); } function loadProgression() { // Ta funkcja w przyszłości użyje pluginu LK.storage.get console.log("Wczytano progresję."); // Na razie nie mamy zapisu, więc funkcja jest pusta, ale musi istnieć. } var talentTreeConfig = { 'Towers': [{ id: 'towers_cost_1', name: 'Efficient Builders', description: 'Reduces the build cost of all towers by 2%', maxLevels: 5, costPerLevel: 1, position: { x: 1024, y: 400 }, dependencies: [] }, { id: 'towers_guard_dmg_2', name: 'Reinforced Arrows', description: 'Increases Guard Tower damage by 5%', maxLevels: 3, costPerLevel: 1, position: { x: 624, y: 650 }, dependencies: ['towers_cost_1'] }, { id: 'towers_crossbow_range_2', name: 'Eagle Eye', description: 'Increases Crossbow Tower range by 5%', maxLevels: 3, costPerLevel: 1, position: { x: 1424, y: 650 }, dependencies: ['towers_cost_1'] }, { id: 'towers_guard_multishot_3', name: 'Hail of Arrows', description: 'Guard Tower gains a 10% chance to fire an additional arrow', maxLevels: 3, costPerLevel: 1, position: { x: 424, y: 900 }, dependencies: ['towers_guard_dmg_2'] }, { id: 'towers_crossbow_poison_3', name: 'Poisoned Bolts', description: 'Crossbow Tower attacks have a 10% chance to poison the target', maxLevels: 3, costPerLevel: 1, position: { x: 1624, y: 900 }, dependencies: ['towers_crossbow_range_2'] }, { id: 'towers_guard_as_4', name: 'Berserker\'s Fury', description: 'Increases Guard Tower attack speed by 30%', maxLevels: 1, costPerLevel: 3, position: { x: 424, y: 1150 }, dependencies: ['towers_guard_multishot_3'] }, { id: 'towers_crossbow_crit_4', name: 'Heartseeker Shot', description: 'Crossbow Tower gains a 20% chance to deal 200% damage', maxLevels: 1, costPerLevel: 3, position: { x: 1624, y: 1150 }, dependencies: ['towers_crossbow_poison_3'] }], 'Army': [{ id: 'army_cost_1', name: 'Barracks Management', description: 'Reduces the recruitment cost of all units by 2%', maxLevels: 5, costPerLevel: 1, position: { x: 1024, y: 400 }, dependencies: [] }, { id: 'army_swordsman_hp_2', name: 'Infantry Vigor', description: 'Increases Swordsman HP by 5%', maxLevels: 4, costPerLevel: 1, position: { x: 450, y: 650 }, dependencies: ['army_cost_1'] }, { id: 'army_horseman_stats_2', name: 'Cavalry Conditioning', description: 'Increases Horseman HP and Damage by 3%', maxLevels: 4, costPerLevel: 1, position: { x: 1024, y: 650 }, dependencies: ['army_cost_1'] }, { id: 'army_free_unit_2', name: 'Surprise Draft', description: 'Grants a 5% chance to get a free, additional unit on recruitment', maxLevels: 3, costPerLevel: 2, position: { x: 1600, y: 650 }, dependencies: ['army_cost_1'] }, { id: 'army_defender_hp_3', name: 'Steel Shields', description: 'Increases Defender HP by 5%', maxLevels: 3, costPerLevel: 1, position: { x: 450, y: 900 }, dependencies: ['army_swordsman_hp_2'] }, { id: 'army_archer_dmg_3', name: 'Precise Shots', description: 'Increases Archer Damage by 5%', maxLevels: 3, costPerLevel: 1, position: { x: 1024, y: 900 }, dependencies: ['army_horseman_stats_2'] }, { id: 'army_swordsman_ultimate_4', name: 'Undying Warrior', description: 'Swordsman deals +30% damage and fights for 2s after receiving a fatal blow', maxLevels: 1, costPerLevel: 3, position: { x: 300, y: 1150 }, dependencies: ['army_defender_hp_3'] }, { id: 'army_defender_ultimate_4', name: 'Stunning Blow', description: 'Defender attacks have a 20% chance to stun the target for 2s', maxLevels: 1, costPerLevel: 3, position: { x: 600, y: 1150 }, dependencies: ['army_defender_hp_3'] }, { id: 'army_archer_ultimate_4', name: 'Salvo', description: 'Archers always fire at two targets simultaneously', maxLevels: 1, costPerLevel: 3, position: { x: 1024, y: 1150 }, dependencies: ['army_archer_dmg_3'] }, { id: 'army_horseman_ultimate_4', name: 'Ghost Rider', description: 'When a Horseman dies, it has a 40% chance to spawn a Swordsman in its place', maxLevels: 1, costPerLevel: 3, position: { x: 1024, y: 1400 }, dependencies: ['army_archer_dmg_3'] }], 'Building': [{ id: 'building_cost_1', name: 'Master Carpenter', description: 'Reduces the build cost of all castle buildings by 4%', maxLevels: 5, costPerLevel: 1, position: { x: 1024, y: 400 }, dependencies: [] }, { id: 'building_tavern_2', name: 'Golden Enterprise', description: 'Increases base income from Taverns by 4%', maxLevels: 5, costPerLevel: 1, position: { x: 450, y: 650 }, dependencies: ['building_cost_1'] }, { id: 'building_infrastructure_2', name: 'Military Infrastructure', description: 'Increases the effectiveness of Smithy and Training Ground by 5%', maxLevels: 2, costPerLevel: 2, position: { x: 1024, y: 650 }, dependencies: ['building_cost_1'] }, { id: 'building_population_2', name: 'Social Development', description: 'Increases population from each Home building by +1', maxLevels: 5, costPerLevel: 1, position: { x: 1600, y: 650 }, dependencies: ['building_cost_1'] }, { id: 'building_reroll_3', name: 'Haggling', description: 'Reduces the cost of rerolling buildings by 20%', maxLevels: 2, costPerLevel: 1, position: { x: 450, y: 900 }, dependencies: ['building_tavern_2'] }, { id: 'building_workshop_3', name: 'Military Innovations', description: 'Reduces the cost of Workshop upgrades by 5%', maxLevels: 3, costPerLevel: 1, position: { x: 1024, y: 900 }, dependencies: ['building_infrastructure_2'] }, { id: 'building_infirmary_3', name: 'Medical Care', description: 'Infirmary speeds up population regeneration by an additional 5%', maxLevels: 3, costPerLevel: 1, position: { x: 1600, y: 900 }, dependencies: ['building_population_2'] }, { id: 'building_gold_4', name: 'Royal Grant', description: 'Start each game with +500 gold', maxLevels: 1, costPerLevel: 5, position: { x: 450, y: 1150 }, dependencies: ['building_reroll_3'] }, { id: 'building_lives_4', name: 'Reinforced Walls', description: 'Start each game with +10 lives', maxLevels: 1, costPerLevel: 5, position: { x: 1600, y: 1150 }, dependencies: ['building_infirmary_3'] }], 'Magic': [{ id: 'magic_mana_kill_1', name: 'Mana Siphon', description: 'Increases mana gained from enemy kills by 10%', maxLevels: 5, costPerLevel: 1, position: { x: 1024, y: 400 }, dependencies: [] }, { id: 'magic_destruction_2', name: 'Path of Destruction', description: 'Increases damage of Fire Wall and Thunderstorm by 4%', maxLevels: 5, costPerLevel: 1, position: { x: 624, y: 650 }, dependencies: ['magic_mana_kill_1'] }, { id: 'magic_manipulation_2', name: 'Path of Manipulation', description: 'Reduces mana cost of Curse, Summon Golem, and Healing Field by 3%', maxLevels: 5, costPerLevel: 1, position: { x: 1424, y: 650 }, dependencies: ['magic_mana_kill_1'] }, { id: 'magic_firewall_duration_3', name: 'Eternal Flame', description: 'Increases Fire Wall duration by 10%', maxLevels: 3, costPerLevel: 1, position: { x: 424, y: 900 }, dependencies: ['magic_destruction_2'] }, { id: 'magic_storm_duration_3', name: 'Lasting Storm', description: 'Increases Thunderstorm duration by 10%', maxLevels: 3, costPerLevel: 1, position: { x: 824, y: 900 }, dependencies: ['magic_destruction_2'] }, { id: 'magic_curse_3', name: 'Deeper Curse', description: 'Increases the effectiveness of Curse by 5%', maxLevels: 3, costPerLevel: 1, position: { x: 1224, y: 900 }, dependencies: ['magic_manipulation_2'] }, { id: 'magic_essence_3', name: 'Reinforced Essence', description: 'Increases Golem HP and Healing Field power by 5%', maxLevels: 3, costPerLevel: 1, position: { x: 1624, y: 900 }, dependencies: ['magic_manipulation_2'] }, { id: 'magic_regen_4', name: 'Mana Regeneration', description: 'Regenerate 1 mana per second', maxLevels: 1, costPerLevel: 5, position: { x: 1224, y: 1150 }, dependencies: ['magic_curse_3'] }, { id: 'magic_golem_4', name: 'Golem\'s Agony', description: 'Golem gains +1 damage for every 10% of its missing HP', maxLevels: 1, costPerLevel: 3, position: { x: 1624, y: 1150 }, dependencies: ['magic_essence_3'] }, { id: 'magic_freecast_4', name: 'Mana Echo', description: 'Grants a 30% chance to cast a spell for free', maxLevels: 1, costPerLevel: 5, position: { x: 824, y: 1150 }, dependencies: ['magic_storm_duration_3'] }] }; function hideBuildingInfoMenu(menu) { if (isHidingUpgradeMenu) { return; } isHidingUpgradeMenu = true; isMenuTransitioning = true; tween(menu, { y: 2732 + 225 }, { duration: 150, easing: tween.easeIn, onFinish: function onFinish() { menu.destroy(); isHidingUpgradeMenu = false; isMenuTransitioning = false; } }); } function shakeScreen(intensity, duration) { var initialX = game.x; var initialY = game.y; var shakeTicker = 0; var shakeDuration = Math.ceil(duration / 10); // Czas trwania w cyklach function doShake() { if (shakeTicker >= shakeDuration) { game.x = initialX; game.y = initialY; return; } var shakeX = (Math.random() - 0.5) * intensity; var shakeY = (Math.random() - 0.5) * intensity; game.x = initialX + shakeX; game.y = initialY + shakeY; shakeTicker++; LK.setTimeout(doShake, 10); } doShake(); } function createHitSparks(x, y) { var numSparks = 5; for (var i = 0; i < numSparks; i++) { var spark = new Container(); var sparkGraphics = spark.attachAsset('star_particle', { anchorX: 0.5, anchorY: 0.5, tint: 0xFFD700 }); spark.x = x; spark.y = y; gateContainer.addChild(spark); var angle = Math.random() * Math.PI * 2; var distance = Math.random() * 40 + 20; tween(spark, { x: x + Math.cos(angle) * distance, y: y + Math.sin(angle) * distance, alpha: 0 }, { duration: 500, easing: tween.easeOut, onFinish: function onFinish() { spark.destroy(); } }); } } function applyActiveWeatherModifiers() { if (activeWeatherEvent === 'RAIN') { if (rainIntervalId) { LK.clearInterval(rainIntervalId); } rainIntervalId = LK.setInterval(function () { for (var i = 0; i < 3; i++) { var drop = new Raindrop(); weatherEffectLayer.addChild(drop); } }, 150); } if (activeWeatherEvent === 'FOG') { towers.forEach(function (tower) { if (tower.towerType === 'mage') { tower.addBuff({ source: 'FOG_DEBUFF', effect: { damage: 0.90 } }); } }); if (fogIntervalId) { LK.clearInterval(fogIntervalId); } // Uruchamiamy "fabrykę" mgły, która dba o stałą jej ilość fogIntervalId = LK.setInterval(function () { if (activeFogWisps.length < 6) { // Utrzymujemy 6 obiektów mgły na ekranie var wisp = new FogWisp(); weatherEffectLayer.addChild(wisp); activeFogWisps.push(wisp); } }, 1000); // Co sekundę sprawdzaj i dodawaj } if (activeWeatherEvent === 'NIGHT') { nightOverlay.visible = true; tween(nightOverlay, { alpha: 0.6 }, { duration: 500 }); } else if (activeWeatherEvent === 'STORM') { nightOverlay.visible = true; tween(nightOverlay, { alpha: 0.4 }, { duration: 500 }); if (stormIntervalId) { LK.clearInterval(stormIntervalId); } stormIntervalId = LK.setInterval(function () { var strikeX = Math.random() * 2048; var strikeY = Math.random() * 2732; var strike = new LightningStrikeEffect(strikeX, strikeY); spellEffectLayer.addChild(strike); shakeScreen(8, 150); }, 2500); if (rainIntervalId) { LK.clearInterval(rainIntervalId); } rainIntervalId = LK.setInterval(function () { for (var i = 0; i < 3; i++) { var drop = new Raindrop(); weatherEffectLayer.addChild(drop); } }, 150); } } function clearActiveWeatherModifiers() { if (rainIntervalId) { LK.clearInterval(rainIntervalId); rainIntervalId = null; } if (fogIntervalId) { LK.clearInterval(fogIntervalId); fogIntervalId = null; } if (stormIntervalId) { LK.clearInterval(stormIntervalId); stormIntervalId = null; } for (var i = activeFogWisps.length - 1; i >= 0; i--) { activeFogWisps[i].destroy(); } activeFogWisps = []; if (nightOverlay.visible) { tween(nightOverlay, { alpha: 0 }, { duration: 500, onFinish: function onFinish() { nightOverlay.visible = false; } }); } towers.forEach(function (tower) { tower.removeBuff('FOG_DEBUFF'); }); } function showSpellOverlay() { spellOverlay.visible = true; tween(spellOverlay, { alpha: 0.7 }, { duration: 650, easing: tween.easeOut }); } function hideSpellOverlay() { tween(spellOverlay, { alpha: 0 }, { duration: 400, easing: tween.easeIn, onFinish: function onFinish() { spellOverlay.visible = false; } }); } var CELL_SIZE = 76; var TOWER_DATA = { 'guard': { 1: { name: 'Guard Tower', cost: 30, damage: 5, fireRate: 60, range: 5 * CELL_SIZE, bulletSpeed: 7, graphic: 'arrow_guard' }, 2: { name: 'Veteran Guard', cost: 80, // Zwiększono z 60 damage: 9, fireRate: 70, multiShot: 2, range: 5 * CELL_SIZE, bulletSpeed: 7, graphic: 'arrow_guard' }, 3: { name: 'Elite Guard', cost: 155, // Zwiększono ze 120 damage: 16, fireRate: 85, multiShot: 3, range: 6 * CELL_SIZE, bulletSpeed: 7, graphic: 'arrow_guard' } }, 'crossbow': { 1: { name: 'Crossbow Tower', cost: 50, damage: 13, fireRate: 150, range: 7 * CELL_SIZE, bulletSpeed: 15, graphic: 'arrow_crossbow' }, 2: { name: 'Heavy Crossbow', cost: 130, // Zwiększono ze 100 damage: 25, fireRate: 150, range: 7.5 * CELL_SIZE, bulletSpeed: 15, graphic: 'arrow_crossbow' }, 3: { name: 'Arbalest', cost: 260, // Zwiększono z 200 damage: 30, fireRate: 150, multiShot: 2, range: 8 * CELL_SIZE, bulletSpeed: 15, graphic: 'arrow_crossbow' } }, 'mage': { 1: { name: 'Mage Tower', cost: 40, damage: 8, fireRate: 90, range: 5 * CELL_SIZE, bulletSpeed: 8, bulletType: 'normal', graphic: 'bullet' }, '2F': { name: 'Fire Mage', cost: 105, damage: 10, fireRate: 120, range: 6 * CELL_SIZE, bulletSpeed: 8, bulletType: 'splash', splashDamage: 8, splashRadius: 1.3 * CELL_SIZE, splashScale: 1.4, graphic: 'projectile_fireball' }, '3F': { name: 'Pyromancer', cost: 200, damage: 15, fireRate: 120, range: 6.5 * CELL_SIZE, bulletSpeed: 8, bulletType: 'splash', splashDamage: 12, splashRadius: 1.6 * CELL_SIZE, splashScale: 1.7, graphic: 'projectile_fireball' }, '2W': { name: 'Water Mage', cost: 105, damage: 9, fireRate: 140, range: 6 * CELL_SIZE, bulletSpeed: 8, bulletType: 'slow', slowAmount: 0.6, multiShot: 2, slowDuration: 120, slowChance: 0.4, graphic: ['ice_spike_1', 'ice_spike_2', 'ice_spike_3', 'ice_spike_4', 'ice_spike_5'] }, '3W': { name: 'Ice Archmage', cost: 210, damage: 12, fireRate: 160, range: 6.5 * CELL_SIZE, bulletSpeed: 8, bulletType: 'slow', slowAmount: 0.5, multiShot: 2, slowDuration: 140, slowChance: 0.6, graphic: ['ice_spike_1', 'ice_spike_2', 'ice_spike_3', 'ice_spike_4', 'ice_spike_5'] }, '2L': { name: 'Lightning Mage', cost: 105, damage: 9, fireRate: 150, range: 7 * CELL_SIZE, bulletSpeed: 20, bulletType: 'chain', chainTargets: 3, chainDamageFalloff: 2, chainChance: 0.25, graphic: 'bullet' }, '3L': { name: 'Stormcaller', cost: 210, damage: 16, fireRate: 150, range: 7.5 * CELL_SIZE, bulletSpeed: 20, bulletType: 'chain', chainTargets: 3, chainDamageFalloff: 2, chainChance: 0.45, graphic: 'bullet' } }, 'banner': { 1: { name: 'War Banner', cost: 50, damage: 0, range: 4 * CELL_SIZE, aura: { 'attackSpeed': 1.15 } }, '2A': { name: 'Banner of Fury', cost: 130, // Zwiększono ze 100 range: 4 * CELL_SIZE, aura: { 'attackSpeed': 1.3 } }, '2B': { name: 'Banner of Command', cost: 130, // Zwiększono ze 100 range: 4 * CELL_SIZE, aura: { 'attackSpeed': 1.15, 'damage': 1.15 } } } }; var BUILDING_DATA = { 'home': { id: 'home', name: 'Home', size: { w: 3, h: 3 }, cost: 50, description: 'Each Home increases your max Population by 5.' }, 'training_ground': { id: 'training_ground', name: 'Training Ground', size: { w: 3, h: 3 }, // Przykładowy rozmiar cost: 100, // Przykładowy koszt description: 'All recruited units gain +5% Health and Attack. This effect stacks.' }, 'workshop': { id: 'workshop', name: 'Workshop', size: { w: 4, h: 3 }, cost: 150, description: 'Unlocks advanced tower upgrades (Level 3+).' }, 'magic_sanctum': { id: 'magic_sanctum', name: 'Magic Sanctum', size: { w: 3, h: 3 }, // Przykładowy rozmiar cost: 100, // Przykładowy koszt description: 'Increases the effectiveness of all spells by 10%. This effect stacks.' }, 'barracks': { id: 'barracks', name: 'Barracks', size: { w: 6, h: 7 }, cost: 200, description: 'Unlocks the Army panel and allows recruitment of units.' }, 'magic_academy': { id: 'magic_academy', name: 'Magic Academy', size: { w: 6, h: 8 }, cost: 200, description: 'Unlocks Mana and the Magic panel, allowing you to cast powerful spells.' }, 'tavern': { id: 'tavern', name: 'Tavern', size: { w: 5, h: 5 }, cost: 120, description: 'Generates income each wave. Each Tavern provides 30g, plus a bonus of +2g for each person in your population.' }, 'smithy': { id: 'smithy', name: 'Smithy', size: { w: 5, h: 6 }, cost: 150, description: 'Provides global buffs to towers and units.' }, 'infirmary': { id: 'infirmary', name: 'Infirmary', size: { w: 4, h: 3 }, cost: 200, description: 'Reduces the time it takes for population to regenerate after a unit dies.' } }; var SPELL_DATA = { 'fire_wall': { id: 'fire_wall', name: 'Fire Wall', description: 'Damages enemies in an area.', cost: 0, radius: 2.5 * CELL_SIZE, duration: 460, dotDamage: 7, dotDuration: 180 }, 'healing_field': { id: 'healing_field', name: 'Healing Field', description: 'Heals allied targets in an area.', cost: 0, heal: 25, radius: 3 * CELL_SIZE }, 'lightning_stun': { id: 'lightning_stun', name: 'Thunderstorm', description: 'Stuns and damages enemies in an area.', cost: 0, stunDuration: 2 * 60, radius: 3 * CELL_SIZE, damage: 15, duration: 90 }, 'summon_golem': { id: 'summon_golem', name: 'Summon Golem', description: 'Summons a temporary blocker unit.', cost: 0, radius: 0 }, 'curse': { id: 'curse', name: 'Curse', description: 'Cursed enemies move slower and take more damage.', cost: 0, damageMultiplier: 1.10, slowFactor: 0.85, duration: 480, radius: 3.5 * CELL_SIZE } }; var LEVEL_DATA_ALL = { 1: { levelName: "Level 1: Royal Road", mapAsset: 'map1', initialGold: 600, initialLives: 20, mapLayout: ["222222221100011222222222", "22222222AA00011222222222", "222222221100011222222222", "222222221100011222222222", "2222222211000AA222222222", "222222221100011222222222", "222222221100011222222222", "222222221100011222222222", "222222221100011222222222", "222222221100011222222222", "222222221100011222222222", "222222221100011222222222", "222222221100011222222222", "222222221100011222222222", "222222221100011222222222", "222222221100011222222222", "222222221100011222222222", "222222221100011222222222", "222222221100011222222222", "222222221100011222222222", "222222221100011222222222", "222222221100011222222222", "222222221100011222222222", "222222221100011222222222", "222222221100011222222222", "222222221100011222222222", "222222221100011222222222", "222222221100011222222222", "222222221100011222222222", "2222222211E0011222222222"], castleMapLayout: ["11111111111111111", "11111111111111111", "11111111111111111", "11111111111111111", "11111111111111111", "11111111111111111", "11111111111111111", "11111111111111111", "11111111111111111", "11111111111111111", "11111111111111111", "11111111111111111", "11111111111111111", "11111111111111111", "11111111111111111", "11111111111111111", "11111111111111111", "11111111111111111", "11111111111111111", "11111111111111111", "11111111111111111", "11111111111111111"], spawnPoints: [{ id: 'mainPath', startX: 11, startY: 5, goalX: 11, goalY: 34, lanes: [{ id: 'mainPath_L', offset: -0.6 }, { id: 'mainPath_C', offset: 0 }, { id: 'mainPath_R', offset: 0.6 }] }], waves: [{ wave: 1, phases: [{ delay: 0, interval: 140, units: [{ spawner: 'mainPath', type: 'normal', count: 12 }] }] }, { wave: 2, phases: [{ delay: 0, interval: 100, units: [{ spawner: 'mainPath', type: 'normal', count: 10 }] }, { delay: 450, interval: 120, units: [{ spawner: 'mainPath', type: 'shield', count: 6 }] }] }, { wave: 3, phases: [{ delay: 0, interval: 110, units: [{ spawner: 'mainPath', type: 'normal', count: 8 }, { spawner: 'mainPath', type: 'shield', count: 3 }] }, { delay: 600, interval: 60, units: [{ spawner: 'mainPath', type: 'rider', count: 9 }] }] }, { wave: 4, phases: [{ delay: 0, interval: 130, units: [{ spawner: 'mainPath', type: 'shield', count: 10 }] }, { delay: 240, interval: 100, units: [{ spawner: 'mainPath', type: 'archer', count: 8 }] }, { delay: 500, interval: 80, units: [{ spawner: 'mainPath', type: 'rider', count: 6 }] }] }, { wave: 5, phases: [{ delay: 0, interval: 70, units: [{ spawner: 'mainPath', type: 'normal', count: 15 }] }, { delay: 400, interval: 110, units: [{ spawner: 'mainPath', type: 'shield', count: 10 }, { spawner: 'mainPath', type: 'archer', count: 8 }] }, { delay: 400, interval: 60, units: [{ spawner: 'mainPath', type: 'rider', count: 10 }] }] }, { wave: 6, phases: [{ delay: 0, interval: 100, units: [{ spawner: 'mainPath', type: 'shield', count: 10 }, { spawner: 'mainPath', type: 'archer', count: 8 }] }, { delay: 400, interval: 80, units: [{ spawner: 'mainPath', type: 'chieftain', count: 1, isBoss: true }, { spawner: 'mainPath', type: 'rider', count: 10 }] }] }] }, 2: { levelName: "Level 2: Old Mill Path", mapAsset: 'map2', initialGold: 650, initialLives: 20, mapLayout: ["110021111222222222222222", "110021111222222222222222", "110021111222222222222222", "110021111222222222222222", "110021111222222222222222", "110021111222222222222222", "110021111222222222222222", "110021111222222222222222", "110021111111111111111111", "110021111111111111111111", "110021111111111111111111", "110000000000000000000000", "110000000000000000000000", "222222222222222222222200", "111111111111111111111100", "111111111111111111111100", "111111111111111111111100", "222222222222222221111100", "222222222222222221111100", "222222222222222221111100", "222222222222222221111100", "222222222222222221111100", "222222222222222221111100", "222222222222222221111100", "222222222222222221111100", "222222222222222221111100", "222222222222222221111100", "222222222222222221111100", "222222222222222221111100", "2222222222222222211111E0"], castleMapLayout: ["11111111111111111", "11111111111111111", "11111111111111111", "11111111111111111", "11111111111111111", "11111111111111111", "11111111111111111", "11111111111111111", "11111111111111111", "11111111111111111", "11111111111111111", "11111111111111111", "11111111111111111", "11111111111111111", "11111111111111111", "11111111111111111", "11111111111111111", "11111111111111111", "11111111111111111", "11111111111111111", "11111111111111111", "11111111111111111"], spawnPoints: [{ id: 'mainPath', startX: 2, startY: 5, goalX: 23, goalY: 34, lanes: [{ id: 'mainPath_L', offset: 0.2 }, { id: 'mainPath_C', offset: 0 }, { id: 'mainPath_R', offset: 1.8 }] }], waves: [{ wave: 1, phases: [{ delay: 0, interval: 120, units: [{ spawner: 'mainPath', type: 'normal', count: 10 }, { spawner: 'mainPath', type: 'rider', count: 4 }] }] }, { wave: 2, phases: [{ delay: 0, interval: 150, units: [{ spawner: 'mainPath', type: 'shield', count: 12 }] }, { delay: 600, interval: 100, units: [{ spawner: 'mainPath', type: 'archer', count: 8 }] }] }, { wave: 3, phases: [{ delay: 0, interval: 80, units: [{ spawner: 'mainPath', type: 'normal', count: 20 }] }, { delay: 500, interval: 50, units: [{ spawner: 'mainPath', type: 'rider', count: 10 }] }] }, { wave: 4, phases: [{ delay: 0, interval: 130, units: [{ spawner: 'mainPath', type: 'shield', count: 15 }] }, { delay: 300, interval: 100, units: [{ spawner: 'mainPath', type: 'archer', count: 12 }] }] }, { wave: 5, phases: [{ delay: 0, interval: 180, units: [{ spawner: 'mainPath', type: 'rider', count: 12 }] }, { delay: 400, interval: 90, units: [{ spawner: 'mainPath', type: 'normal', count: 15 }, { spawner: 'mainPath', type: 'shield', count: 10 }] }] }, { wave: 6, phases: [{ delay: 0, interval: 120, units: [{ spawner: 'mainPath', type: 'shield', count: 18 }] }, { delay: 700, interval: 400, units: [{ spawner: 'mainPath', type: 'chieftain', count: 2, isBoss: true }, { spawner: 'mainPath', type: 'archer', count: 8 }] }] }] }, 3: { levelName: "Level 3: Twin Pass", mapAsset: 'map3', initialGold: 750, initialLives: 20, mapLayout: ["111100000111100000111111", "111100000111100000111111", "111100000111100000111111", "111100000111100000111111", "111100000111100000111111", "111100000111100000111111", "111100000111100000111111", "111100000111100000111111", "111100000111100000111111", "111100000111100000111111", "111100000111100000111111", "111100000111100000111111", "111100000111100000111111", "111100000111100000111111", "111100000111100000111111", "111100000111100000111111", "111100000111100000111111", "111100000111100000111111", "111100000111100000111111", "111100000111100000111111", "111100000111100000111111", "111100000111100000111111", "111100000111100000111111", "111100000111100000111111", "111100000111100000111111", "111100000111100000111111", "111100000111100000111111", "111100000111100000111111", "111100000111100000111111", "111100000111100000111111"], castleMapLayout: ["11111111111111111", "11111111111111111", "11111111111111111", "11111111111111111", "11111111111111111", "11111111111111111", "11111111111111111", "11111111111111111", "11111111111111111", "11111111111111111", "11111111111111111", "11111111111111111", "11111111111111111", "11111111111111111", "11111111111111111", "11111111111111111", "11111111111111111", "11111111111111111", "11111111111111111", "11111111111111111", "11111111111111111", "11111111111111111"], spawnPoints: [{ id: 'leftPath', startX: 4, startY: 5, goalX: 4, goalY: 34, lanes: [{ id: 'leftPath_L', offset: 2 }, { id: 'leftPath_R', offset: 2 }] }, { id: 'rightPath', startX: 13, startY: 5, goalX: 13, goalY: 34, lanes: [{ id: 'rightPath_L', offset: -0.4 }, { id: 'rightPath_R', offset: 0.4 }] }], waves: [{ wave: 1, phases: [{ delay: 0, interval: 140, units: [{ spawner: 'leftPath', type: 'normal', count: 10 }, { spawner: 'rightPath', type: 'normal', count: 10 }] }] }, { wave: 2, phases: [{ delay: 0, interval: 120, units: [{ spawner: 'leftPath', type: 'shield', count: 8 }, { spawner: 'rightPath', type: 'rider', count: 12 }] }] }] } }; var EVENT_DATA = { 'SUNNY': { name: 'Sunny Day', description: 'A calm day. No special effects.', asset: 'card_sunny' }, 'RAIN': { name: 'Rainy Day', description: 'Enemies drop 20% less gold.', asset: 'card_rain' }, 'NIGHT': { name: 'Night', description: 'Enemies are 10% stronger.', asset: 'card_night', modifier: { enemyStatMultiplier: 1.10 } }, 'STORM': { name: 'Storm', description: 'Recruitment of all units is disabled for this wave.', asset: 'card_storm' }, 'FOG': { name: 'Fog', description: 'Magic is disabled. Mage Towers deal 10% less damage.', asset: 'card_fog' } }; var WEATHER_DECK_DATA = { 1: ['SUNNY', 'RAIN', 'SUNNY', 'NIGHT', 'SUNNY'], 2: ['SUNNY', 'RAIN', 'NIGHT', 'SUNNY', 'FOG'], 3: ['SUNNY', 'RAIN', 'NIGHT', 'FOG', 'STORM'] }; var UNIT_DATA = { 'swordsman': { id: 'swordsman', name: 'Swordsman', cost: 25, speed: 1.2, maxHealth: 20, damage: 2, attackSpeed: 90, range: 1.2 * CELL_SIZE, detectionRange: 3 * CELL_SIZE, isRanged: false }, 'defender': { id: 'defender', name: 'Defender', cost: 40, speed: 0.9, maxHealth: 40, damage: 2, attackSpeed: 100, range: 1.2 * CELL_SIZE, detectionRange: 3 * CELL_SIZE, isRanged: false }, 'horseman': { id: 'horseman', name: 'Horseman', cost: 60, speed: 1.8, maxHealth: 50, damage: 5, attackSpeed: 80, range: 1.3 * CELL_SIZE, detectionRange: 3.5 * CELL_SIZE, isRanged: false }, 'archer': { id: 'archer', name: 'Archer', cost: 40, speed: 1.1, maxHealth: 15, damage: 6, attackSpeed: 48, range: 5 * CELL_SIZE, detectionRange: 6 * CELL_SIZE, isRanged: true } }; var isBuildPhase = false; var buildPhaseTimer = 0; var startGameButton; var timerText; var pathId; var buildingDeck; var spellOverlay; var spellEffectLayer; var buildingHand; var castleBuildings; var castleBuildingsLayer; var statsPanel; var statsDetailsContainer; var statsToggleButton; var isStatsPanelExpanded = false; var maxScore; var enemies; var towers; var bullets; var selectedTower; var selectedBuilding; var isDragging; var isDraggingSpell; var lastRecruitActionTick; var panelActionTaken; var gold; var lives; var mana; var numberOfTaverns; var levelData; var isManaUnlocked; var enemiesKilled; var enemiesInCurrentWave; var levelData; var currentWave; var totalWaves; var waveTimer; var waveInProgress; var weatherEffectLayer, nightOverlay; var waveSpawned; var nextWaveTime; var sourceTowers; var grid; var specialZoneIndicators; var wavesText; var towerLayer; var spawnQueue; var waveClock; var enemyLayer; var debugLayer; var buildableTilesLayer; var castleBuildableTilesLayer; var specialTilesLayer; var enemyLayerBottom; var enemyLayerMiddle; var enemyLayerTop; var placementPreview; var buildPanelContainer; var buildButton; var rerollButton; var isBuildPanelOpen; var currentActiveView; var gateContainer; var currentLevelNumber; var castleContainer; var activeWeatherEvent = null; var viewToggleButton; var talentTreeScreen; var castleGrid; var castleBuildPanel; var magicButton; var rainIntervalId = null; var stormIntervalId = null; var fogIntervalId = null; var rainIntervalId = null; var fogIntervalId = null; var activeFogWisps = []; var magicPanelContainer; var armyButton; var armyPanelContainer; var mainActionPanel; var buildPage, magicPage, armyPage; var isMainPanelOpen = false; var currentOpenPage = 'build'; var isArmyUnlocked; var panelActionTaken; var spellPreview; var activeSpells; friendlyUnits = []; var friendlyUnitLayer; var peopleRegenQueue; var currentScreenState = ''; var titleScreenContainer = null; var levelSelectContainer = null; var isHidingUpgradeMenu = false; var isMenuTransitioning = false; function hideUpgradeMenu(menu) { if (isHidingUpgradeMenu) { return; } isHidingUpgradeMenu = true; isMenuTransitioning = true; tween(menu, { y: 2732 + 225 }, { duration: 150, easing: tween.easeIn, onFinish: function onFinish() { menu.destroy(); isHidingUpgradeMenu = false; isMenuTransitioning = false; } }); } function showPageInMainPanel(pageName) { if (buildPage) { buildPage.visible = pageName === 'build'; } if (magicPage) { magicPage.visible = pageName === 'magic'; } if (armyPage) { armyPage.visible = pageName === 'army'; } } function toggleMainPanel(pageToShow) { var targetY; var panelShouldBeOpen; if (isMainPanelOpen && currentOpenPage === pageToShow) { panelShouldBeOpen = false; } else { panelShouldBeOpen = true; } if (panelShouldBeOpen) { showPageInMainPanel(pageToShow); currentOpenPage = pageToShow; targetY = 2732 - 250; isMainPanelOpen = true; } else { targetY = 2732 + 100; isMainPanelOpen = false; } if (mainActionPanel) { tween(mainActionPanel, { y: targetY }, { duration: 300, easing: tween.backOut }); } } function closeActiveUpgradeMenu() { if (isMenuTransitioning) { return; } game.children.forEach(function (child) { if (child instanceof UpgradeMenu) { hideUpgradeMenu(child); } }); for (var i = gateContainer.children.length - 1; i >= 0; i--) { if (gateContainer.children[i].isTowerRange) { gateContainer.removeChild(gateContainer.children[i]); } } selectedTower = null; } function updateUI() { goldText.setText('Gold: ' + gold); livesText.setText('Lives: ' + lives); enemiesText.setText('Enemies: ' + enemiesKilled + '/' + enemiesInCurrentWave); peopleText.setText('People: ' + people + '/' + maxPeople); wavesText.setText('Wave: ' + currentWave + '/' + totalWaves); if (isManaUnlocked) { manaText.visible = true; manaText.setText('Mana: ' + mana); } } function setGold(value) { gold = value; updateUI(); } function setMana(value) { if (isManaUnlocked) { mana = value; updateUI(); } } function getTowerCost(towerType) { var cost = 0; if (TOWER_DATA[towerType] && TOWER_DATA[towerType][1]) { cost = TOWER_DATA[towerType][1].cost; } if (!isBuildPhase && currentWave > 0) { cost = Math.floor(cost * 1.5); } return cost; } function getTowerSellValue(totalValue) { return Math.floor(totalValue * 0.6); } function getBuildingCost(buildingType) { var count = 0; for (var i = 0; i < castleBuildings.length; i++) { if (castleBuildings[i].id === buildingType) { count++; } } var baseCost = BUILDING_DATA[buildingType] ? BUILDING_DATA[buildingType].cost : 0; var finalCost = { gold: Math.floor(baseCost * Math.pow(1.2, count)), people: 0 }; if (progressionData.upgrades['building_cost_1']) { var costReduction = 1 - progressionData.upgrades['building_cost_1'] * 0.04; finalCost.gold = Math.floor(finalCost.gold * costReduction); } return finalCost; } function getModifiedSpellData(spellId) { var originalData = SPELL_DATA[spellId]; var modifiedData = JSON.parse(JSON.stringify(originalData)); var sanctumCount = castleBuildings.filter(function (b) { return b.id === 'magic_sanctum'; }).length; if (sanctumCount > 0) { var spellBonus = Math.pow(1.10, sanctumCount); if (modifiedData.damage) { modifiedData.damage = Math.round(modifiedData.damage * spellBonus); } if (modifiedData.dotDamage) { modifiedData.dotDamage = Math.round(modifiedData.dotDamage * spellBonus); } if (modifiedData.stunDuration) { modifiedData.stunDuration = Math.round(modifiedData.stunDuration * spellBonus); } if (modifiedData.heal) { modifiedData.heal = Math.round(modifiedData.heal * spellBonus); } if (modifiedData.duration) { modifiedData.duration = Math.round(modifiedData.duration * spellBonus); } } // Bonusy z Drzewka Magii if (progressionData.upgrades['magic_destruction_2']) { var dmgBonus = 1 + progressionData.upgrades['magic_destruction_2'] * 0.04; if (modifiedData.damage) { modifiedData.damage *= dmgBonus; } if (modifiedData.dotDamage) { modifiedData.dotDamage *= dmgBonus; } } if (progressionData.upgrades['magic_manipulation_2']) { var costBonus = 1 - progressionData.upgrades['magic_manipulation_2'] * 0.03; modifiedData.cost = Math.floor(modifiedData.cost * costBonus); } var durationKey = 'magic_' + spellId + '_duration_3'; if (progressionData.upgrades[durationKey]) { var durationBonus = 1 + progressionData.upgrades[durationKey] * 0.10; if (modifiedData.duration) { modifiedData.duration *= durationBonus; } if (modifiedData.dotDuration) { modifiedData.dotDuration *= durationBonus; } } // ... i tak dalej dla innych bonusów jak promień, siła efektu ... return modifiedData; } function setupStatsPanel() { isStatsPanelExpanded = false; statsPanel = new Container(); statsPanel.x = 0; statsPanel.y = 0; LK.gui.top.addChild(statsPanel); var panelBg = statsPanel.attachAsset('panel_topbar_background', { anchorX: 0.5, anchorY: 0 }); panelBg.y = -20; statsPanel.hitAreaWidth = panelBg.width; statsPanel.hitAreaHeight = 300; // Zostawiamy dla pewności var maxBgHeight = 260; var minBgHeight = 120; panelBg.height = maxBgHeight; // Ustawiamy na stałe maksymalną wysokość var initialScaleY = minBgHeight / maxBgHeight; panelBg.scale.y = initialScaleY; // Zaczynamy ze zmniejszoną skalą var textY = 35; goldText = new Text2('Gold: 0', { size: 50, fill: 0xFFD700, weight: 800 }); goldText.anchor.set(0.5, 0.5); goldText.x = -400; goldText.y = textY; statsPanel.addChild(goldText); livesText = new Text2('Lives: 0', { size: 50, fill: 0x00FF00, weight: 800 }); livesText.anchor.set(0.5, 0.5); livesText.x = 400; livesText.y = textY; statsPanel.addChild(livesText); timerText = new Text2('', { size: 45, fill: 0xFFD700, weight: 700 }); timerText.anchor.set(0.5, 0.5); timerText.x = 0; timerText.y = textY; timerText.visible = false; statsPanel.addChild(timerText); statsDetailsContainer = new Container(); statsDetailsContainer.y = 70; statsDetailsContainer.visible = false; statsPanel.addChild(statsDetailsContainer); var detailsTextOptions = { size: 45, fill: 0xFFFFFF, weight: 600, align: 'center' }; peopleText = new Text2('People: 0/0', detailsTextOptions); peopleText.anchor.set(0.5, 0.5); peopleText.x = -400; peopleText.y = 40; statsDetailsContainer.addChild(peopleText); manaText = new Text2('Mana: 0', detailsTextOptions); manaText.anchor.set(0.5, 0.5); manaText.x = -400; manaText.y = 90; manaText.visible = false; statsDetailsContainer.addChild(manaText); wavesText = new Text2('Wave: 0/0', detailsTextOptions); wavesText.anchor.set(0.5, 0.5); wavesText.x = 400; wavesText.y = 40; statsDetailsContainer.addChild(wavesText); enemiesText = new Text2('Enemies: 0/0', detailsTextOptions); enemiesText.anchor.set(0.5, 0.5); enemiesText.x = 400; enemiesText.y = 90; statsDetailsContainer.addChild(enemiesText); statsToggleButton = new Container(); statsToggleButton.x = 0; statsToggleButton.y = 35; statsPanel.addChild(statsToggleButton); var arrow = statsToggleButton.attachAsset('player_arrow', { anchorX: 0.5, anchorY: 0.5, tint: 0xFFFFFF }); arrow.scale.set(1.5, 1.5); arrow.rotation = Math.PI / 2; statsToggleButton.down = function () { isStatsPanelExpanded = !isStatsPanelExpanded; var targetScaleY = isStatsPanelExpanded ? 1.0 : initialScaleY; var targetArrowRotation = isStatsPanelExpanded ? -Math.PI / 2 : Math.PI / 2; var targetButtonY = isStatsPanelExpanded ? 225 : 35; if (isStatsPanelExpanded) { statsDetailsContainer.visible = true; } tween(panelBg, { scaleY: targetScaleY }, { duration: 250, easing: tween.easeOut, onFinish: function onFinish() { if (!isStatsPanelExpanded) { statsDetailsContainer.visible = false; } } }); tween(arrow, { rotation: targetArrowRotation }, { duration: 250, easing: tween.easeOut }); tween(statsToggleButton, { y: targetButtonY }, { duration: 250, easing: tween.easeOut }); }; } // --- FUNKCJE EKRANÓW --- function showTitleScreen() { if (titleScreenContainer) { titleScreenContainer.destroy(); } titleScreenContainer = new Container(); game.addChild(titleScreenContainer); currentScreenState = 'title'; if (LK.gui.top) { LK.gui.top.visible = false; } var bg = titleScreenContainer.attachAsset('title_background', {}); bg.scaleX = 1.1; bg.scaleY = 1.1; bg.x = -100; var gameTitleGraphic = titleScreenContainer.attachAsset('title_graphic', { anchorX: 0.5, anchorY: 0.5 }); gameTitleGraphic.x = 2048 / 2; gameTitleGraphic.y = 2732 / 2 - 400; gameTitleGraphic.alpha = 0; var startButton = titleScreenContainer.attachAsset('start_button_graphic', { anchorX: 0.5, anchorY: 0.5 }); startButton.x = 2048 / 2; startButton.y = 2732 / 2 + 200; startButton.interactive = true; startButton.alpha = 0; startButton.down = function () { gameTitleGraphic.destroy(); startButton.destroy(); showLevelSelectScreen(); }; tween(gameTitleGraphic, { alpha: 1 }, { duration: 2000, delay: 3000, easing: tween.easeIn }); tween(startButton, { alpha: 1 }, { duration: 1000, delay: 5000, easing: tween.easeIn }); function animateBg() { if (!bg.parent) { return; } tween(bg, { x: 0 }, { duration: 14000, easing: tween.easeInOut, onFinish: function onFinish() { tween(bg, { x: -100 }, { duration: 14000, easing: tween.easeInOut, onFinish: animateBg }); } }); } animateBg(); } function showLevelSelectScreen() { if (levelSelectContainer) { levelSelectContainer.destroy(); } levelSelectContainer = new Container(); game.addChild(levelSelectContainer); currentScreenState = 'levelSelect'; talentTreeScreen = new TalentTreeScreen(); game.addChild(talentTreeScreen); var parchment = levelSelectContainer.attachAsset('level_select_parchment', { anchorX: 0.5, anchorY: 0.5 }); parchment.x = 2048 / 2; parchment.y = 2732 / 2; parchment.alpha = 0; tween(parchment, { alpha: 1 }, { duration: 500 }); var selectTitle = new Text2("SELECT LEVEL", { size: 100, fill: 0x332211, weight: 700 }); selectTitle.anchor.set(0.5, 0.5); selectTitle.x = 2048 / 2; selectTitle.y = 350; levelSelectContainer.addChild(selectTitle); // NOWY PRZYCISK ULEPSZEŃ var upgradesButton = new Container(); upgradesButton.x = 2048 / 2; upgradesButton.y = 600; levelSelectContainer.addChild(upgradesButton); var upgradesButtonBg = upgradesButton.attachAsset('upgrade_menu_button', { anchorX: 0.5, anchorY: 0.5, tint: 0x0088CC }); var upgradesButtonText = new Text2("Upgrades", { size: 60, fill: 0xFFFFFF }); upgradesButtonText.anchor.set(0.5, 0.5); upgradesButton.addChild(upgradesButtonText); upgradesButton.interactive = true; upgradesButton.down = function () { levelSelectContainer.visible = false; talentTreeScreen.show(); }; var levelKeys = Object.keys(LEVEL_DATA_ALL); var startY = 850; // Przesunięte w dół var buttonSpacing = 250; for (var i = 0; i < levelKeys.length; i++) { var levelKey = levelKeys[i]; var levelData = LEVEL_DATA_ALL[levelKey]; var levelButton = new Container(); levelButton.x = 2048 / 2; levelButton.y = startY + i * buttonSpacing; levelSelectContainer.addChild(levelButton); var levelButtonBg = levelButton.attachAsset('notification', { anchorX: 0.5, anchorY: 0.5, width: 700, height: 180, tint: 0x0088CC }); var levelButtonText = new Text2(levelData.levelName, { size: 60, fill: 0xFFFFFF }); levelButtonText.anchor.set(0.5, 0.5); levelButton.addChild(levelButtonText); levelButton.interactive = true; (function (key) { levelButton.down = function () { if (titleScreenContainer) { titleScreenContainer.destroy(); } levelSelectContainer.destroy(); startGame(key); }; })(levelKey); } } function getPeopleRegenTime() { var hasInfirmary = castleBuildings.some(function (b) { return b.id === 'infirmary'; }); return hasInfirmary ? 5 * 60 : 10 * 60; } function toggleBuildPanel(panelOrState, stateOrPanel) { var panel, forceState; if (typeof panelOrState === 'string') { forceState = panelOrState; panel = stateOrPanel; } else { panel = panelOrState; forceState = stateOrPanel; } var targetY; var shouldBeOpen; if (!panel) { return; } if (forceState === 'open') { shouldBeOpen = true; } else if (forceState === 'close') { shouldBeOpen = false; } else { shouldBeOpen = !isBuildPanelOpen; } if (shouldBeOpen) { targetY = 2732 - 250; isBuildPanelOpen = true; } else { targetY = 2732 + 100; isBuildPanelOpen = false; } tween(panel, { y: targetY }, { duration: 300, easing: tween.backOut }); } function toggleMagicPanel(panel, forceState) { var targetY; var shouldBeOpen; if (!panel) { return; } if (forceState === 'open') { shouldBeOpen = true; } else if (forceState === 'close') { shouldBeOpen = false; } else { shouldBeOpen = !isMagicPanelOpen; } if (shouldBeOpen) { targetY = 2732 - 250; isMagicPanelOpen = true; } else { targetY = 2732 + 100; isMagicPanelOpen = false; } tween(panel, { y: targetY }, { duration: 300, easing: tween.backOut }); } function toggleArmyPanel(panel, forceState) { var targetY; var shouldBeOpen; if (!panel) { return; } if (forceState === 'open') { shouldBeOpen = true; } else if (forceState === 'close') { shouldBeOpen = false; } else { shouldBeOpen = !isArmyPanelOpen; } if (shouldBeOpen) { targetY = 2732 - 250; isArmyPanelOpen = true; } else { targetY = 2732 + 100; isArmyPanelOpen = false; isDraggingSpell = false; activeSpells = []; isDraggingSpell = false; } tween(panel, { y: targetY }, { duration: 300, easing: tween.backOut }); } function drawNewBuildingHand() { while (castleBuildPanel.children.length > 1) { castleBuildPanel.removeChildAt(1); } buildingHand = []; var availableBuildings = buildingDeck.filter(function (buildingType) { // Zaktualizowana logika - te budynki zawsze można budować if (buildingType === 'home' || buildingType === 'training_ground' || buildingType === 'magic_sanctum') { return true; } var alreadyBuilt = castleBuildings.some(function (b) { return b.id === buildingType; }); return !alreadyBuilt; }); var handToDraw = availableBuildings.slice(); for (var i = 0; i < 3; i++) { if (handToDraw.length === 0) { break; } var randomIndex = Math.floor(Math.random() * handToDraw.length); var drawnBuilding = handToDraw.splice(randomIndex, 1)[0]; buildingHand.push(drawnBuilding); } var buildingIconSpacing = 280; var totalBuildingWidth = (buildingHand.length - 1) * buildingIconSpacing; var buildingStartX = -totalBuildingWidth / 2; for (var i = 0; i < buildingHand.length; i++) { var building = new SourceBuilding(buildingHand[i]); building.x = buildingStartX + i * buildingIconSpacing; building.y = 0; castleBuildPanel.addChild(building); } } function placeBuilding(buildingType, gridX, gridY) { var cost = getBuildingCost(buildingType); var buildingSize = BUILDING_DATA[buildingType].size; if (gold >= cost.gold) { // Logika "Puff!" dla NPC var buildArea = { x: castleGrid.x + gridX * CELL_SIZE, y: castleGrid.y + gridY * CELL_SIZE, width: buildingSize.w * CELL_SIZE, height: buildingSize.h * CELL_SIZE }; for (var i = npcs.length - 1; i >= 0; i--) { var npc = npcs[i]; if (npc.x > buildArea.x && npc.x < buildArea.x + buildArea.width && npc.y > buildArea.y && npc.y < buildArea.y + buildArea.height) { // Prosty efekt "Puff!" var puff = new Explosion(npc.x, npc.y, 0.5); npcLayer.addChild(puff); // Znajdź nową pozycję i przenieś var validCells = []; for (var ci = 0; ci < castleGrid.cells.length; ci++) { for (var cj = 0; cj < castleGrid.cells[ci].length; cj++) { if (castleGrid.cells[ci][cj].type === 1) { validCells.push(castleGrid.cells[ci][cj]); } } } if (validCells.length > 0) { var targetCell = validCells[Math.floor(Math.random() * validCells.length)]; npc.x = castleGrid.x + targetCell.x * CELL_SIZE + CELL_SIZE / 2; npc.y = castleGrid.y + targetCell.y * CELL_SIZE + CELL_SIZE / 2; npc.state = 'IDLE'; npc.stateTimer = 60; } else { npc.destroy(); // Na wszelki wypadek, jakby nie było miejsca } } } setGold(gold - cost.gold); var newBuilding = new Building(buildingType, buildingSize); newBuilding.x = castleGrid.x + gridX * CELL_SIZE; newBuilding.y = castleGrid.y + gridY * CELL_SIZE; castleBuildingsLayer.addChild(newBuilding); castleBuildings.push(newBuilding); for (var i = 0; i < buildingSize.w; i++) { for (var j = 0; j < buildingSize.h; j++) { var cell = castleGrid.getCell(gridX + i, gridY + j); if (cell) { cell.type = 5; } } } if (buildingType === 'home') { maxPeople += 5; updateUI(); // Dodaj 2 nowych NPC var npcGraphics = ['npc_man_1', 'npc_man_2', 'npc_woman_1', 'npc_woman_2']; for (var k = 0; k < 2; k++) { var spawnCell = castleGrid.getCell(8, 20); // Przykładowy punkt spawnu var spawnX = castleGrid.x + 8 * CELL_SIZE + CELL_SIZE / 2; var spawnY = castleGrid.y + 20 * CELL_SIZE + CELL_SIZE / 2; var randomGraphic = npcGraphics[Math.floor(Math.random() * npcGraphics.length)]; var newNpc = new NPC(spawnX, spawnY, randomGraphic); npcs.push(newNpc); npcLayer.addChild(newNpc); } } if (buildingType === 'magic_academy' && !isManaUnlocked) { isManaUnlocked = true; manaText.visible = true; magicButton.visible = true; updateUI(); } if (buildingType === 'barracks' && !isArmyUnlocked) { isArmyUnlocked = true; armyButton.visible = true; } var handIndex = buildingHand.indexOf(buildingType); if (handIndex > -1) { buildingHand.splice(handIndex, 1); } for (var i = castleBuildPanel.children.length - 1; i >= 0; i--) { var child = castleBuildPanel.children[i]; if (child.buildingType === buildingType) { child.destroy(); break; } } if (buildingHand.length === 0) { drawNewBuildingHand(); } return true; } else { var notification = game.addChild(new Notification("Not enough gold!")); notification.x = 2048 / 2; notification.y = grid.height - 50; return false; } } function placeTower(gridX, gridY, towerType) { var towerCost = getTowerCost(towerType); if (gold >= towerCost) { var tower = new Tower(towerType || 'default'); tower.placeOnGrid(gridX, gridY); towerLayer.addChild(tower); towers.push(tower); setGold(gold - towerCost); tower.startConstructionAnimation(5, function () {}); if (activeWeatherEvent === 'FOG' && tower.towerType === 'mage') { tower.addBuff({ source: 'FOG_DEBUFF', effect: { damage: EVENT_DATA.FOG.modifier.mageTowerDebuff } }); } return true; } else { var notification = game.addChild(new Notification("Not enough gold!")); notification.x = 2048 / 2; notification.y = grid.height - 50; return false; } } function initializeGameState(levelData) { pathId = 1; maxScore = 0; enemies = []; towers = []; bullets = []; friendlyUnits = []; peopleRegenQueue = []; activeSpells = []; npcs = []; specialZoneIndicators = []; selectedTower = null; castleBuildings = []; // ZMIANA: Dodajemy bonusy startowe var initialGold = levelData.initialGold; if (progressionData.upgrades['building_gold_4']) { initialGold += 500; } gold = initialGold; var initialLives = levelData.initialLives; if (progressionData.upgrades['building_lives_4']) { initialLives += 10; } lives = initialLives; mana = 0; isManaUnlocked = false; isArmyUnlocked = false; enemiesKilled = 0; enemiesInCurrentWave = 0; currentWave = 1; totalWaves = levelData.waves.length; waveInProgress = false; waveSpawned = false; waveTimer = 0; nextWaveTime = 1200; sourceTowers = []; isBuildPanelOpen = false; isMagicPanelOpen = false; isArmyPanelOpen = false; people = 5; maxPeople = 5; currentActiveView = 'gate'; lastRecruitActionTick = 0; } function setupScene(levelData) { gateContainer = new Container(); game.addChild(gateContainer); castleContainer = new Container(); castleBuildingsLayer = new Container(); castleContainer.addChild(castleBuildingsLayer); game.addChild(castleContainer); castleContainer.visible = false; var castleMainBg = LK.getAsset('castle_bg_image', {}); castleContainer.addChildAt(castleMainBg, 0); castleMainBg.x = -100; castleMainBg.y = -300; gateContainer.addChild(LK.getAsset(levelData.mapAsset, { scaleMode: 'nearest' })); debugLayer = new Container(); specialTilesLayer = new Container(); towerLayer = new Container(); friendlyUnitLayer = new Container(); buildableTilesLayer = new Container(); enemyLayerBottom = new Container(); enemyLayerMiddle = new Container(); enemyLayerTop = new Container(); enemyLayer = new Container(); enemyLayer.addChild(enemyLayerBottom); enemyLayer.addChild(enemyLayerMiddle); enemyLayer.addChild(enemyLayerTop); weatherEffectLayer = new Container(); spellOverlay = new Container(); spellOverlay.attachAsset('cell', { width: 2048, height: 2732, tint: 0x000000 }); spellOverlay.alpha = 0; spellOverlay.visible = false; spellEffectLayer = new Container(); nightOverlay = new Container(); nightOverlay.attachAsset('cell', { width: 2048, height: 2732, tint: 0x000000 }); nightOverlay.alpha = 0; nightOverlay.visible = false; gateContainer.addChild(buildableTilesLayer); gateContainer.addChild(debugLayer); gateContainer.addChild(specialTilesLayer); gateContainer.addChild(towerLayer); gateContainer.addChild(friendlyUnitLayer); gateContainer.addChild(enemyLayer); gateContainer.addChild(weatherEffectLayer); gateContainer.addChild(spellOverlay); gateContainer.addChild(spellEffectLayer); gateContainer.addChild(nightOverlay); buildableTilesLayer.visible = false; } function setupGrid(levelData) { grid = new Grid(24, 29 + 6); grid.x = 140; grid.y = 200 - CELL_SIZE * 4; if (levelData.mapAsset === 'map3') { grid.x = 235; } for (var y = 0; y < levelData.mapLayout.length; y++) { for (var x = 0; x < levelData.mapLayout[y].length; x++) { var tileChar = levelData.mapLayout[y][x]; var cell = grid.getCell(x, y + 5); if (cell) { cell.specialEffect = null; if (tileChar === '0') { cell.type = 0; } else if (tileChar === '1' || tileChar === 'A' || tileChar === 'C') { cell.type = 1; if (tileChar === 'A' || tileChar === 'C') { cell.specialEffect = tileChar === 'A' ? 'amplifier' : 'catalyst'; var isTopLeft = (x === 0 || levelData.mapLayout[y][x - 1] !== tileChar) && (y === 0 || !levelData.mapLayout[y - 1] || levelData.mapLayout[y - 1][x] !== tileChar); if (isTopLeft) { var zoneWidth = 1; var zoneHeight = 1; while (x + zoneWidth < levelData.mapLayout[y].length && levelData.mapLayout[y][x + zoneWidth] === tileChar) { zoneWidth++; } while (y + zoneHeight < levelData.mapLayout.length && levelData.mapLayout[y + zoneHeight][x] === tileChar) { zoneHeight++; } var zoneSize = Math.max(zoneWidth, zoneHeight); var assetName = tileChar === 'A' ? 'buff_amplifier_zone' : 'buff_catalyst_zone'; var zoneIndicator = new Container(); var indicatorGraphic = zoneIndicator.attachAsset(assetName, { anchorX: 0.5, anchorY: 0.5, width: CELL_SIZE * (zoneSize + 0.5), height: CELL_SIZE * (zoneSize + 0.5), alpha: 0.7 // Ustawiamy stałą przezroczystość }); zoneIndicator.x = grid.x + x * CELL_SIZE + CELL_SIZE * zoneWidth / 2; zoneIndicator.y = grid.y + (y + 5) * CELL_SIZE + CELL_SIZE * zoneHeight / 2; specialTilesLayer.addChild(zoneIndicator); specialZoneIndicators.push(zoneIndicator); // USUNIĘTO CAŁĄ FUNKCJĘ PULSOWANIA } } var tilePlaceholder = buildableTilesLayer.attachAsset('cell', { anchorX: 0.5, anchorY: 0.5 }); tilePlaceholder.alpha = 0.25; tilePlaceholder.x = grid.x + x * CELL_SIZE + CELL_SIZE / 2; tilePlaceholder.y = grid.y + (y + 5) * CELL_SIZE + CELL_SIZE / 2; } else if (tileChar === 'S') { cell.type = 3; } else if (tileChar === 'E') { cell.type = 4; } else { cell.type = 2; } } } } var castleGridWidth = 17; var castleGridHeight = 22; castleGrid = new Grid(castleGridWidth, castleGridHeight); castleGrid.x = (2048 - castleGridWidth * CELL_SIZE) / 2 - 30; castleGrid.y = (2732 - castleGridHeight * CELL_SIZE) / 2 - 100; castleBuildableTilesLayer = new Container(); castleContainer.addChild(castleBuildableTilesLayer); for (var y = 0; y < levelData.castleMapLayout.length; y++) { for (var x = 0; x < levelData.castleMapLayout[y].length; x++) { var tileChar = levelData.castleMapLayout[y][x]; var cell = castleGrid.getCell(x, y); if (cell) { if (tileChar === '1') { cell.type = 1; var tilePlaceholder = castleBuildableTilesLayer.attachAsset('cell', { anchorX: 0.5, anchorY: 0.5 }); tilePlaceholder.alpha = 0.25; tilePlaceholder.x = castleGrid.x + x * CELL_SIZE + CELL_SIZE / 2; tilePlaceholder.y = castleGrid.y + y * CELL_SIZE + CELL_SIZE / 2; } else { cell.type = 2; } } } } } function setupUIAndPanels() { updateUI(); startGameButton = new Container(); var startBuildingBg = startGameButton.attachAsset('button_start_wave', { anchorX: 0.5, anchorY: 0.5 }); startGameButton.x = 2048 / 2; startGameButton.y = 2732 / 2; gateContainer.addChild(startGameButton); startGameButton.interactive = true; startGameButton.down = function () { var weatherSelection = new WeatherSelection(currentLevelNumber, function () { // Ta funkcja wykona się, gdy okno z pogodą zniknie isBuildPhase = true; buildPhaseTimer = 60 * 10; // Nadal dajemy czas na budowę po evencie timerText.visible = true; }); game.addChild(weatherSelection); startGameButton.destroy(); }; castleBuildPanel = new Container(); castleContainer.addChild(castleBuildPanel); castleBuildPanel.x = 2048 / 2; castleBuildPanel.y = 2732 + 100; var castlePanelBackground = castleBuildPanel.attachAsset('panel_popup_background', { anchorX: 0.5, anchorY: 0.5, width: 1800, height: 400 }); mainActionPanel = new Container(); gateContainer.addChild(mainActionPanel); mainActionPanel.x = 2048 / 2; mainActionPanel.y = 2732 + 100; var panelBackground = mainActionPanel.attachAsset('panel_popup_background', { anchorX: 0.5, anchorY: 0.5, width: 1800, height: 400 }); buildPage = new Container(); mainActionPanel.addChild(buildPage); var towerTypes = ['guard', 'crossbow', 'mage', 'banner']; var towerIconSpacing = 280; var totalWidth = (towerTypes.length - 1) * towerIconSpacing; var towerStartX = -totalWidth / 2; for (var i = 0; i < towerTypes.length; i++) { var tower = new SourceTower(towerTypes[i]); tower.x = towerStartX + i * towerIconSpacing; tower.y = 0; buildPage.addChild(tower); sourceTowers.push(tower); } magicPage = new Container(); mainActionPanel.addChild(magicPage); var spellTypes = Object.keys(SPELL_DATA); var spellIconSpacing = 280; var totalSpellsWidth = (spellTypes.length - 1) * spellIconSpacing; var spellStartX = -totalSpellsWidth / 2; for (var i = 0; i < spellTypes.length; i++) { var spell = new SourceSpell(spellTypes[i]); spell.x = spellStartX + i * spellIconSpacing; spell.y = 0; magicPage.addChild(spell); } armyPage = new Container(); mainActionPanel.addChild(armyPage); var unitTypes = Object.keys(UNIT_DATA); var unitIconSpacing = 280; var totalUnitsWidth = (unitTypes.length - 1) * unitIconSpacing; var unitStartX = -totalUnitsWidth / 2; for (var i = 0; i < unitTypes.length; i++) { var unit = new SourceUnit(unitTypes[i]); unit.x = unitStartX + i * unitIconSpacing; unit.y = 0; armyPage.addChild(unit); } showPageInMainPanel('build'); buildButton = new Container(); game.addChild(buildButton); buildButton.interactive = true; buildButton.x = 2048 / 2 - 400; buildButton.y = 2732 - 100; var buildButtonBg = buildButton.attachAsset('button_build', { anchorX: 0.5, anchorY: 0.5 }); buildButton.down = function () { if (currentActiveView === 'gate') { toggleMainPanel('build'); } else { toggleBuildPanel(castleBuildPanel); } }; magicButton = new Container(); game.addChild(magicButton); magicButton.interactive = true; magicButton.x = 2048 / 2; magicButton.y = 2732 - 100; magicButton.visible = false; var magicButtonBg = magicButton.attachAsset('button_magic', { anchorX: 0.5, anchorY: 0.5 }); magicButton.down = function () { if (currentActiveView === 'gate') { toggleMainPanel('magic'); } }; armyButton = new Container(); game.addChild(armyButton); armyButton.interactive = true; armyButton.x = 2048 / 2 + 400; armyButton.y = 2732 - 100; armyButton.visible = false; var armyButtonBg = armyButton.attachAsset('button_army', { anchorX: 0.5, anchorY: 0.5 }); armyButton.down = function () { if (currentActiveView === 'gate') { toggleMainPanel('army'); } }; viewToggleButton = new Container(); game.addChild(viewToggleButton); viewToggleButton.interactive = true; viewToggleButton.x = 250; viewToggleButton.y = 2732 - 100; var viewToggleButtonBg = viewToggleButton.attachAsset('button_castle', { anchorX: 0.5, anchorY: 0.5 }); viewToggleButton.down = function () { if (currentActiveView === 'gate') { currentActiveView = 'castle'; rerollButton.visible = true; gateContainer.visible = false; castleContainer.visible = true; } else { currentActiveView = 'gate'; rerollButton.visible = false; gateContainer.visible = true; castleContainer.visible = false; } }; rerollButton = new Container(); game.addChild(rerollButton); rerollButton.interactive = true; rerollButton.x = 2048 - 250; rerollButton.y = 2732 - 100; rerollButton.visible = false; var rerollButtonBg = rerollButton.attachAsset('button_reroll', { anchorX: 0.5, anchorY: 0.5 }); rerollButton.down = function () { if (currentActiveView === 'castle') { var rerollCost = 100; if (progressionData.upgrades['building_reroll_3']) { var costReduction = 1 - progressionData.upgrades['building_reroll_3'] * 0.20; rerollCost = Math.floor(rerollCost * costReduction); } if (gold >= rerollCost) { setGold(gold - rerollCost); drawNewBuildingHand(); } else { var notification = game.addChild(new Notification("Not enough gold for reroll!")); notification.x = 2048 / 2; notification.y = grid.height - 50; } } }; placementPreview = new PlacementPreview(); game.addChild(placementPreview); placementPreview.visible = false; spellPreview = new SpellPreview(); game.addChild(spellPreview); spellPreview.visible = false; buildingDeck = ['home', 'workshop', 'barracks', 'magic_academy', 'tavern', 'smithy', 'infirmary', 'training_ground', 'magic_sanctum']; buildingHand = []; drawNewBuildingHand(); } function handleUIClick(x, y) { var buttons = [{ btn: buildButton, visible: true }, { btn: magicButton, visible: magicButton.visible }, { btn: armyButton, visible: armyButton.visible }, { btn: viewToggleButton, visible: true }, { btn: rerollButton, visible: rerollButton.visible }]; for (var i = 0; i < buttons.length; i++) { var b = buttons[i]; if (!b.visible) { continue; } // Nowa, niezawodna metoda sprawdzania granic var width = b.btn.hitAreaWidth; var height = b.btn.hitAreaHeight; var left = b.btn.x - width / 2; var right = b.btn.x + width / 2; var top = b.btn.y - height / 2; var bottom = b.btn.y + height / 2; if (x >= left && x <= right && y >= top && y <= bottom) { b.btn.down(); return true; } } return false; } function startGame(levelNumber) { currentLevelNumber = levelNumber; console.log("Rozpoczynam ładowanie poziomu: " + levelNumber); if (titleScreenContainer) { titleScreenContainer.destroy(); titleScreenContainer = null; } if (levelSelectContainer) { levelSelectContainer.destroy(); levelSelectContainer = null; } currentScreenState = 'gameplay'; if (LK.gui.top) { LK.gui.top.visible = true; } levelData = LEVEL_DATA_ALL[levelNumber]; if (!levelData) { console.error("Nie znaleziono danych dla poziomu: " + levelNumber); return; } initializeGameState(levelData); setupScene(levelData); setupGrid(levelData); if (levelData.spawnPoints) { levelData.spawnPoints.forEach(function (spawner) { spawner.lanes.forEach(function (lane) { var pathId = spawner.id + "_" + lane.id; var goalCell = grid.getCell(spawner.goalX, spawner.goalY); // USUNIĘTO LANE.OFFSET if (goalCell && goalCell.type === 0) { grid.calculatePath(pathId, goalCell); } }); }); } setupStatsPanel(); setupUIAndPanels(); } function handlePanelClick(x, y) { var activePanel = null; if (currentActiveView === 'gate' && isMainPanelOpen) { activePanel = mainActionPanel; } else if (currentActiveView === 'castle' && isBuildPanelOpen) { activePanel = castleBuildPanel; } if (!activePanel) { return false; } var itemsContainer = null; if (activePanel === mainActionPanel) { if (buildPage.visible) { itemsContainer = buildPage; } else if (magicPage.visible) { itemsContainer = magicPage; } else if (armyPage.visible) { itemsContainer = armyPage; } } else { itemsContainer = castleBuildPanel; } if (!itemsContainer) { return false; } var localClickPos = { x: x - activePanel.x, y: y - activePanel.y }; for (var i = 0; i < itemsContainer.children.length; i++) { var item = itemsContainer.children[i]; var itemLeft = item.x - item.width / 2; var itemRight = item.x + item.width / 2; var itemTop = item.y - item.height / 2; var itemBottom = item.y + item.height / 2; if (localClickPos.x >= itemLeft && localClickPos.x <= itemRight && localClickPos.y >= itemTop && localClickPos.y <= itemBottom) { console.log("DEBUG: Kliknięto na item w panelu!", item); if (item instanceof SourceUnit) { item.down(); return true; } if (item instanceof SourceTower || item instanceof SourceBuilding) { isDragging = true; placementPreview.visible = true; var activeGrid = currentActiveView === 'gate' ? grid : castleGrid; var itemSubType = item.towerType || item.buildingType; placementPreview.itemType = item.towerType ? 'tower' : 'building'; placementPreview.subType = itemSubType; if (placementPreview.itemType === 'tower') { placementPreview.itemSize = { w: 2, h: 2 }; } else { placementPreview.itemSize = BUILDING_DATA[itemSubType].size; } placementPreview.updateAppearance(); if (currentActiveView === 'gate') { buildableTilesLayer.visible = true; } else { castleBuildableTilesLayer.visible = true; } placementPreview.snapToGrid(x, y, activeGrid); return true; } if (item instanceof SourceSpell) { if (activeWeatherEvent === 'FOG') { var notification = game.addChild(new Notification("Magic is disabled during a fog!")); notification.x = 2048 / 2; notification.y = grid.height - 50; return true; } var spellData = getModifiedSpellData(item.spellId); if (mana >= spellData.cost) { isDraggingSpell = true; spellPreview.spellId = item.spellId; spellPreview.updateAppearance(spellData, true); spellPreview.updatePosition(x, y); showSpellOverlay(); } else { var notification = game.addChild(new Notification("Not enough mana!")); notification.x = 2048 / 2; notification.y = grid.height - 50; } return true; } } } return false; } function handleClickOnMap(x, y) { if (rerollButton.visible) { var width = 300; var height = 120; var left = rerollButton.x - width / 2; var right = rerollButton.x + width / 2; var top = rerollButton.y - height / 2; var bottom = rerollButton.y + height / 2; if (x >= left && x <= right && y >= top && y <= bottom) { return; } } if (isMenuTransitioning) { return; } var upgradeMenuVisible = game.children.some(function (child) { return child instanceof UpgradeMenu || child instanceof BuildingInfoMenu; }); if (upgradeMenuVisible) { return; } var aPanelIsOpen = isBuildPanelOpen || isMagicPanelOpen || isArmyPanelOpen; if (aPanelIsOpen) { var activePanel; if (isBuildPanelOpen) { activePanel = currentActiveView === 'gate' ? buildPanelContainer : castleBuildPanel; } else if (isMagicPanelOpen) { activePanel = magicPanelContainer; } else if (isArmyPanelOpen) { activePanel = armyPanelContainer; } if (activePanel && !activePanel.getBounds().contains(x, y)) { if (isBuildPanelOpen) { toggleBuildPanel(activePanel, 'close'); } if (isMagicPanelOpen) { toggleMagicPanel(magicPanelContainer, 'close'); } if (isArmyPanelOpen) { toggleArmyPanel(armyPanelContainer, 'close'); } return; } } var clickedOnSomething = false; if (currentActiveView === 'gate') { for (var i = 0; i < towers.length; i++) { var tower = towers[i]; var towerBounds = tower.getBounds(); if (towerBounds.contains(x - tower.x + tower.width / 2, y - tower.y + tower.height / 2)) { tower.down(x, y); clickedOnSomething = true; break; } } } else { for (var i = 0; i < castleBuildings.length; i++) { var building = castleBuildings[i]; var buildingWidth = building.size.w * CELL_SIZE; var buildingHeight = building.size.h * CELL_SIZE; if (x > building.x && x < building.x + buildingWidth && y > building.y && y < building.y + buildingHeight) { if (typeof building.down === 'function') { building.down(); } clickedOnSomething = true; break; } } } if (!clickedOnSomething) { closeActiveUpgradeMenu(); game.children.forEach(function (child) { if (child instanceof BuildingInfoMenu) { hideBuildingInfoMenu(child); } }); } } function handleItemDrop(x, y) { if (isDragging) { isDragging = false; placementPreview.visible = false; if (currentActiveView === 'gate') { buildableTilesLayer.visible = false; if (placementPreview.canPlace) { placeTower(placementPreview.gridX, placementPreview.gridY, placementPreview.subType); } } else { castleBuildableTilesLayer.visible = false; if (placementPreview.canPlace) { placeBuilding(placementPreview.subType, placementPreview.gridX, placementPreview.gridY); } } if (isBuildPanelOpen) { var targetPanel = currentActiveView === 'gate' ? buildPanelContainer : castleBuildPanel; toggleBuildPanel(targetPanel, 'close'); } return true; } return false; } function handleSpellCast(x, y) { if (isDraggingSpell) { isDraggingSpell = false; spellPreview.visible = false; spellPreview.children[0].tint = 0x00BFFF; LK.setTimeout(function () { hideSpellOverlay(); }, 1500); var spellData = getModifiedSpellData(spellPreview.spellId); // ZMIANA JEST TUTAJ if (mana >= spellData.cost) { var spellEffect; if (spellPreview.spellId === 'summon_golem') { var gridX = Math.floor((x - grid.x) / CELL_SIZE); var gridY = Math.floor((y - grid.y) / CELL_SIZE); var cell = grid.getCell(gridX, gridY); if (cell && cell.type === 0) { setMana(mana - spellData.cost); var golemX = grid.x + gridX * CELL_SIZE + CELL_SIZE / 2; var golemY = grid.y + gridY * CELL_SIZE + CELL_SIZE / 2; var golemUnit = new Golem(golemX, golemY); golemUnit.visible = false; friendlyUnitLayer.addChild(golemUnit); friendlyUnits.push(golemUnit); var summonEffect = new GolemSummonEffect(golemX, golemY, function () { golemUnit.visible = true; }); spellEffectLayer.addChild(summonEffect); } else { var notification = game.addChild(new Notification("Cannot summon here!")); notification.x = 2048 / 2; notification.y = grid.height - 50; } } else { setMana(mana - spellData.cost); switch (spellPreview.spellId) { case 'fire_wall': spellEffect = new FireWall(x, y, spellData); var visualSize = spellData.radius * 1.5; var visualCenter = new FireWallVisual(x, y - 90, visualSize); var visualBottom = new FireWallVisual(x, y + spellData.radius * 0.8 - 90, visualSize); spellEffectLayer.addChild(visualCenter); spellEffectLayer.addChild(visualBottom); break; case 'lightning_stun': spellEffect = new ThunderstormEffect(x, y, spellData); break; case 'healing_field': spellEffect = new HealingFieldEffect(x, y, spellData); break; case 'curse': spellEffect = new CurseEffect(x, y, spellData); break; } if (spellEffect) { if (spellPreview.spellId === 'lightning_stun') { game.addChild(spellEffect); } else { spellEffectLayer.addChild(spellEffect); } if (spellPreview.spellId === 'fire_wall') { activeSpells.push(spellEffect); } } } } if (isMagicPanelOpen) { toggleMagicPanel(magicPanelContainer, 'close'); } return true; } return false; } game.down = function (x, y, obj) { if (currentScreenState !== 'gameplay') { if (obj && obj.down) { obj.down(); } return; } if (obj && typeof obj.down === 'function') { obj.down(); return; } if (handlePanelClick(x, y)) { return; } handleClickOnMap(x, y); }; game.move = function (x, y, obj) { if (currentScreenState !== 'gameplay') { return; } if (isDragging) { var activeGrid = currentActiveView === 'gate' ? grid : castleGrid; var yOffset = currentActiveView === 'gate' ? CELL_SIZE * 1.5 : 0; placementPreview.snapToGrid(x, y - yOffset, activeGrid); } else if (isDraggingSpell) { spellPreview.updatePosition(x, y); if (spellPreview.spellId === 'summon_golem') { var gridX = Math.floor((x - grid.x) / CELL_SIZE); var gridY = Math.floor((y - grid.y) / CELL_SIZE); var cell = grid.getCell(gridX, gridY); var canPlace = cell && cell.type === 0; var activeGraphic = spellPreview.getActiveGraphic(); if (activeGraphic) { activeGraphic.tint = canPlace ? 0x00BFFF : 0xFF0000; } } } }; game.up = function (x, y, obj) { if (currentScreenState !== 'gameplay') { return; } if (panelActionTaken) { panelActionTaken = false; return; } if (handleItemDrop(x, y)) { return; } if (handleSpellCast(x, y)) { return; } if (isDraggingSpell) { isDraggingSpell = false; spellPreview.visible = false; hideSpellOverlay(); } var clickedOnMenu = false; var upgradeMenus = game.children.filter(function (child) { return child instanceof UpgradeMenu; }); if (upgradeMenus.length > 0) { for (var i = 0; i < upgradeMenus.length; i++) { var menu = upgradeMenus[i]; var menuBounds = menu.getBounds(); if (menuBounds.contains(x, y)) { clickedOnMenu = true; break; } } } if (!clickedOnMenu && selectedTower) { closeActiveUpgradeMenu(); } }; function updateGameLogic() { if (isBuildPhase) { if (buildPhaseTimer > 0) { buildPhaseTimer--; timerText.setText("Build Time: " + Math.ceil(buildPhaseTimer / 60)); } else { isBuildPhase = false; timerText.visible = false; waveInProgress = true; waveSpawned = false; } } else if (waveInProgress) { if (!waveSpawned) { spawnQueue = []; waveClock = 0; enemiesKilled = 0; var waveData = levelData.waves[currentWave - 1]; if (waveData && waveData.phases) { var allEnemiesInWave = []; var currentTimeOffset = 0; // NOWA LOGIKA DLA MAPY 3, FALE 1-2 if (levelData.levelName === 'Level 3: Twin Pass' && currentWave < 3) { waveData.phases.forEach(function (phase) { // W logice sekwencyjnej ignorujemy delay fazy, bo czas jest liczony ciągle phase.units.forEach(function (unitGroup) { var spawner = levelData.spawnPoints.find(function (sp) { return sp.id === unitGroup.spawner; }); if (!spawner) { return; } // Używamy tylko jednego pasa (pierwszego z brzegu) dla pojedynczego szeregu var laneToUse = spawner.lanes[0]; for (var i = 0; i < unitGroup.count; i++) { var spawnTime = currentTimeOffset + i * (phase.interval || 120); spawnQueue.push({ unitData: { spawner: unitGroup.spawner, type: unitGroup.type, laneId: laneToUse.id }, spawnTime: spawnTime }); allEnemiesInWave.push(unitGroup.type); } // Przesuwamy czas startu następnej grupy o czas trwania obecnej currentTimeOffset += unitGroup.count * (phase.interval || 120) + 60; // +1s przerwy }); }); } else { // STARA/STANDARDOWA LOGIKA DLA INNYCH MAP I FALI 3+ waveData.phases.forEach(function (phase) { currentTimeOffset += phase.delay || 0; var timeInThisPhase = 0; phase.units.forEach(function (unitGroup) { var spawner = levelData.spawnPoints.find(function (sp) { return sp.id === unitGroup.spawner; }); if (!spawner) { return; } var lanesToUse = spawner.lanes; // Dla fali 3+ na mapie z wieloma pasami, spawnuj w parach/grupach if (currentWave >= 3 && lanesToUse.length > 1) { for (var i = 0; i < unitGroup.count; i += lanesToUse.length) { var pairIndex = i / lanesToUse.length; var spawnTimeForPair = currentTimeOffset + pairIndex * (phase.interval || 120); for (var j = 0; j < lanesToUse.length; j++) { if (i + j < unitGroup.count) { spawnQueue.push({ unitData: { spawner: unitGroup.spawner, type: unitGroup.type, laneId: lanesToUse[j].id }, spawnTime: spawnTimeForPair }); allEnemiesInWave.push(unitGroup.type); } } } timeInThisPhase += Math.ceil(unitGroup.count / lanesToUse.length) * (phase.interval || 120); } else { // Standardowe spawnowanie w pojedynczym szeregu (ale na wielu pasach, jeśli są) for (var i = 0; i < unitGroup.count; i++) { var spawnTime = currentTimeOffset + i * (phase.interval || 60); var lane = lanesToUse[i % lanesToUse.length]; spawnQueue.push({ unitData: { spawner: unitGroup.spawner, type: unitGroup.type, laneId: lane.id }, spawnTime: spawnTime }); allEnemiesInWave.push(unitGroup.type); } timeInThisPhase += unitGroup.count * (phase.interval || 60); } }); currentTimeOffset += timeInThisPhase; }); } enemiesInCurrentWave = allEnemiesInWave.length; updateUI(); } waveSpawned = true; } waveClock++; if (spawnQueue.length > 0 && waveClock >= spawnQueue[0].spawnTime) { var spawnData = spawnQueue.shift(); var spawner = levelData.spawnPoints.find(function (sp) { return sp.id === spawnData.unitData.spawner; }); var lane = spawner.lanes.find(function (l) { return l.id === spawnData.unitData.laneId; }); if (spawner && lane) { var enemy = new Enemy(spawnData.unitData.type, spawner.id, lane.id); enemy.laneOffset = lane.offset; enemy.cellX = spawner.startX; enemy.cellY = spawner.startY; enemy.x = grid.x + (enemy.cellX + enemy.laneOffset) * CELL_SIZE + CELL_SIZE / 2; enemy.y = grid.y + enemy.cellY * CELL_SIZE + CELL_SIZE / 2; enemy.waveNumber = currentWave; var healthMultiplier = Math.pow(1.15, currentWave); enemy.maxHealth = Math.round(enemy.maxHealth * healthMultiplier); if (activeWeatherEvent === 'NIGHT') { var nightMultiplier = EVENT_DATA.NIGHT.modifier.enemyStatMultiplier; enemy.maxHealth = Math.round(enemy.maxHealth * nightMultiplier); enemy.damage = Math.round(enemy.damage * nightMultiplier); } enemy.health = enemy.maxHealth; enemy.healthBar.width = enemy.health / enemy.maxHealth * 70; enemyLayerBottom.addChild(enemy); enemies.push(enemy); } } if (spawnQueue.length === 0 && enemies.length === 0) { waveInProgress = false; clearActiveWeatherModifiers(); activeWeatherEvent = null; // Resetujemy pogodę if (numberOfTaverns > 0) { // ... logika tawern (zostaje bez zmian) } currentWave++; if (currentWave > totalWaves) { LK.showYouWin(); } else { // Wywołujemy nasze nowe okno wyboru pogody! var weatherSelection = new WeatherSelection(currentLevelNumber, function () { // Ta funkcja wykona się, gdy okno z pogodą zniknie isBuildPhase = true; buildPhaseTimer = 60 * 10; }); game.addChild(weatherSelection); // Ustawiamy opóźnienie przed rozpoczęciem animacji LK.setTimeout(function () { // Zabezpieczenie na wypadek, gdyby okno zostało zniszczone w międzyczasie if (weatherSelection && !weatherSelection.destroyed) { weatherSelection.startSelection(); } }, 2000); // Opóźnienie 1000ms = 1 sekunda } } } } function updateGameObjects() { if (friendlyUnits) { for (var i = 0; i < friendlyUnits.length; i++) { friendlyUnits[i].update(); } } if (activeSpells) { for (var i = activeSpells.length - 1; i >= 0; i--) { if (activeSpells[i]) { activeSpells[i].update(); } } } if (peopleRegenQueue) { for (var i = peopleRegenQueue.length - 1; i >= 0; i--) { var regenTime = getPeopleRegenTime(); if (LK.ticks - peopleRegenQueue[i] >= regenTime) { if (people < maxPeople) { people++; updateUI(); } peopleRegenQueue.splice(i, 1); } } } for (var i = 0; i < towers.length; i++) { towers[i].update(); } for (var i = bullets.length - 1; i >= 0; i--) { var bullet = bullets[i]; if (!bullet.parent) { bullets.splice(i, 1); } else { bullet.update(); } } // NOWA SEKSCJA - Aktualizacja NPC if (currentActiveView === 'castle' && npcs) { for (var i = npcs.length - 1; i >= 0; i--) { var npc = npcs[i]; if (!npc.parent) { npcs.splice(i, 1); } else { npc.update(); } } } for (var a = enemies.length - 1; a >= 0; a--) { var enemy = enemies[a]; if (enemy.health <= 0) { enemiesKilled++; updateUI(); var goldEarned = enemy.goldValue || 0; setGold(gold + goldEarned); if (isManaUnlocked) { var manaEarned = 0; switch (enemy.manaCategory) { case 'weak': manaEarned = 1; break; case 'strong': manaEarned = 2; break; case 'special': manaEarned = 5; break; case 'boss': manaEarned = 25; break; } setMana(mana + manaEarned); } gateContainer.addChild(new GoldIndicator(goldEarned, enemy.x, enemy.y)); if (enemy.parent) { enemy.parent.removeChild(enemy); } if (enemy.shadow && enemy.shadow.parent) { enemy.shadow.parent.removeChild(enemy.shadow); } enemies.splice(a, 1); continue; } if (enemy.update()) { if (enemy.parent) { enemy.parent.removeChild(enemy); } if (enemy.shadow && enemy.shadow.parent) { enemy.shadow.parent.removeChild(enemy.shadow); } enemies.splice(a, 1); lives = Math.max(0, lives - 1); updateUI(); if (lives <= 0) { LK.showGameOver(); } } } towerLayer.children.sort(function (a, b) { return a.y - b.y; }); friendlyUnitLayer.children.sort(function (a, b) { return a.y - b.y; }); enemyLayerBottom.children.sort(function (a, b) { return a.y - b.y; }); } function updatePreviews() { if (placementPreview.visible) { var activeGrid = currentActiveView === 'gate' ? grid : castleGrid; placementPreview.updatePlacementStatus(activeGrid); } } game.update = function () { if (currentScreenState !== 'gameplay') { return; } updateGameLogic(); updateGameObjects(); updatePreviews(); if (specialZoneIndicators) { for (var i = 0; i < specialZoneIndicators.length; i++) { specialZoneIndicators[i].rotation += 0.001; } } }; showTitleScreen();
===================================================================
--- original.js
+++ change.js
@@ -855,57 +855,59 @@
}
};
return self;
});
-var FogWisp = Container.expand(function (startX, startY, direction) {
+var FogWisp = Container.expand(function () {
var self = Container.call(this);
- self.x = startX;
- self.y = startY;
- self.speed = 0.2 + Math.random() * 0.2;
- self.direction = direction === 'left' ? -1 : 1;
+ var lifetime = 240 + Math.random() * 60; // Ok. 4-5 sekund życia
+ var isFadingOut = false;
+ // Losowa pozycja startowa na ekranie
+ self.x = Math.random() * 2048;
+ self.y = Math.random() * 2732;
+ // Losowy kierunek i prędkość
+ var angle = Math.random() * Math.PI * 2;
+ var speed = 0.3 + Math.random() * 0.3;
+ var dx = Math.cos(angle) * speed;
+ var dy = Math.sin(angle) * speed;
+ // Losowa grafika mgły
var fogAssetNames = ['fog_1', 'fog_2', 'fog_3', 'fog_4'];
- var fogSprites = [];
- var currentSpriteIndex = 0;
- var changeTicker = 0;
- var changeInterval = 240; // Co ile klatek (4 sekundy) ma nastąpić zmiana grafiki
- for (var i = 0; i < fogAssetNames.length; i++) {
- var sprite = self.attachAsset(fogAssetNames[i], {
- anchorX: 0.5,
- anchorY: 0.5
- });
- sprite.alpha = i === 0 ? 0.6 : 0; // Widoczny tylko pierwszy
- sprite.scale.set(1.2 + Math.random() * 0.8);
- fogSprites.push(sprite);
- }
- function changeGraphic() {
- var oldSprite = fogSprites[currentSpriteIndex];
- currentSpriteIndex = (currentSpriteIndex + 1) % fogSprites.length;
- var newSprite = fogSprites[currentSpriteIndex];
- tween(oldSprite, {
- alpha: 0
- }, {
- duration: 1500
- });
- tween(newSprite, {
- alpha: 0.6
- }, {
- duration: 1500,
- delay: 500
- });
- }
+ var randomAssetName = fogAssetNames[Math.floor(Math.random() * fogAssetNames.length)];
+ var sprite = self.attachAsset(randomAssetName, {
+ anchorX: 0.5,
+ anchorY: 0.5
+ });
+ sprite.scale.set(1.5 + Math.random() * 1.0);
+ // Fade-in na starcie
+ self.alpha = 0;
+ tween(self, {
+ alpha: 0.5 + Math.random() * 0.2
+ }, {
+ duration: 1000
+ });
self.update = function () {
+ if (isFadingOut) {
+ return;
+ }
// Ruch
- self.x += self.speed * self.direction;
- if (self.x < -100 || self.x > 2148) {
- // Gdy wyleci za ekran, wróć na drugą stronę
- self.x = self.direction > 0 ? -100 : 2148;
+ self.x += dx;
+ self.y += dy;
+ // Cykl życia
+ lifetime--;
+ if (lifetime <= 0) {
+ isFadingOut = true;
+ tween(self, {
+ alpha: 0
+ }, {
+ duration: 1000,
+ onFinish: function onFinish() {
+ var index = activeFogWisps.indexOf(self);
+ if (index !== -1) {
+ activeFogWisps.splice(index, 1);
+ }
+ self.destroy();
+ }
+ });
}
- // Zmiana grafiki
- changeTicker++;
- if (changeTicker >= changeInterval) {
- changeTicker = 0;
- changeGraphic();
- }
};
return self;
});
var FriendlyUnit = Container.expand(function (startX, startY, unitStats) {
@@ -2206,42 +2208,67 @@
}
if (LK.ticks - lastRecruitActionTick < 10) {
return;
}
- if (gold >= unitData.cost && people > 0) {
+ var finalStats = JSON.parse(JSON.stringify(unitData));
+ // Bonusy z drzewka progresji dla jednostek
+ if (progressionData.upgrades['army_cost_1']) {
+ finalStats.cost = Math.floor(finalStats.cost * (1 - progressionData.upgrades['army_cost_1'] * 0.02));
+ }
+ var hpUpgradeKey = 'army_' + self.unitId + '_hp';
+ if (progressionData.upgrades[hpUpgradeKey]) {
+ finalStats.maxHealth = Math.round(finalStats.maxHealth * (1 + progressionData.upgrades[hpUpgradeKey] * 0.05));
+ }
+ var statsUpgradeKey = 'army_' + self.unitId + '_stats';
+ if (progressionData.upgrades[statsUpgradeKey]) {
+ var bonus = 1 + progressionData.upgrades[statsUpgradeKey] * 0.03;
+ finalStats.maxHealth = Math.round(finalStats.maxHealth * bonus);
+ finalStats.damage = Math.round(finalStats.damage * bonus);
+ }
+ var dmgUpgradeKey = 'army_' + self.unitId + '_dmg';
+ if (progressionData.upgrades[dmgUpgradeKey]) {
+ finalStats.damage = Math.round(finalStats.damage * (1 + progressionData.upgrades[dmgUpgradeKey] * 0.05));
+ }
+ // Sprawdzenie, czy stać gracza po modyfikacjach kosztu
+ if (gold >= finalStats.cost && people > 0) {
lastRecruitActionTick = LK.ticks;
- var finalStats = Object.assign({}, unitData);
- // Bonus z Training Ground
+ var freeUnitChance = (progressionData.upgrades['army_free_unit_2'] || 0) * 0.05;
+ var freeUnitSpawned = Math.random() < freeUnitChance;
+ setGold(gold - finalStats.cost);
+ people--;
+ updateUI();
+ panelActionTaken = true;
+ // Bonusy z budynków
+ var infraBonus = 1 + (progressionData.upgrades['building_infrastructure_2'] || 0) * 0.05;
var trainingGroundsCount = castleBuildings.filter(function (b) {
return b.id === 'training_ground';
}).length;
if (trainingGroundsCount > 0) {
- var unitBonus = Math.pow(1.05, trainingGroundsCount);
+ var unitBonus = Math.pow(1.05, trainingGroundsCount) * infraBonus;
finalStats.maxHealth = Math.round(finalStats.maxHealth * unitBonus);
finalStats.damage = Math.round(finalStats.damage * unitBonus);
}
- // Bonus ze Smithy (jeśli istnieje)
var hasSmithy = castleBuildings.some(function (b) {
return b.id === 'smithy';
});
if (hasSmithy) {
- finalStats.maxHealth = Math.floor(finalStats.maxHealth * 1.15);
- finalStats.damage = Math.floor(finalStats.damage * 1.15);
+ var smithyBonus = 1.15 * infraBonus;
+ finalStats.maxHealth = Math.floor(finalStats.maxHealth * smithyBonus);
+ finalStats.damage = Math.floor(finalStats.damage * smithyBonus);
}
- setGold(gold - finalStats.cost);
- people--;
- updateUI();
- panelActionTaken = true;
- var spawnLaneOffset = 80;
- var spawnLanes = [2048 / 2 - spawnLaneOffset, 2048 / 2, 2048 / 2 + spawnLaneOffset];
- var spawnX = spawnLanes[Math.floor(Math.random() * spawnLanes.length)];
- spawnX += Math.random() * 40 - 20;
- var spawnY = grid.y + (grid.cells[0].length - 3) * CELL_SIZE;
- var newUnit = new FriendlyUnit(spawnX, spawnY, finalStats);
- friendlyUnitLayer.addChild(newUnit);
- friendlyUnits.push(newUnit);
+ var unitsToSpawn = freeUnitSpawned ? 2 : 1;
+ for (var i = 0; i < unitsToSpawn; i++) {
+ var spawnLaneOffset = 80;
+ var spawnLanes = [2048 / 2 - spawnLaneOffset, 2048 / 2, 2048 / 2 + spawnLaneOffset];
+ var spawnX = spawnLanes[Math.floor(Math.random() * spawnLanes.length)];
+ spawnX += Math.random() * 40 - 20;
+ var spawnY = grid.y + (grid.cells[0].length - 3) * CELL_SIZE;
+ var newUnit = new FriendlyUnit(spawnX, spawnY, finalStats);
+ friendlyUnitLayer.addChild(newUnit);
+ friendlyUnits.push(newUnit);
+ }
} else {
- var message = gold < unitData.cost ? "Not enough gold!" : "Not enough people!";
+ var message = gold < finalStats.cost ? "Not enough gold!" : "Not enough people!";
var notification = game.addChild(new Notification(message));
notification.x = 2048 / 2;
notification.y = grid.height - 50;
}
@@ -2339,8 +2366,314 @@
self.y = y;
};
return self;
});
+var TalentTreeScreen = Container.expand(function () {
+ var self = Container.call(this);
+ self.visible = false;
+ var currentCategory = null;
+ var skillNodes = {};
+ var lineContainer = new Container();
+ self.addChild(lineContainer);
+ var backgrounds = {
+ 'Towers': self.attachAsset('bg_towers', {
+ visible: false
+ }),
+ 'Army': self.attachAsset('bg_army', {
+ visible: false
+ }),
+ 'Building': self.attachAsset('bg_building', {
+ visible: false
+ }),
+ 'Magic': self.attachAsset('bg_magic', {
+ visible: false
+ })
+ };
+ self.addChildAt(lineContainer, 1);
+ var pointsText = new Text2('Points: 0', {
+ size: 70,
+ fill: 0xFFD700,
+ weight: 800
+ });
+ pointsText.anchor.set(0.5, 0.5);
+ pointsText.x = 2048 / 2;
+ pointsText.y = 250;
+ self.addChild(pointsText);
+ var backButton = new Container();
+ backButton.x = 200;
+ backButton.y = 100;
+ var backButtonBg = backButton.attachAsset('upgrade_menu_button', {
+ anchorX: 0.5,
+ anchorY: 0.5,
+ tint: 0xcc0000
+ });
+ var backButtonText = new Text2('Back', {
+ size: 60,
+ fill: 0xffffff
+ });
+ backButtonText.anchor.set(0.5, 0.5);
+ backButton.addChild(backButtonText);
+ self.addChild(backButton);
+ backButton.down = function () {
+ self.hide();
+ };
+ var descriptionPanel;
+ var skillNameText, skillDescriptionText, skillStatsText, upgradeButtonInPanel;
+ var currentlySelectedSkillId = null;
+ function createDescriptionPanel() {
+ descriptionPanel = new Container();
+ descriptionPanel.x = 2048 / 2;
+ descriptionPanel.y = 2732 - 350;
+ descriptionPanel.visible = false;
+ self.addChild(descriptionPanel);
+ var panelBg = descriptionPanel.attachAsset('skill_description_panel', {
+ anchorX: 0.5,
+ anchorY: 0.5
+ });
+ panelBg.alpha = 0.8;
+ skillNameText = new Text2("Skill Name", {
+ size: 60,
+ fill: 0xFFD700,
+ weight: 700
+ });
+ skillNameText.anchor.set(0.5, 0);
+ skillNameText.y = -250;
+ descriptionPanel.addChild(skillNameText);
+ skillDescriptionText = new Text2("Description text goes here.", {
+ size: 40,
+ fill: 0xffffff,
+ wordWrap: true,
+ wordWrapWidth: 1600,
+ align: 'center'
+ });
+ skillDescriptionText.anchor.set(0.5, 0);
+ skillDescriptionText.y = -150;
+ descriptionPanel.addChild(skillDescriptionText);
+ skillStatsText = new Text2("Bonus: 10% -> 15%", {
+ size: 45,
+ fill: 0x00ff00
+ });
+ skillStatsText.anchor.set(0.5, 0);
+ skillStatsText.y = -50;
+ descriptionPanel.addChild(skillStatsText);
+ upgradeButtonInPanel = new Container();
+ upgradeButtonInPanel.y = 150;
+ var upgradeBtnBg = upgradeButtonInPanel.attachAsset('upgrade_menu_button', {
+ anchorX: 0.5,
+ anchorY: 0.5,
+ width: 500
+ });
+ var upgradeBtnText = new Text2("Upgrade (1 Pkt)", {
+ size: 50,
+ fill: 0xffffff,
+ weight: 800
+ });
+ upgradeBtnText.anchor.set(0.5, 0.5);
+ upgradeButtonInPanel.addChild(upgradeBtnText);
+ upgradeButtonInPanel.text = upgradeBtnText;
+ descriptionPanel.addChild(upgradeButtonInPanel);
+ upgradeButtonInPanel.down = function () {
+ if (currentlySelectedSkillId) {
+ upgradeSkill(currentlySelectedSkillId);
+ }
+ };
+ }
+ // ##################################################################
+ // ### GŁÓWNA POPRAWKA BŁĘDU JEST W TEJ FUNKCJI ###
+ // ##################################################################
+ function renderSkills(category) {
+ // Krok 1: Wyczyszczenie sceny
+ for (var key in skillNodes) {
+ if (skillNodes[key]) skillNodes[key].destroy();
+ }
+ skillNodes = {};
+ lineContainer.removeChildren();
+ var skills = talentTreeConfig[category];
+ if (!skills) return;
+ // Krok 2: Stworzenie WSZYSTKICH ikon umiejętności dla danej kategorii
+ for (var i = 0; i < skills.length; i++) {
+ var skillData = skills[i];
+ var node = new Container();
+ node.x = skillData.position.x;
+ node.y = skillData.position.y;
+ self.addChild(node);
+ var icon = node.attachAsset('skill_icon', {
+ anchorX: 0.5,
+ anchorY: 0.5,
+ width: 150,
+ height: 150
+ });
+ var name = new Text2(skillData.name, {
+ size: 28,
+ fill: 0xffffff,
+ align: 'center',
+ wordWrap: true,
+ wordWrapWidth: 160
+ });
+ name.anchor.set(0.5, 0);
+ name.y = 85;
+ node.addChild(name);
+ var levelText = new Text2('0 / ' + skillData.maxLevels, {
+ size: 32,
+ fill: 0xffff00,
+ weight: 700
+ });
+ levelText.anchor.set(0.5, 1);
+ levelText.y = -85;
+ node.addChild(levelText);
+ node.levelText = levelText;
+ node.interactive = true;
+ (function (data) {
+ node.down = function () {
+ showDescriptionPanel(data);
+ };
+ })(skillData);
+ // Zapisanie referencji do węzła UI
+ skillNodes[skillData.id] = node;
+ }
+ // Krok 3: Stworzenie WSZYSTKICH linii w osobnej pętli, gdy wszystkie węzły już istnieją
+ for (var i = 0; i < skills.length; i++) {
+ var skillData = skills[i];
+ if (skillData.dependencies && skillData.dependencies.length > 0) {
+ for (var j = 0; j < skillData.dependencies.length; j++) {
+ var parentId = skillData.dependencies[j];
+ var childNode = skillNodes[skillData.id];
+ var parentNode = skillNodes[parentId];
+ if (childNode && parentNode) {
+ var dx = childNode.x - parentNode.x;
+ var dy = childNode.y - parentNode.y;
+ var distance = Math.sqrt(dx * dx + dy * dy);
+ var angle = Math.atan2(dy, dx);
+ var line = lineContainer.attachAsset('skill_line', {
+ anchorX: 0,
+ anchorY: 0.5
+ });
+ line.x = parentNode.x;
+ line.y = parentNode.y;
+ line.width = distance;
+ line.rotation = angle;
+ }
+ }
+ }
+ }
+ // Krok 4: Aktualizacja wyglądu wszystkiego
+ updateAllSkillVisuals();
+ }
+ function showDescriptionPanel(skillData) {
+ currentlySelectedSkillId = skillData.id;
+ descriptionPanel.visible = true;
+ skillNameText.setText(skillData.name);
+ skillDescriptionText.setText(skillData.description);
+ updateDescriptionPanel();
+ }
+ function updateDescriptionPanel() {
+ if (!currentlySelectedSkillId || !descriptionPanel.visible) return;
+ var skillData = talentTreeConfig[currentCategory].find(function (s) {
+ return s.id === currentlySelectedSkillId;
+ });
+ var currentLevel = progressionData.upgrades[currentlySelectedSkillId] || 0;
+ skillStatsText.setText("Level: " + currentLevel + " / " + skillData.maxLevels);
+ var isMaxed = currentLevel >= skillData.maxLevels;
+ upgradeButtonInPanel.visible = !isMaxed;
+ if (!isMaxed) {
+ var canAfford = progressionData.points >= skillData.costPerLevel;
+ upgradeButtonInPanel.alpha = canAfford ? 1.0 : 0.5;
+ upgradeButtonInPanel.text.setText("Upgrade (" + skillData.costPerLevel + " Points)");
+ }
+ }
+ function updateSkillVisuals(node, skillId) {
+ var skillData = talentTreeConfig[currentCategory].find(function (s) {
+ return s.id === skillId;
+ });
+ var currentLevel = progressionData.upgrades[skillId] || 0;
+ node.levelText.setText(currentLevel + ' / ' + skillData.maxLevels);
+ var areDepsMet = true;
+ if (skillData.dependencies && skillData.dependencies.length > 0) {
+ areDepsMet = skillData.dependencies.every(function (depId) {
+ return (progressionData.upgrades[depId] || 0) > 0;
+ });
+ }
+ node.alpha = areDepsMet ? 1.0 : 0.6;
+ var icon = node.children[0];
+ if (currentLevel >= skillData.maxLevels) {
+ icon.tint = 0x00ff00;
+ } else if (currentLevel > 0) {
+ icon.tint = 0x00BFFF;
+ } else {
+ icon.tint = 0x888888;
+ }
+ }
+ function upgradeSkill(skillId) {
+ var skillData = talentTreeConfig[currentCategory].find(function (s) {
+ return s.id === skillId;
+ });
+ var currentLevel = progressionData.upgrades[skillId] || 0;
+ var areDepsMet = true;
+ if (skillData.dependencies && skillData.dependencies.length > 0) {
+ areDepsMet = skillData.dependencies.every(function (depId) {
+ return (progressionData.upgrades[depId] || 0) > 0;
+ });
+ }
+ if (areDepsMet && progressionData.points >= skillData.costPerLevel && currentLevel < skillData.maxLevels) {
+ progressionData.points -= skillData.costPerLevel;
+ progressionData.upgrades[skillId] = (progressionData.upgrades[skillId] || 0) + 1;
+ pointsText.setText('Points: ' + progressionData.points);
+ updateAllSkillVisuals();
+ updateDescriptionPanel();
+ saveProgression();
+ }
+ }
+ function updateAllSkillVisuals() {
+ for (var skillId in skillNodes) {
+ updateSkillVisuals(skillNodes[skillId], skillId);
+ }
+ }
+ function showCategory(category) {
+ currentCategory = category;
+ if (descriptionPanel) descriptionPanel.visible = false;
+ for (var key in backgrounds) {
+ backgrounds[key].visible = key === category;
+ }
+ renderSkills(category);
+ }
+ var categories = Object.keys(talentTreeConfig);
+ var tabSpacing = 320;
+ var startX = (2048 - (categories.length - 1) * tabSpacing) / 2;
+ categories.forEach(function (categoryName, index) {
+ var tab = new Container();
+ tab.x = startX + index * tabSpacing;
+ tab.y = 100;
+ var tabBg = tab.attachAsset('tab_button_bg', {
+ anchorX: 0.5,
+ anchorY: 0.5
+ });
+ var tabText = new Text2(categoryName, {
+ size: 40,
+ fill: 0xffffff
+ });
+ tabText.anchor.set(0.5, 0.5);
+ tab.addChild(tabText);
+ self.addChild(tab);
+ tab.down = function () {
+ showCategory(categoryName);
+ };
+ });
+ createDescriptionPanel();
+ self.show = function () {
+ self.visible = true;
+ loadProgression();
+ pointsText.setText('Points: ' + progressionData.points);
+ showCategory(categories[0]);
+ };
+ self.hide = function () {
+ self.visible = false;
+ if (descriptionPanel) descriptionPanel.visible = false;
+ if (levelSelectContainer) {
+ levelSelectContainer.visible = true;
+ }
+ };
+ return self;
+});
var ThunderstormEffect = Container.expand(function (x, y, spellData) {
var self = Container.call(this);
self.x = x;
self.y = y;
@@ -3201,8 +3534,423 @@
* Game Code
****/
/**** * Game Code
****/
+var progressionData = {
+ points: 0,
+ upgrades: {}
+};
+function saveProgression() {
+ // Ta funkcja w przyszłości użyje pluginu LK.storage.set
+ console.log("Zapisano progresję:", JSON.stringify(progressionData));
+}
+function loadProgression() {
+ // Ta funkcja w przyszłości użyje pluginu LK.storage.get
+ console.log("Wczytano progresję.");
+ // Na razie nie mamy zapisu, więc funkcja jest pusta, ale musi istnieć.
+}
+var talentTreeConfig = {
+ 'Towers': [{
+ id: 'towers_cost_1',
+ name: 'Efficient Builders',
+ description: 'Reduces the build cost of all towers by 2%',
+ maxLevels: 5,
+ costPerLevel: 1,
+ position: {
+ x: 1024,
+ y: 400
+ },
+ dependencies: []
+ }, {
+ id: 'towers_guard_dmg_2',
+ name: 'Reinforced Arrows',
+ description: 'Increases Guard Tower damage by 5%',
+ maxLevels: 3,
+ costPerLevel: 1,
+ position: {
+ x: 624,
+ y: 650
+ },
+ dependencies: ['towers_cost_1']
+ }, {
+ id: 'towers_crossbow_range_2',
+ name: 'Eagle Eye',
+ description: 'Increases Crossbow Tower range by 5%',
+ maxLevels: 3,
+ costPerLevel: 1,
+ position: {
+ x: 1424,
+ y: 650
+ },
+ dependencies: ['towers_cost_1']
+ }, {
+ id: 'towers_guard_multishot_3',
+ name: 'Hail of Arrows',
+ description: 'Guard Tower gains a 10% chance to fire an additional arrow',
+ maxLevels: 3,
+ costPerLevel: 1,
+ position: {
+ x: 424,
+ y: 900
+ },
+ dependencies: ['towers_guard_dmg_2']
+ }, {
+ id: 'towers_crossbow_poison_3',
+ name: 'Poisoned Bolts',
+ description: 'Crossbow Tower attacks have a 10% chance to poison the target',
+ maxLevels: 3,
+ costPerLevel: 1,
+ position: {
+ x: 1624,
+ y: 900
+ },
+ dependencies: ['towers_crossbow_range_2']
+ }, {
+ id: 'towers_guard_as_4',
+ name: 'Berserker\'s Fury',
+ description: 'Increases Guard Tower attack speed by 30%',
+ maxLevels: 1,
+ costPerLevel: 3,
+ position: {
+ x: 424,
+ y: 1150
+ },
+ dependencies: ['towers_guard_multishot_3']
+ }, {
+ id: 'towers_crossbow_crit_4',
+ name: 'Heartseeker Shot',
+ description: 'Crossbow Tower gains a 20% chance to deal 200% damage',
+ maxLevels: 1,
+ costPerLevel: 3,
+ position: {
+ x: 1624,
+ y: 1150
+ },
+ dependencies: ['towers_crossbow_poison_3']
+ }],
+ 'Army': [{
+ id: 'army_cost_1',
+ name: 'Barracks Management',
+ description: 'Reduces the recruitment cost of all units by 2%',
+ maxLevels: 5,
+ costPerLevel: 1,
+ position: {
+ x: 1024,
+ y: 400
+ },
+ dependencies: []
+ }, {
+ id: 'army_swordsman_hp_2',
+ name: 'Infantry Vigor',
+ description: 'Increases Swordsman HP by 5%',
+ maxLevels: 4,
+ costPerLevel: 1,
+ position: {
+ x: 450,
+ y: 650
+ },
+ dependencies: ['army_cost_1']
+ }, {
+ id: 'army_horseman_stats_2',
+ name: 'Cavalry Conditioning',
+ description: 'Increases Horseman HP and Damage by 3%',
+ maxLevels: 4,
+ costPerLevel: 1,
+ position: {
+ x: 1024,
+ y: 650
+ },
+ dependencies: ['army_cost_1']
+ }, {
+ id: 'army_free_unit_2',
+ name: 'Surprise Draft',
+ description: 'Grants a 5% chance to get a free, additional unit on recruitment',
+ maxLevels: 3,
+ costPerLevel: 2,
+ position: {
+ x: 1600,
+ y: 650
+ },
+ dependencies: ['army_cost_1']
+ }, {
+ id: 'army_defender_hp_3',
+ name: 'Steel Shields',
+ description: 'Increases Defender HP by 5%',
+ maxLevels: 3,
+ costPerLevel: 1,
+ position: {
+ x: 450,
+ y: 900
+ },
+ dependencies: ['army_swordsman_hp_2']
+ }, {
+ id: 'army_archer_dmg_3',
+ name: 'Precise Shots',
+ description: 'Increases Archer Damage by 5%',
+ maxLevels: 3,
+ costPerLevel: 1,
+ position: {
+ x: 1024,
+ y: 900
+ },
+ dependencies: ['army_horseman_stats_2']
+ }, {
+ id: 'army_swordsman_ultimate_4',
+ name: 'Undying Warrior',
+ description: 'Swordsman deals +30% damage and fights for 2s after receiving a fatal blow',
+ maxLevels: 1,
+ costPerLevel: 3,
+ position: {
+ x: 300,
+ y: 1150
+ },
+ dependencies: ['army_defender_hp_3']
+ }, {
+ id: 'army_defender_ultimate_4',
+ name: 'Stunning Blow',
+ description: 'Defender attacks have a 20% chance to stun the target for 2s',
+ maxLevels: 1,
+ costPerLevel: 3,
+ position: {
+ x: 600,
+ y: 1150
+ },
+ dependencies: ['army_defender_hp_3']
+ }, {
+ id: 'army_archer_ultimate_4',
+ name: 'Salvo',
+ description: 'Archers always fire at two targets simultaneously',
+ maxLevels: 1,
+ costPerLevel: 3,
+ position: {
+ x: 1024,
+ y: 1150
+ },
+ dependencies: ['army_archer_dmg_3']
+ }, {
+ id: 'army_horseman_ultimate_4',
+ name: 'Ghost Rider',
+ description: 'When a Horseman dies, it has a 40% chance to spawn a Swordsman in its place',
+ maxLevels: 1,
+ costPerLevel: 3,
+ position: {
+ x: 1024,
+ y: 1400
+ },
+ dependencies: ['army_archer_dmg_3']
+ }],
+ 'Building': [{
+ id: 'building_cost_1',
+ name: 'Master Carpenter',
+ description: 'Reduces the build cost of all castle buildings by 4%',
+ maxLevels: 5,
+ costPerLevel: 1,
+ position: {
+ x: 1024,
+ y: 400
+ },
+ dependencies: []
+ }, {
+ id: 'building_tavern_2',
+ name: 'Golden Enterprise',
+ description: 'Increases base income from Taverns by 4%',
+ maxLevels: 5,
+ costPerLevel: 1,
+ position: {
+ x: 450,
+ y: 650
+ },
+ dependencies: ['building_cost_1']
+ }, {
+ id: 'building_infrastructure_2',
+ name: 'Military Infrastructure',
+ description: 'Increases the effectiveness of Smithy and Training Ground by 5%',
+ maxLevels: 2,
+ costPerLevel: 2,
+ position: {
+ x: 1024,
+ y: 650
+ },
+ dependencies: ['building_cost_1']
+ }, {
+ id: 'building_population_2',
+ name: 'Social Development',
+ description: 'Increases population from each Home building by +1',
+ maxLevels: 5,
+ costPerLevel: 1,
+ position: {
+ x: 1600,
+ y: 650
+ },
+ dependencies: ['building_cost_1']
+ }, {
+ id: 'building_reroll_3',
+ name: 'Haggling',
+ description: 'Reduces the cost of rerolling buildings by 20%',
+ maxLevels: 2,
+ costPerLevel: 1,
+ position: {
+ x: 450,
+ y: 900
+ },
+ dependencies: ['building_tavern_2']
+ }, {
+ id: 'building_workshop_3',
+ name: 'Military Innovations',
+ description: 'Reduces the cost of Workshop upgrades by 5%',
+ maxLevels: 3,
+ costPerLevel: 1,
+ position: {
+ x: 1024,
+ y: 900
+ },
+ dependencies: ['building_infrastructure_2']
+ }, {
+ id: 'building_infirmary_3',
+ name: 'Medical Care',
+ description: 'Infirmary speeds up population regeneration by an additional 5%',
+ maxLevels: 3,
+ costPerLevel: 1,
+ position: {
+ x: 1600,
+ y: 900
+ },
+ dependencies: ['building_population_2']
+ }, {
+ id: 'building_gold_4',
+ name: 'Royal Grant',
+ description: 'Start each game with +500 gold',
+ maxLevels: 1,
+ costPerLevel: 5,
+ position: {
+ x: 450,
+ y: 1150
+ },
+ dependencies: ['building_reroll_3']
+ }, {
+ id: 'building_lives_4',
+ name: 'Reinforced Walls',
+ description: 'Start each game with +10 lives',
+ maxLevels: 1,
+ costPerLevel: 5,
+ position: {
+ x: 1600,
+ y: 1150
+ },
+ dependencies: ['building_infirmary_3']
+ }],
+ 'Magic': [{
+ id: 'magic_mana_kill_1',
+ name: 'Mana Siphon',
+ description: 'Increases mana gained from enemy kills by 10%',
+ maxLevels: 5,
+ costPerLevel: 1,
+ position: {
+ x: 1024,
+ y: 400
+ },
+ dependencies: []
+ }, {
+ id: 'magic_destruction_2',
+ name: 'Path of Destruction',
+ description: 'Increases damage of Fire Wall and Thunderstorm by 4%',
+ maxLevels: 5,
+ costPerLevel: 1,
+ position: {
+ x: 624,
+ y: 650
+ },
+ dependencies: ['magic_mana_kill_1']
+ }, {
+ id: 'magic_manipulation_2',
+ name: 'Path of Manipulation',
+ description: 'Reduces mana cost of Curse, Summon Golem, and Healing Field by 3%',
+ maxLevels: 5,
+ costPerLevel: 1,
+ position: {
+ x: 1424,
+ y: 650
+ },
+ dependencies: ['magic_mana_kill_1']
+ }, {
+ id: 'magic_firewall_duration_3',
+ name: 'Eternal Flame',
+ description: 'Increases Fire Wall duration by 10%',
+ maxLevels: 3,
+ costPerLevel: 1,
+ position: {
+ x: 424,
+ y: 900
+ },
+ dependencies: ['magic_destruction_2']
+ }, {
+ id: 'magic_storm_duration_3',
+ name: 'Lasting Storm',
+ description: 'Increases Thunderstorm duration by 10%',
+ maxLevels: 3,
+ costPerLevel: 1,
+ position: {
+ x: 824,
+ y: 900
+ },
+ dependencies: ['magic_destruction_2']
+ }, {
+ id: 'magic_curse_3',
+ name: 'Deeper Curse',
+ description: 'Increases the effectiveness of Curse by 5%',
+ maxLevels: 3,
+ costPerLevel: 1,
+ position: {
+ x: 1224,
+ y: 900
+ },
+ dependencies: ['magic_manipulation_2']
+ }, {
+ id: 'magic_essence_3',
+ name: 'Reinforced Essence',
+ description: 'Increases Golem HP and Healing Field power by 5%',
+ maxLevels: 3,
+ costPerLevel: 1,
+ position: {
+ x: 1624,
+ y: 900
+ },
+ dependencies: ['magic_manipulation_2']
+ }, {
+ id: 'magic_regen_4',
+ name: 'Mana Regeneration',
+ description: 'Regenerate 1 mana per second',
+ maxLevels: 1,
+ costPerLevel: 5,
+ position: {
+ x: 1224,
+ y: 1150
+ },
+ dependencies: ['magic_curse_3']
+ }, {
+ id: 'magic_golem_4',
+ name: 'Golem\'s Agony',
+ description: 'Golem gains +1 damage for every 10% of its missing HP',
+ maxLevels: 1,
+ costPerLevel: 3,
+ position: {
+ x: 1624,
+ y: 1150
+ },
+ dependencies: ['magic_essence_3']
+ }, {
+ id: 'magic_freecast_4',
+ name: 'Mana Echo',
+ description: 'Grants a 30% chance to cast a spell for free',
+ maxLevels: 1,
+ costPerLevel: 5,
+ position: {
+ x: 824,
+ y: 1150
+ },
+ dependencies: ['magic_storm_duration_3']
+ }]
+};
function hideBuildingInfoMenu(menu) {
if (isHidingUpgradeMenu) {
return;
}
@@ -3279,43 +4027,30 @@
}
}, 150);
}
if (activeWeatherEvent === 'FOG') {
- // Debuff dla wież Maga
towers.forEach(function (tower) {
if (tower.towerType === 'mage') {
tower.addBuff({
source: 'FOG_DEBUFF',
effect: {
- damage: 0.90 // 10% mniej obrażeń
+ damage: 0.90
}
});
}
});
- // NOWE, POPRAWNE KOORDYNATY Z TWOJEGO OBRAZKA
- var spawnPoints = [{
- x: 250,
- y: 700,
- direction: 'right'
- }, {
- x: 250,
- y: 2200,
- direction: 'right'
- }, {
- x: 1800,
- y: 800,
- direction: 'left'
- }, {
- x: 1800,
- y: 2100,
- direction: 'left'
- }];
- spawnPoints.forEach(function (point) {
- var wisp = new FogWisp(point.x, point.y, point.direction);
- weatherEffectLayer.addChild(wisp);
- activeFogWisps.push(wisp);
- });
- // Usunęliśmy stary, niepotrzebny fogIntervalId
+ if (fogIntervalId) {
+ LK.clearInterval(fogIntervalId);
+ }
+ // Uruchamiamy "fabrykę" mgły, która dba o stałą jej ilość
+ fogIntervalId = LK.setInterval(function () {
+ if (activeFogWisps.length < 6) {
+ // Utrzymujemy 6 obiektów mgły na ekranie
+ var wisp = new FogWisp();
+ weatherEffectLayer.addChild(wisp);
+ activeFogWisps.push(wisp);
+ }
+ }, 1000); // Co sekundę sprawdzaj i dodawaj
}
if (activeWeatherEvent === 'NIGHT') {
nightOverlay.visible = true;
tween(nightOverlay, {
@@ -3350,8 +4085,39 @@
}
}, 150);
}
}
+function clearActiveWeatherModifiers() {
+ if (rainIntervalId) {
+ LK.clearInterval(rainIntervalId);
+ rainIntervalId = null;
+ }
+ if (fogIntervalId) {
+ LK.clearInterval(fogIntervalId);
+ fogIntervalId = null;
+ }
+ if (stormIntervalId) {
+ LK.clearInterval(stormIntervalId);
+ stormIntervalId = null;
+ }
+ for (var i = activeFogWisps.length - 1; i >= 0; i--) {
+ activeFogWisps[i].destroy();
+ }
+ activeFogWisps = [];
+ if (nightOverlay.visible) {
+ tween(nightOverlay, {
+ alpha: 0
+ }, {
+ duration: 500,
+ onFinish: function onFinish() {
+ nightOverlay.visible = false;
+ }
+ });
+ }
+ towers.forEach(function (tower) {
+ tower.removeBuff('FOG_DEBUFF');
+ });
+}
function showSpellOverlay() {
spellOverlay.visible = true;
tween(spellOverlay, {
alpha: 0.7
@@ -4233,8 +4999,9 @@
var currentLevelNumber;
var castleContainer;
var activeWeatherEvent = null;
var viewToggleButton;
+var talentTreeScreen;
var castleGrid;
var castleBuildPanel;
var magicButton;
var rainIntervalId = null;
@@ -4376,15 +5143,19 @@
}
var baseCost = BUILDING_DATA[buildingType] ? BUILDING_DATA[buildingType].cost : 0;
var finalCost = {
gold: Math.floor(baseCost * Math.pow(1.2, count)),
- people: 0 // Placeholder, if buildings cost people in the future
+ people: 0
};
+ if (progressionData.upgrades['building_cost_1']) {
+ var costReduction = 1 - progressionData.upgrades['building_cost_1'] * 0.04;
+ finalCost.gold = Math.floor(finalCost.gold * costReduction);
+ }
return finalCost;
}
function getModifiedSpellData(spellId) {
var originalData = SPELL_DATA[spellId];
- var modifiedData = JSON.parse(JSON.stringify(originalData)); // Tworzymy głęboką kopię
+ var modifiedData = JSON.parse(JSON.stringify(originalData));
var sanctumCount = castleBuildings.filter(function (b) {
return b.id === 'magic_sanctum';
}).length;
if (sanctumCount > 0) {
@@ -4403,10 +5174,34 @@
}
if (modifiedData.duration) {
modifiedData.duration = Math.round(modifiedData.duration * spellBonus);
}
- // Możemy tu dodać więcej statystyk w razie potrzeby
}
+ // Bonusy z Drzewka Magii
+ if (progressionData.upgrades['magic_destruction_2']) {
+ var dmgBonus = 1 + progressionData.upgrades['magic_destruction_2'] * 0.04;
+ if (modifiedData.damage) {
+ modifiedData.damage *= dmgBonus;
+ }
+ if (modifiedData.dotDamage) {
+ modifiedData.dotDamage *= dmgBonus;
+ }
+ }
+ if (progressionData.upgrades['magic_manipulation_2']) {
+ var costBonus = 1 - progressionData.upgrades['magic_manipulation_2'] * 0.03;
+ modifiedData.cost = Math.floor(modifiedData.cost * costBonus);
+ }
+ var durationKey = 'magic_' + spellId + '_duration_3';
+ if (progressionData.upgrades[durationKey]) {
+ var durationBonus = 1 + progressionData.upgrades[durationKey] * 0.10;
+ if (modifiedData.duration) {
+ modifiedData.duration *= durationBonus;
+ }
+ if (modifiedData.dotDuration) {
+ modifiedData.dotDuration *= durationBonus;
+ }
+ }
+ // ... i tak dalej dla innych bonusów jak promień, siła efektu ...
return modifiedData;
}
function setupStatsPanel() {
isStatsPanelExpanded = false;
@@ -4607,8 +5402,10 @@
}
levelSelectContainer = new Container();
game.addChild(levelSelectContainer);
currentScreenState = 'levelSelect';
+ talentTreeScreen = new TalentTreeScreen();
+ game.addChild(talentTreeScreen);
var parchment = levelSelectContainer.attachAsset('level_select_parchment', {
anchorX: 0.5,
anchorY: 0.5
});
@@ -4628,10 +5425,31 @@
selectTitle.anchor.set(0.5, 0.5);
selectTitle.x = 2048 / 2;
selectTitle.y = 350;
levelSelectContainer.addChild(selectTitle);
+ // NOWY PRZYCISK ULEPSZEŃ
+ var upgradesButton = new Container();
+ upgradesButton.x = 2048 / 2;
+ upgradesButton.y = 600;
+ levelSelectContainer.addChild(upgradesButton);
+ var upgradesButtonBg = upgradesButton.attachAsset('upgrade_menu_button', {
+ anchorX: 0.5,
+ anchorY: 0.5,
+ tint: 0x0088CC
+ });
+ var upgradesButtonText = new Text2("Upgrades", {
+ size: 60,
+ fill: 0xFFFFFF
+ });
+ upgradesButtonText.anchor.set(0.5, 0.5);
+ upgradesButton.addChild(upgradesButtonText);
+ upgradesButton.interactive = true;
+ upgradesButton.down = function () {
+ levelSelectContainer.visible = false;
+ talentTreeScreen.show();
+ };
var levelKeys = Object.keys(LEVEL_DATA_ALL);
- var startY = 800;
+ var startY = 850; // Przesunięte w dół
var buttonSpacing = 250;
for (var i = 0; i < levelKeys.length; i++) {
var levelKey = levelKeys[i];
var levelData = LEVEL_DATA_ALL[levelKey];
@@ -4928,13 +5746,22 @@
friendlyUnits = [];
peopleRegenQueue = [];
activeSpells = [];
npcs = [];
- specialZoneIndicators = []; // NOWA LINIA
+ specialZoneIndicators = [];
selectedTower = null;
castleBuildings = [];
- gold = levelData.initialGold;
- lives = levelData.initialLives;
+ // ZMIANA: Dodajemy bonusy startowe
+ var initialGold = levelData.initialGold;
+ if (progressionData.upgrades['building_gold_4']) {
+ initialGold += 500;
+ }
+ gold = initialGold;
+ var initialLives = levelData.initialLives;
+ if (progressionData.upgrades['building_lives_4']) {
+ initialLives += 10;
+ }
+ lives = initialLives;
mana = 0;
isManaUnlocked = false;
isArmyUnlocked = false;
enemiesKilled = 0;
@@ -5260,9 +6087,21 @@
anchorY: 0.5
});
rerollButton.down = function () {
if (currentActiveView === 'castle') {
- drawNewBuildingHand();
+ var rerollCost = 100;
+ if (progressionData.upgrades['building_reroll_3']) {
+ var costReduction = 1 - progressionData.upgrades['building_reroll_3'] * 0.20;
+ rerollCost = Math.floor(rerollCost * costReduction);
+ }
+ if (gold >= rerollCost) {
+ setGold(gold - rerollCost);
+ drawNewBuildingHand();
+ } else {
+ var notification = game.addChild(new Notification("Not enough gold for reroll!"));
+ notification.x = 2048 / 2;
+ notification.y = grid.height - 50;
+ }
}
};
placementPreview = new PlacementPreview();
game.addChild(placementPreview);