User prompt
Add new types of goblins: Shield goblins: have higher HP than a regular goblin but are slightly slower; starts appearing on wave 7; Wolf: fast enemy that has a low chance to dodge attacks; starts appearing on wave 3
User prompt
The /1000 thing is back; remove it again
User prompt
Goblins every fifth wave should spawn every 0.43333332 seconds
User prompt
Goblins every fifth wave arent clustered, fix that
User prompt
Every 5 waves there is a special wave where its the same as a regular wave but the goblins are a more clustered, making a huge, tightly spaced wave
User prompt
Make the starting wave for it to only have 1 goblin, but every wave adds a new goblin or 2 new goblins randomly
User prompt
Make goblins a little slower
Initial prompt
Make goblins a liitle weaker again
/**** * Plugins ****/ var tween = LK.import("@upit/tween.v1"); var storage = LK.import("@upit/storage.v1", { highScore: 0 }); /**** * Classes ****/ var Goblin = Container.expand(function (pathPoints, health, speed, value) { var self = Container.call(this); self.pathPoints = pathPoints; self.maxHealth = health; self.health = health; self.speed = speed; self.value = value; self.active = true; self.currentPathIndex = 0; self.pathProgress = 0; var goblinGraphics = self.attachAsset('goblin', { anchorX: 0.5, anchorY: 0.5 }); var healthBar = LK.getAsset('tile', { width: 60, height: 10, color: 0x00FF00, anchorX: 0.5, anchorY: 0.5 }); healthBar.y = -40; self.addChild(healthBar); self.x = pathPoints[0].x; self.y = pathPoints[0].y; self.takeDamage = function (damage) { self.health -= damage; // Update health bar var healthPercent = Math.max(0, self.health / self.maxHealth); healthBar.width = 60 * healthPercent; healthBar.tint = healthPercent > 0.5 ? 0x00FF00 : healthPercent > 0.25 ? 0xFFFF00 : 0xFF0000; if (self.health <= 0) { self.die(); } }; self.die = function () { self.active = false; gold += self.value; goldText.setText("Gold: " + gold); score += self.value; scoreText.setText("Score: " + score); LK.setScore(score); LK.getSound('goblin_death').play(); // Create coin effect var coinText = new Text2("+" + self.value, { size: 40, fill: 0xFFD700 }); coinText.x = self.x; coinText.y = self.y; coinText.anchor.set(0.5, 0.5); game.addChild(coinText); tween(coinText, { y: coinText.y - 100, alpha: 0 }, { duration: 1000, onFinish: function onFinish() { coinText.destroy(); } }); self.destroy(); }; self.reachedBase = function () { self.active = false; lives--; livesText.setText("Lives: " + lives); if (lives <= 0) { endGame(); } self.destroy(); }; self.update = function () { if (!self.active) { return; } if (self.currentPathIndex >= self.pathPoints.length - 1) { self.reachedBase(); return; } var currentPoint = self.pathPoints[self.currentPathIndex]; var nextPoint = self.pathPoints[self.currentPathIndex + 1]; var dx = nextPoint.x - currentPoint.x; var dy = nextPoint.y - currentPoint.y; var dist = Math.sqrt(dx * dx + dy * dy); self.pathProgress = self.currentPathIndex / (self.pathPoints.length - 1); // Calculate how far along this segment we are var currentX = self.x - currentPoint.x; var currentY = self.y - currentPoint.y; var segmentProgress = Math.sqrt(currentX * currentX + currentY * currentY) / dist; // Add segment progress to overall progress self.pathProgress += segmentProgress / (self.pathPoints.length - 1); // Move towards next point var dx = nextPoint.x - self.x; var dy = nextPoint.y - self.y; var dist = Math.sqrt(dx * dx + dy * dy); if (dist < self.speed) { // Reached the next point self.currentPathIndex++; } else { // Move along the path self.x += dx / dist * self.speed; self.y += dy / dist * self.speed; } }; return self; }); var Projectile = Container.expand(function (source, target, damage) { var self = Container.call(this); self.source = source; self.target = target; self.damage = damage; self.speed = 15; self.active = true; var projectileGraphics = self.attachAsset('projectile', { anchorX: 0.5, anchorY: 0.5 }); self.x = source.x; self.y = source.y; self.update = function () { if (!self.active || !self.target || !self.target.active) { self.destroy(); return; } // Calculate direction vector var dx = self.target.x - self.x; var dy = self.target.y - self.y; var dist = Math.sqrt(dx * dx + dy * dy); if (dist < 20) { // Hit target self.target.takeDamage(self.damage); LK.getSound('hit').play(); self.active = false; self.destroy(); return; } // Normalize and scale by speed self.x += dx / dist * self.speed; self.y += dy / dist * self.speed; }; return self; }); var Unit = Container.expand(function (tier) { var self = Container.call(this); self.tier = tier || 1; self.damage = Math.pow(2, self.tier - 1); self.range = 300 + self.tier * 50; self.fireRate = 1000 - self.tier * 50; // ms between shots self.lastShot = 0; self.targets = []; self.isDragging = false; self.merging = false; var unitGraphics = self.attachAsset('unit' + self.tier, { anchorX: 0.5, anchorY: 0.5 }); var tierText = new Text2(self.tier.toString(), { size: 40, fill: 0x000000 }); tierText.anchor.set(0.5, 0.5); self.addChild(tierText); // Add unit name text below the level of the tower var unitNames = ["Rock Thrower", "Slinger", "Spear Thrower", "Archer", "Fire Archer", "Crossbowman", "Musketeer", "Cannoneer", "Rifleman", "Grenadier", "Tank"]; var unitNameText = new Text2(unitNames[self.tier - 1], { size: 30, fill: 0x000000 }); unitNameText.anchor.set(0.5, 0); unitNameText.y = 50; // Position below the tier text self.addChild(unitNameText); self.findTarget = function () { if (self.targets.length === 0) { return null; } // Sort targets by progress self.targets.sort(function (a, b) { return b.pathProgress - a.pathProgress; }); // Return the most progressed goblin return self.targets[0]; }; self.shoot = function (target) { if (!target || !target.active) { return; } var now = Date.now(); if (now - self.lastShot < self.fireRate) { return; } self.lastShot = now; var projectile = new Projectile(self, target, self.damage); projectiles.push(projectile); game.addChild(projectile); LK.getSound('shoot').play(); }; self.update = function () { if (self.merging) { return; } // Clean up destroyed targets self.targets = self.targets.filter(function (target) { return target.active; }); var target = self.findTarget(); if (target) { self.shoot(target); } }; self.down = function (x, y, obj) { if (!placingUnit && !gameOver) { self.isDragging = true; selectedUnit = self; if (self.gridPosition) { // Remove from grid grid[self.gridPosition.y][self.gridPosition.x].unit = null; self.gridPosition = null; } } }; self.checkForMerge = function (gridCell) { if (!gridCell || !gridCell.unit || gridCell.unit === self) { return false; } if (gridCell.unit.tier === self.tier && self.tier < 10) { // Merge units mergeUnits(self, gridCell.unit); return true; } return false; }; return self; }); /**** * Initialize Game ****/ var game = new LK.Game({ backgroundColor: 0x558855 }); /**** * Game Code ****/ // Game configuration var gridWidth = 7; var gridHeight = 6; var tileSize = 120; var grid = []; var pathPoints = []; var goblins = []; var projectiles = []; var units = []; var waves = []; var currentWave = 0; var waveInProgress = false; var selectedUnit = null; var placingUnit = null; var gold = 100; var lives = 3; var score = 0; var gameOver = false; var gridOffsetX = (2048 - gridWidth * tileSize) / 2; var gridOffsetY = 400; var highlight = null; var unitCost = 35; // UI elements var waveText; var goldText; var livesText; var scoreText; var nextWaveButton; var unitBuyButton; function initializeGrid() { for (var y = 0; y < gridHeight; y++) { grid[y] = []; for (var x = 0; x < gridWidth; x++) { grid[y][x] = { x: gridOffsetX + x * tileSize + tileSize / 2, y: gridOffsetY + y * tileSize + tileSize / 2, unit: null, isPath: false }; var tile = LK.getAsset('tile', { anchorX: 0.5, anchorY: 0.5 }); tile.x = grid[y][x].x; tile.y = grid[y][x].y; tile.alpha = 0.5; game.addChild(tile); } } } function createPath() { // Define a simple path through the grid var pathCoordinates = [{ x: 0, y: 2 }, { x: 1, y: 2 }, { x: 2, y: 2 }, { x: 3, y: 2 }, { x: 3, y: 3 }, { x: 3, y: 4 }, { x: 4, y: 4 }, { x: 5, y: 4 }, { x: 6, y: 4 }]; for (var i = 0; i < pathCoordinates.length; i++) { var coord = pathCoordinates[i]; var cell = grid[coord.y][coord.x]; cell.isPath = true; var pathTile = LK.getAsset('path', { anchorX: 0.5, anchorY: 0.5 }); pathTile.x = cell.x; pathTile.y = cell.y; pathTile.alpha = 0.7; game.addChild(pathTile); pathPoints.push({ x: cell.x, y: cell.y }); } // Add the base at the end of the path var lastCoord = pathCoordinates[pathCoordinates.length - 1]; var baseCell = grid[lastCoord.y][lastCoord.x]; var base = LK.getAsset('base', { anchorX: 0.5, anchorY: 0.5 }); base.x = baseCell.x + tileSize; base.y = baseCell.y; game.addChild(base); } function initializeWaves() { // Define waves of increasing difficulty waves = [{ count: 1, // Start with 1 goblin health: 8, speed: 1.5, value: 5, delay: 1000 }]; // Adjust health and speed for each subsequent wave for (var i = 1; i < 1000; i++) { // Generate a large number of waves if (i % 5 === 0) { // Special wave every 5th wave waves.push({ count: waves[i - 1].count + (Math.random() < 0.5 ? 1 : 2), // Add 1 or 2 goblins randomly health: Math.round(waves[i - 1].health * 1.02), speed: parseFloat((waves[i - 1].speed * 1.02).toFixed(2)), value: 10 + i, delay: Math.max(50, 200 - i * 10) // Even tighter spacing for special wave }); } else { waves.push({ count: waves[i - 1].count + (Math.random() < 0.5 ? 1 : 2), // Add 1 or 2 goblins randomly health: Math.round(waves[i - 1].health * 1.02), speed: parseFloat((waves[i - 1].speed * 1.02).toFixed(2)), value: 10 + i, delay: Math.max(200, 1000 - i * 50) // Ensure delay doesn't go below 200ms }); } } } function startWave() { if (waveInProgress || gameOver) { return; } waveInProgress = true; currentWave++; // Removed wave limit check to make waves infinite waveText.setText("Wave: " + currentWave + " (Infinite)"); nextWaveButton.visible = false; var wave = waves[currentWave - 1]; var goblinsReleased = 0; var spawnInterval = LK.setInterval(function () { spawnGoblin(wave); goblinsReleased++; if (goblinsReleased >= wave.count) { LK.clearInterval(spawnInterval); // Check every second if wave is complete var checkInterval = LK.setInterval(function () { if (goblins.length === 0) { waveInProgress = false; nextWaveButton.visible = true; LK.clearInterval(checkInterval); } }, 1000); } }, wave.delay); } function spawnGoblin(wave) { var goblin = new Goblin(pathPoints, wave.health, wave.speed, wave.value); goblins.push(goblin); game.addChild(goblin); // Add this goblin as a target for all units in range units.forEach(function (unit) { var dx = unit.x - goblin.x; var dy = unit.y - goblin.y; var dist = Math.sqrt(dx * dx + dy * dy); if (dist <= unit.range) { unit.targets.push(goblin); } }); } function createUnit(tier) { var unit = new Unit(tier); units.push(unit); return unit; } function placeUnit(unit, gridX, gridY) { if (gridX < 0 || gridX >= gridWidth || gridY < 0 || gridY >= gridHeight) { return false; } var gridCell = grid[gridY][gridX]; if (gridCell.isPath || gridCell.unit) { return false; } // Place the unit unit.x = gridCell.x; unit.y = gridCell.y; unit.gridPosition = { x: gridX, y: gridY }; gridCell.unit = unit; // Find nearby targets goblins.forEach(function (goblin) { if (goblin.active) { var dx = unit.x - goblin.x; var dy = unit.y - goblin.y; var dist = Math.sqrt(dx * dx + dy * dy); if (dist <= unit.range) { unit.targets.push(goblin); } } }); LK.getSound('place').play(); return true; } function mergeUnits(unit1, unit2) { // Create a new unit of the next tier var newUnit = createUnit(unit1.tier + 1); game.addChild(newUnit); // Place at the position of the second unit newUnit.gridPosition = unit2.gridPosition; grid[unit2.gridPosition.y][unit2.gridPosition.x].unit = newUnit; newUnit.x = unit2.x; newUnit.y = unit2.y; // Remove the merged units unit1.merging = true; unit2.merging = true; // Effect tween(unit1, { x: unit2.x, y: unit2.y, alpha: 0 }, { duration: 300, onFinish: function onFinish() { unit1.destroy(); removeFromArray(units, unit1); } }); tween(unit2, { alpha: 0 }, { duration: 300, onFinish: function onFinish() { unit2.destroy(); removeFromArray(units, unit2); } }); // Scale up effect for new unit newUnit.scale.x = 0.5; newUnit.scale.y = 0.5; tween(newUnit.scale, { x: 1, y: 1 }, { duration: 300, easing: tween.elasticOut }); LK.getSound('merge').play(); return newUnit; } function buyUnit() { if (gold < unitCost || placingUnit || gameOver) { return; } gold -= unitCost; goldText.setText("Gold: " + gold); placingUnit = createUnit(1); game.addChild(placingUnit); // Create highlight if it doesn't exist if (!highlight) { highlight = LK.getAsset('highlight', { anchorX: 0.5, anchorY: 0.5 }); highlight.alpha = 0.3; game.addChild(highlight); } highlight.visible = true; } function createUI() { // Wave text waveText = new Text2("Wave: 0", { size: 50, fill: 0xFFFFFF }); waveText.anchor.set(0.5, 0); LK.gui.top.addChild(waveText); // Gold text goldText = new Text2("Gold: " + gold, { size: 50, fill: 0xFFD700 }); goldText.anchor.set(0, 0); LK.gui.topLeft.addChild(goldText); goldText.x = 120; // Move away from the top left corner // Lives text livesText = new Text2("Lives: " + lives, { size: 50, fill: 0xFF0000 }); livesText.anchor.set(1, 0); LK.gui.topRight.addChild(livesText); // Score text scoreText = new Text2("Score: " + score, { size: 50, fill: 0xFFFFFF }); scoreText.anchor.set(0.5, 0); LK.gui.top.addChild(scoreText); scoreText.y = 80; // Next wave button nextWaveButton = new Container(); var nextWaveButtonBg = LK.getAsset('tile', { width: 300, height: 100, color: 0x336699, anchorX: 0.5, anchorY: 0.5 }); nextWaveButton.addChild(nextWaveButtonBg); var nextWaveButtonText = new Text2("Next Wave", { size: 50, fill: 0xFFFFFF }); nextWaveButtonText.anchor.set(0.5, 0.5); nextWaveButton.addChild(nextWaveButtonText); nextWaveButton.down = function () { startWave(); }; LK.gui.bottom.addChild(nextWaveButton); nextWaveButton.y = -150; // Buy unit button unitBuyButton = new Container(); var unitBuyButtonBg = LK.getAsset('tile', { width: 300, height: 100, color: 0x669933, anchorX: 0.5, anchorY: 0.5 }); unitBuyButton.addChild(unitBuyButtonBg); var unitBuyButtonText = new Text2("Buy Unit: " + unitCost + "g", { size: 40, fill: 0xFFFFFF }); unitBuyButtonText.anchor.set(0.5, 0.5); unitBuyButton.addChild(unitBuyButtonText); unitBuyButton.down = function () { buyUnit(); }; LK.gui.bottomLeft.addChild(unitBuyButton); unitBuyButton.y = -150; unitBuyButton.x = 170; } function endGame() { if (gameOver) { return; } gameOver = true; if (score > storage.highScore) { storage.highScore = score; } LK.getSound('game_over').play(); LK.showGameOver(); } function removeFromArray(array, item) { var index = array.indexOf(item); if (index !== -1) { array.splice(index, 1); } } function initialize() { initializeGrid(); createPath(); initializeWaves(); createUI(); // Play background music LK.playMusic('bg_music'); // Create highlight for placing units (initially hidden) highlight = LK.getAsset('highlight', { anchorX: 0.5, anchorY: 0.5 }); highlight.alpha = 0.3; highlight.visible = false; game.addChild(highlight); // Start with wave 0 waveText.setText("Wave: 0/" + waves.length); } initialize(); game.move = function (x, y) { if (gameOver) { return; } if (selectedUnit) { selectedUnit.x = x; selectedUnit.y = y; } else if (placingUnit) { // Find the closest grid cell var gridX = Math.floor((x - gridOffsetX) / tileSize); var gridY = Math.floor((y - gridOffsetY) / tileSize); if (gridX >= 0 && gridX < gridWidth && gridY >= 0 && gridY < gridHeight) { var cell = grid[gridY][gridX]; placingUnit.x = x; placingUnit.y = y; highlight.x = cell.x; highlight.y = cell.y; highlight.visible = true; // Change highlight color based on valid placement if (cell.isPath || cell.unit) { highlight.tint = 0xFF0000; } else { highlight.tint = 0x00FF00; } } else { highlight.visible = false; } } }; game.down = function (x, y) { if (gameOver) { return; } // If we're placing a new unit if (placingUnit) { var gridX = Math.floor((x - gridOffsetX) / tileSize); var gridY = Math.floor((y - gridOffsetY) / tileSize); if (gridX >= 0 && gridX < gridWidth && gridY >= 0 && gridY < gridHeight) { if (placeUnit(placingUnit, gridX, gridY)) { placingUnit = null; highlight.visible = false; } } } }; game.up = function (x, y) { if (gameOver) { return; } if (selectedUnit && selectedUnit.isDragging) { var gridX = Math.floor((x - gridOffsetX) / tileSize); var gridY = Math.floor((y - gridOffsetY) / tileSize); if (gridX >= 0 && gridX < gridWidth && gridY >= 0 && gridY < gridHeight) { var gridCell = grid[gridY][gridX]; if (selectedUnit.checkForMerge(gridCell)) { // Merged successfully } else if (!gridCell.isPath && !gridCell.unit) { // Place on empty cell placeUnit(selectedUnit, gridX, gridY); } else { // Invalid placement, return to original position if any if (selectedUnit.gridPosition) { selectedUnit.x = grid[selectedUnit.gridPosition.y][selectedUnit.gridPosition.x].x; selectedUnit.y = grid[selectedUnit.gridPosition.y][selectedUnit.gridPosition.x].y; grid[selectedUnit.gridPosition.y][selectedUnit.gridPosition.x].unit = selectedUnit; } else { // If it didn't have a grid position, destroy it selectedUnit.destroy(); removeFromArray(units, selectedUnit); } } } else { // Dragged off grid, return to original position if any if (selectedUnit.gridPosition) { selectedUnit.x = grid[selectedUnit.gridPosition.y][selectedUnit.gridPosition.x].x; selectedUnit.y = grid[selectedUnit.gridPosition.y][selectedUnit.gridPosition.x].y; grid[selectedUnit.gridPosition.y][selectedUnit.gridPosition.x].unit = selectedUnit; } else { // If it didn't have a grid position, destroy it selectedUnit.destroy(); removeFromArray(units, selectedUnit); } } selectedUnit.isDragging = false; selectedUnit = null; } }; game.update = function () { // Update all active goblins for (var i = goblins.length - 1; i >= 0; i--) { if (goblins[i].active) { goblins[i].update(); } else { removeFromArray(goblins, goblins[i]); } } // Update all active projectiles for (var i = projectiles.length - 1; i >= 0; i--) { if (projectiles[i].active) { projectiles[i].update(); } else { removeFromArray(projectiles, projectiles[i]); } } // Update all units for (var i = 0; i < units.length; i++) { if (!units[i].merging) { units[i].update(); } } // Check if units in range of goblins units.forEach(function (unit) { if (unit.merging) { return; } // Reset targets and find new ones unit.targets = []; goblins.forEach(function (goblin) { if (goblin.active) { var dx = unit.x - goblin.x; var dy = unit.y - goblin.y; var dist = Math.sqrt(dx * dx + dy * dy); if (dist <= unit.range) { unit.targets.push(goblin); } } }); }); };
===================================================================
--- original.js
+++ change.js
@@ -382,9 +382,9 @@
// Add 1 or 2 goblins randomly
health: Math.round(waves[i - 1].health * 1.02),
speed: parseFloat((waves[i - 1].speed * 1.02).toFixed(2)),
value: 10 + i,
- delay: Math.max(100, 500 - i * 25) // Tighter spacing for special wave
+ delay: Math.max(50, 200 - i * 10) // Even tighter spacing for special wave
});
} else {
waves.push({
count: waves[i - 1].count + (Math.random() < 0.5 ? 1 : 2),