User prompt
Elemental Tower Defense: Wizard's Maze
Initial prompt
i want you to create a classical 2d tower defense game. there shall be multiple individual levels that player unlocks next level by beating the previous one and starting from level 1. there shall be a total of 20 levels with increasing difficulty. in this game 10 different type of enemies will enter from one or more entrance points to a maze that leads to home base. at first five levels, always have one entrance point only. from levels 6-15 there shall be no more than 2 entrance points. for the last 5 levels you may select upto 3 entrance if you want but not mandatory. but there is one rule. if there are more than one entries, paths of those different entries shall merge before 65% length of the longest path of maze to home base. also different entry points shall have an individual path not crossing or merging with another for a lenght of at least 35% of longest path to home base. mazes shall be long enough to keep the game playable but also short enough that the game is not too easy. maze will be defended by 5 different type of wizards placed around the maze on tower(s). tower numbers shall be reasonable to maze structure and length. wizards will be fire wizard, ice wizard, nature wizard, dark wizard and light wizard. each wizard will shoot spells including both magic and their elemental damage. base elemental damage will be 100 and base magic damage will be 100. wizards shall be mergeable on towers (only) so their elemental damages merges but magic damages only multiplies by 1.5. for example if two level 1 ice wizard with 100 ice damage and 100 magic damage is merged they will become 1x level 2 ice wizard and with 200 ice damage and 150 magic damage. if one level 1 ice wizard and one level 1 fire wizard is merged then it will become an IF wizard that shoot 100 ice damage, 100 fire damage and 150 magic damage. for creating name of combined wizard you can simply merge first letter if their elemental damages are different. new name letters shall be alphabetical order. only same level wizards can be merged to form one higher level. for example player cannot merge a level 2 and level 1 wizard. but two level 2 wizards can be merged to form a level 3 wizard. there will be no limit of level of wizards. home base will have a base HP scaling with difficulty of each level. whenever an enemy enters to home base, its remaining HP shall be deducted from home base HP. players will select desired wizard type from the menu on bottom and drag them into the tower they desire. players shall spend their gold for hiring wizard to defend the home base. whenever the wizard is placed on tower it will be ready to fire if level is started. level 1 wizards will be 500 gold. players shall also have the choice of hiring a higher level wizard if they want and each level wizard cost shall be %80 high than previous level of wizard. for example a level 1 ice wizard will be 500 gold but a level 2 fire or nature or dark wizard will be 900 gold. player shall be able to select level of the wizard with a + and - signs next to wizard selection box. when a wizard is hired from the box which is higher than level 1, its damage shall be calculated same as merging scenario. for example if player hires a level 3 wizard its damage scale shall be a merge of two level 2 wizards. players will start with 1000 gold to first level and following levels shall have increasing gold according to the increased difficulty (we can later define each levels starting gold). when player kills an enemy its initial HP will be earned as a gold. enemies have reasonable HP so a player shall enough resource to hire new wizards until the end of the level but also not be able to be overpowered to clear level too easily. each level have a specific number of waves and specific number of enemies in each wave. wave per level will be 7, 8, 10, 11, 13, 14, 16, 17, 19, 20, 22, 23, 25, 26, 28, 29, 31, 32, 34, 35 from level 1 to level 20. each wave also have 10-20 random number of enemies from level 1 to 5, 15-25 random number of enemies for level 6-10, 20-35 for levels 11-15 and 35-50 for levels 16-20. players shall be able to start the level whenever they want. however once the level is started they either win or fail the level. however there shall be a pause button. when game is unpaused a countdown shall inform player with a countdown from 3 to give them a breathing space.
/**** * 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; // Calculate wave-based HP multiplier (5% increase per wave) var waveHPMultiplier = Math.pow(1.05, currentWave - 1); switch (self.enemyType) { case 'basic': self.maxHP = (100 + level * 50) * 5 * 1.2 * 1.15 * waveHPMultiplier; self.speed = 2 * 0.8; break; case 'fast': self.maxHP = (50 + level * 25) * 5 * 1.2 * 1.15 * waveHPMultiplier; self.speed = 4 * 0.8; break; case 'tank': self.maxHP = (300 + level * 100) * 5 * 1.2 * 0.7 * 1.15 * waveHPMultiplier; self.speed = 1 * 0.8; 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 / 20; updateGoldDisplay(); self.destroy(); gold += self.goldValue / 20; 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 * 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 furthestEnemy = null; var furthestDistance = -1; 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 > furthestDistance) { furthestDistance = distance; furthestEnemy = enemy; } } return furthestEnemy; }; 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: 200, y: 550 }, { x: 350, y: 550 }, { x: 350, y: 700 }, { x: 500, y: 700 }, { x: 500, y: 850 }, { x: 300, y: 850 }, { x: 300, y: 1000 }, { x: 600, y: 1000 }, { x: 600, y: 1150 }, { x: 800, y: 1150 }, { x: 800, y: 950 }, { x: 950, y: 950 }, { x: 950, y: 1300 }, { x: 700, y: 1300 }, { x: 700, y: 1450 }, { x: 1050, y: 1450 }, { x: 1050, y: 1600 }, { x: 850, y: 1600 }, { x: 850, y: 1750 }, { x: 1200, y: 1750 }, { x: 1200, y: 1400 }, { x: 1350, y: 1400 }, { x: 1350, y: 1900 }, { x: 1150, y: 1900 }, { x: 1150, y: 2050 }, { x: 1500, y: 2050 }, { x: 1500, y: 1700 }, { x: 1650, y: 1700 }, { x: 1650, y: 2200 }, { x: 1850, y: 2200 }]; // 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); var levelText = new Text2('Level: ' + currentLevel, { size: 60, fill: 0x000000, font: "'GillSans-Bold',Impact,'Arial Black',Tahoma" }); levelText.anchor.set(1, 0); levelText.x = 2048 - 40; // 40px from right edge levelText.y = 250; scoreboardContainer.addChild(levelText); 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: 420, y: 485 }, { x: 580, y: 640 }, { x: 220, y: 930 }, { x: 670, y: 930 }, { x: 930, y: 1675 }, { x: 540, y: 1225 }, { x: 870, y: 1205 }, { x: 1275, y: 1310 }, { x: 900, y: 1375 }, { x: 1275, y: 1580 }, { x: 1260, y: 1975 }, { x: 1575, y: 1790 }]; 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 = 1174; startWaveButton.y = 400; 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 * 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: ' + Math.ceil(gold)); } function updateBaseHPDisplay() { baseHPText.setText('Base HP: ' + baseHP); } function updateWaveDisplay() { waveText.setText('Wave: ' + currentWave + '/' + maxWaves); } function updateLevelDisplay() { levelText.setText('Level: ' + currentLevel); } 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(); updateLevelDisplay(); 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;
// Calculate wave-based HP multiplier (5% increase per wave)
var waveHPMultiplier = Math.pow(1.05, currentWave - 1);
switch (self.enemyType) {
case 'basic':
self.maxHP = (100 + level * 50) * 5 * 1.2 * 1.15 * waveHPMultiplier;
self.speed = 2 * 0.8;
break;
case 'fast':
self.maxHP = (50 + level * 25) * 5 * 1.2 * 1.15 * waveHPMultiplier;
self.speed = 4 * 0.8;
break;
case 'tank':
self.maxHP = (300 + level * 100) * 5 * 1.2 * 0.7 * 1.15 * waveHPMultiplier;
self.speed = 1 * 0.8;
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 / 20;
updateGoldDisplay();
self.destroy();
gold += self.goldValue / 20;
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 * 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 furthestEnemy = null;
var furthestDistance = -1;
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 > furthestDistance) {
furthestDistance = distance;
furthestEnemy = enemy;
}
}
return furthestEnemy;
};
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: 200,
y: 550
}, {
x: 350,
y: 550
}, {
x: 350,
y: 700
}, {
x: 500,
y: 700
}, {
x: 500,
y: 850
}, {
x: 300,
y: 850
}, {
x: 300,
y: 1000
}, {
x: 600,
y: 1000
}, {
x: 600,
y: 1150
}, {
x: 800,
y: 1150
}, {
x: 800,
y: 950
}, {
x: 950,
y: 950
}, {
x: 950,
y: 1300
}, {
x: 700,
y: 1300
}, {
x: 700,
y: 1450
}, {
x: 1050,
y: 1450
}, {
x: 1050,
y: 1600
}, {
x: 850,
y: 1600
}, {
x: 850,
y: 1750
}, {
x: 1200,
y: 1750
}, {
x: 1200,
y: 1400
}, {
x: 1350,
y: 1400
}, {
x: 1350,
y: 1900
}, {
x: 1150,
y: 1900
}, {
x: 1150,
y: 2050
}, {
x: 1500,
y: 2050
}, {
x: 1500,
y: 1700
}, {
x: 1650,
y: 1700
}, {
x: 1650,
y: 2200
}, {
x: 1850,
y: 2200
}];
// 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);
var levelText = new Text2('Level: ' + currentLevel, {
size: 60,
fill: 0x000000,
font: "'GillSans-Bold',Impact,'Arial Black',Tahoma"
});
levelText.anchor.set(1, 0);
levelText.x = 2048 - 40; // 40px from right edge
levelText.y = 250;
scoreboardContainer.addChild(levelText);
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: 420,
y: 485
}, {
x: 580,
y: 640
}, {
x: 220,
y: 930
}, {
x: 670,
y: 930
}, {
x: 930,
y: 1675
}, {
x: 540,
y: 1225
}, {
x: 870,
y: 1205
}, {
x: 1275,
y: 1310
}, {
x: 900,
y: 1375
}, {
x: 1275,
y: 1580
}, {
x: 1260,
y: 1975
}, {
x: 1575,
y: 1790
}];
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 = 1174;
startWaveButton.y = 400;
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 * 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: ' + Math.ceil(gold));
}
function updateBaseHPDisplay() {
baseHPText.setText('Base HP: ' + baseHP);
}
function updateWaveDisplay() {
waveText.setText('Wave: ' + currentWave + '/' + maxWaves);
}
function updateLevelDisplay() {
levelText.setText('Level: ' + currentLevel);
}
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();
updateLevelDisplay();
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