User prompt
Aumenta y disminuye la escala de las torres en 8% cada segundo. A las torres que se colocan y a las predeterminadas. ↪💡 Consider importing and using the following plugins: @upit/tween.v1
User prompt
Please fix the bug: 'ReferenceError: baseGraphics is not defined' in or related to this line: 'tween(baseGraphics, {' Line Number: 657 ↪💡 Consider importing and using the following plugins: @upit/tween.v1
User prompt
Ahora agrega la animacion de escalado a las torres con un 8% ↪💡 Consider importing and using the following plugins: @upit/tween.v1
User prompt
Cambialo a 8% ↪💡 Consider importing and using the following plugins: @upit/tween.v1
User prompt
Aumenta y disminuye la escala de los enemigos en 15% cada segundo ↪💡 Consider importing and using the following plugins: @upit/tween.v1
User prompt
Cambialo a 10 grados ↪💡 Consider importing and using the following plugins: @upit/tween.v1
User prompt
Puedes hacer que los enemigos roten 5 grados a un lado cada segundo y despues al otro lado ↪💡 Consider importing and using the following plugins: @upit/tween.v1
User prompt
las torres capyking y capyqueen solo pueden colocarse una vez. despues se bloquean
User prompt
Cambia el precio de CapyQueen a 200 y el de CapyKing a 250
User prompt
Cambia el precio de capyBomb a 80
User prompt
Aun se sigue viendo la eiqueta de Gold, lives y Score. Escondelas en la pantalla inicial
User prompt
No se debe de ver las etiquetas en la pantalla de bienvenida. Ubica la instruccion en la parte inferior de la pantalla.
User prompt
Ahora quiero que crees una pantalla de bienvenida. En esta pantalla debe haber una imagen de fondo y una instruccion de presionar en cualquier lado para continuar.
User prompt
En la pantalla de seleccion de nivel. la descripcion del boton lobby dice hotel, cambialo por castle
User prompt
Mueve las imagenes media celda a la izquierda y media celda arriba.
User prompt
Mueve las imagenes media celda a la derecha y media celda arriba.
User prompt
Quiero que las imagenes de los cuadrados de grat hall sean assets separados para poder editarlos.
User prompt
Has que las imagenes de los cuadrados de grat hall sean independientes.
User prompt
Posiciona dos celdas arriba a los cuadros
/**** * Plugins ****/ var tween = LK.import("@upit/tween.v1"); /**** * Classes ****/ var Bullet = Container.expand(function (startX, startY, targetEnemy, damage, speed) { var self = Container.call(this); self.targetEnemy = targetEnemy; self.damage = damage || 10; self.speed = speed || 5; self.x = startX; self.y = startY; var bulletGraphics = self.attachAsset('bullet', { anchorX: 0.5, anchorY: 0.5 }); self.update = function () { if (!self.targetEnemy || !self.targetEnemy.parent) { self.destroy(); return; } var dx = self.targetEnemy.x - self.x; var dy = self.targetEnemy.y - self.y; var distance = Math.sqrt(dx * dx + dy * dy); if (distance < self.speed) { // Apply damage to target enemy self.targetEnemy.health -= self.damage; if (self.targetEnemy.health <= 0) { self.targetEnemy.health = 0; } else { self.targetEnemy.healthBar.width = self.targetEnemy.health / self.targetEnemy.maxHealth * 70; } // Apply special effects based on bullet type if (self.type === 'splash') { // Create visual splash effect var splashEffect = new EffectIndicator(self.targetEnemy.x, self.targetEnemy.y, 'splash'); game.addChild(splashEffect); // Splash damage to nearby enemies var splashRadius = CELL_SIZE * 1.5; for (var i = 0; i < enemies.length; i++) { var otherEnemy = enemies[i]; if (otherEnemy !== self.targetEnemy) { var splashDx = otherEnemy.x - self.targetEnemy.x; var splashDy = otherEnemy.y - self.targetEnemy.y; var splashDistance = Math.sqrt(splashDx * splashDx + splashDy * splashDy); if (splashDistance <= splashRadius) { // Apply splash damage (50% of original damage) otherEnemy.health -= self.damage * 0.5; if (otherEnemy.health <= 0) { otherEnemy.health = 0; } else { otherEnemy.healthBar.width = otherEnemy.health / otherEnemy.maxHealth * 70; } } } } } else if (self.type === 'slow') { // Prevent slow effect on immune enemies if (!self.targetEnemy.isImmune) { // Create visual slow effect var slowEffect = new EffectIndicator(self.targetEnemy.x, self.targetEnemy.y, 'slow'); game.addChild(slowEffect); // Apply slow effect // Make slow percentage scale with tower level (default 50%, up to 80% at max level) var slowPct = 0.5; if (self.sourceTowerLevel !== undefined) { // Scale: 50% at level 1, 60% at 2, 65% at 3, 70% at 4, 75% at 5, 80% at 6 var slowLevels = [0.5, 0.6, 0.65, 0.7, 0.75, 0.8]; var idx = Math.max(0, Math.min(5, self.sourceTowerLevel - 1)); slowPct = slowLevels[idx]; } if (!self.targetEnemy.slowed) { self.targetEnemy.originalSpeed = self.targetEnemy.speed; self.targetEnemy.speed *= 1 - slowPct; // Slow by X% self.targetEnemy.slowed = true; self.targetEnemy.slowDuration = 180; // 3 seconds at 60 FPS } else { self.targetEnemy.slowDuration = 180; // Reset duration } } } else if (self.type === 'queen') { // Prevent slow effect on immune enemies (Queen tower with enhanced effects) if (!self.targetEnemy.isImmune) { // Create visual slow effect var slowEffect = new EffectIndicator(self.targetEnemy.x, self.targetEnemy.y, 'slow'); game.addChild(slowEffect); // Apply enhanced slow effect - double the reduction and duration // Make slow percentage scale with tower level (default 100%, up to 160% at max level) var slowPct = 1.0; // Double the base slow effect if (self.sourceTowerLevel !== undefined) { // Scale: 100% at level 1, 120% at 2, 130% at 3, 140% at 4, 150% at 5, 160% at 6 var slowLevels = [1.0, 1.2, 1.3, 1.4, 1.5, 1.6]; var idx = Math.max(0, Math.min(5, self.sourceTowerLevel - 1)); slowPct = slowLevels[idx]; } // Cap slow percentage to prevent negative speeds slowPct = Math.min(slowPct, 0.95); // Maximum 95% slow to prevent stopping if (!self.targetEnemy.slowed) { self.targetEnemy.originalSpeed = self.targetEnemy.speed; self.targetEnemy.speed *= 1 - slowPct; // Slow by X% self.targetEnemy.slowed = true; self.targetEnemy.slowDuration = 360; // 6 seconds at 60 FPS (double duration) } else { self.targetEnemy.slowDuration = 360; // Reset duration (double) } } } else if (self.type === 'poison') { // Prevent poison effect on immune enemies if (!self.targetEnemy.isImmune) { // Create visual poison effect var poisonEffect = new EffectIndicator(self.targetEnemy.x, self.targetEnemy.y, 'poison'); game.addChild(poisonEffect); // Apply poison effect self.targetEnemy.poisoned = true; self.targetEnemy.poisonDamage = self.damage * 0.2; // 20% of original damage per tick self.targetEnemy.poisonDuration = 300; // 5 seconds at 60 FPS } } else if (self.type === 'bomb') { // Prevent poison effect on immune enemies if (!self.targetEnemy.isImmune) { // Create visual poison effect var poisonEffect = new EffectIndicator(self.targetEnemy.x, self.targetEnemy.y, 'poison'); game.addChild(poisonEffect); // Apply poison effect self.targetEnemy.poisoned = true; self.targetEnemy.poisonDamage = self.damage * 0.2; // 20% of original damage per tick self.targetEnemy.poisonDuration = 300; // 5 seconds at 60 FPS } } else if (self.type === 'sniper') { // Create visual critical hit effect for sniper var sniperEffect = new EffectIndicator(self.targetEnemy.x, self.targetEnemy.y, 'sniper'); game.addChild(sniperEffect); } self.destroy(); } else { var angle = Math.atan2(dy, dx); self.x += Math.cos(angle) * self.speed; self.y += Math.sin(angle) * self.speed; } }; return self; }); var DebugCell = Container.expand(function () { var self = Container.call(this); var cellGraphics = self.attachAsset(getCellAssetName(), { anchorX: 0.5, anchorY: 0.5 }); cellGraphics.tint = Math.random() * 0xffffff; var debugArrows = []; var numberLabel = new Text2('0', { size: 30, fill: 0xFFFFFF, weight: 800 }); numberLabel.anchor.set(.5, .5); self.addChild(numberLabel); self.update = function () {}; self.down = function () { return; if (self.cell.type == 0 || self.cell.type == 1) { self.cell.type = self.cell.type == 1 ? 0 : 1; if (grid.pathFind()) { self.cell.type = self.cell.type == 1 ? 0 : 1; grid.pathFind(); var notification = game.addChild(new Notification("Path is blocked!")); notification.x = 2048 / 2; notification.y = grid.height - 50; } grid.renderDebug(); } }; self.removeArrows = function () { while (debugArrows.length) { self.removeChild(debugArrows.pop()); } }; self.render = function (data) { // Check if we need to update the cell asset based on current level var currentAssetName = getCellAssetName(); if (cellGraphics.assetName !== currentAssetName) { // Remove old graphics self.removeChild(cellGraphics); // Create new graphics with correct asset for current level cellGraphics = self.attachAsset(currentAssetName, { anchorX: 0.5, anchorY: 0.5 }); cellGraphics.assetName = currentAssetName; } switch (data.type) { case 0: case 2: { if (data.pathId != pathId) { self.removeArrows(); numberLabel.setText("-"); cellGraphics.tint = 0x880000; return; } numberLabel.visible = false; var towerInRangeHighlight = false; if (selectedTower && data.towersInRange && data.towersInRange.indexOf(selectedTower) !== -1) { towerInRangeHighlight = true; cellGraphics.tint = 0x0088ff; } else { // Check if this is Great Hall level and we're in the middle path from entrance to exit if (selectedLevel === 'greathall') { var gridWidth = 24; var gridHeight = 29 + 6; var centerX = Math.floor(gridWidth / 2); // Center column (12) // Check if current cell is in the center column path or adjacent cells from entrance (top) to exit (bottom) if (data.x >= centerX - 3 && data.x <= centerX + 2 && data.y >= 5 && data.y <= gridHeight - 5) { cellGraphics.tint = 0xFF0000; // Red color for the middle path and adjacent cells } else { cellGraphics.tint = 0xFFFFFF; // Remove blue background - set to white/transparent } } else { cellGraphics.tint = 0xFFFFFF; // Remove blue background - set to white/transparent } } while (debugArrows.length > data.targets.length) { self.removeChild(debugArrows.pop()); } for (var a = 0; a < data.targets.length; a++) { var destination = data.targets[a]; var ox = destination.x - data.x; var oy = destination.y - data.y; var angle = Math.atan2(oy, ox); if (!debugArrows[a]) { debugArrows[a] = LK.getAsset('arrow', { anchorX: -.5, anchorY: 0.5 }); debugArrows[a].alpha = 0; self.addChildAt(debugArrows[a], 1); } debugArrows[a].rotation = angle; } break; } case 1: { self.removeArrows(); // Check if this is Pool level and we're in the rectangular pool area if (selectedLevel === 'pool') { var gridWidth = 24; var gridHeight = 29 + 6; var centerX = Math.floor(gridWidth / 2); // Center column (12) var centerY = Math.floor(gridHeight / 2); // Center row var rectWidth = 6; // Rectangle width (6 cells) var rectHeight = 6; // Rectangle height (6 cells) var rectLeft = centerX - Math.floor(rectWidth / 2); var rectRight = centerX + Math.floor(rectWidth / 2); var rectTop = centerY - Math.floor(rectHeight / 2); var rectBottom = centerY + Math.floor(rectHeight / 2); // Check if current cell is within the rectangular pool zone if (data.x >= rectLeft && data.x < rectRight && data.y >= rectTop && data.y < rectBottom) { // Use gray color for the rectangular pool area tween(cellGraphics, { tint: 0x888888 }, { duration: 500, easing: tween.easeOut }); } else { cellGraphics.tint = 0xaaaaaa; // Regular wall color } } else { cellGraphics.tint = 0xaaaaaa; // Regular wall color for other levels } numberLabel.visible = false; break; } case 3: { self.removeArrows(); cellGraphics.tint = 0x008800; numberLabel.visible = false; break; } } numberLabel.setText(Math.floor(data.score / 1000) / 10); }; }); var DifficultySelector = Container.expand(function () { var self = Container.call(this); // Background overlay var overlay = self.attachAsset('notification', { anchorX: 0.5, anchorY: 0.5 }); overlay.width = 2048; overlay.height = 2732; overlay.tint = 0x000000; overlay.alpha = 0.8; // Title var titleShadow = new Text2("Select Difficulty", { size: 120, fill: 0x000000, weight: 800 }); titleShadow.anchor.set(0.5, 0.5); titleShadow.x = 4; titleShadow.y = -800; self.addChild(titleShadow); var title = new Text2("Select Difficulty", { size: 120, fill: 0xFFFFFF, weight: 800 }); title.anchor.set(0.5, 0.5); title.y = -800; self.addChild(title); // Difficulty buttons var difficulties = [{ name: "Easy", color: 0x00AA00, multiplier: 0.7, description: "Enemies have 30% less health" }, { name: "Normal", color: 0x0088FF, multiplier: 1.0, description: "Standard difficulty" }, { name: "Hard", color: 0xFFFF00, multiplier: 1.5, description: "Enemies have 50% more health" }, { name: "Insane", color: 0xFF0000, multiplier: 2.0, description: "Enemies have 100% more health" }]; var buttonSpacing = 300; var startY = -200; for (var i = 0; i < difficulties.length; i++) { var difficulty = difficulties[i]; var button = new Container(); var buttonBg = button.attachAsset('notification', { anchorX: 0.5, anchorY: 0.5 }); buttonBg.width = 600; buttonBg.height = 200; buttonBg.tint = difficulty.color; // Button title shadow var buttonTitleShadow = new Text2(difficulty.name, { size: 80, fill: 0x000000, weight: 800 }); buttonTitleShadow.anchor.set(0.5, 0.5); buttonTitleShadow.x = 4; buttonTitleShadow.y = -20; button.addChild(buttonTitleShadow); // Button title var buttonTitle = new Text2(difficulty.name, { size: 80, fill: 0xFFFFFF, weight: 800 }); buttonTitle.anchor.set(0.5, 0.5); buttonTitle.y = -20; button.addChild(buttonTitle); // Description shadow var descShadow = new Text2(difficulty.description, { size: 40, fill: 0x000000, weight: 400 }); descShadow.anchor.set(0.5, 0.5); descShadow.x = 2; descShadow.y = 30; button.addChild(descShadow); // Description var desc = new Text2(difficulty.description, { size: 40, fill: 0xFFFFFF, weight: 400 }); desc.anchor.set(0.5, 0.5); desc.y = 28; button.addChild(desc); button.y = startY + i * buttonSpacing; button.difficulty = difficulty; self.addChild(button); // Button click handler button.down = function (x, y, obj) { var selectedDifficulty = this.difficulty; difficultyMultiplier = selectedDifficulty.multiplier; // Set initial gold based on difficulty switch (selectedDifficulty.name) { case "Easy": setGold(80); break; case "Normal": setGold(85); break; case "Hard": setGold(100); break; case "Insane": setGold(150); break; } // Show selection feedback var notification = game.addChild(new Notification("Difficulty set to " + selectedDifficulty.name + "!")); notification.x = 2048 / 2; notification.y = 2732 / 2; // Remove difficulty selector self.destroy(); }; } return self; }); // This update method was incorrectly placed here and should be removed var EffectIndicator = Container.expand(function (x, y, type) { var self = Container.call(this); self.x = x; self.y = y; var effectGraphics = self.attachAsset('rangeCircle', { anchorX: 0.5, anchorY: 0.5 }); effectGraphics.blendMode = 1; switch (type) { case 'splash': effectGraphics.tint = 0x33CC00; effectGraphics.width = effectGraphics.height = CELL_SIZE * 1.5; break; case 'slow': effectGraphics.tint = 0x9900FF; effectGraphics.width = effectGraphics.height = CELL_SIZE; break; case 'poison': effectGraphics.tint = 0x00FFAA; effectGraphics.width = effectGraphics.height = CELL_SIZE; break; case 'sniper': effectGraphics.tint = 0xFF5500; effectGraphics.width = effectGraphics.height = CELL_SIZE; break; case 'explosion': effectGraphics.tint = 0xFF4500; effectGraphics.width = effectGraphics.height = CELL_SIZE * 3; // Larger explosion effect break; } effectGraphics.alpha = 0.7; self.alpha = 0; // Animate the effect tween(self, { alpha: 0.8, scaleX: 1.5, scaleY: 1.5 }, { duration: 200, easing: tween.easeOut, onFinish: function onFinish() { tween(self, { alpha: 0, scaleX: 2, scaleY: 2 }, { duration: 300, easing: tween.easeIn, onFinish: function onFinish() { self.destroy(); } }); } }); return self; }); // Base enemy class for common functionality var Enemy = Container.expand(function (type) { var self = Container.call(this); self.type = type || 'normal'; self.speed = .01; self.cellX = 0; self.cellY = 0; self.currentCellX = 0; self.currentCellY = 0; self.currentTarget = undefined; self.maxHealth = 100; self.health = self.maxHealth; self.bulletsTargetingThis = []; self.waveNumber = currentWave; self.isFlying = false; self.isImmune = false; self.isBoss = false; // Check if this is a boss wave // Check if this is a boss wave // Apply different stats based on enemy type switch (self.type) { case 'fast': self.speed *= 2; // Twice as fast self.maxHealth = 100; break; case 'immune': self.isImmune = true; self.maxHealth = 80; break; case 'flying': self.isFlying = true; self.maxHealth = 80; break; case 'swarm': self.maxHealth = 50; // Weaker enemies break; case 'normal': default: // Normal enemy uses default values break; } if (currentWave % 10 === 0 && currentWave > 0 && type !== 'swarm') { self.isBoss = true; // Boss enemies have 20x health and are larger self.maxHealth *= 20; // Slower speed for bosses self.speed = self.speed * 0.7; } self.health = self.maxHealth; // Get appropriate asset for this enemy type var assetId = 'enemy'; if (self.type !== 'normal') { assetId = 'enemy_' + self.type; } var enemyGraphics = self.attachAsset(assetId, { anchorX: 0.5, anchorY: 0.5 }); // Scale up boss enemies if (self.isBoss) { enemyGraphics.scaleX = 1.8; enemyGraphics.scaleY = 1.8; } // Fall back to regular enemy asset if specific type asset not found // Apply tint to differentiate enemy types /*switch (self.type) { case 'fast': enemyGraphics.tint = 0x00AAFF; // Blue for fast enemies break; case 'immune': enemyGraphics.tint = 0xAA0000; // Red for immune enemies break; case 'flying': enemyGraphics.tint = 0xFFFF00; // Yellow for flying enemies break; case 'swarm': enemyGraphics.tint = 0xFF00FF; // Pink for swarm enemies break; }*/ // Create shadow for flying enemies if (self.isFlying) { // Create a shadow container that will be added to the shadow layer self.shadow = new Container(); // Clone the enemy graphics for the shadow var shadowGraphics = self.shadow.attachAsset(assetId || 'enemy', { anchorX: 0.5, anchorY: 0.5 }); // Apply shadow effect shadowGraphics.tint = 0x000000; // Black shadow shadowGraphics.alpha = 0.4; // Semi-transparent // If this is a boss, scale up the shadow to match if (self.isBoss) { shadowGraphics.scaleX = 1.8; shadowGraphics.scaleY = 1.8; } // Position shadow slightly offset self.shadow.x = 20; // Offset right self.shadow.y = 20; // Offset down // Ensure shadow has the same rotation as the enemy shadowGraphics.rotation = enemyGraphics.rotation; } var healthBarOutline = self.attachAsset('healthBarOutline', { anchorX: 0, anchorY: 0.5 }); var healthBarBG = self.attachAsset('healthBar', { anchorX: 0, anchorY: 0.5 }); var healthBar = self.attachAsset('healthBar', { anchorX: 0, anchorY: 0.5 }); healthBarBG.y = healthBarOutline.y = healthBar.y = -enemyGraphics.height / 2 - 10; healthBarOutline.x = -healthBarOutline.width / 2; healthBarBG.x = healthBar.x = -healthBar.width / 2 - .5; healthBar.tint = 0x00ff00; healthBarBG.tint = 0xff0000; self.healthBar = healthBar; // Add scaling animation function self.startScalingAnimation = function () { // Function to animate to larger scale (108%) function scaleUp() { tween(enemyGraphics, { scaleX: 1.08, scaleY: 1.08 }, { duration: 1000, easing: tween.easeInOut, onFinish: function onFinish() { scaleDown(); } }); } // Function to animate to smaller scale (92%) function scaleDown() { tween(enemyGraphics, { scaleX: 0.92, scaleY: 0.92 }, { duration: 1000, easing: tween.easeInOut, onFinish: function onFinish() { scaleUp(); } }); } // Start the animation cycle scaleUp(); }; self.update = function () { if (self.health <= 0) { self.health = 0; self.healthBar.width = 0; } // Handle slow effect if (self.isImmune) { // Immune enemies cannot be slowed or poisoned, clear any such effects self.slowed = false; self.slowEffect = false; self.poisoned = false; self.poisonEffect = false; // Reset speed to original if needed if (self.originalSpeed !== undefined) { self.speed = self.originalSpeed; } } else { // Handle slow effect if (self.slowed) { // Visual indication of slowed status if (!self.slowEffect) { self.slowEffect = true; } self.slowDuration--; if (self.slowDuration <= 0) { self.speed = self.originalSpeed; self.slowed = false; self.slowEffect = false; // Only reset tint if not poisoned if (!self.poisoned) { enemyGraphics.tint = 0xFFFFFF; // Reset tint } } } // Handle poison effect if (self.poisoned) { // Visual indication of poisoned status if (!self.poisonEffect) { self.poisonEffect = true; } // Apply poison damage every 30 frames (twice per second) if (LK.ticks % 30 === 0) { self.health -= self.poisonDamage; if (self.health <= 0) { self.health = 0; } self.healthBar.width = self.health / self.maxHealth * 70; } self.poisonDuration--; if (self.poisonDuration <= 0) { self.poisoned = false; self.poisonEffect = false; // Only reset tint if not slowed if (!self.slowed) { enemyGraphics.tint = 0xFFFFFF; // Reset tint } } } } // Set tint based on effect status if (self.isImmune) { enemyGraphics.tint = 0xFFFFFF; } else if (self.poisoned && self.slowed) { // Combine poison (0x00FFAA) and slow (0x9900FF) colors // Simple average: R: (0+153)/2=76, G: (255+0)/2=127, B: (170+255)/2=212 enemyGraphics.tint = 0x4C7FD4; } else if (self.poisoned) { enemyGraphics.tint = 0x00FFAA; } else if (self.slowed) { enemyGraphics.tint = 0x9900FF; } else { enemyGraphics.tint = 0xFFFFFF; } // Initialize scaling animation on first update if (!self.scalingInitialized) { self.scalingInitialized = true; // Start the continuous scaling animation self.startScalingAnimation(); } if (self.currentTarget) { var ox = self.currentTarget.x - self.currentCellX; var oy = self.currentTarget.y - self.currentCellY; if (ox !== 0 || oy !== 0) { var angle = Math.atan2(oy, ox); if (enemyGraphics.targetRotation === undefined) { enemyGraphics.targetRotation = angle; enemyGraphics.rotation = angle; } else { if (Math.abs(angle - enemyGraphics.targetRotation) > 0.05) { tween.stop(enemyGraphics, { rotation: true }); // Calculate the shortest angle to rotate var currentRotation = enemyGraphics.rotation; var angleDiff = angle - currentRotation; // Normalize angle difference to -PI to PI range for shortest path while (angleDiff > Math.PI) { angleDiff -= Math.PI * 2; } while (angleDiff < -Math.PI) { angleDiff += Math.PI * 2; } enemyGraphics.targetRotation = angle; tween(enemyGraphics, { rotation: currentRotation + angleDiff }, { duration: 250, easing: tween.easeOut }); } } } } healthBarOutline.y = healthBarBG.y = healthBar.y = -enemyGraphics.height / 2 - 10; }; return self; }); var GoldIndicator = Container.expand(function (value, x, y) { var self = Container.call(this); var shadowText = new Text2("+" + value, { size: 45, fill: 0x000000, weight: 800 }); shadowText.anchor.set(0.5, 0.5); shadowText.x = 2; shadowText.y = 2; self.addChild(shadowText); var goldText = new Text2("+" + value, { size: 45, fill: 0xFFD700, weight: 800 }); goldText.anchor.set(0.5, 0.5); self.addChild(goldText); self.x = x; self.y = y; self.alpha = 0; self.scaleX = 0.5; self.scaleY = 0.5; tween(self, { alpha: 1, scaleX: 1.2, scaleY: 1.2, y: y - 40 }, { duration: 50, easing: tween.easeOut, onFinish: function onFinish() { tween(self, { alpha: 0, scaleX: 1.5, scaleY: 1.5, y: y - 80 }, { duration: 600, easing: tween.easeIn, delay: 800, onFinish: function onFinish() { self.destroy(); } }); } }); return self; }); var Grid = Container.expand(function (gridWidth, gridHeight) { var self = Container.call(this); self.cells = []; self.spawns = []; self.goals = []; for (var i = 0; i < gridWidth; i++) { self.cells[i] = []; for (var j = 0; j < gridHeight; j++) { self.cells[i][j] = { score: 0, pathId: 0, towersInRange: [] }; } } /* Cell Types 0: Transparent floor 1: Wall 2: Spawn 3: Goal */ for (var i = 0; i < gridWidth; i++) { for (var j = 0; j < gridHeight; j++) { var cell = self.cells[i][j]; var cellType = i === 0 || i === gridWidth - 1 || j <= 4 || j >= gridHeight - 4 ? 1 : 0; if (i > 11 - 3 && i <= 11 + 3) { if (j === 0) { cellType = 2; self.spawns.push(cell); } else if (j <= 4) { cellType = 0; } else if (j === gridHeight - 1) { cellType = 3; self.goals.push(cell); } else if (j >= gridHeight - 4) { cellType = 0; } } // Add rectangular no-tower zone for Pool level in the middle area if (selectedLevel === 'pool') { // Create a large rectangular area in the center where towers cannot be placed var centerX = Math.floor(gridWidth / 2); // Center column (12) var centerY = Math.floor(gridHeight / 2); // Center row var rectWidth = 6; // Rectangle width (6 cells) var rectHeight = 16; // Rectangle height (16 cells) var rectLeft = centerX - Math.floor(rectWidth / 2); var rectRight = centerX + Math.floor(rectWidth / 2); var rectTop = centerY - Math.floor(rectHeight / 2); var rectBottom = centerY + Math.floor(rectHeight / 2); // Check if current cell is within the rectangular zone if (i >= rectLeft && i < rectRight && j >= rectTop && j < rectBottom) { // Only block tower placement if it's a walkable area (not spawn/goal/wall) if (cellType === 0) { cellType = 1; // Set to wall type to prevent tower placement } } } cell.type = cellType; cell.x = i; cell.y = j; cell.upLeft = self.cells[i - 1] && self.cells[i - 1][j - 1]; cell.up = self.cells[i - 1] && self.cells[i - 1][j]; cell.upRight = self.cells[i - 1] && self.cells[i - 1][j + 1]; cell.left = self.cells[i][j - 1]; cell.right = self.cells[i][j + 1]; cell.downLeft = self.cells[i + 1] && self.cells[i + 1][j - 1]; cell.down = self.cells[i + 1] && self.cells[i + 1][j]; cell.downRight = self.cells[i + 1] && self.cells[i + 1][j + 1]; cell.neighbors = [cell.upLeft, cell.up, cell.upRight, cell.right, cell.downRight, cell.down, cell.downLeft, cell.left]; cell.targets = []; if (j > 3 && j <= gridHeight - 4) { var debugCell = new DebugCell(); self.addChild(debugCell); debugCell.cell = cell; debugCell.x = i * CELL_SIZE; debugCell.y = j * CELL_SIZE; cell.debugCell = debugCell; } } } self.getCell = function (x, y) { return self.cells[x] && self.cells[x][y]; }; self.pathFind = function () { var before = new Date().getTime(); var toProcess = self.goals.concat([]); maxScore = 0; pathId += 1; for (var a = 0; a < toProcess.length; a++) { toProcess[a].pathId = pathId; } function processNode(node, targetValue, targetNode) { if (node && node.type != 1) { if (node.pathId < pathId || targetValue < node.score) { node.targets = [targetNode]; } else if (node.pathId == pathId && targetValue == node.score) { node.targets.push(targetNode); } if (node.pathId < pathId || targetValue < node.score) { node.score = targetValue; if (node.pathId != pathId) { toProcess.push(node); } node.pathId = pathId; if (targetValue > maxScore) { maxScore = targetValue; } } } } while (toProcess.length) { var nodes = toProcess; toProcess = []; for (var a = 0; a < nodes.length; a++) { var node = nodes[a]; var targetScore = node.score + 14142; if (node.up && node.left && node.up.type != 1 && node.left.type != 1) { processNode(node.upLeft, targetScore, node); } if (node.up && node.right && node.up.type != 1 && node.right.type != 1) { processNode(node.upRight, targetScore, node); } if (node.down && node.right && node.down.type != 1 && node.right.type != 1) { processNode(node.downRight, targetScore, node); } if (node.down && node.left && node.down.type != 1 && node.left.type != 1) { processNode(node.downLeft, targetScore, node); } targetScore = node.score + 10000; processNode(node.up, targetScore, node); processNode(node.right, targetScore, node); processNode(node.down, targetScore, node); processNode(node.left, targetScore, node); } } for (var a = 0; a < self.spawns.length; a++) { if (self.spawns[a].pathId != pathId) { console.warn("Spawn blocked"); return true; } } for (var a = 0; a < enemies.length; a++) { var enemy = enemies[a]; // Skip enemies that haven't entered the viewable area yet if (enemy.currentCellY < 4) { continue; } // Skip flying enemies from path check as they can fly over obstacles if (enemy.isFlying) { continue; } var target = self.getCell(enemy.cellX, enemy.cellY); if (enemy.currentTarget) { if (enemy.currentTarget.pathId != pathId) { if (!target || target.pathId != pathId) { console.warn("Enemy blocked 1 "); return true; } } } else if (!target || target.pathId != pathId) { console.warn("Enemy blocked 2"); return true; } } console.log("Speed", new Date().getTime() - before); }; self.renderDebug = function () { for (var i = 0; i < gridWidth; i++) { for (var j = 0; j < gridHeight; j++) { var debugCell = self.cells[i][j].debugCell; if (debugCell) { debugCell.render(self.cells[i][j]); } } } }; self.updateEnemy = function (enemy) { var cell = grid.getCell(enemy.cellX, enemy.cellY); if (cell.type == 3) { return true; } if (enemy.isFlying && enemy.shadow) { enemy.shadow.x = enemy.x + 20; // Match enemy x-position + offset enemy.shadow.y = enemy.y + 20; // Match enemy y-position + offset // Match shadow rotation with enemy rotation if (enemy.children[0] && enemy.shadow.children[0]) { enemy.shadow.children[0].rotation = enemy.children[0].rotation; } } // Check if the enemy has reached the entry area (y position is at least 5) var hasReachedEntryArea = enemy.currentCellY >= 4; // If enemy hasn't reached the entry area yet, just move down vertically if (!hasReachedEntryArea) { // Move directly downward enemy.currentCellY += enemy.speed; // Rotate enemy graphic to face downward (PI/2 radians = 90 degrees) var angle = Math.PI / 2; if (enemy.children[0] && enemy.children[0].targetRotation === undefined) { enemy.children[0].targetRotation = angle; enemy.children[0].rotation = angle; } else if (enemy.children[0]) { if (Math.abs(angle - enemy.children[0].targetRotation) > 0.05) { tween.stop(enemy.children[0], { rotation: true }); // Calculate the shortest angle to rotate var currentRotation = enemy.children[0].rotation; var angleDiff = angle - currentRotation; // Normalize angle difference to -PI to PI range for shortest path while (angleDiff > Math.PI) { angleDiff -= Math.PI * 2; } while (angleDiff < -Math.PI) { angleDiff += Math.PI * 2; } // Set target rotation and animate to it enemy.children[0].targetRotation = angle; tween(enemy.children[0], { rotation: currentRotation + angleDiff }, { duration: 250, easing: tween.easeOut }); } } // Update enemy's position enemy.x = grid.x + enemy.currentCellX * CELL_SIZE; enemy.y = grid.y + enemy.currentCellY * CELL_SIZE; // If enemy has now reached the entry area, update cell coordinates if (enemy.currentCellY >= 4) { enemy.cellX = Math.round(enemy.currentCellX); enemy.cellY = Math.round(enemy.currentCellY); } return false; } // After reaching entry area, handle flying enemies differently if (enemy.isFlying) { // Flying enemies head straight to the closest goal if (!enemy.flyingTarget) { // Set flying target to the closest goal enemy.flyingTarget = self.goals[0]; // Find closest goal if there are multiple if (self.goals.length > 1) { var closestDist = Infinity; for (var i = 0; i < self.goals.length; i++) { var goal = self.goals[i]; var dx = goal.x - enemy.cellX; var dy = goal.y - enemy.cellY; var dist = dx * dx + dy * dy; if (dist < closestDist) { closestDist = dist; enemy.flyingTarget = goal; } } } } // Move directly toward the goal var ox = enemy.flyingTarget.x - enemy.currentCellX; var oy = enemy.flyingTarget.y - enemy.currentCellY; var dist = Math.sqrt(ox * ox + oy * oy); if (dist < enemy.speed) { // Reached the goal return true; } var angle = Math.atan2(oy, ox); // Rotate enemy graphic to match movement direction if (enemy.children[0] && enemy.children[0].targetRotation === undefined) { enemy.children[0].targetRotation = angle; enemy.children[0].rotation = angle; } else if (enemy.children[0]) { if (Math.abs(angle - enemy.children[0].targetRotation) > 0.05) { tween.stop(enemy.children[0], { rotation: true }); // Calculate the shortest angle to rotate var currentRotation = enemy.children[0].rotation; var angleDiff = angle - currentRotation; // Normalize angle difference to -PI to PI range for shortest path while (angleDiff > Math.PI) { angleDiff -= Math.PI * 2; } while (angleDiff < -Math.PI) { angleDiff += Math.PI * 2; } // Set target rotation and animate to it enemy.children[0].targetRotation = angle; tween(enemy.children[0], { rotation: currentRotation + angleDiff }, { duration: 250, easing: tween.easeOut }); } } // Update the cell position to track where the flying enemy is enemy.cellX = Math.round(enemy.currentCellX); enemy.cellY = Math.round(enemy.currentCellY); enemy.currentCellX += Math.cos(angle) * enemy.speed; enemy.currentCellY += Math.sin(angle) * enemy.speed; enemy.x = grid.x + enemy.currentCellX * CELL_SIZE; enemy.y = grid.y + enemy.currentCellY * CELL_SIZE; // Update shadow position if this is a flying enemy return false; } // Handle normal pathfinding enemies if (!enemy.currentTarget) { enemy.currentTarget = cell.targets[0]; } if (enemy.currentTarget) { if (cell.score < enemy.currentTarget.score) { enemy.currentTarget = cell; } var ox = enemy.currentTarget.x - enemy.currentCellX; var oy = enemy.currentTarget.y - enemy.currentCellY; var dist = Math.sqrt(ox * ox + oy * oy); if (dist < enemy.speed) { enemy.cellX = Math.round(enemy.currentCellX); enemy.cellY = Math.round(enemy.currentCellY); enemy.currentTarget = undefined; return; } var angle = Math.atan2(oy, ox); enemy.currentCellX += Math.cos(angle) * enemy.speed; enemy.currentCellY += Math.sin(angle) * enemy.speed; } enemy.x = grid.x + enemy.currentCellX * CELL_SIZE; enemy.y = grid.y + enemy.currentCellY * CELL_SIZE; }; }); var LevelSelector = Container.expand(function () { var self = Container.call(this); // Background overlay var overlay = self.attachAsset('notification', { anchorX: 0.5, anchorY: 0.5 }); overlay.width = 2048; overlay.height = 2732; overlay.tint = 0x000000; overlay.alpha = 0.8; // Title var titleShadow = new Text2("Select Level", { size: 120, fill: 0x000000, weight: 800 }); titleShadow.anchor.set(0.5, 0.5); titleShadow.x = 4; titleShadow.y = -900; self.addChild(titleShadow); var title = new Text2("Select Level", { size: 120, fill: 0xFFFFFF, weight: 800 }); title.anchor.set(0.5, 0.5); title.y = -900; self.addChild(title); // Level buttons var levels = [{ name: "Garden", color: 0x00AA00, description: "A peaceful garden setting" }, { name: "Pool", color: 0x0088FF, description: "Battle by the swimming pool" }, { name: "Lobby", color: 0xFFAA00, description: "Defend the castle lobby" }, { name: "Great Hall", color: 0xAA00AA, description: "Epic battles in the great hall" }]; var buttonSpacing = 300; var startY = -200; for (var i = 0; i < levels.length; i++) { var level = levels[i]; var button = new Container(); var buttonBg = button.attachAsset('notification', { anchorX: 0.5, anchorY: 0.5 }); buttonBg.width = 600; buttonBg.height = 200; buttonBg.tint = level.color; // Button title shadow var buttonTitleShadow = new Text2(level.name, { size: 80, fill: 0x000000, weight: 800 }); buttonTitleShadow.anchor.set(0.5, 0.5); buttonTitleShadow.x = 4; buttonTitleShadow.y = -20; button.addChild(buttonTitleShadow); // Button title var buttonTitle = new Text2(level.name, { size: 80, fill: 0xFFFFFF, weight: 800 }); buttonTitle.anchor.set(0.5, 0.5); buttonTitle.y = -20; button.addChild(buttonTitle); // Description shadow var descShadow = new Text2(level.description, { size: 40, fill: 0x000000, weight: 400 }); descShadow.anchor.set(0.5, 0.5); descShadow.x = 2; descShadow.y = 30; button.addChild(descShadow); // Description var desc = new Text2(level.description, { size: 40, fill: 0xFFFFFF, weight: 400 }); desc.anchor.set(0.5, 0.5); desc.y = 28; button.addChild(desc); button.y = startY + i * buttonSpacing; button.level = level; self.addChild(button); // Button click handler button.down = function (x, y, obj) { var levelData = this.level; // Store the selected level globally selectedLevel = levelData.name.toLowerCase().replace(' ', ''); // Clean up existing lobby square images first for (var i = 0; i < lobbySquareImages.length; i++) { towerLayer.removeChild(lobbySquareImages[i]); } lobbySquareImages = []; // Regenerate grid for Pool level to add rectangular pool area if (selectedLevel === 'pool') { // Remove existing grid debugLayer.removeChild(grid); // Create new grid with pool configuration grid = new Grid(24, 29 + 6); grid.x = 150; grid.y = 200 - CELL_SIZE * 4; grid.pathFind(); grid.renderDebug(); debugLayer.addChild(grid); } // Create 3 wall cell squares for Lobby level if (selectedLevel === 'lobby') { // Remove existing grid debugLayer.removeChild(grid); // Create new grid with lobby configuration grid = new Grid(24, 29 + 6); grid.x = 150; grid.y = 200 - CELL_SIZE * 4; // Define positions for the 3 wall squares (4x4 each) var wallSquares = [{ startX: 4, startY: 10 }, // Left square { startX: 10, startY: 16 }, // Center square { startX: 16, startY: 10 } // Right square ]; // Create the wall squares for (var s = 0; s < wallSquares.length; s++) { var square = wallSquares[s]; for (var i = 0; i < 4; i++) { for (var j = 0; j < 4; j++) { var cell = grid.getCell(square.startX + i, square.startY + j); if (cell && cell.type === 0) { // Only modify walkable cells cell.type = 1; // Set to wall type } } } } grid.pathFind(); grid.renderDebug(); debugLayer.addChild(grid); // Create images over the 4x4 wall squares var squareAssets = ['lobby_square_1', 'lobby_square_2', 'lobby_square_3']; for (var s = 0; s < wallSquares.length; s++) { var square = wallSquares[s]; var squareImage = game.attachAsset(squareAssets[s], { anchorX: 0.5, anchorY: 0.5 }); // Position image over the center of the 4x4 square, moved half a cell left and up var squareCenterX = grid.x + (square.startX + 2) * CELL_SIZE; var squareCenterY = grid.y + (square.startY + 2) * CELL_SIZE; squareImage.x = squareCenterX - CELL_SIZE / 2; squareImage.y = squareCenterY - CELL_SIZE / 2; // Add to tower layer so it appears above the grid cells towerLayer.addChild(squareImage); lobbySquareImages.push(squareImage); } } // Create 2 wall cell squares for Great Hall level at the bottom if (selectedLevel === 'greathall') { // Remove existing grid debugLayer.removeChild(grid); // Create new grid with great hall configuration grid = new Grid(24, 29 + 6); grid.x = 150; grid.y = 200 - CELL_SIZE * 4; // Define positions for the 2 wall squares (4x4 each) at the bottom var gridHeight = 29 + 6; var wallSquares = [{ startX: 4, startY: gridHeight - 10 // Bottom left square, moved up 2 cells }, { startX: 16, startY: gridHeight - 10 // Bottom right square, moved up 2 cells }]; // Create the wall squares for (var s = 0; s < wallSquares.length; s++) { var square = wallSquares[s]; for (var i = 0; i < 4; i++) { for (var j = 0; j < 4; j++) { var cell = grid.getCell(square.startX + i, square.startY + j); if (cell && cell.type === 0) { // Only modify walkable cells cell.type = 1; // Set to wall type } } } } grid.pathFind(); grid.renderDebug(); debugLayer.addChild(grid); // Create images over the 4x4 wall squares // Create images over the 4x4 wall squares using separate assets var squareAssets = ['greathall_square_1', 'greathall_square_2']; for (var s = 0; s < wallSquares.length; s++) { var square = wallSquares[s]; var squareImage = game.attachAsset(squareAssets[s], { anchorX: 0.5, anchorY: 0.5 }); // Position image over the center of the 4x4 square, moved half a cell left and up var squareCenterX = grid.x + (square.startX + 2) * CELL_SIZE; var squareCenterY = grid.y + (square.startY + 2) * CELL_SIZE; squareImage.x = squareCenterX - CELL_SIZE / 2; squareImage.y = squareCenterY - CELL_SIZE / 2; // Add to tower layer so it appears above the grid cells towerLayer.addChild(squareImage); lobbySquareImages.push(squareImage); } } // Update background image immediately when level is selected var newBackgroundAssetName = getBackgroundAssetName(); game.removeChild(backgroundImage); backgroundImage = game.attachAsset(newBackgroundAssetName, { anchorX: 0.5, anchorY: 0.5 }); backgroundImage.x = 2048 / 2; backgroundImage.y = 2732 / 2; backgroundImage.width = 2048; backgroundImage.height = 2732; // Add background to the back of the game game.addChildAt(backgroundImage, 0); // Add or remove pool rectangle image based on selected level if (poolRectangleImage) { game.removeChild(poolRectangleImage); poolRectangleImage = null; } if (selectedLevel === 'pool') { poolRectangleImage = game.attachAsset('pool_rectangle', { anchorX: 0.5, anchorY: 0.5 }); // Position over the rectangular pool area (center of grid + pool area center offset) var gridWidth = 24; var gridHeight = 35; var centerX = Math.floor(gridWidth / 2); // Center column (12) var centerY = Math.floor(gridHeight / 2); // Center row var rectWidth = 6; // Rectangle width (6 cells) var rectHeight = 16; // Rectangle height (16 cells) var rectLeft = centerX - Math.floor(rectWidth / 2); var rectTop = centerY - Math.floor(rectHeight / 2); // Calculate the actual center of the rectangular pool area in pixels var poolCenterX = grid.x + (rectLeft + rectWidth / 2) * CELL_SIZE; var poolCenterY = grid.y + (rectTop + rectHeight / 2) * CELL_SIZE; poolRectangleImage.x = poolCenterX - CELL_SIZE + CELL_SIZE / 2 - 0.5 - 0.5 - 1 - 2; poolRectangleImage.y = poolCenterY - CELL_SIZE + CELL_SIZE / 2 - 0.5 - 0.5 - 1 - 2; // Keep pool rectangle image at normal tint (not gray) so it appears above the gray cells poolRectangleImage.tint = 0xFFFFFF; // Keep original white/normal tint // Add to tower layer so it appears above the grid cells towerLayer.addChild(poolRectangleImage); } // Trigger grid debug render to update cell assets immediately grid.renderDebug(); // Show selection feedback var notification = game.addChild(new Notification("Level set to " + levelData.name + "!")); notification.x = 2048 / 2; notification.y = 2732 / 2; // Remove level selector and show difficulty selector self.destroy(); // Show difficulty selector var difficultySelector = new DifficultySelector(); difficultySelector.x = 2048 / 2; difficultySelector.y = 2732 / 2; game.addChild(difficultySelector); }; } return self; }); var NextWaveButton = Container.expand(function () { var self = Container.call(this); var buttonBackground = self.attachAsset('notification', { anchorX: 0.5, anchorY: 0.5 }); buttonBackground.width = 300; buttonBackground.height = 100; buttonBackground.tint = 0x0088FF; var buttonText = new Text2("Next Wave", { size: 50, fill: 0xFFFFFF, weight: 800 }); buttonText.anchor.set(0.5, 0.5); self.addChild(buttonText); self.enabled = false; self.visible = false; self.update = function () { if (waveIndicator && waveIndicator.gameStarted && currentWave < totalWaves) { self.enabled = true; self.visible = true; buttonBackground.tint = 0x0088FF; self.alpha = 1; } else { self.enabled = false; self.visible = false; buttonBackground.tint = 0x888888; self.alpha = 0.7; } }; self.down = function () { if (!self.enabled) { return; } if (waveIndicator.gameStarted && currentWave < totalWaves) { currentWave++; // Increment to the next wave directly waveTimer = 0; // Reset wave timer waveInProgress = true; waveSpawned = false; // Get the type of the current wave (which is now the next wave) var waveType = waveIndicator.getWaveTypeName(currentWave); var enemyCount = waveIndicator.getEnemyCount(currentWave); var notification = game.addChild(new Notification("Wave " + currentWave + " (" + waveType + " - " + enemyCount + " enemies) activated!")); notification.x = 2048 / 2; notification.y = grid.height - 150; } }; return self; }); var Notification = Container.expand(function (message) { var self = Container.call(this); var notificationGraphics = self.attachAsset('notification', { anchorX: 0.5, anchorY: 0.5 }); var notificationText = new Text2(message, { size: 50, fill: 0x000000, weight: 800 }); notificationText.anchor.set(0.5, 0.5); notificationGraphics.width = notificationText.width + 30; self.addChild(notificationText); self.alpha = 1; var fadeOutTime = 120; self.update = function () { if (fadeOutTime > 0) { fadeOutTime--; self.alpha = Math.min(fadeOutTime / 120 * 2, 1); } else { self.destroy(); } }; return self; }); var SourceTower = Container.expand(function (towerType) { var self = Container.call(this); self.towerType = towerType || 'default'; // Increase size of base for easier touch var assetName = 'tower_' + self.towerType; var baseGraphics = self.attachAsset(assetName, { anchorX: 0.5, anchorY: 0.5, scaleX: 0.99, scaleY: 0.99, y: -40 }); var towerCost = getTowerCost(self.towerType); // Add shadow for tower type label below the tower var towerNames = { 'default': 'CapyShot', 'rapid': 'CapyBlast', 'sniper': 'CapySniper', 'splash': 'CapyPult', 'slow': 'CapyFreeze', 'poison': 'CapyPoison', 'bomb': 'CapyBomb', 'queen': 'CapyQueen', 'king': 'CapyKing' }; var displayName = towerNames[self.towerType] || self.towerType.charAt(0).toUpperCase() + self.towerType.slice(1); var typeLabelShadow = new Text2(displayName, { size: 40, fill: 0x000000, weight: 800 }); typeLabelShadow.anchor.set(0.5, 0.5); typeLabelShadow.x = 4; // Shadow offset but still centered relative to tower typeLabelShadow.y = 75 + 4; // Position higher up with shadow offset self.addChild(typeLabelShadow); // Add tower type label below the tower var typeLabel = new Text2(displayName, { size: 40, fill: 0xFFFFFF, weight: 800 }); typeLabel.anchor.set(0.5, 0.5); typeLabel.x = 0; // Explicitly center horizontally typeLabel.y = 75; // Position higher up self.addChild(typeLabel); // Add cost shadow var costLabelShadow = new Text2(towerCost, { size: 50, fill: 0x000000, weight: 800 }); costLabelShadow.anchor.set(0.5, 0.5); costLabelShadow.x = 4; // Shadow offset but centered horizontally costLabelShadow.y = 115 + 4; // Position higher up with shadow offset self.addChild(costLabelShadow); // Add cost label var costLabel = new Text2(towerCost, { size: 50, fill: 0xFFD700, weight: 800 }); costLabel.anchor.set(0.5, 0.5); costLabel.x = 0; // Explicitly center horizontally costLabel.y = 115; // Position higher up self.addChild(costLabel); // Add scaling animation function self.startScalingAnimation = function () { // Function to animate to larger scale (108%) function scaleUp() { tween(baseGraphics, { scaleX: 1.07, // Slightly smaller than tower scaling to 1.07 scaleY: 1.07 }, { duration: 1000, easing: tween.easeInOut, onFinish: function onFinish() { scaleDown(); } }); } // Function to animate to smaller scale (93%) function scaleDown() { tween(baseGraphics, { scaleX: 0.93, // Slightly smaller than tower scaling to 0.93 scaleY: 0.93 }, { duration: 1000, easing: tween.easeInOut, onFinish: function onFinish() { scaleUp(); } }); } // Start the animation cycle scaleUp(); }; self.update = function () { // Initialize scaling animation on first update if (!self.scalingInitialized) { self.scalingInitialized = true; // Start the continuous scaling animation self.startScalingAnimation(); } // Check if player can afford this tower var canAfford = gold >= getTowerCost(self.towerType); // Check if this is a single-use tower that has already been placed var isBlocked = self.towerType === 'king' && placedTowers.king || self.towerType === 'queen' && placedTowers.queen; // Set opacity based on affordability and placement status if (isBlocked) { self.alpha = 0.3; // Very dim for blocked towers } else { self.alpha = canAfford ? 1 : 0.5; } }; return self; }); /** * TOWER SYSTEM ARCHITECTURE * * The tower system consists of 4 main components: * 1. SourceTower - Menu towers at bottom for selecting tower types * 2. TowerPreview - Visual preview when dragging to place towers * 3. Tower - Functional placed towers that attack enemies * 4. UpgradeMenu - UI for upgrading/selling towers * * TOWER TYPES (6 total): * - default: Balanced tower (gray, 5 gold) * - rapid: Fast firing, low damage (blue, 15 gold) * - sniper: Long range, high damage (orange, 25 gold) * - splash: Area damage (green, 35 gold) * - slow: Slows enemies (purple, 45 gold) * - poison: Damage over time (teal, 55 gold) * * UPGRADE SYSTEM: * - 6 levels maximum per tower * - Exponential cost scaling: base_cost * (2^(level-1)) * - Final upgrade costs 1.75x but gives 2x effect * - Visual level indicators (dots) below each tower * * TARGETING SYSTEM: * - Ground enemies: Prioritized by path score (closer to exit) * - Flying enemies: Prioritized by distance to goal * - Range calculated dynamically based on tower type and level * * SPECIAL MECHANICS: * - Towers occupy 2x2 grid cells * - Cannot block enemy paths * - Recoil animation when firing * - Type-specific bullet effects and visuals */ var Tower = Container.expand(function (id) { var self = Container.call(this); self.id = id || 'default'; self.level = 1; self.maxLevel = 6; self.gridX = 0; self.gridY = 0; self.range = 3 * CELL_SIZE; // RANGE CALCULATION SYSTEM // Each tower type has unique range scaling: // - Sniper: Starts at 5 blocks, huge boost at max level (12 blocks) // - Splash: Short range (2-4 blocks) for balance // - Others: Standard scaling with incremental increases per level self.getRange = function () { // Always calculate range based on tower type and level switch (self.id) { case 'sniper': // Sniper: base 5, +0.8 per level, but final upgrade gets a huge boost if (self.level === self.maxLevel) { return 12 * CELL_SIZE; // Significantly increased range for max level } return (5 + (self.level - 1) * 0.8) * CELL_SIZE; case 'splash': // Splash: base 2, +0.2 per level (max ~4 blocks at max level) return (2 + (self.level - 1) * 0.2) * CELL_SIZE; case 'rapid': // Rapid: base 2.5, +0.5 per level return (2.5 + (self.level - 1) * 0.5) * CELL_SIZE; case 'slow': // Slow: base 3.5, +0.5 per level return (3.5 + (self.level - 1) * 0.5) * CELL_SIZE; case 'poison': // Poison: base 3.2, +0.5 per level return (3.2 + (self.level - 1) * 0.5) * CELL_SIZE; case 'bomb': // Bomb: base 3.2, +0.5 per level (same as poison) return (3.2 + (self.level - 1) * 0.5) * CELL_SIZE; case 'queen': // Queen: base 7.875, +1.125 per level (125% increase over slow/CapyFreeze - additional 50% boost) return (7.875 + (self.level - 1) * 1.125) * CELL_SIZE; case 'king': // King: base 8.5, +1.36 per level, huge boost at max level (70% increase over sniper/CapySniper - reduced by 30%) if (self.level === self.maxLevel) { return 20.4 * CELL_SIZE; // Significantly increased range for max level (70% increase) } return (8.5 + (self.level - 1) * 1.36) * CELL_SIZE; // 70% increase over sniper base default: // Default: base 3, +0.5 per level return (3 + (self.level - 1) * 0.5) * CELL_SIZE; } }; self.cellsInRange = []; self.fireRate = 60; self.bulletSpeed = 5; self.damage = 10; self.lastFired = 0; self.targetEnemy = null; switch (self.id) { case 'rapid': self.fireRate = 30; self.damage = 5; self.range = 2.5 * CELL_SIZE; self.bulletSpeed = 7; break; case 'sniper': self.fireRate = 90; self.damage = 25; self.range = 5 * CELL_SIZE; self.bulletSpeed = 25; break; case 'splash': self.fireRate = 75; self.damage = 15; self.range = 2 * CELL_SIZE; self.bulletSpeed = 4; break; case 'slow': self.fireRate = 50; self.damage = 8; self.range = 3.5 * CELL_SIZE; self.bulletSpeed = 5; break; case 'poison': self.fireRate = 70; self.damage = 12; self.range = 3.2 * CELL_SIZE; self.bulletSpeed = 5; break; case 'bomb': self.fireRate = 70; self.damage = 50; self.range = 3.2 * CELL_SIZE; self.bulletSpeed = 5; // Special bomb tower behavior - explodes after 2 seconds self.isBombTower = true; self.explosionTimer = 120; // 2 seconds at 60fps self.hasExploded = false; break; case 'queen': // Queen tower (enhanced version of slow/CapyFreeze) self.fireRate = 50; self.damage = 24; self.range = 5.25 * CELL_SIZE; self.bulletSpeed = 5; break; case 'king': // King tower (5x damage multiplier over sniper/CapySniper) self.fireRate = 90; self.damage = 125; // 25 * 5 = 125 self.range = 5 * CELL_SIZE; self.bulletSpeed = 25; break; } var assetName = 'tower_' + self.id; var baseGraphics = self.attachAsset(assetName, { anchorX: 0.5, anchorY: 0.5 }); var levelIndicators = []; var maxDots = self.maxLevel; var dotSpacing = baseGraphics.width / (maxDots + 1); var dotSize = CELL_SIZE / 6; for (var i = 0; i < maxDots; i++) { var dot = new Container(); var outlineCircle = dot.attachAsset('towerLevelIndicator', { anchorX: 0.5, anchorY: 0.5 }); outlineCircle.width = dotSize + 4; outlineCircle.height = dotSize + 4; outlineCircle.tint = 0x000000; var towerLevelIndicator = dot.attachAsset('towerLevelIndicator', { anchorX: 0.5, anchorY: 0.5 }); towerLevelIndicator.width = dotSize; towerLevelIndicator.height = dotSize; towerLevelIndicator.tint = 0xCCCCCC; dot.x = -CELL_SIZE + dotSpacing * (i + 1); dot.y = CELL_SIZE * 0.7; self.addChild(dot); levelIndicators.push(dot); } var gunContainer = new Container(); self.addChild(gunContainer); var gunGraphics = gunContainer.attachAsset('defense', { anchorX: 0.5, anchorY: 0.5 }); gunGraphics.alpha = 0.2; self.updateLevelIndicators = function () { for (var i = 0; i < maxDots; i++) { var dot = levelIndicators[i]; var towerLevelIndicator = dot.children[1]; if (i < self.level) { towerLevelIndicator.tint = 0xFFFFFF; } else { towerLevelIndicator.tint = 0xCCCCCC; } } }; self.updateLevelIndicators(); // Add scaling animation function self.startScalingAnimation = function () { // Function to animate to larger scale (108%) function scaleUp() { tween(baseGraphics, { scaleX: 1.08, scaleY: 1.08 }, { duration: 1000, easing: tween.easeInOut, onFinish: function onFinish() { scaleDown(); } }); } // Function to animate to smaller scale (92%) function scaleDown() { tween(baseGraphics, { scaleX: 0.92, scaleY: 0.92 }, { duration: 1000, easing: tween.easeInOut, onFinish: function onFinish() { scaleUp(); } }); } // Start the animation cycle scaleUp(); }; self.refreshCellsInRange = function () { for (var i = 0; i < self.cellsInRange.length; i++) { var cell = self.cellsInRange[i]; var towerIndex = cell.towersInRange.indexOf(self); if (towerIndex !== -1) { cell.towersInRange.splice(towerIndex, 1); } } self.cellsInRange = []; var rangeRadius = self.getRange() / CELL_SIZE; var centerX = self.gridX + 1; var centerY = self.gridY + 1; var minI = Math.floor(centerX - rangeRadius - 0.5); var maxI = Math.ceil(centerX + rangeRadius + 0.5); var minJ = Math.floor(centerY - rangeRadius - 0.5); var maxJ = Math.ceil(centerY + rangeRadius + 0.5); for (var i = minI; i <= maxI; i++) { for (var j = minJ; j <= maxJ; j++) { var closestX = Math.max(i, Math.min(centerX, i + 1)); var closestY = Math.max(j, Math.min(centerY, j + 1)); var deltaX = closestX - centerX; var deltaY = closestY - centerY; var distanceSquared = deltaX * deltaX + deltaY * deltaY; if (distanceSquared <= rangeRadius * rangeRadius) { var cell = grid.getCell(i, j); if (cell) { self.cellsInRange.push(cell); cell.towersInRange.push(self); } } } } grid.renderDebug(); }; self.getTotalValue = function () { var baseTowerCost = getTowerCost(self.id); var totalInvestment = baseTowerCost; var baseUpgradeCost = baseTowerCost; // Upgrade cost now scales with base tower cost for (var i = 1; i < self.level; i++) { totalInvestment += Math.floor(baseUpgradeCost * Math.pow(2, i - 1)); } return totalInvestment; }; self.upgrade = function () { if (self.level < self.maxLevel) { // Exponential upgrade cost: base cost * (2 ^ (level-1)), scaled by tower base cost var baseUpgradeCost = getTowerCost(self.id); var upgradeCost; // Make last upgrade level extra expensive if (self.level === self.maxLevel - 1) { upgradeCost = Math.floor(baseUpgradeCost * Math.pow(2, self.level - 1) * 3.5 / 2); // Half the cost for final upgrade } else { upgradeCost = Math.floor(baseUpgradeCost * Math.pow(2, self.level - 1)); } if (gold >= upgradeCost) { setGold(gold - upgradeCost); self.level++; // No need to update self.range here; getRange() is now the source of truth // Apply tower-specific upgrades based on type if (self.id === 'rapid') { if (self.level === self.maxLevel) { // Extra powerful last upgrade (double the effect) self.fireRate = Math.max(4, 30 - self.level * 9); // double the effect self.damage = 5 + self.level * 10; // double the effect self.bulletSpeed = 7 + self.level * 2.4; // double the effect } else { self.fireRate = Math.max(15, 30 - self.level * 3); // Fast tower gets faster with upgrades self.damage = 5 + self.level * 3; self.bulletSpeed = 7 + self.level * 0.7; } } else { if (self.level === self.maxLevel) { // Extra powerful last upgrade for all other towers (double the effect) self.fireRate = Math.max(5, 60 - self.level * 24); // double the effect self.damage = 10 + self.level * 20; // double the effect self.bulletSpeed = 5 + self.level * 2.4; // double the effect } else { self.fireRate = Math.max(20, 60 - self.level * 8); self.damage = 10 + self.level * 5; self.bulletSpeed = 5 + self.level * 0.5; } } self.refreshCellsInRange(); self.updateLevelIndicators(); if (self.level > 1) { var levelDot = levelIndicators[self.level - 1].children[1]; tween(levelDot, { scaleX: 1.5, scaleY: 1.5 }, { duration: 300, easing: tween.elasticOut, onFinish: function onFinish() { tween(levelDot, { scaleX: 1, scaleY: 1 }, { duration: 200, easing: tween.easeOut }); } }); } return true; } else { var notification = game.addChild(new Notification("Not enough gold to upgrade!")); notification.x = 2048 / 2; notification.y = grid.height - 50; return false; } } return false; }; // TARGETING PRIORITY SYSTEM // Ground enemies: Use pathfinding score (lower = closer to exit) // Flying enemies: Use distance to their flight goal // This ensures towers target the most dangerous enemies first self.findTarget = function () { var closestEnemy = null; var closestScore = Infinity; for (var i = 0; i < enemies.length; i++) { var enemy = enemies[i]; var dx = enemy.x - self.x; var dy = enemy.y - self.y; var distance = Math.sqrt(dx * dx + dy * dy); // Check if enemy is in range if (distance <= self.getRange()) { // Handle flying enemies differently - they can be targeted regardless of path if (enemy.isFlying) { // For flying enemies, prioritize by distance to the goal if (enemy.flyingTarget) { var goalX = enemy.flyingTarget.x; var goalY = enemy.flyingTarget.y; var distToGoal = Math.sqrt((goalX - enemy.cellX) * (goalX - enemy.cellX) + (goalY - enemy.cellY) * (goalY - enemy.cellY)); // Use distance to goal as score if (distToGoal < closestScore) { closestScore = distToGoal; closestEnemy = enemy; } } else { // If no flying target yet (shouldn't happen), prioritize by distance to tower if (distance < closestScore) { closestScore = distance; closestEnemy = enemy; } } } else { // For ground enemies, use the original path-based targeting // Get the cell for this enemy var cell = grid.getCell(enemy.cellX, enemy.cellY); if (cell && cell.pathId === pathId) { // Use the cell's score (distance to exit) for prioritization // Lower score means closer to exit if (cell.score < closestScore) { closestScore = cell.score; closestEnemy = enemy; } } } } } if (!closestEnemy) { self.targetEnemy = null; } return closestEnemy; }; self.update = function () { // Initialize scaling animation on first update if (!self.scalingInitialized) { self.scalingInitialized = true; // Start the continuous scaling animation self.startScalingAnimation(); } // Handle bomb tower special behavior if (self.isBombTower && !self.hasExploded) { self.explosionTimer--; if (self.explosionTimer <= 0) { self.explode(); return; // Don't continue with normal tower behavior after exploding } // Visual warning when close to explosion if (self.explosionTimer <= 60) { // Last second warning var warningIntensity = Math.sin(self.explosionTimer * 0.5) * 0.5 + 0.5; baseGraphics.tint = 0xFF0000 * warningIntensity + 0xFFFFFF * (1 - warningIntensity); } } // Normal tower behavior for non-bomb towers only if (!self.isBombTower) { self.targetEnemy = self.findTarget(); if (self.targetEnemy) { var dx = self.targetEnemy.x - self.x; var dy = self.targetEnemy.y - self.y; var angle = Math.atan2(dy, dx); gunContainer.rotation = angle; // Apply game speed to fire rate - higher speed means faster firing var adjustedFireRate = self.fireRate / gameSpeed; if (LK.ticks - self.lastFired >= adjustedFireRate) { self.fire(); self.lastFired = LK.ticks; } } } }; self.down = function (x, y, obj) { // Don't show upgrade menu for bomb towers since they explode if (self.isBombTower) { var notification = game.addChild(new Notification("Bomb towers cannot be upgraded - they explode!")); notification.x = 2048 / 2; notification.y = grid.height - 50; return; } var existingMenus = game.children.filter(function (child) { return child instanceof UpgradeMenu; }); var hasOwnMenu = false; var rangeCircle = null; for (var i = 0; i < game.children.length; i++) { if (game.children[i].isTowerRange && game.children[i].tower === self) { rangeCircle = game.children[i]; break; } } for (var i = 0; i < existingMenus.length; i++) { if (existingMenus[i].tower === self) { hasOwnMenu = true; break; } } if (hasOwnMenu) { for (var i = 0; i < existingMenus.length; i++) { if (existingMenus[i].tower === self) { hideUpgradeMenu(existingMenus[i]); } } if (rangeCircle) { game.removeChild(rangeCircle); } selectedTower = null; grid.renderDebug(); return; } for (var i = 0; i < existingMenus.length; i++) { existingMenus[i].destroy(); } for (var i = game.children.length - 1; i >= 0; i--) { if (game.children[i].isTowerRange) { game.removeChild(game.children[i]); } } selectedTower = self; var rangeIndicator = new Container(); rangeIndicator.isTowerRange = true; rangeIndicator.tower = self; game.addChild(rangeIndicator); rangeIndicator.x = self.x; rangeIndicator.y = self.y; var rangeGraphics = rangeIndicator.attachAsset('rangeCircle', { anchorX: 0.5, anchorY: 0.5 }); rangeGraphics.width = rangeGraphics.height = self.getRange() * 2; rangeGraphics.alpha = 0.2; var upgradeMenu = new UpgradeMenu(self); game.addChild(upgradeMenu); upgradeMenu.x = 2048 / 2; tween(upgradeMenu, { y: 2732 - 225 }, { duration: 200, easing: tween.backOut }); grid.renderDebug(); }; self.isInRange = function (enemy) { if (!enemy) { return false; } var dx = enemy.x - self.x; var dy = enemy.y - self.y; var distance = Math.sqrt(dx * dx + dy * dy); return distance <= self.getRange(); }; // BULLET EFFECT SYSTEM // Each tower type creates specialized bullets: // - splash: Area damage to nearby enemies // - slow: Reduces enemy speed (scales with tower level) // - poison: Damage over time effect // - sniper: High damage with visual critical effect // Bullets track their type and apply effects on impact self.fire = function () { if (self.targetEnemy) { var potentialDamage = 0; for (var i = 0; i < self.targetEnemy.bulletsTargetingThis.length; i++) { potentialDamage += self.targetEnemy.bulletsTargetingThis[i].damage; } if (self.targetEnemy.health > potentialDamage) { var bulletX = self.x + Math.cos(gunContainer.rotation) * 40; var bulletY = self.y + Math.sin(gunContainer.rotation) * 40; var bullet = new Bullet(bulletX, bulletY, self.targetEnemy, self.damage, self.bulletSpeed * gameSpeed); // Set bullet type based on tower type bullet.type = self.id; // For slow and queen towers, pass level for scaling slow effect if (self.id === 'slow' || self.id === 'queen') { bullet.sourceTowerLevel = self.level; } // Customize bullet appearance based on tower type switch (self.id) { case 'rapid': bullet.children[0].tint = 0x00AAFF; bullet.children[0].width = 20; bullet.children[0].height = 20; break; case 'sniper': bullet.children[0].tint = 0xFF5500; bullet.children[0].width = 15; bullet.children[0].height = 15; break; case 'splash': bullet.children[0].tint = 0x33CC00; bullet.children[0].width = 40; bullet.children[0].height = 40; break; case 'slow': bullet.children[0].tint = 0x9900FF; bullet.children[0].width = 35; bullet.children[0].height = 35; break; case 'poison': bullet.children[0].tint = 0x00FFAA; bullet.children[0].width = 35; bullet.children[0].height = 35; break; case 'bomb': bullet.children[0].tint = 0x00FFAA; bullet.children[0].width = 35; bullet.children[0].height = 35; break; case 'queen': // Queen bullets (similar to slow) bullet.children[0].tint = 0x9900FF; bullet.children[0].width = 35; bullet.children[0].height = 35; break; case 'king': // King bullets (similar to sniper) bullet.children[0].tint = 0xFF5500; bullet.children[0].width = 15; bullet.children[0].height = 15; break; } game.addChild(bullet); bullets.push(bullet); self.targetEnemy.bulletsTargetingThis.push(bullet); // --- Fire recoil effect for gunContainer --- // Stop any ongoing recoil tweens before starting a new one tween.stop(gunContainer, { x: true, y: true, scaleX: true, scaleY: true }); // Always use the original resting position for recoil, never accumulate offset if (gunContainer._restX === undefined) { gunContainer._restX = 0; } if (gunContainer._restY === undefined) { gunContainer._restY = 0; } if (gunContainer._restScaleX === undefined) { gunContainer._restScaleX = 1; } if (gunContainer._restScaleY === undefined) { gunContainer._restScaleY = 1; } // Reset to resting position before animating (in case of interrupted tweens) gunContainer.x = gunContainer._restX; gunContainer.y = gunContainer._restY; gunContainer.scaleX = gunContainer._restScaleX; gunContainer.scaleY = gunContainer._restScaleY; // Calculate recoil offset (recoil back along the gun's rotation) var recoilDistance = 8; var recoilX = -Math.cos(gunContainer.rotation) * recoilDistance; var recoilY = -Math.sin(gunContainer.rotation) * recoilDistance; // Animate recoil back from the resting position tween(gunContainer, { x: gunContainer._restX + recoilX, y: gunContainer._restY + recoilY }, { duration: 60, easing: tween.cubicOut, onFinish: function onFinish() { // Animate return to original position/scale tween(gunContainer, { x: gunContainer._restX, y: gunContainer._restY }, { duration: 90, easing: tween.cubicIn }); } }); } } }; self.explode = function () { if (self.hasExploded) return; self.hasExploded = true; // Create explosion visual effect var explosionEffect = new EffectIndicator(self.x, self.y, 'explosion'); game.addChild(explosionEffect); // Deal area damage to all enemies in range var explosionRange = self.getRange() * 1.5; // Larger explosion range var explosionDamage = self.damage * (self.level + 2); // Scaled damage based on level for (var i = 0; i < enemies.length; i++) { var enemy = enemies[i]; var dx = enemy.x - self.x; var dy = enemy.y - self.y; var distance = Math.sqrt(dx * dx + dy * dy); if (distance <= explosionRange) { // Apply explosion damage enemy.health -= explosionDamage; if (enemy.health <= 0) { enemy.health = 0; } else { enemy.healthBar.width = enemy.health / enemy.maxHealth * 70; } // Create damage indicator for each hit enemy var damageEffect = new EffectIndicator(enemy.x, enemy.y, 'explosion'); game.addChild(damageEffect); } } // Remove tower from grid and clean up var gridX = self.gridX; var gridY = self.gridY; for (var i = 0; i < 2; i++) { for (var j = 0; j < 2; j++) { var cell = grid.getCell(gridX + i, gridY + j); if (cell) { cell.type = 0; // Clear cell var towerIndex = cell.towersInRange.indexOf(self); if (towerIndex !== -1) { cell.towersInRange.splice(towerIndex, 1); } } } } // Remove from towers array var towerIndex = towers.indexOf(self); if (towerIndex !== -1) { towers.splice(towerIndex, 1); } // Close any upgrade menu for this tower var existingMenus = game.children.filter(function (child) { return child instanceof UpgradeMenu; }); for (var i = 0; i < existingMenus.length; i++) { if (existingMenus[i].tower === self) { hideUpgradeMenu(existingMenus[i]); } } // Remove range indicator if exists for (var i = game.children.length - 1; i >= 0; i--) { if (game.children[i].isTowerRange && game.children[i].tower === self) { game.removeChild(game.children[i]); } } // Clear selected tower if this was selected if (selectedTower === self) { selectedTower = null; } // Animate tower destruction tween(self, { scaleX: 2, scaleY: 2, alpha: 0 }, { duration: 300, easing: tween.easeOut, onFinish: function onFinish() { towerLayer.removeChild(self); grid.pathFind(); grid.renderDebug(); } }); }; self.placeOnGrid = function (gridX, gridY) { self.gridX = gridX; self.gridY = gridY; self.x = grid.x + gridX * CELL_SIZE + CELL_SIZE / 2; self.y = grid.y + gridY * CELL_SIZE + CELL_SIZE / 2; for (var i = 0; i < 2; i++) { for (var j = 0; j < 2; j++) { var cell = grid.getCell(gridX + i, gridY + j); if (cell) { cell.type = 1; } } } self.refreshCellsInRange(); }; return self; }); var TowerPreview = Container.expand(function () { var self = Container.call(this); var towerRange = 3; var rangeInPixels = towerRange * CELL_SIZE; self.towerType = 'default'; self.hasEnoughGold = true; var rangeIndicator = new Container(); self.addChild(rangeIndicator); var rangeGraphics = rangeIndicator.attachAsset('rangeCircle', { anchorX: 0.5, anchorY: 0.5 }); rangeGraphics.alpha = 0.3; var assetName = 'towerpreview_' + self.towerType; var previewGraphics = self.attachAsset(assetName, { anchorX: 0.5, anchorY: 0.5 }); previewGraphics.width = CELL_SIZE * 2; previewGraphics.height = CELL_SIZE * 2; self.canPlace = false; self.gridX = 0; self.gridY = 0; self.blockedByEnemy = false; self.update = function () { var previousHasEnoughGold = self.hasEnoughGold; self.hasEnoughGold = gold >= getTowerCost(self.towerType); // Only update appearance if the affordability status has changed if (previousHasEnoughGold !== self.hasEnoughGold) { self.updateAppearance(); } }; self.updateAppearance = function () { // Update the preview graphics asset to match the tower type var newAssetName = 'towerpreview_' + self.towerType; if (previewGraphics.assetName !== newAssetName) { // Remove old graphics self.removeChild(previewGraphics); // Create new graphics with correct asset previewGraphics = self.attachAsset(newAssetName, { anchorX: 0.5, anchorY: 0.5 }); previewGraphics.width = CELL_SIZE * 2; previewGraphics.height = CELL_SIZE * 2; previewGraphics.assetName = newAssetName; } // Use Tower class to get the source of truth for range var tempTower = new Tower(self.towerType); var previewRange = tempTower.getRange(); // Clean up tempTower to avoid memory leaks if (tempTower && tempTower.destroy) { tempTower.destroy(); } // Set range indicator using unified range logic rangeGraphics.width = rangeGraphics.height = previewRange * 2; if (!self.canPlace) { previewGraphics.tint = 0xFF0000; // Red tint when cannot place } else if (!self.hasEnoughGold) { previewGraphics.tint = 0xFFFFFF; // Keep white when insufficient gold } else { previewGraphics.tint = 0xFFFFFF; // White when can place } }; self.updatePlacementStatus = function () { var validGridPlacement = true; if (self.gridY <= 4 || self.gridY + 1 >= grid.cells[0].length - 4) { validGridPlacement = false; } else { for (var i = 0; i < 2; i++) { for (var j = 0; j < 2; j++) { var cell = grid.getCell(self.gridX + i, self.gridY + j); if (!cell || cell.type !== 0) { validGridPlacement = false; break; } } if (!validGridPlacement) { break; } } } self.blockedByEnemy = false; if (validGridPlacement) { for (var i = 0; i < enemies.length; i++) { var enemy = enemies[i]; if (enemy.currentCellY < 4) { continue; } // Only check non-flying enemies, flying enemies can pass over towers if (!enemy.isFlying) { if (enemy.cellX >= self.gridX && enemy.cellX < self.gridX + 2 && enemy.cellY >= self.gridY && enemy.cellY < self.gridY + 2) { self.blockedByEnemy = true; break; } if (enemy.currentTarget) { var targetX = enemy.currentTarget.x; var targetY = enemy.currentTarget.y; if (targetX >= self.gridX && targetX < self.gridX + 2 && targetY >= self.gridY && targetY < self.gridY + 2) { self.blockedByEnemy = true; break; } } } } } self.canPlace = validGridPlacement && !self.blockedByEnemy; self.hasEnoughGold = gold >= getTowerCost(self.towerType); self.updateAppearance(); }; self.checkPlacement = function () { self.updatePlacementStatus(); }; self.snapToGrid = function (x, y) { var gridPosX = x - grid.x; var gridPosY = y - grid.y; self.gridX = Math.floor(gridPosX / CELL_SIZE); self.gridY = Math.floor(gridPosY / CELL_SIZE); self.x = grid.x + self.gridX * CELL_SIZE + CELL_SIZE / 2; self.y = grid.y + self.gridY * CELL_SIZE + CELL_SIZE / 2; self.checkPlacement(); }; return self; }); var UpgradeMenu = Container.expand(function (tower) { var self = Container.call(this); self.tower = tower; self.y = 2732 + 225; var menuBackground = self.attachAsset('notification', { anchorX: 0.5, anchorY: 0.5 }); menuBackground.width = 2048; menuBackground.height = 500; menuBackground.tint = 0x444444; menuBackground.alpha = 0.9; var towerNames = { 'default': 'CapyShot', 'rapid': 'CapyBlast', 'sniper': 'CapySniper', 'splash': 'CapyPult', 'slow': 'CapyFreeze', 'poison': 'CapyPoison', 'bomb': 'CapyBomb', 'queen': 'CapyQueen', 'king': 'CapyKing' }; var displayName = towerNames[self.tower.id] || self.tower.id.charAt(0).toUpperCase() + self.tower.id.slice(1); var towerTypeText = new Text2(displayName + ' Tower', { size: 70, fill: 0xFFFFFF, weight: 800 }); towerTypeText.anchor.set(0, 0); towerTypeText.x = -840; towerTypeText.y = -160; self.addChild(towerTypeText); var statsText = new Text2('Level: ' + self.tower.level + '/' + self.tower.maxLevel + '\nDamage: ' + self.tower.damage + '\nFire Rate: ' + (60 / self.tower.fireRate).toFixed(1) + '/s', { size: 70, fill: 0xFFFFFF, weight: 400 }); statsText.anchor.set(0, 0.5); statsText.x = -840; statsText.y = 50; self.addChild(statsText); var buttonsContainer = new Container(); buttonsContainer.x = 500; self.addChild(buttonsContainer); var upgradeButton = new Container(); buttonsContainer.addChild(upgradeButton); var buttonBackground = upgradeButton.attachAsset('notification', { anchorX: 0.5, anchorY: 0.5 }); buttonBackground.width = 500; buttonBackground.height = 150; var isMaxLevel = self.tower.level >= self.tower.maxLevel; // Exponential upgrade cost: base cost * (2 ^ (level-1)), scaled by tower base cost var baseUpgradeCost = getTowerCost(self.tower.id); var upgradeCost; if (isMaxLevel) { upgradeCost = 0; } else if (self.tower.level === self.tower.maxLevel - 1) { upgradeCost = Math.floor(baseUpgradeCost * Math.pow(2, self.tower.level - 1) * 3.5 / 2); } else { upgradeCost = Math.floor(baseUpgradeCost * Math.pow(2, self.tower.level - 1)); } buttonBackground.tint = isMaxLevel ? 0x888888 : gold >= upgradeCost ? 0x00AA00 : 0x888888; var buttonText = new Text2(isMaxLevel ? 'Max Level' : 'Upgrade: ' + upgradeCost + ' gold', { size: 60, fill: 0xFFFFFF, weight: 800 }); buttonText.anchor.set(0.5, 0.5); upgradeButton.addChild(buttonText); var sellButton = new Container(); buttonsContainer.addChild(sellButton); var sellButtonBackground = sellButton.attachAsset('notification', { anchorX: 0.5, anchorY: 0.5 }); sellButtonBackground.width = 500; sellButtonBackground.height = 150; sellButtonBackground.tint = 0xCC0000; var totalInvestment = self.tower.getTotalValue ? self.tower.getTotalValue() : 0; var sellValue = getTowerSellValue(totalInvestment); var sellButtonText = new Text2('Sell: +' + sellValue + ' gold', { size: 60, fill: 0xFFFFFF, weight: 800 }); sellButtonText.anchor.set(0.5, 0.5); sellButton.addChild(sellButtonText); upgradeButton.y = -85; sellButton.y = 85; var closeButton = new Container(); self.addChild(closeButton); var closeBackground = closeButton.attachAsset('notification', { anchorX: 0.5, anchorY: 0.5 }); closeBackground.width = 90; closeBackground.height = 90; closeBackground.tint = 0xAA0000; var closeText = new Text2('X', { size: 68, fill: 0xFFFFFF, weight: 800 }); closeText.anchor.set(0.5, 0.5); closeButton.addChild(closeText); closeButton.x = menuBackground.width / 2 - 57; closeButton.y = -menuBackground.height / 2 + 57; upgradeButton.down = function (x, y, obj) { if (self.tower.level >= self.tower.maxLevel) { var notification = game.addChild(new Notification("Tower is already at max level!")); notification.x = 2048 / 2; notification.y = grid.height - 50; return; } if (self.tower.upgrade()) { // Exponential upgrade cost: base cost * (2 ^ (level-1)), scaled by tower base cost var baseUpgradeCost = getTowerCost(self.tower.id); if (self.tower.level >= self.tower.maxLevel) { upgradeCost = 0; } else if (self.tower.level === self.tower.maxLevel - 1) { upgradeCost = Math.floor(baseUpgradeCost * Math.pow(2, self.tower.level - 1) * 3.5 / 2); } else { upgradeCost = Math.floor(baseUpgradeCost * Math.pow(2, self.tower.level - 1)); } statsText.setText('Level: ' + self.tower.level + '/' + self.tower.maxLevel + '\nDamage: ' + self.tower.damage + '\nFire Rate: ' + (60 / self.tower.fireRate).toFixed(1) + '/s'); buttonText.setText('Upgrade: ' + upgradeCost + ' gold'); var totalInvestment = self.tower.getTotalValue ? self.tower.getTotalValue() : 0; var sellValue = Math.floor(totalInvestment * 0.6); sellButtonText.setText('Sell: +' + sellValue + ' gold'); if (self.tower.level >= self.tower.maxLevel) { buttonBackground.tint = 0x888888; buttonText.setText('Max Level'); } var rangeCircle = null; for (var i = 0; i < game.children.length; i++) { if (game.children[i].isTowerRange && game.children[i].tower === self.tower) { rangeCircle = game.children[i]; break; } } if (rangeCircle) { var rangeGraphics = rangeCircle.children[0]; rangeGraphics.width = rangeGraphics.height = self.tower.getRange() * 2; } else { var newRangeIndicator = new Container(); newRangeIndicator.isTowerRange = true; newRangeIndicator.tower = self.tower; game.addChildAt(newRangeIndicator, 0); newRangeIndicator.x = self.tower.x; newRangeIndicator.y = self.tower.y; var rangeGraphics = newRangeIndicator.attachAsset('rangeCircle', { anchorX: 0.5, anchorY: 0.5 }); rangeGraphics.width = rangeGraphics.height = self.tower.getRange() * 2; rangeGraphics.alpha = 0.3; } tween(self, { scaleX: 1.05, scaleY: 1.05 }, { duration: 100, easing: tween.easeOut, onFinish: function onFinish() { tween(self, { scaleX: 1, scaleY: 1 }, { duration: 100, easing: tween.easeIn }); } }); } }; sellButton.down = function (x, y, obj) { var totalInvestment = self.tower.getTotalValue ? self.tower.getTotalValue() : 0; var sellValue = getTowerSellValue(totalInvestment); setGold(gold + sellValue); var notification = game.addChild(new Notification("Tower sold for " + sellValue + " gold!")); notification.x = 2048 / 2; notification.y = grid.height - 50; // Reset placement tracking for single-use towers when sold if (self.tower.id === 'king') { placedTowers.king = false; } else if (self.tower.id === 'queen') { placedTowers.queen = false; } var gridX = self.tower.gridX; var gridY = self.tower.gridY; for (var i = 0; i < 2; i++) { for (var j = 0; j < 2; j++) { var cell = grid.getCell(gridX + i, gridY + j); if (cell) { cell.type = 0; var towerIndex = cell.towersInRange.indexOf(self.tower); if (towerIndex !== -1) { cell.towersInRange.splice(towerIndex, 1); } } } } if (selectedTower === self.tower) { selectedTower = null; } var towerIndex = towers.indexOf(self.tower); if (towerIndex !== -1) { towers.splice(towerIndex, 1); } towerLayer.removeChild(self.tower); grid.pathFind(); grid.renderDebug(); self.destroy(); for (var i = 0; i < game.children.length; i++) { if (game.children[i].isTowerRange && game.children[i].tower === self.tower) { game.removeChild(game.children[i]); break; } } }; closeButton.down = function (x, y, obj) { hideUpgradeMenu(self); selectedTower = null; grid.renderDebug(); }; self.update = function () { if (self.tower.level >= self.tower.maxLevel) { if (buttonText.text !== 'Max Level') { buttonText.setText('Max Level'); buttonBackground.tint = 0x888888; } return; } // Exponential upgrade cost: base cost * (2 ^ (level-1)), scaled by tower base cost var baseUpgradeCost = getTowerCost(self.tower.id); var currentUpgradeCost; if (self.tower.level >= self.tower.maxLevel) { currentUpgradeCost = 0; } else if (self.tower.level === self.tower.maxLevel - 1) { currentUpgradeCost = Math.floor(baseUpgradeCost * Math.pow(2, self.tower.level - 1) * 3.5 / 2); } else { currentUpgradeCost = Math.floor(baseUpgradeCost * Math.pow(2, self.tower.level - 1)); } var canAfford = gold >= currentUpgradeCost; buttonBackground.tint = canAfford ? 0x00AA00 : 0x888888; var newText = 'Upgrade: ' + currentUpgradeCost + ' gold'; if (buttonText.text !== newText) { buttonText.setText(newText); } }; return self; }); var WaveIndicator = Container.expand(function () { var self = Container.call(this); self.gameStarted = false; self.waveMarkers = []; self.waveTypes = []; self.enemyCounts = []; self.indicatorWidth = 0; self.lastBossType = null; // Track the last boss type to avoid repeating var blockWidth = 400; var totalBlocksWidth = blockWidth * totalWaves; var startMarker = new Container(); var startBlock = startMarker.attachAsset('notification', { anchorX: 0.5, anchorY: 0.5 }); startBlock.width = blockWidth - 10; startBlock.height = 70 * 2; startBlock.tint = 0x00AA00; // Add shadow for start text var startTextShadow = new Text2("Start Game", { size: 50, fill: 0x000000, weight: 800 }); startTextShadow.anchor.set(0.5, 0.5); startTextShadow.x = 4; startTextShadow.y = 4; startMarker.addChild(startTextShadow); var startText = new Text2("Start Game", { size: 50, fill: 0xFFFFFF, weight: 800 }); startText.anchor.set(0.5, 0.5); startMarker.addChild(startText); startMarker.x = -self.indicatorWidth; self.addChild(startMarker); self.waveMarkers.push(startMarker); startMarker.down = function () { if (!self.gameStarted) { self.gameStarted = true; currentWave = 0; waveTimer = nextWaveTime; startBlock.tint = 0x00FF00; startText.setText("Started!"); startTextShadow.setText("Started!"); // Make sure shadow position remains correct after text change startTextShadow.x = 4; startTextShadow.y = 4; var notification = game.addChild(new Notification("Game started! Wave 1 incoming!")); notification.x = 2048 / 2; notification.y = grid.height - 150; } }; for (var i = 0; i < totalWaves; i++) { var marker = new Container(); var block = marker.attachAsset('notification', { anchorX: 0.5, anchorY: 0.5 }); block.width = blockWidth - 10; block.height = 70 * 2; // --- Begin new unified wave logic --- var waveType = "normal"; var enemyType = "normal"; var enemyCount = 10; var isBossWave = (i + 1) % 10 === 0; // Ensure all types appear in early waves if (i === 0) { block.tint = 0xAAAAAA; waveType = "Normal"; enemyType = "normal"; enemyCount = 10; } else if (i === 1) { block.tint = 0x00AAFF; waveType = "Fast"; enemyType = "fast"; enemyCount = 10; } else if (i === 2) { block.tint = 0xAA0000; waveType = "Immune"; enemyType = "immune"; enemyCount = 10; } else if (i === 3) { block.tint = 0xFFFF00; waveType = "Flying"; enemyType = "flying"; enemyCount = 10; } else if (i === 4) { block.tint = 0xFF00FF; waveType = "Swarm"; enemyType = "swarm"; enemyCount = 30; } else if (isBossWave) { // Boss waves: cycle through all boss types, last boss is always flying var bossTypes = ['normal', 'fast', 'immune', 'flying']; var bossTypeIndex = Math.floor((i + 1) / 10) - 1; if (i === totalWaves - 1) { // Last boss is always flying enemyType = 'flying'; waveType = "Boss Flying"; block.tint = 0xFFFF00; } else { enemyType = bossTypes[bossTypeIndex % bossTypes.length]; switch (enemyType) { case 'normal': block.tint = 0xAAAAAA; waveType = "Boss Normal"; break; case 'fast': block.tint = 0x00AAFF; waveType = "Boss Fast"; break; case 'immune': block.tint = 0xAA0000; waveType = "Boss Immune"; break; case 'flying': block.tint = 0xFFFF00; waveType = "Boss Flying"; break; } } enemyCount = 1; // Make the wave indicator for boss waves stand out // Set boss wave color to the color of the wave type switch (enemyType) { case 'normal': block.tint = 0xAAAAAA; break; case 'fast': block.tint = 0x00AAFF; break; case 'immune': block.tint = 0xAA0000; break; case 'flying': block.tint = 0xFFFF00; break; default: block.tint = 0xFF0000; break; } } else if ((i + 1) % 5 === 0) { // Every 5th non-boss wave is fast block.tint = 0x00AAFF; waveType = "Fast"; enemyType = "fast"; enemyCount = 10; } else if ((i + 1) % 4 === 0) { // Every 4th non-boss wave is immune block.tint = 0xAA0000; waveType = "Immune"; enemyType = "immune"; enemyCount = 10; } else if ((i + 1) % 7 === 0) { // Every 7th non-boss wave is flying block.tint = 0xFFFF00; waveType = "Flying"; enemyType = "flying"; enemyCount = 10; } else if ((i + 1) % 3 === 0) { // Every 3rd non-boss wave is swarm block.tint = 0xFF00FF; waveType = "Swarm"; enemyType = "swarm"; enemyCount = 30; } else { block.tint = 0xAAAAAA; waveType = "Normal"; enemyType = "normal"; enemyCount = 10; } // --- End new unified wave logic --- // Mark boss waves with a special visual indicator if (isBossWave && enemyType !== 'swarm') { // Add a crown or some indicator to the wave marker for boss waves var bossIndicator = marker.attachAsset('towerLevelIndicator', { anchorX: 0.5, anchorY: 0.5 }); bossIndicator.width = 30; bossIndicator.height = 30; bossIndicator.tint = 0xFFD700; // Gold color bossIndicator.y = -block.height / 2 - 15; // Change the wave type text to indicate boss waveType = "BOSS"; } // Store the wave type and enemy count self.waveTypes[i] = enemyType; self.enemyCounts[i] = enemyCount; // Add shadow for wave type - 30% smaller than before var waveTypeShadow = new Text2(waveType, { size: 56, fill: 0x000000, weight: 800 }); waveTypeShadow.anchor.set(0.5, 0.5); waveTypeShadow.x = 4; waveTypeShadow.y = 4; marker.addChild(waveTypeShadow); // Add wave type text - 30% smaller than before var waveTypeText = new Text2(waveType, { size: 56, fill: 0xFFFFFF, weight: 800 }); waveTypeText.anchor.set(0.5, 0.5); waveTypeText.y = 0; marker.addChild(waveTypeText); // Add shadow for wave number - 20% larger than before var waveNumShadow = new Text2((i + 1).toString(), { size: 48, fill: 0x000000, weight: 800 }); waveNumShadow.anchor.set(1.0, 1.0); waveNumShadow.x = blockWidth / 2 - 16 + 5; waveNumShadow.y = block.height / 2 - 12 + 5; marker.addChild(waveNumShadow); // Main wave number text - 20% larger than before var waveNum = new Text2((i + 1).toString(), { size: 48, fill: 0xFFFFFF, weight: 800 }); waveNum.anchor.set(1.0, 1.0); waveNum.x = blockWidth / 2 - 16; waveNum.y = block.height / 2 - 12; marker.addChild(waveNum); marker.x = -self.indicatorWidth + (i + 1) * blockWidth; self.addChild(marker); self.waveMarkers.push(marker); } // Get wave type for a specific wave number self.getWaveType = function (waveNumber) { if (waveNumber < 1 || waveNumber > totalWaves) { return "normal"; } // If this is a boss wave (waveNumber % 10 === 0), and the type is the same as lastBossType // then we should return a different boss type var waveType = self.waveTypes[waveNumber - 1]; return waveType; }; // Get enemy count for a specific wave number self.getEnemyCount = function (waveNumber) { if (waveNumber < 1 || waveNumber > totalWaves) { return 10; } return self.enemyCounts[waveNumber - 1]; }; // Get display name for a wave type self.getWaveTypeName = function (waveNumber) { var type = self.getWaveType(waveNumber); var typeName = type.charAt(0).toUpperCase() + type.slice(1); // Add boss prefix for boss waves (every 10th wave) if (waveNumber % 10 === 0 && waveNumber > 0 && type !== 'swarm') { typeName = "BOSS"; } return typeName; }; self.positionIndicator = new Container(); var indicator = self.positionIndicator.attachAsset('towerLevelIndicator', { anchorX: 0.5, anchorY: 0.5 }); indicator.width = blockWidth - 10; indicator.height = 16; indicator.tint = 0xffad0e; indicator.y = -65; var indicator2 = self.positionIndicator.attachAsset('towerLevelIndicator', { anchorX: 0.5, anchorY: 0.5 }); indicator2.width = blockWidth - 10; indicator2.height = 16; indicator2.tint = 0xffad0e; indicator2.y = 65; var leftWall = self.positionIndicator.attachAsset('towerLevelIndicator', { anchorX: 0.5, anchorY: 0.5 }); leftWall.width = 16; leftWall.height = 146; leftWall.tint = 0xffad0e; leftWall.x = -(blockWidth - 16) / 2; var rightWall = self.positionIndicator.attachAsset('towerLevelIndicator', { anchorX: 0.5, anchorY: 0.5 }); rightWall.width = 16; rightWall.height = 146; rightWall.tint = 0xffad0e; rightWall.x = (blockWidth - 16) / 2; self.addChild(self.positionIndicator); self.update = function () { var progress = waveTimer / nextWaveTime; var moveAmount = (progress + currentWave) * blockWidth; for (var i = 0; i < self.waveMarkers.length; i++) { var marker = self.waveMarkers[i]; marker.x = -moveAmount + i * blockWidth; } self.positionIndicator.x = 0; for (var i = 0; i < totalWaves + 1; i++) { var marker = self.waveMarkers[i]; if (i === 0) { continue; } var block = marker.children[0]; if (i - 1 < currentWave) { block.alpha = .5; } } self.handleWaveProgression = function () { if (!self.gameStarted) { return; } if (currentWave < totalWaves) { waveTimer++; if (waveTimer >= nextWaveTime) { waveTimer = 0; currentWave++; waveInProgress = true; waveSpawned = false; if (currentWave != 1) { var waveType = self.getWaveTypeName(currentWave); var enemyCount = self.getEnemyCount(currentWave); var notification = game.addChild(new Notification("Wave " + currentWave + " (" + waveType + " - " + enemyCount + " enemies) incoming!")); notification.x = 2048 / 2; notification.y = grid.height - 150; } } } }; self.handleWaveProgression(); }; return self; }); var WelcomeScreen = Container.expand(function () { var self = Container.call(this); // Background image var backgroundImage = self.attachAsset('welcome_background', { anchorX: 0.5, anchorY: 0.5 }); backgroundImage.x = 0; backgroundImage.y = 0; backgroundImage.width = 2048; backgroundImage.height = 2732; // Main instruction text shadow - positioned at bottom var instructionShadow = new Text2("Press anywhere to continue", { size: 80, fill: 0x000000, weight: 800 }); instructionShadow.anchor.set(0.5, 1.0); instructionShadow.x = 4; instructionShadow.y = 2732 / 2 - 200 + 4; self.addChild(instructionShadow); // Main instruction text - positioned at bottom var instructionText = new Text2("Press anywhere to continue", { size: 80, fill: 0xFFFFFF, weight: 800 }); instructionText.anchor.set(0.5, 1.0); instructionText.x = 0; instructionText.y = 2732 / 2 - 200; self.addChild(instructionText); // Pulsing animation for the text self.animationTimer = 0; self.update = function () { self.animationTimer += 0.05; var alpha = 0.7 + 0.3 * Math.sin(self.animationTimer); instructionText.alpha = alpha; instructionShadow.alpha = alpha; }; // Handle any touch/click to continue self.down = function (x, y, obj) { // Show UI labels when leaving welcome screen goldText.visible = true; goldTextShadow.visible = true; livesText.visible = true; livesTextShadow.visible = true; scoreText.visible = true; scoreTextShadow.visible = true; // Remove welcome screen and show level selector self.destroy(); // Show level selector var levelSelector = new LevelSelector(); levelSelector.x = 2048 / 2; levelSelector.y = 2732 / 2; game.addChild(levelSelector); }; return self; }); /**** * Initialize Game ****/ var game = new LK.Game({ backgroundColor: 0x333333 }); /**** * Game Code ****/ // Add background image based on selected level function getBackgroundAssetName() { switch (selectedLevel) { case 'garden': return 'background_garden'; case 'pool': return 'background_pool'; case 'lobby': return 'background_lobby'; case 'greathall': return 'background_greathall'; default: return 'background_garden'; } } function getCellAssetName() { switch (selectedLevel) { case 'garden': return 'cell_garden'; case 'pool': return 'cell_pool'; case 'lobby': return 'cell_lobby'; case 'greathall': return 'cell_greathall'; default: return 'cell_garden'; } } var backgroundImage = game.attachAsset(getBackgroundAssetName(), { anchorX: 0.5, anchorY: 0.5 }); backgroundImage.x = 2048 / 2; backgroundImage.y = 2732 / 2; // Scale background to cover the entire screen backgroundImage.width = 2048; backgroundImage.height = 2732; var isHidingUpgradeMenu = false; function hideUpgradeMenu(menu) { if (isHidingUpgradeMenu) { return; } isHidingUpgradeMenu = true; tween(menu, { y: 2732 + 225 }, { duration: 150, easing: tween.easeIn, onFinish: function onFinish() { menu.destroy(); isHidingUpgradeMenu = false; } }); } var CELL_SIZE = 76; var pathId = 1; var maxScore = 0; var enemies = []; var towers = []; var bullets = []; var defenses = []; var selectedTower = null; var gold = 80; var difficultyMultiplier = 1.0; // Will be set by difficulty selector var selectedLevel = 'garden'; // Default level, will be set by level selector var lives = 20; var score = 0; var currentWave = 0; var totalWaves = 50; var waveTimer = 0; var waveInProgress = false; var waveSpawned = false; var nextWaveTime = 12000 / 2; var sourceTower = null; var enemiesToSpawn = 10; // Default number of enemies per wave var goldTextShadow = new Text2('Gold: ' + gold, { size: 60, fill: 0x000000, weight: 800 }); goldTextShadow.anchor.set(0.5, 0.5); var goldText = new Text2('Gold: ' + gold, { size: 60, fill: 0xFFD700, weight: 800 }); goldText.anchor.set(0.5, 0.5); var livesTextShadow = new Text2('Lives: ' + lives, { size: 60, fill: 0x000000, weight: 800 }); livesTextShadow.anchor.set(0.5, 0.5); var livesText = new Text2('Lives: ' + lives, { size: 60, fill: 0x00FF00, weight: 800 }); livesText.anchor.set(0.5, 0.5); var scoreTextShadow = new Text2('Score: ' + score, { size: 60, fill: 0x000000, weight: 800 }); scoreTextShadow.anchor.set(0.5, 0.5); var scoreText = new Text2('Score: ' + score, { size: 60, fill: 0xFF0000, weight: 800 }); scoreText.anchor.set(0.5, 0.5); var topMargin = 50; var centerX = 2048 / 2; var spacing = 400; LK.gui.top.addChild(goldTextShadow); LK.gui.top.addChild(livesTextShadow); LK.gui.top.addChild(scoreTextShadow); LK.gui.top.addChild(goldText); LK.gui.top.addChild(livesText); LK.gui.top.addChild(scoreText); // Position shadow texts with offset goldTextShadow.x = -spacing + 2; goldTextShadow.y = topMargin + 2; livesTextShadow.x = 0 + 2; livesTextShadow.y = topMargin + 2; scoreTextShadow.x = spacing + 2; scoreTextShadow.y = topMargin + 2; // Position main texts livesText.x = 0; livesText.y = topMargin; goldText.x = -spacing; goldText.y = topMargin; scoreText.x = spacing; scoreText.y = topMargin; function updateUI() { goldTextShadow.setText('Gold: ' + gold); livesTextShadow.setText('Lives: ' + lives); scoreTextShadow.setText('Score: ' + score); goldText.setText('Gold: ' + gold); livesText.setText('Lives: ' + lives); scoreText.setText('Score: ' + score); } function setGold(value) { gold = value; updateUI(); } var debugLayer = new Container(); var towerLayer = new Container(); // Create three separate layers for enemy hierarchy var enemyLayerBottom = new Container(); // For normal enemies var enemyLayerMiddle = new Container(); // For shadows var enemyLayerTop = new Container(); // For flying enemies var enemyLayer = new Container(); // Main container to hold all enemy layers // Add layers in correct order (bottom first, then middle for shadows, then top) enemyLayer.addChild(enemyLayerBottom); enemyLayer.addChild(enemyLayerMiddle); enemyLayer.addChild(enemyLayerTop); var grid = new Grid(24, 29 + 6); grid.x = 150; grid.y = 200 - CELL_SIZE * 4; grid.pathFind(); grid.renderDebug(); debugLayer.addChild(grid); // Add pool rectangle image if on pool level var poolRectangleImage = null; // Add lobby square images if on lobby level var lobbySquareImages = []; if (selectedLevel === 'pool') { poolRectangleImage = game.attachAsset('pool_rectangle', { anchorX: 0.5, anchorY: 0.5 }); // Position over the rectangular pool area (center of grid + pool area center offset) var gridWidth = 24; var gridHeight = 35; var centerX = Math.floor(gridWidth / 2); // Center column (12) var centerY = Math.floor(gridHeight / 2); // Center row var rectWidth = 6; // Rectangle width (6 cells) var rectHeight = 16; // Rectangle height (16 cells) var rectLeft = centerX - Math.floor(rectWidth / 2); var rectTop = centerY - Math.floor(rectHeight / 2); // Calculate the actual center of the rectangular pool area in pixels var poolCenterX = grid.x + (rectLeft + rectWidth / 2) * CELL_SIZE; var poolCenterY = grid.y + (rectTop + rectHeight / 2) * CELL_SIZE; poolRectangleImage.x = poolCenterX - CELL_SIZE + CELL_SIZE / 2 - 0.5 - 0.5 - 1 - 2; poolRectangleImage.y = poolCenterY - CELL_SIZE + CELL_SIZE / 2 - 0.5 - 0.5 - 1 - 2; // Keep pool rectangle image at normal tint (not gray) so it appears above the gray cells poolRectangleImage.tint = 0xFFFFFF; // Keep original white/normal tint // Add to tower layer so it appears above the grid cells towerLayer.addChild(poolRectangleImage); } game.addChild(debugLayer); game.addChild(towerLayer); game.addChild(enemyLayer); var offset = 0; var towerPreview = new TowerPreview(); game.addChild(towerPreview); towerPreview.visible = false; var isDragging = false; function wouldBlockPath(gridX, gridY) { var cells = []; for (var i = 0; i < 2; i++) { for (var j = 0; j < 2; j++) { var cell = grid.getCell(gridX + i, gridY + j); if (cell) { cells.push({ cell: cell, originalType: cell.type }); cell.type = 1; } } } var blocked = grid.pathFind(); for (var i = 0; i < cells.length; i++) { cells[i].cell.type = cells[i].originalType; } grid.pathFind(); grid.renderDebug(); return blocked; } function getTowerCost(towerType) { var cost = 5; switch (towerType) { case 'rapid': cost = 15; break; case 'sniper': cost = 25; break; case 'splash': cost = 35; break; case 'slow': cost = 45; break; case 'poison': cost = 55; break; case 'bomb': cost = 80; break; case 'queen': cost = 200; break; case 'king': cost = 250; break; } return cost; } function getTowerSellValue(totalValue) { return waveIndicator && waveIndicator.gameStarted ? Math.floor(totalValue * 0.6) : totalValue; } function placeTower(gridX, gridY, towerType) { var towerCost = getTowerCost(towerType); if (gold >= towerCost) { // Check if this is a single-use tower that has already been placed if (towerType === 'king' && placedTowers.king || towerType === 'queen' && placedTowers.queen) { var notification = game.addChild(new Notification("This tower can only be placed once!")); notification.x = 2048 / 2; notification.y = grid.height - 50; return false; } var tower = new Tower(towerType || 'default'); tower.placeOnGrid(gridX, gridY); towerLayer.addChild(tower); towers.push(tower); setGold(gold - towerCost); // Mark single-use towers as placed if (towerType === 'king') { placedTowers.king = true; } else if (towerType === 'queen') { placedTowers.queen = true; } grid.pathFind(); grid.renderDebug(); return true; } else { var notification = game.addChild(new Notification("Not enough gold!")); notification.x = 2048 / 2; notification.y = grid.height - 50; return false; } } game.down = function (x, y, obj) { var upgradeMenuVisible = game.children.some(function (child) { return child instanceof UpgradeMenu; }); if (upgradeMenuVisible) { return; } for (var i = 0; i < sourceTowers.length; i++) { var tower = sourceTowers[i]; if (x >= tower.x - tower.width / 2 && x <= tower.x + tower.width / 2 && y >= tower.y - tower.height / 2 && y <= tower.y + tower.height / 2) { towerPreview.visible = true; isDragging = true; towerPreview.towerType = tower.towerType; towerPreview.updateAppearance(); // Apply the same offset as in move handler to ensure consistency when starting drag towerPreview.snapToGrid(x, y - CELL_SIZE * 1.5); break; } } }; game.move = function (x, y, obj) { if (isDragging) { // Shift the y position upward by 1.5 tiles to show preview above finger towerPreview.snapToGrid(x, y - CELL_SIZE * 1.5); } }; game.up = function (x, y, obj) { var clickedOnTower = false; for (var i = 0; i < towers.length; i++) { var tower = towers[i]; var towerLeft = tower.x - tower.width / 2; var towerRight = tower.x + tower.width / 2; var towerTop = tower.y - tower.height / 2; var towerBottom = tower.y + tower.height / 2; if (x >= towerLeft && x <= towerRight && y >= towerTop && y <= towerBottom) { clickedOnTower = true; break; } } var upgradeMenus = game.children.filter(function (child) { return child instanceof UpgradeMenu; }); if (upgradeMenus.length > 0 && !isDragging && !clickedOnTower) { var clickedOnMenu = false; for (var i = 0; i < upgradeMenus.length; i++) { var menu = upgradeMenus[i]; var menuWidth = 2048; var menuHeight = 450; var menuLeft = menu.x - menuWidth / 2; var menuRight = menu.x + menuWidth / 2; var menuTop = menu.y - menuHeight / 2; var menuBottom = menu.y + menuHeight / 2; if (x >= menuLeft && x <= menuRight && y >= menuTop && y <= menuBottom) { clickedOnMenu = true; break; } } if (!clickedOnMenu) { for (var i = 0; i < upgradeMenus.length; i++) { var menu = upgradeMenus[i]; hideUpgradeMenu(menu); } for (var i = game.children.length - 1; i >= 0; i--) { if (game.children[i].isTowerRange) { game.removeChild(game.children[i]); } } selectedTower = null; grid.renderDebug(); } } if (isDragging) { isDragging = false; if (towerPreview.canPlace) { if (!wouldBlockPath(towerPreview.gridX, towerPreview.gridY)) { placeTower(towerPreview.gridX, towerPreview.gridY, towerPreview.towerType); } else { var notification = game.addChild(new Notification("Tower would block the path!")); notification.x = 2048 / 2; notification.y = grid.height - 50; } } else if (towerPreview.blockedByEnemy) { var notification = game.addChild(new Notification("Cannot build: Enemy in the way!")); notification.x = 2048 / 2; notification.y = grid.height - 50; } else if (towerPreview.visible) { var notification = game.addChild(new Notification("Cannot build here!")); notification.x = 2048 / 2; notification.y = grid.height - 50; } towerPreview.visible = false; if (isDragging) { var upgradeMenus = game.children.filter(function (child) { return child instanceof UpgradeMenu; }); for (var i = 0; i < upgradeMenus.length; i++) { upgradeMenus[i].destroy(); } } } }; var waveIndicator = new WaveIndicator(); waveIndicator.x = 2048 / 2; waveIndicator.y = 2732 - 80; game.addChild(waveIndicator); var nextWaveButtonContainer = new Container(); var nextWaveButton = new NextWaveButton(); nextWaveButton.x = 2048 - 200; nextWaveButton.y = 2732 - 100 + 20; nextWaveButtonContainer.addChild(nextWaveButton); game.addChild(nextWaveButtonContainer); // Velocity control system var gameSpeed = 1.0; var speedMultipliers = [1.0, 1.3, 1.5, 2.0]; var currentSpeedIndex = 0; // Track tower placement for single-use towers var placedTowers = { king: false, queen: false }; var velocityButton = new Container(); var velocityButtonBackground = velocityButton.attachAsset('notification', { anchorX: 0.5, anchorY: 0.5 }); velocityButtonBackground.width = 280; velocityButtonBackground.height = 120; velocityButtonBackground.tint = 0x00AA88; var velocityButtonText = new Text2("Velocity", { size: 45, fill: 0xFFFFFF, weight: 800 }); velocityButtonText.anchor.set(0.5, 0.5); velocityButtonText.y = -15; // Move velocity text up slightly var velocitySpeedText = new Text2("x" + gameSpeed.toFixed(1), { size: 40, fill: 0xFFD700, weight: 800 }); velocitySpeedText.anchor.set(0.5, 0.5); velocitySpeedText.y = 32; // Increase spacing by 2 pixels (was 30, now 32) velocityButton.addChild(velocityButtonText); velocityButton.addChild(velocitySpeedText); velocityButton.x = 200; velocityButton.y = 2732 - 80; // Move button lower (was -120, now -80) velocityButton.visible = false; velocityButton.enabled = false; game.addChild(velocityButton); var towerTypes = ['default', 'rapid', 'sniper', 'splash', 'slow', 'poison', 'bomb', 'queen', 'king']; var sourceTowers = []; var towerSpacing = 220; // Adjusted spacing for 9 towers var startX = 2048 / 2 - towerTypes.length * towerSpacing / 2 + towerSpacing / 2; var towerY = 2732 - CELL_SIZE * 3 - 90; for (var i = 0; i < towerTypes.length; i++) { var tower = new SourceTower(towerTypes[i]); tower.x = startX + i * towerSpacing; tower.y = towerY; towerLayer.addChild(tower); sourceTowers.push(tower); } sourceTower = null; enemiesToSpawn = 10; velocityButton.down = function () { if (!velocityButton.enabled) { return; } currentSpeedIndex = (currentSpeedIndex + 1) % speedMultipliers.length; gameSpeed = speedMultipliers[currentSpeedIndex]; velocitySpeedText.setText("x" + gameSpeed.toFixed(1)); // Visual feedback tween(velocityButton, { scaleX: 1.1, scaleY: 1.1 }, { duration: 100, easing: tween.easeOut, onFinish: function onFinish() { tween(velocityButton, { scaleX: 1.0, scaleY: 1.0 }, { duration: 100, easing: tween.easeIn }); } }); var notification = game.addChild(new Notification("Game speed set to x" + gameSpeed.toFixed(1) + "!")); notification.x = 2048 / 2; notification.y = grid.height - 100; }; velocityButton.update = function () { if (waveIndicator && waveIndicator.gameStarted) { velocityButton.enabled = true; velocityButton.visible = true; velocityButton.alpha = 1; } else { velocityButton.enabled = false; velocityButton.visible = false; velocityButton.alpha = 0.7; } }; // Show welcome screen at start var welcomeScreen = new WelcomeScreen(); welcomeScreen.x = 2048 / 2; welcomeScreen.y = 2732 / 2; game.addChild(welcomeScreen); // Hide UI labels during welcome screen goldText.visible = false; goldTextShadow.visible = false; livesText.visible = false; livesTextShadow.visible = false; scoreText.visible = false; scoreTextShadow.visible = false; game.update = function () { // Apply game speed to wave progression if (waveInProgress) { if (!waveSpawned) { waveSpawned = true; // Get wave type and enemy count from the wave indicator var waveType = waveIndicator.getWaveType(currentWave); var enemyCount = waveIndicator.getEnemyCount(currentWave); // Check if this is a boss wave var isBossWave = currentWave % 10 === 0 && currentWave > 0; if (isBossWave && waveType !== 'swarm') { // Boss waves have just 1 enemy regardless of what the wave indicator says enemyCount = 1; // Show boss announcement var notification = game.addChild(new Notification("⚠️ BOSS WAVE! ⚠️")); notification.x = 2048 / 2; notification.y = grid.height - 200; } // Spawn the appropriate number of enemies for (var i = 0; i < enemyCount; i++) { var enemy = new Enemy(waveType); // Add enemy to the appropriate layer based on type if (enemy.isFlying) { // Add flying enemy to the top layer enemyLayerTop.addChild(enemy); // If it's a flying enemy, add its shadow to the middle layer if (enemy.shadow) { enemyLayerMiddle.addChild(enemy.shadow); } } else { // Add normal/ground enemies to the bottom layer enemyLayerBottom.addChild(enemy); } // Scale difficulty with wave number but don't apply to boss // as bosses already have their health multiplier // Use exponential scaling for health var healthMultiplier = Math.pow(1.12, currentWave); // ~20% increase per wave // Apply difficulty multiplier enemy.maxHealth = Math.round(enemy.maxHealth * healthMultiplier * difficultyMultiplier); enemy.health = enemy.maxHealth; // Increment speed slightly with wave number //enemy.speed = enemy.speed + currentWave * 0.002; // All enemy types now spawn in the middle 6 tiles at the top spacing var gridWidth = 24; var midPoint = Math.floor(gridWidth / 2); // 12 // Find a column that isn't occupied by another enemy that's not yet in view var availableColumns = []; for (var col = midPoint - 3; col < midPoint + 3; col++) { var columnOccupied = false; // Check if any enemy is already in this column but not yet in view for (var e = 0; e < enemies.length; e++) { if (enemies[e].cellX === col && enemies[e].currentCellY < 4) { columnOccupied = true; break; } } if (!columnOccupied) { availableColumns.push(col); } } // If all columns are occupied, use original random method var spawnX; if (availableColumns.length > 0) { // Choose a random unoccupied column spawnX = availableColumns[Math.floor(Math.random() * availableColumns.length)]; } else { // Fallback to random if all columns are occupied spawnX = midPoint - 3 + Math.floor(Math.random() * 6); // x from 9 to 14 } var spawnY = -1 - Math.random() * 5; // Random distance above the grid for spreading enemy.cellX = spawnX; enemy.cellY = 5; // Position after entry enemy.currentCellX = spawnX; enemy.currentCellY = spawnY; enemy.waveNumber = currentWave; // Apply speed multiplier to enemy enemy.speed = enemy.speed * gameSpeed; enemies.push(enemy); } } var currentWaveEnemiesRemaining = false; for (var i = 0; i < enemies.length; i++) { if (enemies[i].waveNumber === currentWave) { currentWaveEnemiesRemaining = true; break; } } if (waveSpawned && !currentWaveEnemiesRemaining) { waveInProgress = false; waveSpawned = false; } } // Apply speed multiplier to existing enemies for (var a = enemies.length - 1; a >= 0; a--) { var enemy = enemies[a]; // Apply speed multiplier to enemy movement (but only update if speed has changed) if (enemy.lastGameSpeed === undefined || enemy.lastGameSpeed !== gameSpeed) { // Reset speed to base and reapply multiplier if (enemy.lastGameSpeed !== undefined) { enemy.speed = enemy.speed / enemy.lastGameSpeed; } enemy.speed = enemy.speed * gameSpeed; enemy.lastGameSpeed = gameSpeed; } if (enemy.health <= 0) { for (var i = 0; i < enemy.bulletsTargetingThis.length; i++) { var bullet = enemy.bulletsTargetingThis[i]; bullet.targetEnemy = null; } // Boss enemies give more gold and score var goldEarned = enemy.isBoss ? Math.floor(50 + (enemy.waveNumber - 1) * 5) : Math.floor(1 + (enemy.waveNumber - 1) * 0.5); var goldIndicator = new GoldIndicator(goldEarned, enemy.x, enemy.y); game.addChild(goldIndicator); setGold(gold + goldEarned); // Give more score for defeating a boss var scoreValue = enemy.isBoss ? 100 : 5; score += scoreValue; // Add a notification for boss defeat if (enemy.isBoss) { var notification = game.addChild(new Notification("Boss defeated! +" + goldEarned + " gold!")); notification.x = 2048 / 2; notification.y = grid.height - 150; } updateUI(); // Clean up shadow if it's a flying enemy if (enemy.isFlying && enemy.shadow) { enemyLayerMiddle.removeChild(enemy.shadow); enemy.shadow = null; } // Remove enemy from the appropriate layer if (enemy.isFlying) { enemyLayerTop.removeChild(enemy); } else { enemyLayerBottom.removeChild(enemy); } enemies.splice(a, 1); continue; } if (grid.updateEnemy(enemy)) { // Clean up shadow if it's a flying enemy if (enemy.isFlying && enemy.shadow) { enemyLayerMiddle.removeChild(enemy.shadow); enemy.shadow = null; } // Remove enemy from the appropriate layer if (enemy.isFlying) { enemyLayerTop.removeChild(enemy); } else { enemyLayerBottom.removeChild(enemy); } enemies.splice(a, 1); lives = Math.max(0, lives - 1); updateUI(); if (lives <= 0) { LK.showGameOver(); } } } for (var i = bullets.length - 1; i >= 0; i--) { if (!bullets[i].parent) { if (bullets[i].targetEnemy) { var targetEnemy = bullets[i].targetEnemy; var bulletIndex = targetEnemy.bulletsTargetingThis.indexOf(bullets[i]); if (bulletIndex !== -1) { targetEnemy.bulletsTargetingThis.splice(bulletIndex, 1); } } bullets.splice(i, 1); } } if (towerPreview.visible) { towerPreview.checkPlacement(); } if (currentWave >= totalWaves && enemies.length === 0 && !waveInProgress) { LK.showYouWin(); } };
===================================================================
--- original.js
+++ change.js
@@ -1555,9 +1555,48 @@
costLabel.anchor.set(0.5, 0.5);
costLabel.x = 0; // Explicitly center horizontally
costLabel.y = 115; // Position higher up
self.addChild(costLabel);
+ // Add scaling animation function
+ self.startScalingAnimation = function () {
+ // Function to animate to larger scale (108%)
+ function scaleUp() {
+ tween(baseGraphics, {
+ scaleX: 1.07,
+ // Slightly smaller than tower scaling to 1.07
+ scaleY: 1.07
+ }, {
+ duration: 1000,
+ easing: tween.easeInOut,
+ onFinish: function onFinish() {
+ scaleDown();
+ }
+ });
+ }
+ // Function to animate to smaller scale (93%)
+ function scaleDown() {
+ tween(baseGraphics, {
+ scaleX: 0.93,
+ // Slightly smaller than tower scaling to 0.93
+ scaleY: 0.93
+ }, {
+ duration: 1000,
+ easing: tween.easeInOut,
+ onFinish: function onFinish() {
+ scaleUp();
+ }
+ });
+ }
+ // Start the animation cycle
+ scaleUp();
+ };
self.update = function () {
+ // Initialize scaling animation on first update
+ if (!self.scalingInitialized) {
+ self.scalingInitialized = true;
+ // Start the continuous scaling animation
+ self.startScalingAnimation();
+ }
// Check if player can afford this tower
var canAfford = gold >= getTowerCost(self.towerType);
// Check if this is a single-use tower that has already been placed
var isBlocked = self.towerType === 'king' && placedTowers.king || self.towerType === 'queen' && placedTowers.queen;
@@ -1766,8 +1805,39 @@
}
}
};
self.updateLevelIndicators();
+ // Add scaling animation function
+ self.startScalingAnimation = function () {
+ // Function to animate to larger scale (108%)
+ function scaleUp() {
+ tween(baseGraphics, {
+ scaleX: 1.08,
+ scaleY: 1.08
+ }, {
+ duration: 1000,
+ easing: tween.easeInOut,
+ onFinish: function onFinish() {
+ scaleDown();
+ }
+ });
+ }
+ // Function to animate to smaller scale (92%)
+ function scaleDown() {
+ tween(baseGraphics, {
+ scaleX: 0.92,
+ scaleY: 0.92
+ }, {
+ duration: 1000,
+ easing: tween.easeInOut,
+ onFinish: function onFinish() {
+ scaleUp();
+ }
+ });
+ }
+ // Start the animation cycle
+ scaleUp();
+ };
self.refreshCellsInRange = function () {
for (var i = 0; i < self.cellsInRange.length; i++) {
var cell = self.cellsInRange[i];
var towerIndex = cell.towersInRange.indexOf(self);
@@ -1933,8 +2003,14 @@
}
return closestEnemy;
};
self.update = function () {
+ // Initialize scaling animation on first update
+ if (!self.scalingInitialized) {
+ self.scalingInitialized = true;
+ // Start the continuous scaling animation
+ self.startScalingAnimation();
+ }
// Handle bomb tower special behavior
if (self.isBombTower && !self.hasExploded) {
self.explosionTimer--;
if (self.explosionTimer <= 0) {
White circle with black outline. Blue background.. In-Game asset. 2d. High contrast. No shadows
Quitale el fondo blanco
Cambia la resortera por una espada.
Cambia la resortera por un arco. Añade una bufanda y has el casco mas pequeño
Elimina la espada. Añade una catapulta en la parte de atras de la cabeza con una piedra. La catapulta debe de tener la piedra al lado izquierdo simulando se va a lanzar a la derecha. Haz la cabeza un poco mas pequeña.
Elimina la resortera. Añadele un arco y una fleca con la punta de hielo. Dale unos colores mas frios a la ilsutracion. Adorna el casco con piedras birllantes celestes.
Elimina la resortera. Añadele un arco y una flecha con la punta verde que suelta un humo verde. Adorna el casco con piedras birllantes verdes.
Quitale el casco y la resortera. Utiliza una paleta de colores grises. Has una expresion seria. Ojos rojos sin pupila. Utiliza los rasgos y formas de un capibara.
Agregale un sombrero rojo de robinhood, cambia la explesion a sonrisa malvada sin mostrar los dientes. Recuerda usar los rasgos y formas de un capibara.
Ponle un gorro de piloto cafe y una bufanda cafe. Simulando que es un piloto
Ponle un escudo de metal delante de la cabeza. Tapando la boca y el cuello. Recuerda usar los rasgos y formas de un capibara. El escudo debe de ser de un plomo claro
Has este capibara mas joven y pequeño. Recuerda usar los rasgos y formas de un capibara. Añadele una sonrisa sin mostrar los dientes. Sin fondo
Textura de cesped. Recuerda la escala 1:1. In-Game asset. 2d. High contrast. No shadows. In-Game asset. 2d. High contrast. No shadows
Deja plano. Que no se nore las lineas de la cuadricula
Quitale la espada. Ahora has una bomba con la forma del capibara y su cabeza.
Haz aun mas pequeñas a las piedras, mucho mas
Por favor haz la textura de piedras cuadradas desde arriba, las piedras color plomo
Haz mas pequeñas las piedras mucho mas
Mejora la paleta a un gris mas claro, no cambies nada mas
Has mas suave la textura,
Quitale la resortera y el casco. Añadele una corona de reina con gemas. Añade tambein un arco dorado y una fleca con una punta morada brillante. Dame la imagen sin fondo. Ponle pestañas pequeñas en los ojos.
Quitale la resortera y el casco. Añadele una corona de rey con gemas. Añade tambein una capa roja. Agrega en la parte de atras un cañon. Dame la imagen sin fondo.
Recrealo en una escultura. Utiliza un estilo cartoon. Usas colores grises.
Recrealo en una escultura. Utiliza un estilo cartoon. Usas colores grises. Recuerda que es un capibara usas sus rasgos y formas. Damelo sin fondo
Recrealo en una escultura. Utiliza un estilo cartoon. Usas colores grises. Recuerda que es un capibara usas sus rasgos y formas. Damelo sin fondo
Recrealo en una escultura. Utiliza un estilo cartoon. Usas colores grises. Recuerda que es un capibara usas sus rasgos y formas. Damelo sin fondo
Recrealo en una escultura. Utiliza un estilo cartoon. Usas colores grises. Recuerda que es un capibara usas sus rasgos y formas. Damelo sin fondo. Tambien quita el arco y la flecha
Recrealo en una escultura. Utiliza un estilo cartoon. Usas colores grises. Recuerda que es un capibara usas sus rasgos y formas. Damelo sin fondo, Quita el cañon de fondo
Utilizando este ejemplo. Hay un castillo al fondo. Al frente esta el rey capybara y la reina capybara tranquilos y los capibaras habitantes estan rodeandolos felices. Elimina el cañon de detras. Agrega el titulo de juego en la parte superior: Capybara Castle. In-Game asset. 2d. High contrast.