User prompt
gold amount is always rounded up to an integer
User prompt
wizards always attacks the furthest enemy in enemy path, not the closest one to wizard.
User prompt
raise enemy hp increase by 5% between waves
User prompt
increase enemy health by 15%
User prompt
show level under wave
User prompt
move all assets north by 100 pixels
User prompt
all enemies moves 20% slower
User prompt
move all assets to east by 150 pixels
User prompt
move all assets south by 300 pixels
User prompt
revert back tank enemy health decrease and then decrease it by 30%
User prompt
decrease tank enemy health only by 20%
User prompt
decrease gold gain by 50%
User prompt
increase range by 15% and increase enemy health by 20%
User prompt
halve enemy health
/**** * Plugins ****/ var storage = LK.import("@upit/storage.v1"); var tween = LK.import("@upit/tween.v1"); /**** * Classes ****/ var DamageDisplay = Container.expand(function (damage, x, y) { var self = Container.call(this); self.x = x; self.y = y; // Create damage text var damageText = new Text2(Math.floor(damage).toString(), { size: 90, fill: 0xFFFFFF, font: "'GillSans-Bold',Impact,'Arial Black',Tahoma" }); damageText.anchor.set(0.5, 0.5); self.addChild(damageText); // Animate the damage display var startY = self.y; var targetY = startY - 100; // Tween upward movement and fade out tween(self, { y: targetY, alpha: 0 }, { duration: 1000, easing: tween.easeOut, onFinish: function onFinish() { self.destroy(); } }); return self; }); var Enemy = Container.expand(function (enemyType, level) { var self = Container.call(this); self.enemyType = enemyType || 'basic'; self.level = level || 1; self.pathIndex = 0; self.pathProgress = 0; self.speed = 2; switch (self.enemyType) { case 'basic': self.maxHP = (100 + level * 50) * 5; self.speed = 2; break; case 'fast': self.maxHP = (50 + level * 25) * 5; self.speed = 4; break; case 'tank': self.maxHP = (300 + level * 100) * 5; self.speed = 1; break; } self.hp = self.maxHP; self.goldValue = self.maxHP; self.originalSpeed = self.speed; self.isSlowed = false; self.slowEndTime = 0; self.isStunned = false; self.stunEndTime = 0; self.isKnockedBack = false; self.knockbackEndTime = 0; self.knockbackSpeed = 0; self.pathHistory = []; // Store path history for knockback self.isVulnerable = false; self.vulnerabilityEndTime = 0; self.vulnerabilityMultiplier = 1.2; // 20% additional damage self.isBurning = false; self.burnEndTime = 0; self.burnDamagePerTick = 0; self.burnTickInterval = 30; // 0.5 seconds at 60fps self.lastBurnTickTime = 0; var enemyGraphics = self.attachAsset('enemy_' + self.enemyType, { anchorX: 0.5, anchorY: 0.5 }); var hpBar = new Container(); var hpBackground = LK.getAsset('ui_background', { width: 60, height: 8, anchorX: 0.5, anchorY: 0.5 }); hpBackground.tint = 0x000000; hpBar.addChild(hpBackground); var hpFill = LK.getAsset('ui_background', { width: 60, height: 8, anchorX: 0, anchorY: 0.5 }); hpFill.tint = 0x00ff00; hpFill.x = -30; hpBar.addChild(hpFill); hpBar.y = -40; self.addChild(hpBar); self.update = function () { // Check if slow effect should end if (self.isSlowed && LK.ticks >= self.slowEndTime) { self.speed = self.originalSpeed; self.isSlowed = false; tween.stop(self, { tint: true }); self.tint = 0xFFFFFF; } // Check if stun effect should end if (self.isStunned && LK.ticks >= self.stunEndTime) { self.speed = self.originalSpeed; self.isStunned = false; tween.stop(self, { tint: true }); self.tint = 0xFFFFFF; } // Check if knockback effect should end if (self.isKnockedBack && LK.ticks >= self.knockbackEndTime) { self.speed = self.originalSpeed; self.isKnockedBack = false; self.knockbackSpeed = 0; tween.stop(self, { tint: true }); self.tint = 0xFFFFFF; } // Check if vulnerability effect should end if (self.isVulnerable && LK.ticks >= self.vulnerabilityEndTime) { self.isVulnerable = false; tween.stop(self, { tint: true }); self.tint = 0xFFFFFF; } // Check if burn effect should end if (self.isBurning && LK.ticks >= self.burnEndTime) { self.isBurning = false; self.burnDamagePerTick = 0; tween.stop(self, { tint: true }); self.tint = 0xFFFFFF; } // Process burn damage over time if (self.isBurning && LK.ticks - self.lastBurnTickTime >= self.burnTickInterval) { self.hp -= self.burnDamagePerTick; self.lastBurnTickTime = LK.ticks; // Create damage display for burn damage var damageDisplay = new DamageDisplay(self.burnDamagePerTick, self.x, self.y - 30); damageDisplays.push(damageDisplay); game.addChild(damageDisplay); if (self.hp <= 0) { self.die(); } } if (currentPath && currentPath.length > 0) { self.followPath(); } // Update HP bar var hpPercent = self.hp / self.maxHP; hpFill.width = 60 * hpPercent; hpFill.tint = hpPercent > 0.5 ? 0x00ff00 : hpPercent > 0.25 ? 0xffff00 : 0xff0000; }; self.followPath = function () { if (self.pathIndex >= currentPath.length) { self.reachBase(); return; } // Handle knockback movement if (self.isKnockedBack) { // Move backwards along the path history if (self.pathHistory.length > 0) { var lastPosition = self.pathHistory[self.pathHistory.length - 1]; var dx = lastPosition.x - self.x; var dy = lastPosition.y - self.y; var distance = Math.sqrt(dx * dx + dy * dy); if (distance > 2) { self.x += dx / distance * self.knockbackSpeed; self.y += dy / distance * self.knockbackSpeed; } else { // Remove the reached position from history self.pathHistory.pop(); } } return; } var targetPoint = currentPath[self.pathIndex]; var dx = targetPoint.x - self.x; var dy = targetPoint.y - self.y; var distance = Math.sqrt(dx * dx + dy * dy); if (distance < 5) { self.pathIndex++; return; } // Store current position in path history before moving self.pathHistory.push({ x: self.x, y: self.y }); // Limit history size to prevent memory issues if (self.pathHistory.length > 100) { self.pathHistory.shift(); } self.x += dx / distance * self.speed; self.y += dy / distance * self.speed; }; self.takeDamage = function (damage) { var finalDamage = damage; if (self.isVulnerable) { finalDamage = damage * self.vulnerabilityMultiplier; } // Create damage display var damageDisplay = new DamageDisplay(finalDamage, self.x, self.y - 30); damageDisplays.push(damageDisplay); game.addChild(damageDisplay); self.hp -= finalDamage; LK.getSound('enemyHit').play(); if (self.hp <= 0) { self.die(); } }; self.die = function () { LK.getSound('enemyDeath').play(); for (var i = 0; i < enemies.length; i++) { if (enemies[i] === self) { enemies.splice(i, 1); break; } } gold += self.goldValue / 10; updateGoldDisplay(); self.destroy(); gold += self.goldValue / 10; updateGoldDisplay(); }; self.applySlow = function () { if (!self.isSlowed) { self.speed = self.originalSpeed * 0.5; // 50% speed reduction self.isSlowed = true; self.slowEndTime = LK.ticks + 180; // 3 seconds at 60fps // Apply blue tint to indicate slow effect tween(self, { tint: 0x88CCFF }, { duration: 500 }); } else { // Refresh slow duration if already slowed self.slowEndTime = LK.ticks + 180; } }; self.applyStun = function () { if (!self.isStunned) { self.speed = 0; // Cannot move when stunned self.isStunned = true; self.stunEndTime = LK.ticks + 60; // 1 second at 60fps // Apply yellow tint to indicate stun effect tween(self, { tint: 0xFFFF00 }, { duration: 500 }); } else { // Refresh stun duration if already stunned self.stunEndTime = LK.ticks + 60; } }; self.applyKnockback = function () { if (!self.isKnockedBack) { self.knockbackSpeed = 3; // Speed for moving backwards self.isKnockedBack = true; self.knockbackEndTime = LK.ticks + 60; // 1 second at 60fps // Apply purple tint to indicate knockback effect tween(self, { tint: 0xFF00FF }, { duration: 300 }); } else { // Refresh knockback duration if already knocked back self.knockbackEndTime = LK.ticks + 60; } }; self.applyVulnerability = function () { if (!self.isVulnerable) { self.isVulnerable = true; self.vulnerabilityEndTime = LK.ticks + 300; // 5 seconds at 60fps // Apply green tint to indicate vulnerability effect tween(self, { tint: 0x00FF88 }, { duration: 500 }); } else { // Refresh vulnerability duration if already vulnerable self.vulnerabilityEndTime = LK.ticks + 300; } }; self.applyBurn = function (projectileDamage) { if (!self.isBurning) { self.isBurning = true; self.burnEndTime = LK.ticks + 300; // 5 seconds at 60fps self.burnDamagePerTick = projectileDamage * 0.5 / 10; // 50% of projectile damage over 10 ticks (5 seconds) self.lastBurnTickTime = LK.ticks; // Apply orange tint to indicate burn effect tween(self, { tint: 0xFF4400 }, { duration: 500 }); } else { // Refresh burn duration and recalculate damage if new burn is stronger var newBurnDamage = projectileDamage * 0.5 / 10; if (newBurnDamage > self.burnDamagePerTick) { self.burnDamagePerTick = newBurnDamage; } self.burnEndTime = LK.ticks + 300; } }; self.reachBase = function () { baseHP -= self.hp; updateBaseHPDisplay(); if (baseHP <= 0) { LK.showGameOver(); } for (var i = 0; i < enemies.length; i++) { if (enemies[i] === self) { enemies.splice(i, 1); break; } } self.destroy(); }; return self; }); var Projectile = Container.expand(function (startX, startY, target, damage, wizardType) { var self = Container.call(this); self.x = startX; self.y = startY; self.target = target; self.damage = damage; self.speed = 8; self.wizardType = wizardType || 'fire'; var projectileGraphics = self.attachAsset('projectile', { anchorX: 0.5, anchorY: 0.5 }); self.update = function () { if (!self.target || self.target.destroyed) { self.destroy(); return; } var dx = self.target.x - self.x; var dy = self.target.y - self.y; var distance = Math.sqrt(dx * dx + dy * dy); if (distance < 10) { // Calculate actual damage taken (considering vulnerability) var actualDamage = self.damage; if (self.target.isVulnerable) { actualDamage = self.damage * self.target.vulnerabilityMultiplier; } self.target.takeDamage(self.damage); // Apply slow effect if ice wizard projectile if (self.wizardType === 'ice') { self.target.applySlow(); } // Apply stun effect if light wizard projectile if (self.wizardType === 'light') { self.target.applyStun(); } // Apply knockback effect if dark wizard projectile if (self.wizardType === 'dark') { self.target.applyKnockback(); } // Apply vulnerability effect if nature wizard projectile if (self.wizardType === 'nature') { self.target.applyVulnerability(); } // Apply burn effect if fire wizard projectile if (self.wizardType === 'fire') { self.target.applyBurn(self.damage); } self.destroy(); return; } self.x += dx / distance * self.speed; self.y += dy / distance * self.speed; }; return self; }); var TowerSlot = Container.expand(function (x, y, towerNumber) { var self = Container.call(this); self.x = x; self.y = y; self.isEmpty = true; self.wizard = null; self.towerNumber = towerNumber; var slotGraphics = self.attachAsset('tower_slot', { anchorX: 0.5, anchorY: 0.5 }); var towerNumberText = new Text2(self.towerNumber.toString(), { size: 20, fill: 0xFFFFFF }); towerNumberText.anchor.set(0.5, 0.5); towerNumberText.y = 0; self.addChild(towerNumberText); self.down = function (x, y, obj) { if (self.isEmpty && (gameState === 'setup' || gameState === 'playing')) { selectedTowerSlot = self; gameState = 'wizard_selection'; createWizardSelectionPopup(); } else if (!self.isEmpty && self.wizard && (gameState === 'setup' || gameState === 'playing')) { // Upgrade existing wizard selectedTowerSlot = self; gameState = 'wizard_upgrade'; createWizardUpgradePopup(); } }; self.placeWizard = function (wizardType) { var cost = 500; if (gold >= cost) { gold -= cost; updateGoldDisplay(); self.wizard = new Wizard(wizardType, 1); self.wizard.x = self.x; self.wizard.y = self.y; self.isEmpty = false; game.addChild(self.wizard); wizards.push(self.wizard); draggedWizard = null; } }; return self; }); var Wizard = Container.expand(function (elementType, level) { var self = Container.call(this); self.elementType = elementType || 'fire'; self.level = level || 1; self.elementalDamage = 100 * self.level; self.magicDamage = 100 * self.level; self.range = (150 + self.level * 20) * 1.15; self.attackSpeed = (1000 - self.level * 50) * 0.9; // ms between attacks - 10% faster self.lastAttackTime = 0; self.cost = 500 * Math.pow(1.8, self.level - 1); var wizardGraphics = self.attachAsset('wizard_' + self.elementType, { anchorX: 0.5, anchorY: 0.5 }); var levelText = new Text2(self.level.toString(), { size: 24, fill: 0x000000, font: "'GillSans-Bold',Impact,'Arial Black',Tahoma" }); levelText.anchor.set(0.5, 0.5); levelText.y = -50; self.addChild(levelText); self.update = function () { if (LK.ticks - self.lastAttackTime > self.attackSpeed / 16.67) { var target = self.findTarget(); if (target) { self.attack(target); self.lastAttackTime = LK.ticks; } } }; self.findTarget = function () { var closestEnemy = null; var closestDistance = Infinity; for (var i = 0; i < enemies.length; i++) { var enemy = enemies[i]; var distance = Math.sqrt(Math.pow(enemy.x - self.x, 2) + Math.pow(enemy.y - self.y, 2)); if (distance <= self.range && distance < closestDistance) { closestDistance = distance; closestEnemy = enemy; } } return closestEnemy; }; self.attack = function (target) { var projectile = new Projectile(self.x, self.y, target, self.elementalDamage + self.magicDamage, self.elementType); projectiles.push(projectile); game.addChild(projectile); LK.getSound('shoot').play(); }; return self; }); var WizardIcon = Container.expand(function (elementType) { var self = Container.call(this); self.elementType = elementType; var iconGraphics = self.attachAsset('wizard_' + elementType, { anchorX: 0.5, anchorY: 0.5, scaleX: 0.8, scaleY: 0.8 }); var costText = new Text2('500', { size: 20, fill: 0xFFFFFF }); costText.anchor.set(0.5, 0); costText.y = 45; self.addChild(costText); self.down = function (x, y, obj) { // Wizard icons are now handled by tower slots }; return self; }); /**** * Initialize Game ****/ var game = new LK.Game({ backgroundColor: 0x228B22 }); /**** * Game Code ****/ // Add cobblestone background var cobblestoneBackground = game.addChild(LK.getAsset('cobblestone_bg', { anchorX: 0, anchorY: 0 })); cobblestoneBackground.x = 0; cobblestoneBackground.y = 0; // Game state variables var currentLevel = 1; var currentWave = 1; var maxWaves = 10; var gold = 1000; var baseHP = 10000; var gameState = 'setup'; // 'setup', 'playing', 'paused', 'between_waves', 'wizard_selection' var waveSpawnTimer = 0; var enemiesSpawnedThisWave = 0; var enemiesPerWave = 10; var draggedWizard = null; var gameStarted = false; var waveStartTime = 0; var lastWaveEndTime = 0; var lastEnemySpawnTime = 0; var selectedTowerSlot = null; var wizardSelectionPopup = null; var waveInProgress = false; var allEnemiesEnteredMaze = false; var lastEnemyEnteredTime = 0; var enemySpawnDelay = 60; // 1 second at 60fps var lastSpawnTime = 0; var currentWaveEnemyType; // Game object arrays var enemies = []; var wizards = []; var projectiles = []; var towerSlots = []; var wizardIcons = []; var damageDisplays = []; // Path system var currentPath = []; var pathPoints = [{ x: 50, y: 350 }, { x: 200, y: 350 }, { x: 200, y: 500 }, { x: 350, y: 500 }, { x: 350, y: 650 }, { x: 150, y: 650 }, { x: 150, y: 800 }, { x: 450, y: 800 }, { x: 450, y: 950 }, { x: 650, y: 950 }, { x: 650, y: 750 }, { x: 800, y: 750 }, { x: 800, y: 1100 }, { x: 550, y: 1100 }, { x: 550, y: 1250 }, { x: 900, y: 1250 }, { x: 900, y: 1400 }, { x: 700, y: 1400 }, { x: 700, y: 1550 }, { x: 1050, y: 1550 }, { x: 1050, y: 1200 }, { x: 1200, y: 1200 }, { x: 1200, y: 1700 }, { x: 1000, y: 1700 }, { x: 1000, y: 1850 }, { x: 1350, y: 1850 }, { x: 1350, y: 1500 }, { x: 1500, y: 1500 }, { x: 1500, y: 2000 }, { x: 1700, y: 2000 }]; // UI elements - Create scoreboard container without background var scoreboardContainer = new Container(); var goldText = new Text2('Gold: ' + gold, { size: 60, fill: 0x000000, font: "'GillSans-Bold',Impact,'Arial Black',Tahoma" }); goldText.anchor.set(1, 0); goldText.x = 2048 - 40; // 40px from right edge goldText.y = 40; scoreboardContainer.addChild(goldText); var baseHPText = new Text2('Base HP: ' + baseHP, { size: 60, fill: 0x000000, font: "'GillSans-Bold',Impact,'Arial Black',Tahoma" }); baseHPText.anchor.set(1, 0); baseHPText.x = 2048 - 40; // 40px from right edge baseHPText.y = 110; scoreboardContainer.addChild(baseHPText); var waveText = new Text2('Wave: ' + currentWave + '/' + maxWaves, { size: 60, fill: 0x000000, font: "'GillSans-Bold',Impact,'Arial Black',Tahoma" }); waveText.anchor.set(1, 0); waveText.x = 2048 - 40; // 40px from right edge waveText.y = 180; scoreboardContainer.addChild(waveText); game.addChild(scoreboardContainer); // Initialize path currentPath = pathPoints; // Create continuous road path between waypoints for (var i = 0; i < currentPath.length - 1; i++) { var startPoint = currentPath[i]; var endPoint = currentPath[i + 1]; // Calculate distance and direction between points var dx = endPoint.x - startPoint.x; var dy = endPoint.y - startPoint.y; var distance = Math.sqrt(dx * dx + dy * dy); var steps = Math.ceil(distance / 50); // Place road tiles every 50 pixels // Create road tiles along the path for (var j = 0; j <= steps; j++) { var progress = j / steps; var roadX = startPoint.x + dx * progress; var roadY = startPoint.y + dy * progress; var roadTile = game.addChild(LK.getAsset('road_tile', { anchorX: 0.5, anchorY: 0.5 })); roadTile.x = roadX; roadTile.y = roadY; roadTile.tint = 0x444444; // Darker road color } } // Create home base var homeBase = game.addChild(LK.getAsset('home_base', { anchorX: 0.5, anchorY: 0.5 })); homeBase.x = currentPath[currentPath.length - 1].x; homeBase.y = currentPath[currentPath.length - 1].y; // Create tower slots - positioned outside enemy path but within level 1 wizard range (170px) of enemy path var towerPositions = [{ x: 270, y: 285 }, { x: 430, y: 440 }, { x: 70, y: 730 }, { x: 520, y: 730 }, { x: 780, y: 1475 }, { x: 390, y: 1025 }, { x: 720, y: 1005 }, { x: 1125, y: 1110 }, { x: 750, y: 1175 }, { x: 1125, y: 1380 }, { x: 1110, y: 1775 }, { x: 1425, y: 1590 }]; for (var i = 0; i < towerPositions.length; i++) { var slot = new TowerSlot(towerPositions[i].x, towerPositions[i].y, i + 1); towerSlots.push(slot); game.addChild(slot); } // Create UI bottom panel var uiPanel = game.addChild(LK.getAsset('ui_background', { anchorX: 0, anchorY: 1 })); uiPanel.x = 0; uiPanel.y = 2732; // Create start wave button var startWaveButton = LK.getAsset('ui_background', { width: 200, height: 60, anchorX: 0.5, anchorY: 0.5 }); startWaveButton.tint = 0x00AA00; startWaveButton.x = 1024; startWaveButton.y = 200; var startWaveText = new Text2('Start Game', { size: 30, fill: 0xFFFFFF }); startWaveText.anchor.set(0.5, 0.5); startWaveButton.addChild(startWaveText); startWaveButton.visible = true; game.addChild(startWaveButton); // Wizard icons removed - now handled by tower slot clicks // Add click handlers startWaveButton.down = function () { if (gameState === 'menu' || gameState === 'setup') { gameState = 'playing'; startWaveButton.visible = false; gameStarted = true; // Calculate max waves based on level var wavesPerLevel = [7, 8, 10, 11, 13, 14, 16, 17, 19, 20, 22, 23, 25, 26, 28, 29, 31, 32, 34, 35]; maxWaves = wavesPerLevel[currentLevel - 1] || 10; enemiesPerWave = getEnemiesPerWave(); updateWaveDisplay(); // Start first wave automatically waveInProgress = true; allEnemiesEnteredMaze = false; lastSpawnTime = 0; } }; // Create wizard selection popup function createWizardSelectionPopup() { wizardSelectionPopup = new Container(); // Background - make it larger to accommodate level selection var popupBg = LK.getAsset('ui_background', { width: 2000, height: 1600, anchorX: 0.5, anchorY: 0.5 }); popupBg.tint = 0x222222; popupBg.x = 1024; popupBg.y = 1366; wizardSelectionPopup.addChild(popupBg); // Title var titleText = new Text2('Select Wizard & Level', { size: 50, fill: 0xFFFFFF }); titleText.anchor.set(0.5, 0.5); titleText.x = 1024; titleText.y = 1050; wizardSelectionPopup.addChild(titleText); // Add selected wizard type and level tracking wizardSelectionPopup.selectedWizardType = 'fire'; wizardSelectionPopup.selectedLevel = 1; // Wizard type buttons var wizardTypes = ['fire', 'ice', 'nature', 'dark', 'light']; var wizardNames = ['Fire Wizard', 'Ice Wizard', 'Nature Wizard', 'Dark Wizard', 'Light Wizard']; var wizardTypeButtons = []; for (var i = 0; i < wizardTypes.length; i++) { var wizardBtn = LK.getAsset('wizard_' + wizardTypes[i], { anchorX: 0.5, anchorY: 0.5 }); wizardBtn.x = 824; // Move wizard icon to left wizardBtn.y = 1150 + i * 120; // Arrange vertically from top to bottom wizardBtn.wizardType = wizardTypes[i]; wizardBtn.alpha = wizardTypes[i] === 'fire' ? 1.0 : 0.6; // Highlight selected // Add wizard name text var nameText = new Text2(wizardNames[i], { size: 40, fill: 0xFFFFFF }); nameText.anchor.set(0, 0.5); nameText.x = 924; // Position to right of wizard icon nameText.y = 1130 + i * 120; wizardSelectionPopup.addChild(nameText); // Add cost text var wizardCostText = new Text2('Cost: 500', { size: 32, fill: 0xFFFF00 }); wizardCostText.anchor.set(0, 0.5); wizardCostText.x = 924; wizardCostText.y = 1160 + i * 120; wizardSelectionPopup.addChild(wizardCostText); // Add level text var wizardLevelText = new Text2('Level: 1', { size: 32, fill: 0x00FF00 }); wizardLevelText.anchor.set(0, 0.5); wizardLevelText.x = 924; wizardLevelText.y = 1190 + i * 120; wizardSelectionPopup.addChild(wizardLevelText); // Add elemental damage text var elementNames = { 'fire': 'Fire', 'ice': 'Ice', 'nature': 'Nature', 'dark': 'Dark', 'light': 'Light' }; var wizardDamageText = new Text2(elementNames[wizardTypes[i]] + ': 100', { size: 32, fill: 0xFF6600 }); wizardDamageText.anchor.set(0, 0.5); wizardDamageText.x = 1224; wizardDamageText.y = 1160 + i * 120; wizardSelectionPopup.addChild(wizardDamageText); // Add magic damage text var wizardMagicDamageText = new Text2('Magic: 100', { size: 32, fill: 0x9966FF }); wizardMagicDamageText.anchor.set(0, 0.5); wizardMagicDamageText.x = 1224; wizardMagicDamageText.y = 1190 + i * 120; wizardSelectionPopup.addChild(wizardMagicDamageText); // Store references to text elements for updates wizardBtn.nameText = nameText; wizardBtn.costText = wizardCostText; wizardBtn.levelText = wizardLevelText; wizardBtn.damageText = wizardDamageText; wizardBtn.magicDamageText = wizardMagicDamageText; wizardBtn.down = function () { // Update selected wizard type wizardSelectionPopup.selectedWizardType = this.wizardType; // Update button highlighting for (var j = 0; j < wizardTypeButtons.length; j++) { wizardTypeButtons[j].alpha = wizardTypeButtons[j].wizardType === this.wizardType ? 1.0 : 0.6; } // Update cost display updateCostDisplay(); // Update individual wizard info updateWizardInfo(); }; wizardSelectionPopup.addChild(wizardBtn); wizardTypeButtons.push(wizardBtn); } // Level selection section var levelTitleText = new Text2('Level:', { size: 40, fill: 0xFFFFFF }); levelTitleText.anchor.set(0.5, 0.5); levelTitleText.x = 1024; levelTitleText.y = 1770; wizardSelectionPopup.addChild(levelTitleText); // Level buttons (1-10) var levelButtons = []; for (var i = 1; i <= 10; i++) { var levelBtn = LK.getAsset('ui_background', { width: 80, height: 60, anchorX: 0.5, anchorY: 0.5 }); levelBtn.tint = i === 1 ? 0x00AA00 : 0x666666; // Highlight level 1 levelBtn.x = 400 + (i - 1) * 80; levelBtn.y = 1850; levelBtn.level = i; // Add level number text var levelNumText = new Text2(i.toString(), { size: 30, fill: 0xFFFFFF }); levelNumText.anchor.set(0.5, 0.5); levelBtn.addChild(levelNumText); levelBtn.down = function () { // Update selected level wizardSelectionPopup.selectedLevel = this.level; // Update button highlighting for (var j = 0; j < levelButtons.length; j++) { levelButtons[j].tint = levelButtons[j].level === this.level ? 0x00AA00 : 0x666666; } // Update cost display updateCostDisplay(); // Update individual wizard info updateWizardInfo(); }; wizardSelectionPopup.addChild(levelBtn); levelButtons.push(levelBtn); } // Cost display var costText = new Text2('Cost: 500', { size: 35, fill: 0xFFFF00 }); costText.anchor.set(0.5, 0.5); costText.x = 1024; costText.y = 1930; wizardSelectionPopup.addChild(costText); // Function to update cost display function updateCostDisplay() { var cost = 500 * Math.pow(1.8, wizardSelectionPopup.selectedLevel - 1); costText.setText('Cost: ' + Math.floor(cost)); costText.tint = gold >= cost ? 0xFFFF00 : 0xFF0000; // Yellow if affordable, red if not } // Function to update wizard info display function updateWizardInfo() { var cost = 500 * Math.pow(1.8, wizardSelectionPopup.selectedLevel - 1); var elementalDamage = 100 * wizardSelectionPopup.selectedLevel; var magicDamage = 100 * wizardSelectionPopup.selectedLevel; var elementNames = { 'fire': 'Fire', 'ice': 'Ice', 'nature': 'Nature', 'dark': 'Dark', 'light': 'Light' }; for (var i = 0; i < wizardTypeButtons.length; i++) { var btn = wizardTypeButtons[i]; btn.costText.setText('Cost: ' + Math.floor(cost)); btn.costText.tint = gold >= cost ? 0xFFFF00 : 0xFF0000; btn.levelText.setText('Level: ' + wizardSelectionPopup.selectedLevel); btn.damageText.setText(elementNames[btn.wizardType] + ': ' + elementalDamage); btn.magicDamageText.setText('Magic: ' + magicDamage); } } // Hire button var hireBtn = LK.getAsset('ui_background', { width: 200, height: 60, anchorX: 0.5, anchorY: 0.5 }); hireBtn.tint = 0x00AA00; hireBtn.x = 924; hireBtn.y = 2000; var hireText = new Text2('Hire', { size: 30, fill: 0xFFFFFF }); hireText.anchor.set(0.5, 0.5); hireBtn.addChild(hireText); hireBtn.down = function () { placeWizardOnSelectedSlot(wizardSelectionPopup.selectedWizardType, wizardSelectionPopup.selectedLevel); }; wizardSelectionPopup.addChild(hireBtn); // Close button var closeBtn = LK.getAsset('ui_background', { width: 100, height: 50, anchorX: 0.5, anchorY: 0.5 }); closeBtn.tint = 0xff0000; closeBtn.x = 1400; closeBtn.y = 1050; var closeText = new Text2('X', { size: 30, fill: 0xFFFFFF }); closeText.anchor.set(0.5, 0.5); closeBtn.addChild(closeText); closeBtn.down = function () { closeWizardSelectionPopup(); }; wizardSelectionPopup.addChild(closeBtn); game.addChild(wizardSelectionPopup); } function closeWizardSelectionPopup() { if (wizardSelectionPopup) { wizardSelectionPopup.destroy(); wizardSelectionPopup = null; selectedTowerSlot = null; gameState = gameStarted ? 'playing' : 'setup'; } } function createWizardUpgradePopup() { var wizard = selectedTowerSlot.wizard; var upgradePopup = new Container(); // Background var popupBg = LK.getAsset('ui_background', { width: 1000, height: 800, anchorX: 0.5, anchorY: 0.5 }); popupBg.tint = 0x222222; popupBg.x = 1024; popupBg.y = 1366; upgradePopup.addChild(popupBg); // Title var titleText = new Text2('Upgrade Wizard', { size: 50, fill: 0xFFFFFF }); titleText.anchor.set(0.5, 0.5); titleText.x = 1024; titleText.y = 1100; upgradePopup.addChild(titleText); // Current wizard info var currentInfoText = new Text2('Current Level: ' + wizard.level, { size: 35, fill: 0xFFFFFF }); currentInfoText.anchor.set(0.5, 0.5); currentInfoText.x = 1024; currentInfoText.y = 1200; upgradePopup.addChild(currentInfoText); var elementNames = { 'fire': 'Fire', 'ice': 'Ice', 'nature': 'Nature', 'dark': 'Dark', 'light': 'Light' }; var currentDamageText = new Text2(elementNames[wizard.elementType] + ': ' + wizard.elementalDamage + ' | Magic: ' + wizard.magicDamage, { size: 30, fill: 0x00FF00 }); currentDamageText.anchor.set(0.5, 0.5); currentDamageText.x = 1024; currentDamageText.y = 1250; upgradePopup.addChild(currentDamageText); // Upgrade info (if not max level) if (wizard.level < 10) { var upgradeCost = Math.floor(500 * Math.pow(1.8, wizard.level) * 0.6); var newElementalDamage = 100 * (wizard.level + 1); var newMagicDamage = 100 * (wizard.level + 1); var upgradeInfoText = new Text2('Upgrade to Level ' + (wizard.level + 1), { size: 35, fill: 0xFFFF00 }); upgradeInfoText.anchor.set(0.5, 0.5); upgradeInfoText.x = 1024; upgradeInfoText.y = 1350; upgradePopup.addChild(upgradeInfoText); var upgradeDamageText = new Text2(elementNames[wizard.elementType] + ': ' + newElementalDamage + ' | Magic: ' + newMagicDamage, { size: 30, fill: 0x00FF00 }); upgradeDamageText.anchor.set(0.5, 0.5); upgradeDamageText.x = 1024; upgradeDamageText.y = 1400; upgradePopup.addChild(upgradeDamageText); var costText = new Text2('Cost: ' + upgradeCost, { size: 35, fill: gold >= upgradeCost ? 0xFFFF00 : 0xFF0000 }); costText.anchor.set(0.5, 0.5); costText.x = 1024; costText.y = 1450; upgradePopup.addChild(costText); // Upgrade button var upgradeBtn = LK.getAsset('ui_background', { width: 200, height: 60, anchorX: 0.5, anchorY: 0.5 }); upgradeBtn.tint = gold >= upgradeCost ? 0x00AA00 : 0x666666; upgradeBtn.x = 924; upgradeBtn.y = 1550; var upgradeText = new Text2('Upgrade', { size: 30, fill: 0xFFFFFF }); upgradeText.anchor.set(0.5, 0.5); upgradeBtn.addChild(upgradeText); upgradeBtn.down = function () { if (gold >= upgradeCost) { upgradeWizard(wizard, upgradeCost); upgradePopup.destroy(); selectedTowerSlot = null; gameState = gameStarted ? 'playing' : 'setup'; } }; upgradePopup.addChild(upgradeBtn); } else { // Max level reached var maxLevelText = new Text2('Maximum Level Reached!', { size: 40, fill: 0xFF6600 }); maxLevelText.anchor.set(0.5, 0.5); maxLevelText.x = 1024; maxLevelText.y = 1400; upgradePopup.addChild(maxLevelText); } // Close button var closeBtn = LK.getAsset('ui_background', { width: 100, height: 50, anchorX: 0.5, anchorY: 0.5 }); closeBtn.tint = 0xff0000; closeBtn.x = 1124; closeBtn.y = 1550; var closeText = new Text2('X', { size: 30, fill: 0xFFFFFF }); closeText.anchor.set(0.5, 0.5); closeBtn.addChild(closeText); closeBtn.down = function () { upgradePopup.destroy(); selectedTowerSlot = null; gameState = gameStarted ? 'playing' : 'setup'; }; upgradePopup.addChild(closeBtn); game.addChild(upgradePopup); } function upgradeWizard(wizard, cost) { gold -= cost; updateGoldDisplay(); // Upgrade wizard stats wizard.level++; wizard.elementalDamage = 100 * wizard.level; wizard.magicDamage = 100 * wizard.level; wizard.range = (150 + wizard.level * 20) * 1.15; wizard.attackSpeed = (1000 - wizard.level * 50) * 0.9; wizard.cost = 500 * Math.pow(1.8, wizard.level - 1); // Update level display on wizard var levelText = wizard.children.find(function (child) { return child instanceof Text2; }); if (levelText) { levelText.setText(wizard.level.toString()); } // Update range circle if it exists if (wizard.rangeCircle) { wizard.rangeCircle.scaleX = wizard.range / 45; wizard.rangeCircle.scaleY = wizard.range / 45; } } function placeWizardOnSelectedSlot(wizardType, level) { if (selectedTowerSlot && selectedTowerSlot.isEmpty) { var cost = 500 * Math.pow(1.8, level - 1); if (gold >= cost) { gold -= cost; updateGoldDisplay(); var wizard = new Wizard(wizardType, level); wizard.x = selectedTowerSlot.x; wizard.y = selectedTowerSlot.y; selectedTowerSlot.wizard = wizard; selectedTowerSlot.isEmpty = false; // Add range visualization var rangeCircle = LK.getAsset('tower_slot', { anchorX: 0.5, anchorY: 0.5, scaleX: wizard.range / 45, scaleY: wizard.range / 45 }); rangeCircle.tint = 0x00ff00; rangeCircle.alpha = 0.2; rangeCircle.x = wizard.x; rangeCircle.y = wizard.y; game.addChild(rangeCircle); wizard.rangeCircle = rangeCircle; game.addChild(wizard); wizards.push(wizard); closeWizardSelectionPopup(); } } } // Helper functions function updateGoldDisplay() { goldText.setText('Gold: ' + gold); } function updateBaseHPDisplay() { baseHPText.setText('Base HP: ' + baseHP); } function updateWaveDisplay() { waveText.setText('Wave: ' + currentWave + '/' + maxWaves); } function spawnEnemy() { // Determine enemy type for this wave - same type for all enemies in wave if (currentWaveEnemyType === undefined) { var rand = Math.random(); if (rand < 0.6) currentWaveEnemyType = 'basic';else if (rand < 0.85) currentWaveEnemyType = 'fast';else currentWaveEnemyType = 'tank'; } var enemy = new Enemy(currentWaveEnemyType, currentLevel); enemy.x = currentPath[0].x; enemy.y = currentPath[0].y; enemies.push(enemy); game.addChild(enemy); } function getEnemiesPerWave() { var min, max; if (currentLevel >= 1 && currentLevel <= 5) { min = 10; max = 20; } else if (currentLevel >= 6 && currentLevel <= 10) { min = 15; max = 25; } else if (currentLevel >= 11 && currentLevel <= 15) { min = 20; max = 35; } else if (currentLevel >= 16 && currentLevel <= 20) { min = 35; max = 50; } else { min = 50; max = 70; } return Math.floor(Math.random() * (max - min + 1)) + min; } function startNextWave() { currentWave++; enemiesSpawnedThisWave = 0; enemiesPerWave = getEnemiesPerWave(); lastWaveEndTime = LK.ticks; gameState = 'playing'; currentWaveEnemyType = undefined; // Reset enemy type for new wave updateWaveDisplay(); } // Game update loop game.update = function () { // Only update game logic if not in setup or wizard selection if (gameState === 'setup' || gameState === 'wizard_selection' || gameState === 'wizard_upgrade') { return; } // Spawn enemies with proper timing if (gameState === 'playing' && waveInProgress && enemiesSpawnedThisWave < enemiesPerWave) { // Only spawn if enough time has passed since last spawn if (LK.ticks - lastSpawnTime >= enemySpawnDelay) { spawnEnemy(); enemiesSpawnedThisWave++; lastSpawnTime = LK.ticks; // Check if all enemies have been spawned for this wave if (enemiesSpawnedThisWave >= enemiesPerWave) { allEnemiesEnteredMaze = true; lastEnemyEnteredTime = LK.ticks; } } } // Check if wave is complete - all enemies spawned and all enemies cleared if (waveInProgress && allEnemiesEnteredMaze && enemies.length === 0) { // Wait 5 seconds after last enemy entered before allowing next wave if (LK.ticks - lastEnemyEnteredTime >= 300) { // 5 seconds at 60fps waveInProgress = false; allEnemiesEnteredMaze = false; enemiesSpawnedThisWave = 0; gameState = 'between_waves'; lastWaveEndTime = LK.ticks; waveStartTime = 0; if (currentWave < maxWaves) { // Automatically start next wave after 5 seconds startNextWave(); waveInProgress = true; allEnemiesEnteredMaze = false; lastSpawnTime = 0; } else { // Level complete currentLevel++; currentWave = 0; // Will be incremented in startNextWave var wavesPerLevel = [7, 8, 10, 11, 13, 14, 16, 17, 19, 20, 22, 23, 25, 26, 28, 29, 31, 32, 34, 35]; maxWaves = wavesPerLevel[currentLevel - 1] || 10; enemiesPerWave = getEnemiesPerWave(); if (currentLevel > 20) { LK.showYouWin(); return; } // Automatically start next level startNextWave(); waveInProgress = true; allEnemiesEnteredMaze = false; lastSpawnTime = 0; } } } // Update projectiles for (var i = projectiles.length - 1; i >= 0; i--) { var projectile = projectiles[i]; if (projectile.destroyed) { projectiles.splice(i, 1); } } // Clean up destroyed enemies for (var i = enemies.length - 1; i >= 0; i--) { if (enemies[i].destroyed) { enemies.splice(i, 1); } } // Clean up destroyed wizards for (var i = wizards.length - 1; i >= 0; i--) { if (wizards[i].destroyed) { wizards.splice(i, 1); } } // Clean up destroyed damage displays for (var i = damageDisplays.length - 1; i >= 0; i--) { if (damageDisplays[i].destroyed) { damageDisplays.splice(i, 1); } } };
/****
* Plugins
****/
var storage = LK.import("@upit/storage.v1");
var tween = LK.import("@upit/tween.v1");
/****
* Classes
****/
var DamageDisplay = Container.expand(function (damage, x, y) {
var self = Container.call(this);
self.x = x;
self.y = y;
// Create damage text
var damageText = new Text2(Math.floor(damage).toString(), {
size: 90,
fill: 0xFFFFFF,
font: "'GillSans-Bold',Impact,'Arial Black',Tahoma"
});
damageText.anchor.set(0.5, 0.5);
self.addChild(damageText);
// Animate the damage display
var startY = self.y;
var targetY = startY - 100;
// Tween upward movement and fade out
tween(self, {
y: targetY,
alpha: 0
}, {
duration: 1000,
easing: tween.easeOut,
onFinish: function onFinish() {
self.destroy();
}
});
return self;
});
var Enemy = Container.expand(function (enemyType, level) {
var self = Container.call(this);
self.enemyType = enemyType || 'basic';
self.level = level || 1;
self.pathIndex = 0;
self.pathProgress = 0;
self.speed = 2;
switch (self.enemyType) {
case 'basic':
self.maxHP = (100 + level * 50) * 5;
self.speed = 2;
break;
case 'fast':
self.maxHP = (50 + level * 25) * 5;
self.speed = 4;
break;
case 'tank':
self.maxHP = (300 + level * 100) * 5;
self.speed = 1;
break;
}
self.hp = self.maxHP;
self.goldValue = self.maxHP;
self.originalSpeed = self.speed;
self.isSlowed = false;
self.slowEndTime = 0;
self.isStunned = false;
self.stunEndTime = 0;
self.isKnockedBack = false;
self.knockbackEndTime = 0;
self.knockbackSpeed = 0;
self.pathHistory = []; // Store path history for knockback
self.isVulnerable = false;
self.vulnerabilityEndTime = 0;
self.vulnerabilityMultiplier = 1.2; // 20% additional damage
self.isBurning = false;
self.burnEndTime = 0;
self.burnDamagePerTick = 0;
self.burnTickInterval = 30; // 0.5 seconds at 60fps
self.lastBurnTickTime = 0;
var enemyGraphics = self.attachAsset('enemy_' + self.enemyType, {
anchorX: 0.5,
anchorY: 0.5
});
var hpBar = new Container();
var hpBackground = LK.getAsset('ui_background', {
width: 60,
height: 8,
anchorX: 0.5,
anchorY: 0.5
});
hpBackground.tint = 0x000000;
hpBar.addChild(hpBackground);
var hpFill = LK.getAsset('ui_background', {
width: 60,
height: 8,
anchorX: 0,
anchorY: 0.5
});
hpFill.tint = 0x00ff00;
hpFill.x = -30;
hpBar.addChild(hpFill);
hpBar.y = -40;
self.addChild(hpBar);
self.update = function () {
// Check if slow effect should end
if (self.isSlowed && LK.ticks >= self.slowEndTime) {
self.speed = self.originalSpeed;
self.isSlowed = false;
tween.stop(self, {
tint: true
});
self.tint = 0xFFFFFF;
}
// Check if stun effect should end
if (self.isStunned && LK.ticks >= self.stunEndTime) {
self.speed = self.originalSpeed;
self.isStunned = false;
tween.stop(self, {
tint: true
});
self.tint = 0xFFFFFF;
}
// Check if knockback effect should end
if (self.isKnockedBack && LK.ticks >= self.knockbackEndTime) {
self.speed = self.originalSpeed;
self.isKnockedBack = false;
self.knockbackSpeed = 0;
tween.stop(self, {
tint: true
});
self.tint = 0xFFFFFF;
}
// Check if vulnerability effect should end
if (self.isVulnerable && LK.ticks >= self.vulnerabilityEndTime) {
self.isVulnerable = false;
tween.stop(self, {
tint: true
});
self.tint = 0xFFFFFF;
}
// Check if burn effect should end
if (self.isBurning && LK.ticks >= self.burnEndTime) {
self.isBurning = false;
self.burnDamagePerTick = 0;
tween.stop(self, {
tint: true
});
self.tint = 0xFFFFFF;
}
// Process burn damage over time
if (self.isBurning && LK.ticks - self.lastBurnTickTime >= self.burnTickInterval) {
self.hp -= self.burnDamagePerTick;
self.lastBurnTickTime = LK.ticks;
// Create damage display for burn damage
var damageDisplay = new DamageDisplay(self.burnDamagePerTick, self.x, self.y - 30);
damageDisplays.push(damageDisplay);
game.addChild(damageDisplay);
if (self.hp <= 0) {
self.die();
}
}
if (currentPath && currentPath.length > 0) {
self.followPath();
}
// Update HP bar
var hpPercent = self.hp / self.maxHP;
hpFill.width = 60 * hpPercent;
hpFill.tint = hpPercent > 0.5 ? 0x00ff00 : hpPercent > 0.25 ? 0xffff00 : 0xff0000;
};
self.followPath = function () {
if (self.pathIndex >= currentPath.length) {
self.reachBase();
return;
}
// Handle knockback movement
if (self.isKnockedBack) {
// Move backwards along the path history
if (self.pathHistory.length > 0) {
var lastPosition = self.pathHistory[self.pathHistory.length - 1];
var dx = lastPosition.x - self.x;
var dy = lastPosition.y - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance > 2) {
self.x += dx / distance * self.knockbackSpeed;
self.y += dy / distance * self.knockbackSpeed;
} else {
// Remove the reached position from history
self.pathHistory.pop();
}
}
return;
}
var targetPoint = currentPath[self.pathIndex];
var dx = targetPoint.x - self.x;
var dy = targetPoint.y - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance < 5) {
self.pathIndex++;
return;
}
// Store current position in path history before moving
self.pathHistory.push({
x: self.x,
y: self.y
});
// Limit history size to prevent memory issues
if (self.pathHistory.length > 100) {
self.pathHistory.shift();
}
self.x += dx / distance * self.speed;
self.y += dy / distance * self.speed;
};
self.takeDamage = function (damage) {
var finalDamage = damage;
if (self.isVulnerable) {
finalDamage = damage * self.vulnerabilityMultiplier;
}
// Create damage display
var damageDisplay = new DamageDisplay(finalDamage, self.x, self.y - 30);
damageDisplays.push(damageDisplay);
game.addChild(damageDisplay);
self.hp -= finalDamage;
LK.getSound('enemyHit').play();
if (self.hp <= 0) {
self.die();
}
};
self.die = function () {
LK.getSound('enemyDeath').play();
for (var i = 0; i < enemies.length; i++) {
if (enemies[i] === self) {
enemies.splice(i, 1);
break;
}
}
gold += self.goldValue / 10;
updateGoldDisplay();
self.destroy();
gold += self.goldValue / 10;
updateGoldDisplay();
};
self.applySlow = function () {
if (!self.isSlowed) {
self.speed = self.originalSpeed * 0.5; // 50% speed reduction
self.isSlowed = true;
self.slowEndTime = LK.ticks + 180; // 3 seconds at 60fps
// Apply blue tint to indicate slow effect
tween(self, {
tint: 0x88CCFF
}, {
duration: 500
});
} else {
// Refresh slow duration if already slowed
self.slowEndTime = LK.ticks + 180;
}
};
self.applyStun = function () {
if (!self.isStunned) {
self.speed = 0; // Cannot move when stunned
self.isStunned = true;
self.stunEndTime = LK.ticks + 60; // 1 second at 60fps
// Apply yellow tint to indicate stun effect
tween(self, {
tint: 0xFFFF00
}, {
duration: 500
});
} else {
// Refresh stun duration if already stunned
self.stunEndTime = LK.ticks + 60;
}
};
self.applyKnockback = function () {
if (!self.isKnockedBack) {
self.knockbackSpeed = 3; // Speed for moving backwards
self.isKnockedBack = true;
self.knockbackEndTime = LK.ticks + 60; // 1 second at 60fps
// Apply purple tint to indicate knockback effect
tween(self, {
tint: 0xFF00FF
}, {
duration: 300
});
} else {
// Refresh knockback duration if already knocked back
self.knockbackEndTime = LK.ticks + 60;
}
};
self.applyVulnerability = function () {
if (!self.isVulnerable) {
self.isVulnerable = true;
self.vulnerabilityEndTime = LK.ticks + 300; // 5 seconds at 60fps
// Apply green tint to indicate vulnerability effect
tween(self, {
tint: 0x00FF88
}, {
duration: 500
});
} else {
// Refresh vulnerability duration if already vulnerable
self.vulnerabilityEndTime = LK.ticks + 300;
}
};
self.applyBurn = function (projectileDamage) {
if (!self.isBurning) {
self.isBurning = true;
self.burnEndTime = LK.ticks + 300; // 5 seconds at 60fps
self.burnDamagePerTick = projectileDamage * 0.5 / 10; // 50% of projectile damage over 10 ticks (5 seconds)
self.lastBurnTickTime = LK.ticks;
// Apply orange tint to indicate burn effect
tween(self, {
tint: 0xFF4400
}, {
duration: 500
});
} else {
// Refresh burn duration and recalculate damage if new burn is stronger
var newBurnDamage = projectileDamage * 0.5 / 10;
if (newBurnDamage > self.burnDamagePerTick) {
self.burnDamagePerTick = newBurnDamage;
}
self.burnEndTime = LK.ticks + 300;
}
};
self.reachBase = function () {
baseHP -= self.hp;
updateBaseHPDisplay();
if (baseHP <= 0) {
LK.showGameOver();
}
for (var i = 0; i < enemies.length; i++) {
if (enemies[i] === self) {
enemies.splice(i, 1);
break;
}
}
self.destroy();
};
return self;
});
var Projectile = Container.expand(function (startX, startY, target, damage, wizardType) {
var self = Container.call(this);
self.x = startX;
self.y = startY;
self.target = target;
self.damage = damage;
self.speed = 8;
self.wizardType = wizardType || 'fire';
var projectileGraphics = self.attachAsset('projectile', {
anchorX: 0.5,
anchorY: 0.5
});
self.update = function () {
if (!self.target || self.target.destroyed) {
self.destroy();
return;
}
var dx = self.target.x - self.x;
var dy = self.target.y - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance < 10) {
// Calculate actual damage taken (considering vulnerability)
var actualDamage = self.damage;
if (self.target.isVulnerable) {
actualDamage = self.damage * self.target.vulnerabilityMultiplier;
}
self.target.takeDamage(self.damage);
// Apply slow effect if ice wizard projectile
if (self.wizardType === 'ice') {
self.target.applySlow();
}
// Apply stun effect if light wizard projectile
if (self.wizardType === 'light') {
self.target.applyStun();
}
// Apply knockback effect if dark wizard projectile
if (self.wizardType === 'dark') {
self.target.applyKnockback();
}
// Apply vulnerability effect if nature wizard projectile
if (self.wizardType === 'nature') {
self.target.applyVulnerability();
}
// Apply burn effect if fire wizard projectile
if (self.wizardType === 'fire') {
self.target.applyBurn(self.damage);
}
self.destroy();
return;
}
self.x += dx / distance * self.speed;
self.y += dy / distance * self.speed;
};
return self;
});
var TowerSlot = Container.expand(function (x, y, towerNumber) {
var self = Container.call(this);
self.x = x;
self.y = y;
self.isEmpty = true;
self.wizard = null;
self.towerNumber = towerNumber;
var slotGraphics = self.attachAsset('tower_slot', {
anchorX: 0.5,
anchorY: 0.5
});
var towerNumberText = new Text2(self.towerNumber.toString(), {
size: 20,
fill: 0xFFFFFF
});
towerNumberText.anchor.set(0.5, 0.5);
towerNumberText.y = 0;
self.addChild(towerNumberText);
self.down = function (x, y, obj) {
if (self.isEmpty && (gameState === 'setup' || gameState === 'playing')) {
selectedTowerSlot = self;
gameState = 'wizard_selection';
createWizardSelectionPopup();
} else if (!self.isEmpty && self.wizard && (gameState === 'setup' || gameState === 'playing')) {
// Upgrade existing wizard
selectedTowerSlot = self;
gameState = 'wizard_upgrade';
createWizardUpgradePopup();
}
};
self.placeWizard = function (wizardType) {
var cost = 500;
if (gold >= cost) {
gold -= cost;
updateGoldDisplay();
self.wizard = new Wizard(wizardType, 1);
self.wizard.x = self.x;
self.wizard.y = self.y;
self.isEmpty = false;
game.addChild(self.wizard);
wizards.push(self.wizard);
draggedWizard = null;
}
};
return self;
});
var Wizard = Container.expand(function (elementType, level) {
var self = Container.call(this);
self.elementType = elementType || 'fire';
self.level = level || 1;
self.elementalDamage = 100 * self.level;
self.magicDamage = 100 * self.level;
self.range = (150 + self.level * 20) * 1.15;
self.attackSpeed = (1000 - self.level * 50) * 0.9; // ms between attacks - 10% faster
self.lastAttackTime = 0;
self.cost = 500 * Math.pow(1.8, self.level - 1);
var wizardGraphics = self.attachAsset('wizard_' + self.elementType, {
anchorX: 0.5,
anchorY: 0.5
});
var levelText = new Text2(self.level.toString(), {
size: 24,
fill: 0x000000,
font: "'GillSans-Bold',Impact,'Arial Black',Tahoma"
});
levelText.anchor.set(0.5, 0.5);
levelText.y = -50;
self.addChild(levelText);
self.update = function () {
if (LK.ticks - self.lastAttackTime > self.attackSpeed / 16.67) {
var target = self.findTarget();
if (target) {
self.attack(target);
self.lastAttackTime = LK.ticks;
}
}
};
self.findTarget = function () {
var closestEnemy = null;
var closestDistance = Infinity;
for (var i = 0; i < enemies.length; i++) {
var enemy = enemies[i];
var distance = Math.sqrt(Math.pow(enemy.x - self.x, 2) + Math.pow(enemy.y - self.y, 2));
if (distance <= self.range && distance < closestDistance) {
closestDistance = distance;
closestEnemy = enemy;
}
}
return closestEnemy;
};
self.attack = function (target) {
var projectile = new Projectile(self.x, self.y, target, self.elementalDamage + self.magicDamage, self.elementType);
projectiles.push(projectile);
game.addChild(projectile);
LK.getSound('shoot').play();
};
return self;
});
var WizardIcon = Container.expand(function (elementType) {
var self = Container.call(this);
self.elementType = elementType;
var iconGraphics = self.attachAsset('wizard_' + elementType, {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 0.8,
scaleY: 0.8
});
var costText = new Text2('500', {
size: 20,
fill: 0xFFFFFF
});
costText.anchor.set(0.5, 0);
costText.y = 45;
self.addChild(costText);
self.down = function (x, y, obj) {
// Wizard icons are now handled by tower slots
};
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0x228B22
});
/****
* Game Code
****/
// Add cobblestone background
var cobblestoneBackground = game.addChild(LK.getAsset('cobblestone_bg', {
anchorX: 0,
anchorY: 0
}));
cobblestoneBackground.x = 0;
cobblestoneBackground.y = 0;
// Game state variables
var currentLevel = 1;
var currentWave = 1;
var maxWaves = 10;
var gold = 1000;
var baseHP = 10000;
var gameState = 'setup'; // 'setup', 'playing', 'paused', 'between_waves', 'wizard_selection'
var waveSpawnTimer = 0;
var enemiesSpawnedThisWave = 0;
var enemiesPerWave = 10;
var draggedWizard = null;
var gameStarted = false;
var waveStartTime = 0;
var lastWaveEndTime = 0;
var lastEnemySpawnTime = 0;
var selectedTowerSlot = null;
var wizardSelectionPopup = null;
var waveInProgress = false;
var allEnemiesEnteredMaze = false;
var lastEnemyEnteredTime = 0;
var enemySpawnDelay = 60; // 1 second at 60fps
var lastSpawnTime = 0;
var currentWaveEnemyType;
// Game object arrays
var enemies = [];
var wizards = [];
var projectiles = [];
var towerSlots = [];
var wizardIcons = [];
var damageDisplays = [];
// Path system
var currentPath = [];
var pathPoints = [{
x: 50,
y: 350
}, {
x: 200,
y: 350
}, {
x: 200,
y: 500
}, {
x: 350,
y: 500
}, {
x: 350,
y: 650
}, {
x: 150,
y: 650
}, {
x: 150,
y: 800
}, {
x: 450,
y: 800
}, {
x: 450,
y: 950
}, {
x: 650,
y: 950
}, {
x: 650,
y: 750
}, {
x: 800,
y: 750
}, {
x: 800,
y: 1100
}, {
x: 550,
y: 1100
}, {
x: 550,
y: 1250
}, {
x: 900,
y: 1250
}, {
x: 900,
y: 1400
}, {
x: 700,
y: 1400
}, {
x: 700,
y: 1550
}, {
x: 1050,
y: 1550
}, {
x: 1050,
y: 1200
}, {
x: 1200,
y: 1200
}, {
x: 1200,
y: 1700
}, {
x: 1000,
y: 1700
}, {
x: 1000,
y: 1850
}, {
x: 1350,
y: 1850
}, {
x: 1350,
y: 1500
}, {
x: 1500,
y: 1500
}, {
x: 1500,
y: 2000
}, {
x: 1700,
y: 2000
}];
// UI elements - Create scoreboard container without background
var scoreboardContainer = new Container();
var goldText = new Text2('Gold: ' + gold, {
size: 60,
fill: 0x000000,
font: "'GillSans-Bold',Impact,'Arial Black',Tahoma"
});
goldText.anchor.set(1, 0);
goldText.x = 2048 - 40; // 40px from right edge
goldText.y = 40;
scoreboardContainer.addChild(goldText);
var baseHPText = new Text2('Base HP: ' + baseHP, {
size: 60,
fill: 0x000000,
font: "'GillSans-Bold',Impact,'Arial Black',Tahoma"
});
baseHPText.anchor.set(1, 0);
baseHPText.x = 2048 - 40; // 40px from right edge
baseHPText.y = 110;
scoreboardContainer.addChild(baseHPText);
var waveText = new Text2('Wave: ' + currentWave + '/' + maxWaves, {
size: 60,
fill: 0x000000,
font: "'GillSans-Bold',Impact,'Arial Black',Tahoma"
});
waveText.anchor.set(1, 0);
waveText.x = 2048 - 40; // 40px from right edge
waveText.y = 180;
scoreboardContainer.addChild(waveText);
game.addChild(scoreboardContainer);
// Initialize path
currentPath = pathPoints;
// Create continuous road path between waypoints
for (var i = 0; i < currentPath.length - 1; i++) {
var startPoint = currentPath[i];
var endPoint = currentPath[i + 1];
// Calculate distance and direction between points
var dx = endPoint.x - startPoint.x;
var dy = endPoint.y - startPoint.y;
var distance = Math.sqrt(dx * dx + dy * dy);
var steps = Math.ceil(distance / 50); // Place road tiles every 50 pixels
// Create road tiles along the path
for (var j = 0; j <= steps; j++) {
var progress = j / steps;
var roadX = startPoint.x + dx * progress;
var roadY = startPoint.y + dy * progress;
var roadTile = game.addChild(LK.getAsset('road_tile', {
anchorX: 0.5,
anchorY: 0.5
}));
roadTile.x = roadX;
roadTile.y = roadY;
roadTile.tint = 0x444444; // Darker road color
}
}
// Create home base
var homeBase = game.addChild(LK.getAsset('home_base', {
anchorX: 0.5,
anchorY: 0.5
}));
homeBase.x = currentPath[currentPath.length - 1].x;
homeBase.y = currentPath[currentPath.length - 1].y;
// Create tower slots - positioned outside enemy path but within level 1 wizard range (170px) of enemy path
var towerPositions = [{
x: 270,
y: 285
}, {
x: 430,
y: 440
}, {
x: 70,
y: 730
}, {
x: 520,
y: 730
}, {
x: 780,
y: 1475
}, {
x: 390,
y: 1025
}, {
x: 720,
y: 1005
}, {
x: 1125,
y: 1110
}, {
x: 750,
y: 1175
}, {
x: 1125,
y: 1380
}, {
x: 1110,
y: 1775
}, {
x: 1425,
y: 1590
}];
for (var i = 0; i < towerPositions.length; i++) {
var slot = new TowerSlot(towerPositions[i].x, towerPositions[i].y, i + 1);
towerSlots.push(slot);
game.addChild(slot);
}
// Create UI bottom panel
var uiPanel = game.addChild(LK.getAsset('ui_background', {
anchorX: 0,
anchorY: 1
}));
uiPanel.x = 0;
uiPanel.y = 2732;
// Create start wave button
var startWaveButton = LK.getAsset('ui_background', {
width: 200,
height: 60,
anchorX: 0.5,
anchorY: 0.5
});
startWaveButton.tint = 0x00AA00;
startWaveButton.x = 1024;
startWaveButton.y = 200;
var startWaveText = new Text2('Start Game', {
size: 30,
fill: 0xFFFFFF
});
startWaveText.anchor.set(0.5, 0.5);
startWaveButton.addChild(startWaveText);
startWaveButton.visible = true;
game.addChild(startWaveButton);
// Wizard icons removed - now handled by tower slot clicks
// Add click handlers
startWaveButton.down = function () {
if (gameState === 'menu' || gameState === 'setup') {
gameState = 'playing';
startWaveButton.visible = false;
gameStarted = true;
// Calculate max waves based on level
var wavesPerLevel = [7, 8, 10, 11, 13, 14, 16, 17, 19, 20, 22, 23, 25, 26, 28, 29, 31, 32, 34, 35];
maxWaves = wavesPerLevel[currentLevel - 1] || 10;
enemiesPerWave = getEnemiesPerWave();
updateWaveDisplay();
// Start first wave automatically
waveInProgress = true;
allEnemiesEnteredMaze = false;
lastSpawnTime = 0;
}
};
// Create wizard selection popup
function createWizardSelectionPopup() {
wizardSelectionPopup = new Container();
// Background - make it larger to accommodate level selection
var popupBg = LK.getAsset('ui_background', {
width: 2000,
height: 1600,
anchorX: 0.5,
anchorY: 0.5
});
popupBg.tint = 0x222222;
popupBg.x = 1024;
popupBg.y = 1366;
wizardSelectionPopup.addChild(popupBg);
// Title
var titleText = new Text2('Select Wizard & Level', {
size: 50,
fill: 0xFFFFFF
});
titleText.anchor.set(0.5, 0.5);
titleText.x = 1024;
titleText.y = 1050;
wizardSelectionPopup.addChild(titleText);
// Add selected wizard type and level tracking
wizardSelectionPopup.selectedWizardType = 'fire';
wizardSelectionPopup.selectedLevel = 1;
// Wizard type buttons
var wizardTypes = ['fire', 'ice', 'nature', 'dark', 'light'];
var wizardNames = ['Fire Wizard', 'Ice Wizard', 'Nature Wizard', 'Dark Wizard', 'Light Wizard'];
var wizardTypeButtons = [];
for (var i = 0; i < wizardTypes.length; i++) {
var wizardBtn = LK.getAsset('wizard_' + wizardTypes[i], {
anchorX: 0.5,
anchorY: 0.5
});
wizardBtn.x = 824; // Move wizard icon to left
wizardBtn.y = 1150 + i * 120; // Arrange vertically from top to bottom
wizardBtn.wizardType = wizardTypes[i];
wizardBtn.alpha = wizardTypes[i] === 'fire' ? 1.0 : 0.6; // Highlight selected
// Add wizard name text
var nameText = new Text2(wizardNames[i], {
size: 40,
fill: 0xFFFFFF
});
nameText.anchor.set(0, 0.5);
nameText.x = 924; // Position to right of wizard icon
nameText.y = 1130 + i * 120;
wizardSelectionPopup.addChild(nameText);
// Add cost text
var wizardCostText = new Text2('Cost: 500', {
size: 32,
fill: 0xFFFF00
});
wizardCostText.anchor.set(0, 0.5);
wizardCostText.x = 924;
wizardCostText.y = 1160 + i * 120;
wizardSelectionPopup.addChild(wizardCostText);
// Add level text
var wizardLevelText = new Text2('Level: 1', {
size: 32,
fill: 0x00FF00
});
wizardLevelText.anchor.set(0, 0.5);
wizardLevelText.x = 924;
wizardLevelText.y = 1190 + i * 120;
wizardSelectionPopup.addChild(wizardLevelText);
// Add elemental damage text
var elementNames = {
'fire': 'Fire',
'ice': 'Ice',
'nature': 'Nature',
'dark': 'Dark',
'light': 'Light'
};
var wizardDamageText = new Text2(elementNames[wizardTypes[i]] + ': 100', {
size: 32,
fill: 0xFF6600
});
wizardDamageText.anchor.set(0, 0.5);
wizardDamageText.x = 1224;
wizardDamageText.y = 1160 + i * 120;
wizardSelectionPopup.addChild(wizardDamageText);
// Add magic damage text
var wizardMagicDamageText = new Text2('Magic: 100', {
size: 32,
fill: 0x9966FF
});
wizardMagicDamageText.anchor.set(0, 0.5);
wizardMagicDamageText.x = 1224;
wizardMagicDamageText.y = 1190 + i * 120;
wizardSelectionPopup.addChild(wizardMagicDamageText);
// Store references to text elements for updates
wizardBtn.nameText = nameText;
wizardBtn.costText = wizardCostText;
wizardBtn.levelText = wizardLevelText;
wizardBtn.damageText = wizardDamageText;
wizardBtn.magicDamageText = wizardMagicDamageText;
wizardBtn.down = function () {
// Update selected wizard type
wizardSelectionPopup.selectedWizardType = this.wizardType;
// Update button highlighting
for (var j = 0; j < wizardTypeButtons.length; j++) {
wizardTypeButtons[j].alpha = wizardTypeButtons[j].wizardType === this.wizardType ? 1.0 : 0.6;
}
// Update cost display
updateCostDisplay();
// Update individual wizard info
updateWizardInfo();
};
wizardSelectionPopup.addChild(wizardBtn);
wizardTypeButtons.push(wizardBtn);
}
// Level selection section
var levelTitleText = new Text2('Level:', {
size: 40,
fill: 0xFFFFFF
});
levelTitleText.anchor.set(0.5, 0.5);
levelTitleText.x = 1024;
levelTitleText.y = 1770;
wizardSelectionPopup.addChild(levelTitleText);
// Level buttons (1-10)
var levelButtons = [];
for (var i = 1; i <= 10; i++) {
var levelBtn = LK.getAsset('ui_background', {
width: 80,
height: 60,
anchorX: 0.5,
anchorY: 0.5
});
levelBtn.tint = i === 1 ? 0x00AA00 : 0x666666; // Highlight level 1
levelBtn.x = 400 + (i - 1) * 80;
levelBtn.y = 1850;
levelBtn.level = i;
// Add level number text
var levelNumText = new Text2(i.toString(), {
size: 30,
fill: 0xFFFFFF
});
levelNumText.anchor.set(0.5, 0.5);
levelBtn.addChild(levelNumText);
levelBtn.down = function () {
// Update selected level
wizardSelectionPopup.selectedLevel = this.level;
// Update button highlighting
for (var j = 0; j < levelButtons.length; j++) {
levelButtons[j].tint = levelButtons[j].level === this.level ? 0x00AA00 : 0x666666;
}
// Update cost display
updateCostDisplay();
// Update individual wizard info
updateWizardInfo();
};
wizardSelectionPopup.addChild(levelBtn);
levelButtons.push(levelBtn);
}
// Cost display
var costText = new Text2('Cost: 500', {
size: 35,
fill: 0xFFFF00
});
costText.anchor.set(0.5, 0.5);
costText.x = 1024;
costText.y = 1930;
wizardSelectionPopup.addChild(costText);
// Function to update cost display
function updateCostDisplay() {
var cost = 500 * Math.pow(1.8, wizardSelectionPopup.selectedLevel - 1);
costText.setText('Cost: ' + Math.floor(cost));
costText.tint = gold >= cost ? 0xFFFF00 : 0xFF0000; // Yellow if affordable, red if not
}
// Function to update wizard info display
function updateWizardInfo() {
var cost = 500 * Math.pow(1.8, wizardSelectionPopup.selectedLevel - 1);
var elementalDamage = 100 * wizardSelectionPopup.selectedLevel;
var magicDamage = 100 * wizardSelectionPopup.selectedLevel;
var elementNames = {
'fire': 'Fire',
'ice': 'Ice',
'nature': 'Nature',
'dark': 'Dark',
'light': 'Light'
};
for (var i = 0; i < wizardTypeButtons.length; i++) {
var btn = wizardTypeButtons[i];
btn.costText.setText('Cost: ' + Math.floor(cost));
btn.costText.tint = gold >= cost ? 0xFFFF00 : 0xFF0000;
btn.levelText.setText('Level: ' + wizardSelectionPopup.selectedLevel);
btn.damageText.setText(elementNames[btn.wizardType] + ': ' + elementalDamage);
btn.magicDamageText.setText('Magic: ' + magicDamage);
}
}
// Hire button
var hireBtn = LK.getAsset('ui_background', {
width: 200,
height: 60,
anchorX: 0.5,
anchorY: 0.5
});
hireBtn.tint = 0x00AA00;
hireBtn.x = 924;
hireBtn.y = 2000;
var hireText = new Text2('Hire', {
size: 30,
fill: 0xFFFFFF
});
hireText.anchor.set(0.5, 0.5);
hireBtn.addChild(hireText);
hireBtn.down = function () {
placeWizardOnSelectedSlot(wizardSelectionPopup.selectedWizardType, wizardSelectionPopup.selectedLevel);
};
wizardSelectionPopup.addChild(hireBtn);
// Close button
var closeBtn = LK.getAsset('ui_background', {
width: 100,
height: 50,
anchorX: 0.5,
anchorY: 0.5
});
closeBtn.tint = 0xff0000;
closeBtn.x = 1400;
closeBtn.y = 1050;
var closeText = new Text2('X', {
size: 30,
fill: 0xFFFFFF
});
closeText.anchor.set(0.5, 0.5);
closeBtn.addChild(closeText);
closeBtn.down = function () {
closeWizardSelectionPopup();
};
wizardSelectionPopup.addChild(closeBtn);
game.addChild(wizardSelectionPopup);
}
function closeWizardSelectionPopup() {
if (wizardSelectionPopup) {
wizardSelectionPopup.destroy();
wizardSelectionPopup = null;
selectedTowerSlot = null;
gameState = gameStarted ? 'playing' : 'setup';
}
}
function createWizardUpgradePopup() {
var wizard = selectedTowerSlot.wizard;
var upgradePopup = new Container();
// Background
var popupBg = LK.getAsset('ui_background', {
width: 1000,
height: 800,
anchorX: 0.5,
anchorY: 0.5
});
popupBg.tint = 0x222222;
popupBg.x = 1024;
popupBg.y = 1366;
upgradePopup.addChild(popupBg);
// Title
var titleText = new Text2('Upgrade Wizard', {
size: 50,
fill: 0xFFFFFF
});
titleText.anchor.set(0.5, 0.5);
titleText.x = 1024;
titleText.y = 1100;
upgradePopup.addChild(titleText);
// Current wizard info
var currentInfoText = new Text2('Current Level: ' + wizard.level, {
size: 35,
fill: 0xFFFFFF
});
currentInfoText.anchor.set(0.5, 0.5);
currentInfoText.x = 1024;
currentInfoText.y = 1200;
upgradePopup.addChild(currentInfoText);
var elementNames = {
'fire': 'Fire',
'ice': 'Ice',
'nature': 'Nature',
'dark': 'Dark',
'light': 'Light'
};
var currentDamageText = new Text2(elementNames[wizard.elementType] + ': ' + wizard.elementalDamage + ' | Magic: ' + wizard.magicDamage, {
size: 30,
fill: 0x00FF00
});
currentDamageText.anchor.set(0.5, 0.5);
currentDamageText.x = 1024;
currentDamageText.y = 1250;
upgradePopup.addChild(currentDamageText);
// Upgrade info (if not max level)
if (wizard.level < 10) {
var upgradeCost = Math.floor(500 * Math.pow(1.8, wizard.level) * 0.6);
var newElementalDamage = 100 * (wizard.level + 1);
var newMagicDamage = 100 * (wizard.level + 1);
var upgradeInfoText = new Text2('Upgrade to Level ' + (wizard.level + 1), {
size: 35,
fill: 0xFFFF00
});
upgradeInfoText.anchor.set(0.5, 0.5);
upgradeInfoText.x = 1024;
upgradeInfoText.y = 1350;
upgradePopup.addChild(upgradeInfoText);
var upgradeDamageText = new Text2(elementNames[wizard.elementType] + ': ' + newElementalDamage + ' | Magic: ' + newMagicDamage, {
size: 30,
fill: 0x00FF00
});
upgradeDamageText.anchor.set(0.5, 0.5);
upgradeDamageText.x = 1024;
upgradeDamageText.y = 1400;
upgradePopup.addChild(upgradeDamageText);
var costText = new Text2('Cost: ' + upgradeCost, {
size: 35,
fill: gold >= upgradeCost ? 0xFFFF00 : 0xFF0000
});
costText.anchor.set(0.5, 0.5);
costText.x = 1024;
costText.y = 1450;
upgradePopup.addChild(costText);
// Upgrade button
var upgradeBtn = LK.getAsset('ui_background', {
width: 200,
height: 60,
anchorX: 0.5,
anchorY: 0.5
});
upgradeBtn.tint = gold >= upgradeCost ? 0x00AA00 : 0x666666;
upgradeBtn.x = 924;
upgradeBtn.y = 1550;
var upgradeText = new Text2('Upgrade', {
size: 30,
fill: 0xFFFFFF
});
upgradeText.anchor.set(0.5, 0.5);
upgradeBtn.addChild(upgradeText);
upgradeBtn.down = function () {
if (gold >= upgradeCost) {
upgradeWizard(wizard, upgradeCost);
upgradePopup.destroy();
selectedTowerSlot = null;
gameState = gameStarted ? 'playing' : 'setup';
}
};
upgradePopup.addChild(upgradeBtn);
} else {
// Max level reached
var maxLevelText = new Text2('Maximum Level Reached!', {
size: 40,
fill: 0xFF6600
});
maxLevelText.anchor.set(0.5, 0.5);
maxLevelText.x = 1024;
maxLevelText.y = 1400;
upgradePopup.addChild(maxLevelText);
}
// Close button
var closeBtn = LK.getAsset('ui_background', {
width: 100,
height: 50,
anchorX: 0.5,
anchorY: 0.5
});
closeBtn.tint = 0xff0000;
closeBtn.x = 1124;
closeBtn.y = 1550;
var closeText = new Text2('X', {
size: 30,
fill: 0xFFFFFF
});
closeText.anchor.set(0.5, 0.5);
closeBtn.addChild(closeText);
closeBtn.down = function () {
upgradePopup.destroy();
selectedTowerSlot = null;
gameState = gameStarted ? 'playing' : 'setup';
};
upgradePopup.addChild(closeBtn);
game.addChild(upgradePopup);
}
function upgradeWizard(wizard, cost) {
gold -= cost;
updateGoldDisplay();
// Upgrade wizard stats
wizard.level++;
wizard.elementalDamage = 100 * wizard.level;
wizard.magicDamage = 100 * wizard.level;
wizard.range = (150 + wizard.level * 20) * 1.15;
wizard.attackSpeed = (1000 - wizard.level * 50) * 0.9;
wizard.cost = 500 * Math.pow(1.8, wizard.level - 1);
// Update level display on wizard
var levelText = wizard.children.find(function (child) {
return child instanceof Text2;
});
if (levelText) {
levelText.setText(wizard.level.toString());
}
// Update range circle if it exists
if (wizard.rangeCircle) {
wizard.rangeCircle.scaleX = wizard.range / 45;
wizard.rangeCircle.scaleY = wizard.range / 45;
}
}
function placeWizardOnSelectedSlot(wizardType, level) {
if (selectedTowerSlot && selectedTowerSlot.isEmpty) {
var cost = 500 * Math.pow(1.8, level - 1);
if (gold >= cost) {
gold -= cost;
updateGoldDisplay();
var wizard = new Wizard(wizardType, level);
wizard.x = selectedTowerSlot.x;
wizard.y = selectedTowerSlot.y;
selectedTowerSlot.wizard = wizard;
selectedTowerSlot.isEmpty = false;
// Add range visualization
var rangeCircle = LK.getAsset('tower_slot', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: wizard.range / 45,
scaleY: wizard.range / 45
});
rangeCircle.tint = 0x00ff00;
rangeCircle.alpha = 0.2;
rangeCircle.x = wizard.x;
rangeCircle.y = wizard.y;
game.addChild(rangeCircle);
wizard.rangeCircle = rangeCircle;
game.addChild(wizard);
wizards.push(wizard);
closeWizardSelectionPopup();
}
}
}
// Helper functions
function updateGoldDisplay() {
goldText.setText('Gold: ' + gold);
}
function updateBaseHPDisplay() {
baseHPText.setText('Base HP: ' + baseHP);
}
function updateWaveDisplay() {
waveText.setText('Wave: ' + currentWave + '/' + maxWaves);
}
function spawnEnemy() {
// Determine enemy type for this wave - same type for all enemies in wave
if (currentWaveEnemyType === undefined) {
var rand = Math.random();
if (rand < 0.6) currentWaveEnemyType = 'basic';else if (rand < 0.85) currentWaveEnemyType = 'fast';else currentWaveEnemyType = 'tank';
}
var enemy = new Enemy(currentWaveEnemyType, currentLevel);
enemy.x = currentPath[0].x;
enemy.y = currentPath[0].y;
enemies.push(enemy);
game.addChild(enemy);
}
function getEnemiesPerWave() {
var min, max;
if (currentLevel >= 1 && currentLevel <= 5) {
min = 10;
max = 20;
} else if (currentLevel >= 6 && currentLevel <= 10) {
min = 15;
max = 25;
} else if (currentLevel >= 11 && currentLevel <= 15) {
min = 20;
max = 35;
} else if (currentLevel >= 16 && currentLevel <= 20) {
min = 35;
max = 50;
} else {
min = 50;
max = 70;
}
return Math.floor(Math.random() * (max - min + 1)) + min;
}
function startNextWave() {
currentWave++;
enemiesSpawnedThisWave = 0;
enemiesPerWave = getEnemiesPerWave();
lastWaveEndTime = LK.ticks;
gameState = 'playing';
currentWaveEnemyType = undefined; // Reset enemy type for new wave
updateWaveDisplay();
}
// Game update loop
game.update = function () {
// Only update game logic if not in setup or wizard selection
if (gameState === 'setup' || gameState === 'wizard_selection' || gameState === 'wizard_upgrade') {
return;
}
// Spawn enemies with proper timing
if (gameState === 'playing' && waveInProgress && enemiesSpawnedThisWave < enemiesPerWave) {
// Only spawn if enough time has passed since last spawn
if (LK.ticks - lastSpawnTime >= enemySpawnDelay) {
spawnEnemy();
enemiesSpawnedThisWave++;
lastSpawnTime = LK.ticks;
// Check if all enemies have been spawned for this wave
if (enemiesSpawnedThisWave >= enemiesPerWave) {
allEnemiesEnteredMaze = true;
lastEnemyEnteredTime = LK.ticks;
}
}
}
// Check if wave is complete - all enemies spawned and all enemies cleared
if (waveInProgress && allEnemiesEnteredMaze && enemies.length === 0) {
// Wait 5 seconds after last enemy entered before allowing next wave
if (LK.ticks - lastEnemyEnteredTime >= 300) {
// 5 seconds at 60fps
waveInProgress = false;
allEnemiesEnteredMaze = false;
enemiesSpawnedThisWave = 0;
gameState = 'between_waves';
lastWaveEndTime = LK.ticks;
waveStartTime = 0;
if (currentWave < maxWaves) {
// Automatically start next wave after 5 seconds
startNextWave();
waveInProgress = true;
allEnemiesEnteredMaze = false;
lastSpawnTime = 0;
} else {
// Level complete
currentLevel++;
currentWave = 0; // Will be incremented in startNextWave
var wavesPerLevel = [7, 8, 10, 11, 13, 14, 16, 17, 19, 20, 22, 23, 25, 26, 28, 29, 31, 32, 34, 35];
maxWaves = wavesPerLevel[currentLevel - 1] || 10;
enemiesPerWave = getEnemiesPerWave();
if (currentLevel > 20) {
LK.showYouWin();
return;
}
// Automatically start next level
startNextWave();
waveInProgress = true;
allEnemiesEnteredMaze = false;
lastSpawnTime = 0;
}
}
}
// Update projectiles
for (var i = projectiles.length - 1; i >= 0; i--) {
var projectile = projectiles[i];
if (projectile.destroyed) {
projectiles.splice(i, 1);
}
}
// Clean up destroyed enemies
for (var i = enemies.length - 1; i >= 0; i--) {
if (enemies[i].destroyed) {
enemies.splice(i, 1);
}
}
// Clean up destroyed wizards
for (var i = wizards.length - 1; i >= 0; i--) {
if (wizards[i].destroyed) {
wizards.splice(i, 1);
}
}
// Clean up destroyed damage displays
for (var i = damageDisplays.length - 1; i >= 0; i--) {
if (damageDisplays[i].destroyed) {
damageDisplays.splice(i, 1);
}
}
};
Fort to defend. In-Game asset. 2d. High contrast. No shadows
bulky warrior like a tank. In-Game asset. 2d. High contrast. No shadows
fast and slim warrior to represent speed. In-Game asset. 2d. High contrast. No shadows
in game asset, colored, 2d warrior, face covered with helmet
dark wizard 2d in game asset. In-Game asset. 2d. High contrast. No shadows
mostly transparent grey circle. In-Game asset. 2d. High contrast. No shadows
top view, stone high tower.. In-Game asset. 2d. High contrast. No shadows
a fire wizard. In-Game asset. 2d. High contrast. No shadows
cornerstone 2d in game asset from top view
2d in game asset ice wizard. In-Game asset. 2d. High contrast. No shadows
2d in game asset nature (green) wizard.
2d in game asset light (yellow) wizard.
magic bolt traveling in air
2d, top view, very light grey, very very very small tiled (at least 1000 tiles), cobblestone road texture