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),