Code edit (12 edits merged)
Please save this source code
User prompt
Erase all Tower Menu code
User prompt
Erase all debug code
User prompt
Please fix the bug: 'Uncaught ReferenceError: worldPos is not defined' in or related to this line: 'var dx = worldPos.x - doge.x;' Line Number: 1287
Code edit (4 edits merged)
Please save this source code
User prompt
Please fix the bug: 'TypeError: enemy.takeDamage is not a function' in or related to this line: 'enemy.takeDamage(self.damage);' Line Number: 69
Code edit (1 edits merged)
Please save this source code
Code edit (20 edits merged)
Please save this source code
User prompt
Please fix the bug: 'Uncaught TypeError: Cannot read properties of null (reading 'length')' in or related to this line: 'self.graphic = self.attachAsset(null, {}); // Start with no asset, will be set by init' Line Number: 472
User prompt
Please fix the bug: 'Uncaught TypeError: Cannot read properties of null (reading 'length')' in or related to this line: 'self.graphic = self.attachAsset(null, {}); // Start with no asset' Line Number: 119
User prompt
Please fix the bug: 'Uncaught TypeError: LK.Rectangle is not a constructor' in or related to this line: 'var background = new LK.Rectangle({' Line Number: 559
Code edit (1 edits merged)
Please save this source code
Code edit (2 edits merged)
Please save this source code
User prompt
Please fix the bug: 'TypeError: Cannot set properties of undefined (setting 'fill')' in or related to this line: 'barkButton.style.fill = 0xffcc00; // Restore color' Line Number: 748
User prompt
Please fix the bug: 'Cannot read properties of undefined (reading 'height')' in or related to this line: 'var SCREEN_HEIGHT = LK.screen.height; // Get screen height /**** NEW ****/' Line Number: 435
User prompt
Please fix the bug: 'LK.getScreenSize is not a function' in or related to this line: 'var screenSize = LK.getScreenSize();' Line Number: 434
User prompt
Please fix the bug: 'Cannot read properties of undefined (reading 'height')' in or related to this line: 'var SCREEN_HEIGHT = LK.canvas.height; // Get screen height /**** NEW ****/' Line Number: 434
Code edit (1 edits merged)
Please save this source code
User prompt
DOGE vs. Bureaucracy
Initial prompt
Create the foundational code for a 2D top-down Hero Defense game called "DOGE vs. Bureaucracy" using the LK Game Engine. The game features the Doge meme character defending a goal zone from waves of bureaucracy-themed enemies, assisted by office-supply-themed towers placed by the player. **I. Core Game Setup:** 1. Initialize the main game object using `LK.Game`. 2. Create necessary containers: `gameContainer` for gameplay elements (hero, enemies, towers, projectiles), and `uiContainer` for UI elements (text, buttons). Ensure `uiContainer` renders on top. 3. Define a variable `playerCurrency` initialized to a starting value (e.g., 100). 4. Define a `goalZone` object (can be a simple Container with position and size) with a `health` property (e.g., 100). **II. Hero Object (DOGE):** 1. Create a `Hero` class using `Container.expand`. 2. **Assets:** Use a placeholder asset `doge_sprite` (define via `LK.init.image`) for the visual representation. Include basic states like 'idle', 'walk', 'attack'. 3. **Stats:** Add properties for `health` (e.g., 50), `speed` (e.g., 3), and `attackRange` (e.g., 50), `attackDamage` (e.g., 5), `attackCooldown` (e.g., 0.5 seconds). Add a `currentAttackCooldown` timer. 4. **Movement:** * Implement click-to-move. Use `self.down` on the `gameContainer` (or a background layer) to get target coordinates. * In the `Hero.update` method, move the Hero towards the `targetX`, `targetY` at its `speed`. Stop when close to the target. Basic direction vector calculation is sufficient for now (no complex pathfinding needed initially). * Update the visual state to 'walk' when moving, 'idle' when stopped. 5. **Auto-Attack ("Much Bark"):** * In `Hero.update`, decrement `currentAttackCooldown`. * If `currentAttackCooldown <= 0`: * Find the nearest `Enemy` object within `attackRange`. * If an enemy is found: * Apply `attackDamage` to the enemy's health. * Reset `currentAttackCooldown` to `attackCooldown`. * Briefly set the visual state to 'attack'. * (Optional: Create a short-lived visual effect or placeholder `bark_projectile` object). **III. Enemy Object (Paperwork Swarm - Basic):** 1. Create an `Enemy` base class using `Container.expand`. Add basic properties like `health`, `speed`. 2. Create a `PaperworkEnemy` class inheriting from `Enemy` using `Container.expand`. 3. **Assets:** Use a placeholder asset `paper_sprite` (`LK.init.image`). 4. **Stats:** Set low `health` (e.g., 10) and moderate `speed` (e.g., 1.5). Add a property `currencyValue` (e.g., 5). 5. **Movement:** * Define a simple, predefined path using an array of coordinates `pathPoints`. * In `PaperworkEnemy.update`, move the enemy along the `pathPoints` towards the next point in the array at its `speed`. * If the enemy reaches the `goalZone`'s coordinates: decrease `goalZone.health` (e.g., by 10) and destroy the enemy object. 6. **Defeat:** When `health <= 0`, award `currencyValue` to `playerCurrency` and destroy the enemy object. **IV. Tower Object (Stapler Turret - Basic):** 1. Create a `Tower` base class using `Container.expand`. Add properties `cost`, `attackRange`, `attackDamage`, `attackCooldown`. 2. Create a `StaplerTower` class inheriting from `Tower` using `Container.expand`. 3. **Assets:** Use a placeholder asset `stapler_sprite` (`LK.init.image`). 4. **Stats:** Set `cost` (e.g., 50), `attackRange` (e.g., 100), `attackDamage` (e.g., 8), `attackCooldown` (e.g., 1 second). Add `currentAttackCooldown` timer. 5. **Placement:** * Create placeholder `BuildSpot` objects (simple Containers) at predefined locations. * When a `BuildSpot` is clicked (`self.down`): * If `playerCurrency >= StaplerTower.cost`: * Subtract the cost from `playerCurrency`. * Create a new `StaplerTower` instance at the `BuildSpot`'s position. * Make the `BuildSpot` inactive/occupied. 6. **Auto-Attack:** * In `StaplerTower.update`, decrement `currentAttackCooldown`. * If `currentAttackCooldown <= 0`: * Find the nearest `Enemy` within `attackRange`. * If an enemy is found: * Apply `attackDamage` to the enemy's health. * Reset `currentAttackCooldown` to `attackCooldown`. * (Optional: Create placeholder `staple_projectile` object traveling towards the enemy). **V. Basic Game Loop (`game.update`):** 1. Update the Hero instance. 2. Iterate through and update all active Enemy instances. Check for defeat conditions. 3. Iterate through and update all active Tower instances. 4. Implement basic enemy spawning: Use `LK.setInterval` or a timer within `game.update` to spawn a new `PaperworkEnemy` at a starting point every few seconds (e.g., 3 seconds). Add a counter to limit the total number of enemies spawned per wave (e.g., 10 enemies). 5. Check for lose condition: If `goalZone.health <= 0`, stop the game loop or display a "Game Over" message. 6. (Optional: Add win condition check - e.g., after X enemies spawned and defeated). **VI. Basic UI:** 1. Use `Text2` objects within the `uiContainer`. 2. Create text elements to display: * `playerCurrency`. * `goalZone.health`. 3. Update these text elements regularly (e.g., within `game.update` or only when values change). **Instructions for Ava:** * Use placeholder assets (like `doge_sprite`, `paper_sprite`, `stapler_sprite`) defined using `LK.init.image` with basic dimensions. I will replace these with actual art later. * Use simple geometric shapes (`LK.init.shape`) for projectiles or build spots if needed initially. * Focus on setting up the object structures (`Container.expand`), core properties, and the basic `update` logic for movement, attacking, spawning, and health/currency management. * Implement the click handling (`self.down`) for hero movement and tower placement. * Ensure basic collision detection logic (enemy reaching goal zone) and targeting logic (finding nearest enemy) is functional. * Keep the initial implementation simple; advanced features like multiple enemy/tower types, complex pathfinding, hero abilities, upgrades, detailed animations, sound effects, and sophisticated wave logic will be added later.
/**** * Plugins ****/ var tween = LK.import("@upit/tween.v1"); var storage = LK.import("@upit/storage.v1", { highScore: 0 }); /**** * Classes ****/ var BarkWave = Container.expand(function () { var self = Container.call(this); var graphic = self.attachAsset('barkWave', { anchorX: 0.5, anchorY: 0.5, alpha: 0.5 }); self.damage = 3; // Slightly increased damage for manual ability self.duration = 30; // frames self.radius = 100; self.maxRadius = 350; // Slightly larger radius for manual ability self.update = function () { self.radius += 15; // Expand slightly faster graphic.scaleX = self.radius / 100; graphic.scaleY = self.radius / 100; graphic.alpha -= 0.017; // Check for enemies in range ONCE per enemy per wave activation // To avoid hitting the same enemy multiple times with one wave if (!self.enemiesHit) { self.enemiesHit = []; } // Initialize if needed for (var i = 0; i < enemies.length; i++) { var enemy = enemies[i]; // Only check if enemy hasn't been hit by this wave yet if (self.enemiesHit.indexOf(enemy) === -1) { var dx = enemy.x - self.x; var dy = enemy.y - self.y; var distance = Math.sqrt(dx * dx + dy * dy); if (distance < self.radius) { enemy.takeDamage(self.damage); self.enemiesHit.push(enemy); // Mark enemy as hit } } } self.duration--; if (self.duration <= 0 || self.radius >= self.maxRadius) { self.destroy(); } }; return self; }); // --- MODIFIED BuildSpot Class --- // --- MODIFY BuildSpot Class --- var BuildSpot = Container.expand(function () { var self = Container.call(this); self.graphic = self.attachAsset('buildSpot', { anchorX: 0.5, anchorY: 0.5, alpha: 0.5 }); self.hasTower = false; self.tower = null; self.selectionIconContainer = null; // This will hold the icons self.areIconsVisible = false; // Define offsets for icons relative to the BuildSpot's center // (These are local coordinates within the selectionIconContainer if you use one, // or directly relative to the BuildSpot if icons are direct children of BuildSpot) var iconOffsets = [{ x: -(self.graphic.width / 2 + 45), y: 0, towerKey: 'stapler' }, // Left (SpotRadius + IconRadius/2 + Margin) { x: 0, y: -(self.graphic.height / 2 + 45), towerKey: 'blocker' }, // Above { x: self.graphic.width / 2 + 45, y: 0, towerKey: 'placeholder' } // Right ]; self.showSelectionIcons = function () { if (self.hasTower || self.areIconsVisible) return; if (currentActiveBuildSpot && currentActiveBuildSpot !== self) { currentActiveBuildSpot.hideSelectionIcons(); } currentActiveBuildSpot = self; // Create a container for the icons, positioned AT the BuildSpot's location // This container will be added to the SAME parent as the BuildSpot (e.g., 'level') self.selectionIconContainer = new Container(); self.selectionIconContainer.x = self.x; // Position container at buildspot's x (in 'level' space) self.selectionIconContainer.y = self.y; // Position container at buildspot's y (in 'level' space) self.parent.addChild(self.selectionIconContainer); // Add to 'level' (BuildSpot's parent) for (var i = 0; i < iconOffsets.length; i++) { var offsetData = iconOffsets[i]; var towerTypeKey = offsetData.towerKey; var towerInfo = TOWER_DATA[towerTypeKey]; if (!towerInfo || !towerInfo.levels || !towerInfo.levels[0]) continue; var cost = towerInfo.levels[0].cost; // Create icon button within the selectionIconContainer // Its x,y will be relative to the selectionIconContainer's origin (which is the BuildSpot's center) var iconButton = new Container(); iconButton.x = offsetData.x; // Use the defined offset iconButton.y = offsetData.y; // Use the defined offset self.selectionIconContainer.addChild(iconButton); var iconGraphicAsset = towerInfo.iconAsset || 'iconPlaceholder'; var iconGraphic = iconButton.attachAsset(iconGraphicAsset, { anchorX: 0.5, anchorY: 0.5 }); iconGraphic.interactive = true; iconGraphic.towerTypeKey = towerTypeKey; var costText = new Text2("$" + cost, { size: 20, fill: 0xFFFFFF, stroke: 0x000000, strokeThickness: 2 }); costText.anchor.set(0.5, -0.7); // Position below the icon's center iconButton.addChild(costText); if (currency < cost) { iconButton.alpha = 0.5; iconGraphic.interactive = false; } else { iconButton.alpha = 1.0; } iconGraphic.down = function () { var currentCost = TOWER_DATA[this.towerTypeKey].levels[0].cost; if (currency >= currentCost) { self.buildSelectedTower(this.towerTypeKey); // hideSelectionIcons will be called by the main game.down logic } else { spawnFloatingText("Need More $!", self.x + offsetData.x, self.y + offsetData.y - 30, { fill: 0xFF0000 }); } // IMPORTANT: After an icon is clicked, we want to hide the icons. // The main game.down will handle this if the click is "outside". // If the click IS on an icon, the icon's own handler executes, then game.down. // So, we can call hideSelectionIcons here too, or rely on game.down. // For simplicity, let game.down handle closing. }; } self.areIconsVisible = true; LK.getSound('uiOpenMenu').play(); }; self.hideSelectionIcons = function () { if (self.selectionIconContainer) { self.selectionIconContainer.destroy(); // Destroy the container and all its children (icons) self.selectionIconContainer = null; } self.areIconsVisible = false; if (currentActiveBuildSpot === self) { currentActiveBuildSpot = null; } }; self.updateAffordability = function () { if (!self.areIconsVisible || !self.selectionIconContainer) return; var iconButtons = self.selectionIconContainer.children; for (var i = 0; i < iconButtons.length; i++) { var iconContainer = iconButtons[i]; // This is the Container for icon+text if (!iconContainer.children || iconContainer.children.length === 0) continue; var iconGraphic = iconContainer.getChildAt(0); // Assuming graphic is first child if (iconGraphic && iconGraphic.towerTypeKey) { // Check if it's one of our tower icons var towerTypeKey = iconGraphic.towerTypeKey; var cost = TOWER_DATA[towerTypeKey] ? TOWER_DATA[towerTypeKey].levels[0].cost : Infinity; if (currency < cost) { iconContainer.alpha = 0.5; iconGraphic.interactive = false; } else { iconContainer.alpha = 1.0; iconGraphic.interactive = true; } } } }; self.buildSelectedTower = function (towerTypeKey) { if (self.hasTower || !TOWER_DATA[towerTypeKey]) { return; } var towerLevelData = TOWER_DATA[towerTypeKey].levels[0]; // Always build level 0 initially if (currency < towerLevelData.cost) { return; } // Double check affordability currency -= towerLevelData.cost; currencyText.setText("$: " + currency); var newTower; if (towerTypeKey === 'stapler') { newTower = new StaplerTower(); } else if (towerTypeKey === 'blocker') { newTower = new BureaucracyBlockerTower(); } // else if (towerTypeKey === 'placeholder' || your_third_type) newTower = new YourThirdTower(); if (newTower) { // newTower.init(0); // Constructor should call initializeTowerFromData(self, type, 0) newTower.x = 0; newTower.y = 0; // Relative to this BuildSpot self.addChild(newTower); self.tower = newTower; self.hasTower = true; self.graphic.alpha = 0.1; // Dim the build spot graphic var buildSfx = TOWER_DATA[towerTypeKey].buildSfx || 'buildTower'; // Fallback sfx LK.getSound(buildSfx).play(); spawnFloatingText(TOWER_DATA[towerTypeKey].name + " BUILT!", self.x, self.y - 50, { fill: 0x00FF00 }); // After building, update affordability for ALL visible icons if (currentActiveBuildSpot) { currentActiveBuildSpot.updateAffordability(); } // Or iterate all build spots that might have icons visible (if multiple can be open) } }; // Main click handler for the BuildSpot itself self.down = function () { // This is the click on the BuildSpot itself if (self.hasTower) { spawnFloatingText("Tower here!", self.x, self.y, { fill: 0xCCCCCC }); // If any other spot's menu is open, close it. if (currentActiveBuildSpot && currentActiveBuildSpot !== self) { currentActiveBuildSpot.hideSelectionIcons(); } // Also close self's menu if it was somehow open (shouldn't be if tower exists) if (self.areIconsVisible) self.hideSelectionIcons(); return; } if (self.areIconsVisible) { self.hideSelectionIcons(); } else { self.showSelectionIcons(); } }; return self; }); // Modify BureaucracyBlockerTower // --- MODIFY BureaucracyBlockerTower --- var BureaucracyBlockerTower = Container.expand(function () { var self = Container.call(this); self.graphic = self.attachAsset(TOWER_DATA['blocker'].levels[0].asset, { anchorX: 0.5, anchorY: 0.5 }); self.range = 250; // Will be set by init self.slowFactor = 0.5; // Will be set by init self.enemiesSlowed = []; self.towerType = 'blocker'; self.currentLevel = 0; self.init = function (levelIndex) { initializeTowerFromData(self, 'blocker', levelIndex); self.clearAllSlows(); }; self.clearAllSlows = function () { for (var i = 0; i < self.enemiesSlowed.length; i++) { var enemy = self.enemiesSlowed[i]; if (enemy && enemy.parent && enemy.isSlowedByBlocker === self) { enemy.speed = enemy.originalSpeed; enemy.isSlowedByBlocker = null; if (enemy.graphic) { enemy.graphic.tint = 0xFFFFFF; } } } self.enemiesSlowed = []; }; self.update = function () { // --- REPLACE THIS BLOCK for towerGlobalPos --- // var globalPos = self.parent ? self.parent.toGlobal(self.position) : self.position; // OLD WAY // --- WITH THIS --- var buildSpot = self.parent; if (!buildSpot || !buildSpot.parent) { return; } var levelContainer = buildSpot.parent; var towerGlobalPos = levelContainer.toGlobal(buildSpot.position); // Tower is at BuildSpot's origin // --- END REPLACEMENT --- var newlySlowedThisFrame = []; for (var i = 0; i < enemies.length; i++) { var enemy = enemies[i]; if (!enemy || !enemy.parent) { continue; } var dx = enemy.x - towerGlobalPos.x; var dy = enemy.y - towerGlobalPos.y; var distanceSq = dx * dx + dy * dy; if (distanceSq < self.range * self.range) { if (!enemy.isSlowedByBlocker) { enemy.originalSpeed = enemy.speed; enemy.speed *= self.slowFactor; enemy.isSlowedByBlocker = self; if (enemy.graphic) { enemy.graphic.tint = 0xAAAAFF; } } newlySlowedThisFrame.push(enemy); } } // Check enemies that were slowed last frame but might be out of range now for (var i = self.enemiesSlowed.length - 1; i >= 0; i--) { var previouslySlowedEnemy = self.enemiesSlowed[i]; if (!previouslySlowedEnemy || !previouslySlowedEnemy.parent || newlySlowedThisFrame.indexOf(previouslySlowedEnemy) === -1) { // Enemy is gone or no longer in range by this tower if (previouslySlowedEnemy && previouslySlowedEnemy.isSlowedByBlocker === self) { // Only unslow if WE slowed it previouslySlowedEnemy.speed = previouslySlowedEnemy.originalSpeed; previouslySlowedEnemy.isSlowedByBlocker = null; if (previouslySlowedEnemy.graphic) { previouslySlowedEnemy.graphic.tint = 0xFFFFFF; } // Reset tint } self.enemiesSlowed.splice(i, 1); } } self.enemiesSlowed = newlySlowedThisFrame; // Update the list of currently slowed enemies }; // When tower is destroyed, make sure to unslow any enemies it was affecting var originalDestroy = self.destroy; self.destroy = function () { for (var i = 0; i < self.enemiesSlowed.length; i++) { var enemy = self.enemiesSlowed[i]; if (enemy && enemy.parent && enemy.isSlowedByBlocker === self) { enemy.speed = enemy.originalSpeed; enemy.isSlowedByBlocker = null; if (enemy.graphic) { enemy.graphic.tint = 0xFFFFFF; } } } self.enemiesSlowed = []; if (originalDestroy) { originalDestroy.call(self); } else if (self.parent) { self.parent.removeChild(self); } // Basic destroy }; return self; }); // DogeHero - Now includes auto-attack and manual bark ability logic var DogeHero = Container.expand(function () { var self = Container.call(this); self.graphic = self.attachAsset('dogeHero', { anchorX: 0.5, anchorY: 0.5 }); // Expose graphic if needed for hit check self.width = self.graphic.width; // Store size for hit checks self.height = self.graphic.height; self.speed = 5; self.targetX = self.x; self.targetY = self.y; // Auto Attack Stats self.autoAttackRange = 180; self.autoAttackDamage = 2; self.autoAttackCooldownTime = 45; self.currentAutoAttackCooldown = 0; // Manual Bark Ability Stats self.manualBarkCooldownTime = 300; self.currentManualBarkCooldown = 0; self.update = function () { // Movement var dx = self.targetX - self.x; var dy = self.targetY - self.y; var distance = Math.sqrt(dx * dx + dy * dy); if (distance > self.speed) { dx = dx / distance * self.speed; dy = dy / distance * self.speed; self.x += dx; self.y += dy; } else if (distance > 0) { self.x = self.targetX; self.y = self.targetY; } // Cooldowns if (self.currentAutoAttackCooldown > 0) { self.currentAutoAttackCooldown--; } if (self.currentManualBarkCooldown > 0) { self.currentManualBarkCooldown--; } // Auto Attack Logic if (self.currentAutoAttackCooldown <= 0) { var closestEnemy = null; var minDistanceSq = self.autoAttackRange * self.autoAttackRange; for (var i = 0; i < enemies.length; i++) { var enemy = enemies[i]; var ex = enemy.x - self.x; var ey = enemy.y - self.y; var distSq = ex * ex + ey * ey; if (distSq < minDistanceSq) { minDistanceSq = distSq; closestEnemy = enemy; } } if (closestEnemy) { closestEnemy.takeDamage(self.autoAttackDamage); LK.getSound('dogeAutoAttack').play(); self.currentAutoAttackCooldown = self.autoAttackCooldownTime; } } }; self.setTarget = function (x, y) { self.targetX = x; self.targetY = y; }; self.manualBark = function () { if (self.currentManualBarkCooldown <= 0) { var wave = new BarkWave(); wave.x = self.x; wave.y = self.y; game.addChild(wave); LK.getSound('dogeBark').play(); self.currentManualBarkCooldown = self.manualBarkCooldownTime; return true; } return false; }; return self; }); // Enemy class remains largely the same var Enemy = Container.expand(function () { var self = Container.call(this); var graphic = self.attachAsset('enemyPaper', { anchorX: 0.5, anchorY: 0.5 }); self.health = 3; self.speed = 2; self.value = 10; // Currency earned when killed self.currentPathIndex = 0; self.update = function () { // Check if pathPoints is loaded and valid if (!pathPoints || pathPoints.length === 0) { return; } if (self.currentPathIndex < pathPoints.length) { var target = pathPoints[self.currentPathIndex]; if (!target) { // Safety check self.currentPathIndex++; return; } var dx = target.x - self.x; var dy = target.y - self.y; var distance = Math.sqrt(dx * dx + dy * dy); // Use a slightly larger threshold for path progression if (distance < self.speed * 1.5) { // Adjusted threshold self.currentPathIndex++; // Check if enemy reached the goal if (self.currentPathIndex >= pathPoints.length) { playerLives--; livesText.setText("Lives: " + playerLives); LK.getSound('enemyReachGoal').play(); if (playerLives <= 0) { // Game over if (LK.getScore() > storage.highScore) { storage.highScore = LK.getScore(); } LK.showGameOver(); } self.destroy(); return; } } else { // Move towards the target point dx = dx / distance * self.speed; dy = dy / distance * self.speed; self.x += dx; self.y += dy; } } else { // If somehow past the end of path but not destroyed, remove it console.log("Enemy past end of path, destroying."); self.destroy(); } }; self.takeDamage = function (amount) { self.health -= amount; // Flash red when taking damage LK.effects.flashObject(self, 0xff0000, 200); if (self.health <= 0 && self.parent) { // Added check for self.parent before accessing currency currency += self.value; currencyText.setText("$: " + currency); LK.setScore(LK.getScore() + self.value); scoreText.setText("Score: " + LK.getScore()); LK.getSound('enemyDeath').play(); self.destroy(); } }; return self; }); // GameLevel class remains the same var GameLevel = Container.expand(function () { var self = Container.call(this); var pathGraphics = []; // Store path visuals var buildSpotGraphics = []; // Store build spot visuals self.createPath = function (pathData) { // Clear previous path graphics pathGraphics.forEach(function (tile) { tile.destroy(); }); pathGraphics = []; // Assume pathData is the pathPoints array for (var i = 0; i < pathData.length - 1; i++) { var start = pathData[i]; var end = pathData[i + 1]; if (!start || !end) { continue; } // Safety check // Calculate direction and distance var dx = end.x - start.x; var dy = end.y - start.y; var distance = Math.sqrt(dx * dx + dy * dy); // Adjust tile spacing slightly var steps = Math.ceil(distance / 90); // Slightly closer tiles for (var j = 0; j < steps; j++) { var ratio = j / steps; var x = start.x + dx * ratio; var y = start.y + dy * ratio; var tile = LK.getAsset('pathTile', { x: x, y: y, alpha: 0.3, // Make path fainter anchorX: 0.5, anchorY: 0.5 }); self.addChild(tile); pathGraphics.push(tile); // Store reference } } }; self.createBuildSpots = function (spotsData) { // Clear previous build spots buildSpotGraphics.forEach(function (spot) { spot.destroy(); }); buildSpotGraphics = []; self.buildSpots = []; // Clear the logical array too for (var i = 0; i < spotsData.length; i++) { if (!spotsData[i]) { continue; } // Safety check var spot = new BuildSpot(); spot.x = spotsData[i].x; spot.y = spotsData[i].y; self.addChild(spot); self.buildSpots.push(spot); // Store logical spot buildSpotGraphics.push(spot); // Store graphical spot } }; return self; }); // Goal class remains the same var Goal = Container.expand(function () { var self = Container.call(this); var graphic = self.attachAsset('goal', { anchorX: 0.5, anchorY: 0.5, alpha: 0.6 }); return self; }); // --- NEW ENEMY: RedTapeWorm --- var RedTapeWorm = Container.expand(function () { var self = Container.call(this); // Inherit from Enemy - this copies properties and methods if Enemy is set up for it. // If Enemy is not a true prototypal base, we'll redefine common things. // For simplicity, let's assume Enemy provides a good base or we'll set manually. // Call parent constructor if applicable // Override or set specific properties self.graphic = self.attachAsset('enemyRedTapeWorm', { anchorX: 0.5, anchorY: 0.5 }); // Use new asset self.health = 20; // High health self.speed = 0.75; // Very slow speed self.value = 25; // More currency // Optional: Custom sound on spawn or movement // LK.getSound('tapeStretch').play(); // (Could be spammy if played on update) // The base Enemy.update() and Enemy.takeDamage() should work if inherited. // If not, you'd copy and paste that logic here, adjusting as needed. // For now, assume base Enemy update handles pathing and goal reaching. // Custom onDefeat behavior if needed (e.g., spawn smaller tapes - too complex for now) var originalDestroy = self.destroy; // Keep a reference to base destroy self.destroy = function () { // spawnFloatingText("So Bureaucratic!", self.x, self.y - 50, { fill: 0xFF6666 }); // Example originalDestroy.call(self); // Call the original destroy method }; return self; }); // --- Base Tower Functionality (Conceptual - can be mixed into specific towers) --- // This isn't a formal class, but concepts to apply var StaplerTower = Container.expand(function () { var self = Container.call(this); self.graphic = self.attachAsset(TOWER_DATA['stapler'].levels[0].asset, { anchorX: 0.5, anchorY: 0.5 }); self.fireRate = 60; // Set from TOWER_DATA in init self.range = 300; // Set from TOWER_DATA in init self.damage = 1; // Set from TOWER_DATA in init self.lastFired = 0; self.towerType = 'stapler'; self.currentLevel = 0; self.init = function (levelIndex) { initializeTowerFromData(self, 'stapler', levelIndex); self.lastFired = 0; }; self.update = function () { self.lastFired++; if (self.lastFired >= self.fireRate) { var closestEnemy = null; var minDistanceSq = self.range * self.range; // --- REPLACE THIS BLOCK for towerGlobalPos --- // var globalPos = self.parent.toGlobal(self.position); // OLD WAY // --- WITH THIS --- var buildSpot = self.parent; if (!buildSpot || !buildSpot.parent) { return; } // Safety: buildSpot should exist and have a parent (level) var levelContainer = buildSpot.parent; var towerGlobalPos = levelContainer.toGlobal(buildSpot.position); // Tower is at BuildSpot's origin // --- END REPLACEMENT --- for (var i = 0; i < enemies.length; i++) { var enemy = enemies[i]; var dx = enemy.x - towerGlobalPos.x; var dy = enemy.y - towerGlobalPos.y; var distanceSq = dx * dx + dy * dy; if (distanceSq < minDistanceSq) { minDistanceSq = distanceSq; closestEnemy = enemy; } } if (closestEnemy) { self.shoot(closestEnemy); self.lastFired = 0; } } }; self.shoot = function (target) { var bullet = new TowerBullet(); // --- REPLACE THIS BLOCK for towerGlobalPos --- // var globalPos = self.parent.toGlobal(self.position); // OLD WAY // --- WITH THIS --- var buildSpot = self.parent; if (!buildSpot || !buildSpot.parent) { return; } var levelContainer = buildSpot.parent; var towerGlobalPos = levelContainer.toGlobal(buildSpot.position); // Tower is at BuildSpot's origin // --- END REPLACEMENT --- bullet.x = towerGlobalPos.x; bullet.y = towerGlobalPos.y; bullet.target = target; bullet.damage = self.damage; game.addChild(bullet); bullets.push(bullet); LK.getSound('shoot').play(); }; initializeTowerFromData(self, 'stapler', 0); return self; }); // TowerBullet class remains the same var TowerBullet = Container.expand(function () { var self = Container.call(this); var graphic = self.attachAsset('bullet', { anchorX: 0.5, anchorY: 0.5 }); self.speed = 10; self.damage = 1; self.target = null; self.update = function () { // Check if target exists and is still in the game if (!self.target || !self.target.parent) { self.destroy(); return; } var dx = self.target.x - self.x; var dy = self.target.y - self.y; var distance = Math.sqrt(dx * dx + dy * dy); // Use speed as collision threshold if (distance < self.speed) { self.target.takeDamage(self.damage); self.destroy(); return; } // Normalize and multiply by speed dx = dx / distance * self.speed; dy = dy / distance * self.speed; self.x += dx; self.y += dy; }; return self; }); /**** * Initialize Game ****/ // UI elements var game = new LK.Game({ backgroundColor: 0x558855 // Darker green background }); /**** * Game Code ****/ // This isn't a formal class, but concepts to apply // --- Base Tower Functionality (Conceptual - can be mixed into specific towers) --- // Specific build sounds for each tower type if desired // --- New Sound Effects --- // Bureaucracy Blocker // Even more so // Slightly bigger/cooler // Stapler // --- Tower Level Assets (Placeholders) --- // You'll also need actual tower icons for the buttons eventually // For + symbols // --- UI Assets for Popup --- //LK.init.shape('debugTargetMarker', {width:25, height:25, color:0x00ff00, shape:'ellipse'}) //LK.init.shape('debugMarker', {width:20, height:20, color:0xff0000, shape:'ellipse'}) // For drawing lines if needed later // <-- NEW DEBUG ASSET // Added sound for auto-attack // Game constants // <-- NEW Green Target Marker // --- NEW ASSETS --- // Placeholder path // Placeholder path // Sound for bureaucracy blocker or paperwork // Sound for red tape worm var TOWER_DATA = { 'stapler': { name: 'Stapler Turret', iconAsset: 'towerStaplerLvl1', // Use Lvl1 icon for selection button initially buildSfx: 'buildStapler', // You'll need to define this sound levels: [{ asset: 'towerStaplerLvl1', cost: 50, damage: 1, range: 300, fireRate: 60, description: "Basic Stapler" }, { asset: 'towerStaplerLvl2', cost: 75, damage: 2, range: 320, fireRate: 55, description: "Improved Firepower" }, { asset: 'towerStaplerLvl3', cost: 125, damage: 3, range: 350, fireRate: 50, description: "Max Staples!" }] }, 'blocker': { name: 'Bureaucracy Blocker', iconAsset: 'towerBlockerLvl1', buildSfx: 'buildBlocker', // You'll need to define this sound levels: [{ asset: 'towerBlockerLvl1', cost: 75, slowFactor: 0.5, range: 250, description: "Slows nearby red tape." }, { asset: 'towerBlockerLvl2', cost: 100, slowFactor: 0.4, range: 275, description: "Wider, stronger slow." }, { asset: 'towerBlockerLvl3', cost: 150, slowFactor: 0.3, range: 300, description: "Bureaucratic Gridlock!" }] } }; // --- Base Tower Functionality Helper --- // <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< AND initializeTowerFromData function initializeTowerFromData(towerInstance, towerTypeKey, levelIndex) { var towerTypeData = TOWER_DATA[towerTypeKey]; // Now TOWER_DATA will exist if (!towerTypeData) { // console.error("Invalid tower type:", towerTypeKey); // LK Engine doesn't show console // You could use a visual error like a Text2 message if this happens var errorText = new Text2("Error: No data for " + towerTypeKey, { fill: 0xFF0000, size: 20 }); LK.gui.top.addChild(errorText); LK.setTimeout(function () { errorText.destroy(); }, 3000); return; } var levelData = towerTypeData.levels[levelIndex]; if (!levelData) { // console.error("Invalid level for tower:", towerTypeKey, levelIndex); var errorTextLvl = new Text2("Error: No lvl " + levelIndex + " for " + towerTypeKey, { fill: 0xFF0000, size: 20 }); LK.gui.top.addChild(errorTextLvl); LK.setTimeout(function () { errorTextLvl.destroy(); }, 3000); return; } towerInstance.towerType = towerTypeKey; towerInstance.currentLevel = levelIndex; if (towerInstance.graphic) { towerInstance.graphic.setAsset(levelData.asset); } else { // This should ideally not happen if graphic is pre-attached towerInstance.graphic = towerInstance.attachAsset(levelData.asset, { anchorX: 0.5, anchorY: 0.5 }); } // Apply stats if (levelData.damage !== undefined) { towerInstance.damage = levelData.damage; } if (levelData.range !== undefined) { towerInstance.range = levelData.range; } if (levelData.fireRate !== undefined) { towerInstance.fireRate = levelData.fireRate; } if (levelData.slowFactor !== undefined) { towerInstance.slowFactor = levelData.slowFactor; } } var ICON_SELECT_OFFSETS = [{ x: -(150 / 2 + 80 / 2 + 10), y: 0, towerKey: 'stapler' }, // Left { x: 0, y: -(150 / 2 + 80 / 2 + 10), towerKey: 'blocker' }, // Above { x: 150 / 2 + 80 / 2 + 10, y: 0, towerKey: 'placeholder' } // Right ]; // Ensure 'placeholder' exists in TOWER_DATA with at least a cost and iconAsset if (TOWER_DATA && !TOWER_DATA['placeholder']) { TOWER_DATA['placeholder'] = { name: "???", iconAsset: 'iconPlaceholder', levels: [{ cost: 9999, asset: 'iconPlaceholder' }] }; } var TOWER_COST = 50; var WAVE_DELAY = 300; // frames between waves var MAX_WAVES = 10; // Access screen dimensions directly from LK var SCREEN_HEIGHT = 2732; // Standard iPad Pro height (portrait mode) var SCREEN_WIDTH = 2048; // Standard iPad Pro width (portrait mode) var PAN_THRESHOLD = SCREEN_HEIGHT * 0.25; // Start panning when Doge is in top/bottom 25% /**** NEW ****/ var MAP_HEIGHT = 3000; // Define total map height /**** NEW - ADJUST AS NEEDED ****/ // Game variables //var clickMarker; // <-- NEW: Reference to the visual click marker //var targetMarker; // <-- NEW: Reference to the green target marker var activeTowerMenu = null; // To ensure only one menu is open var currentActiveBuildSpot = null; // Track which spot's icons are open var lastWorldClickX = 0; // <-- NEW: Store last click world X var lastWorldClickY = 0; // <-- NEW: Store last click world Y var currency = 100; var playerLives = 5; var currentWave = 0; var waveTimer = 0; var isWaveActive = false; var enemies = []; var bullets = []; var pathPoints = []; var level; var doge; var goal; // --- HELPER FUNCTION FOR FLOATING TEXT --- function spawnFloatingText(text, x, y, options) { var defaultOptions = { size: 30, fill: 0xFFFFFF, // White text stroke: 0x000000, strokeThickness: 2, duration: 60, // frames (1 second at 60fps) velocityY: -1.5, // Pixels per frame upwards alphaFadeSpeed: 0.015 }; var settings = Object.assign({}, defaultOptions, options); // Merge user options var floatingText = new Text2(text, { size: settings.size, fill: settings.fill, stroke: settings.stroke, strokeThickness: settings.strokeThickness, anchorX: 0.5, // Center the text anchorY: 0.5 }); floatingText.x = x; floatingText.y = y; floatingText.alpha = 1.0; game.addChild(floatingText); // Add to main game container to scroll with world var framesLived = 0; floatingText.update = function () { floatingText.y += settings.velocityY; floatingText.alpha -= settings.alphaFadeSpeed; framesLived++; if (framesLived >= settings.duration || floatingText.alpha <= 0) { floatingText.destroy(); } }; // Add this to a list of updatable text objects if your engine doesn't auto-update children with .update // For LK Engine, if it's a child of `game` and has an `update` method, it should be called. // If not, you'll need a global array like `activeFloatingTexts` and iterate it in `game.update`. // Let's assume LK.Game handles child updates for now. } var scoreText = new Text2("Score: 0", { size: 50, fill: 0xFFFFFF }); // White text scoreText.anchor.set(0.5, 0); LK.gui.top.addChild(scoreText); var waveText = new Text2("Wave: 0/" + MAX_WAVES, { size: 50, fill: 0xFFFFFF }); waveText.anchor.set(1, 0); // Anchor top-right waveText.x = -100; // Position from right edge LK.gui.topRight.addChild(waveText); var currencyText = new Text2("$: " + currency, { size: 50, fill: 0xFFFFFF }); currencyText.anchor.set(0.5, 0); currencyText.y = 60; LK.gui.top.addChild(currencyText); var livesText = new Text2("Lives: " + playerLives, { size: 50, fill: 0xFFFFFF }); livesText.anchor.set(1, 0); // Anchor top-right livesText.x = -100; livesText.y = 60; LK.gui.topRight.addChild(livesText); // Removed old info text, replaced by Bark button // var infoText = new Text2(...) // Bark Button /**** NEW ****/ var barkButton = new Text2("BARK!", { size: 60, fill: 0xffcc00, // Doge color stroke: 0x000000, strokeThickness: 4 }); barkButton.anchor.set(0.5, 1); // Anchor bottom-center barkButton.y = -50; // Position from bottom edge barkButton.interactive = true; // Make it clickable barkButton.down = function () { if (doge) { var success = doge.manualBark(); if (success) { // Optional: visual feedback on button press barkButton.scale.set(1.1); LK.setTimeout(function () { barkButton.scale.set(1.0); }, 100); } } }; LK.gui.bottom.addChild(barkButton); // NEW: On-Screen Debug Text var debugText = new Text2("Debug Info", { size: 24, // Smaller font fill: 0xFFFF00, // Yellow text align: "left", // Align text left stroke: 0x000000, strokeThickness: 2 }); debugText.anchor.set(0, 1); // Anchor bottom-left debugText.x = 10; debugText.y = -10; // Position from bottom edge LK.gui.bottomLeft.addChild(debugText); // Add to a corner // Initialize game level and path function initializeGame() { game.y = 0; game.scale.set(1); // Reset position and scale level = new GameLevel(); game.addChild(level); // Path Points (Adjust Y values for map height) pathPoints = [{ x: SCREEN_WIDTH * 0.1, y: 100 }, { x: SCREEN_WIDTH * 0.1, y: 500 }, { x: SCREEN_WIDTH * 0.4, y: 500 }, { x: SCREEN_WIDTH * 0.4, y: 1000 }, { x: SCREEN_WIDTH * 0.7, y: 1000 }, { x: SCREEN_WIDTH * 0.7, y: 1500 }, { x: SCREEN_WIDTH * 0.3, y: 1500 }, { x: SCREEN_WIDTH * 0.3, y: 2000 }, { x: SCREEN_WIDTH * 0.8, y: 2000 }, { x: SCREEN_WIDTH * 0.8, y: 2800 }]; level.createPath(pathPoints); // Build Spots (Adjust Y values for map height) var buildSpots = [{ x: SCREEN_WIDTH * 0.25, y: 300 }, { x: SCREEN_WIDTH * 0.25, y: 750 }, { x: SCREEN_WIDTH * 0.55, y: 750 }, { x: SCREEN_WIDTH * 0.55, y: 1250 }, { x: SCREEN_WIDTH * 0.85, y: 1250 }, { x: SCREEN_WIDTH * 0.50, y: 1750 }, { x: SCREEN_WIDTH * 0.15, y: 1750 }, { x: SCREEN_WIDTH * 0.50, y: 2400 }, { x: SCREEN_WIDTH * 0.90, y: 2400 }]; level.createBuildSpots(buildSpots); // Goal goal = new Goal(); if (pathPoints.length > 0) { goal.x = pathPoints[pathPoints.length - 1].x; goal.y = pathPoints[pathPoints.length - 1].y; game.addChild(goal); } currentActiveBuildSpot = null; // Doge doge = new DogeHero(); doge.x = SCREEN_WIDTH / 2; doge.y = SCREEN_HEIGHT / 2; doge.targetX = doge.x; doge.targetY = doge.y; game.addChild(doge); // Debug Markers (Remove or comment out if not needed) // clickMarker = LK.getAsset('debugMarker'); clickMarker.visible = false; clickMarker.alpha = 0.8; game.addChild(clickMarker); // targetMarker = LK.getAsset('debugTargetMarker'); targetMarker.visible = true; targetMarker.alpha = 0.7; targetMarker.x = doge.targetX; targetMarker.y = doge.targetY; game.addChild(targetMarker); // Reset Variables currency = 100; playerLives = 5; currentWave = 0; waveTimer = 0; isWaveActive = false; enemies.forEach(function (e) { if (e.parent) { e.destroy(); } }); enemies = []; bullets.forEach(function (b) { if (b.parent) { b.destroy(); } }); bullets = []; // Update UI currencyText.setText("$: " + currency); livesText.setText("Lives: " + playerLives); waveText.setText("Wave: " + currentWave + "/" + MAX_WAVES); scoreText.setText("Score: " + LK.getScore()); barkButton.setText("BARK!"); // updateDebugText(); // Remove if debug text removed LK.playMusic('bgmusic'); } // NEW: Function to update debug text display //function updateDebugText() { // if (!debugText || !doge) { // return; // } // Don't update if elements don't exist // var debugLines = ["GameY: " + game.y.toFixed(1), "GameScaleY: " + game.scale.y.toFixed(2), // <-- Added Scale Display // "DogeXY: " + doge.x.toFixed(1) + ", " + doge.y.toFixed(1), "DogeTarget: " + doge.targetX.toFixed(1) + ", " + doge.targetY.toFixed(1), "LastClickW: " + lastWorldClickX.toFixed(1) + ", " + lastWorldClickY.toFixed(1)]; // debugText.setText(debugLines.join("\n")); function spawnWave() { currentWave++; waveText.setText("Wave: " + currentWave + "/" + MAX_WAVES); var enemyCount = 5 + currentWave * 2; var spawnInterval = 60; // frames between enemy spawns function spawnEnemy(count) { if (count <= 0 || !pathPoints || pathPoints.length === 0) { // Added check for pathPoints isWaveActive = true; // Mark wave active even if spawning failed/finished return; } var enemy = new Enemy(); enemy.x = pathPoints[0].x; enemy.y = pathPoints[0].y; // Increase difficulty with each wave enemy.health = 2 + Math.floor(currentWave / 2); if (currentWave > 5) { enemy.speed = 2.5; } if (currentWave > 8) { enemy.speed = 3; } game.addChild(enemy); // Add enemy to the main game container enemies.push(enemy); // Use LK.setTimeout for delays LK.setTimeout(function () { spawnEnemy(count - 1); }, spawnInterval * 16.67); // Use ~16.67ms for 60fps } isWaveActive = false; // Mark wave as starting (will be set true after first spawn attempt or completion) spawnEnemy(enemyCount); } function checkWaveComplete() { if (isWaveActive && enemies.length === 0) { // Double check if we really spawned everything for the wave before proceeding // This basic check assumes spawning finishes before all enemies are killed isWaveActive = false; waveTimer = 0; // Reset timer for next wave delay if (currentWave >= MAX_WAVES) { // Player wins if (LK.getScore() > storage.highScore) { storage.highScore = LK.getScore(); } LK.showYouWin(); } } } // Event Handlers /**** MODIFIED ****/ var dragDoge = false; game.down = function (x, y, obj) { var worldX = x; var worldY = y; // Using raw world-space coords from event var clickedOnAnInteractiveIcon = false; // Check if the click was on an active selection icon if (currentActiveBuildSpot && currentActiveBuildSpot.selectionIconContainer) { var iconsInMenu = currentActiveBuildSpot.selectionIconContainer.children; for (var i = 0; i < iconsInMenu.length; i++) { var iconButtonContainer = iconsInMenu[i]; // obj from game.down is the *topmost* interactive element. // If it's the iconGraphic inside iconButtonContainer, its .down will fire. var iconGraphic = iconButtonContainer.getChildAt(0); // Assuming graphic is first child if (obj === iconGraphic) { clickedOnAnInteractiveIcon = true; // The icon's own .down handler will manage building and closing. // We might still want to hide icons if the build fails (e.g. re-check cost) // but for now, let the icon handler close on success. break; } } } // If the click was on an icon, let its handler do the work. if (clickedOnAnInteractiveIcon) { return; } // If click was not on an icon, check if it was on a BuildSpot graphic itself // to open/close its menu. var clickedOnBuildSpotGraphic = false; if (level && level.buildSpots) { for (var i = 0; i < level.buildSpots.length; i++) { var spot = level.buildSpots[i]; if (obj === spot.graphic) { // LK engine gives us the target 'obj' clickedOnBuildSpotGraphic = true; // The spot.down() handler will be called by the engine. break; } } } // If the click was on a build spot graphic, its .down handler will manage the menu. if (clickedOnBuildSpotGraphic) { return; } // If we've reached here, the click was not on an active icon and not on a buildspot graphic. // So, if a menu is open, close it. if (currentActiveBuildSpot) { currentActiveBuildSpot.hideSelectionIcons(); // currentActiveBuildSpot will be set to null inside hideSelectionIcons } // --- Doge Movement Logic (if click wasn't handled by UI/icons/spots) --- var targetObjectClicked = obj; var clickedOnDoge = false; var clickedOnBuildSpot = null; // 1. Check for clicking Doge (using worldPos) if (doge && targetObjectClicked === doge.graphic) { clickedOnDoge = true; } else if (doge) { var dx = worldX - doge.x; var dy = worldY - doge.y; if (Math.sqrt(dx * dx + dy * dy) < doge.width / 2) { clickedOnDoge = true; } } // 2. Check for clicking BuildSpot (using worldPos) if (!clickedOnDoge && level && level.buildSpots) { for (var i = 0; i < level.buildSpots.length; i++) { var spot = level.buildSpots[i]; if (spot && !spot.hasTower) { var spotDx = worldX - spot.x; var spotDy = worldY - spot.y; var spotRadius = spot.graphic ? spot.graphic.width / 2 : 75; if (Math.sqrt(spotDx * spotDx + spotDy * spotDy) < spotRadius) { clickedOnBuildSpot = spot; // Optional highlight // if (spot.graphic) { spot.graphic.alpha = 1.0; LK.setTimeout(function() { if (spot.graphic && !spot.hasTower) spot.graphic.alpha = 0.5; }, 300);} break; } } } } // 3. Execute Action if (clickedOnDoge && doge) { dragDoge = true; doge.setTarget(doge.x, doge.y); } else if (clickedOnBuildSpot && currency >= TOWER_COST) { dragDoge = false; } else if (doge) { dragDoge = false; doge.setTarget(worldPos.x, worldPos.y); } // Use worldPos directly }; game.move = function (x, y, obj) { if (dragDoge && doge) { // --- Use Raw x, y as World Coordinates --- var worldX = x; var worldY = y; doge.x = worldX; doge.y = worldY; doge.setTarget(doge.x, doge.y); // Update target while dragging // --- Optional Debug Update --- // lastWorldClickX = worldX; lastWorldClickY = worldY; } }; game.up = function (x, y, obj) { if (dragDoge) { dragDoge = false; } }; game.up = function (x, y, obj) { if (dragDoge) { dragDoge = false; } }; // Main game loop /**** MODIFIED ****/ game.update = function () { // --- Wave Management --- if (!isWaveActive && currentWave < MAX_WAVES) { // Only increment timer if not won yet waveTimer++; if (waveTimer >= WAVE_DELAY) { waveTimer = 0; // Reset timer immediately spawnWave(); } } // --- Update Bullets --- (Remove destroyed ones) for (var i = bullets.length - 1; i >= 0; i--) { if (!bullets[i].parent) { bullets.splice(i, 1); } } // --- Update Enemies --- (Remove destroyed ones) for (var i = enemies.length - 1; i >= 0; i--) { if (!enemies[i].parent) { enemies.splice(i, 1); } } // --- Update Doge (handles its own cooldowns and attacks) --- if (doge && doge.parent) { // Check if doge exists doge.update(); // Make sure Doge's update runs } // --- Check Wave Completion --- checkWaveComplete(); // --- Update Bark Button UI --- /**** NEW ****/ if (doge) { // Check if doge exists if (doge.currentManualBarkCooldown > 0) { var secondsLeft = Math.ceil(doge.currentManualBarkCooldown / 60); // Approx seconds barkButton.setText("WAIT (" + secondsLeft + ")"); barkButton.setText("WAIT (" + secondsLeft + ")", { fill: 0x888888 }); // Grey out text } else { barkButton.setText("BARK!"); barkButton.setText("BARK!", { fill: 0xffcc00 }); // Restore color } } // --- Camera Panning Logic --- /**** NEW ****/ if (doge) { var dogeScreenY = doge.y + game.y; var targetGameY = game.y; if (dogeScreenY < PAN_THRESHOLD) { targetGameY = -(doge.y - PAN_THRESHOLD); } else if (dogeScreenY > SCREEN_HEIGHT - PAN_THRESHOLD) { targetGameY = -(doge.y - (SCREEN_HEIGHT - PAN_THRESHOLD)); } var minGameY = -(MAP_HEIGHT - SCREEN_HEIGHT); if (isNaN(minGameY)) { minGameY = 0; } var maxGameY = 0; targetGameY = Math.max(minGameY, Math.min(maxGameY, targetGameY)); var camSmoothFactor = 0.1; game.y += (targetGameY - game.y) * camSmoothFactor; if (Math.abs(game.y - targetGameY) < 1) { game.y = targetGameY; } } // --- Update Debug Text --- /**** NEW ****/ // Update Debug Info (Remove or comment out) // if (targetMarker && doge) { targetMarker.x = doge.targetX; targetMarker.y = doge.targetY; targetMarker.visible = true; } else if (targetMarker) { targetMarker.visible = false; } // updateDebugText(); }; // Initialize the game LK.setScore(0); // Reset score on start initializeGame();
===================================================================
--- original.js
+++ change.js
@@ -49,9 +49,10 @@
}
};
return self;
});
-// In BuildSpot class:
+// --- MODIFIED BuildSpot Class ---
+// --- MODIFY BuildSpot Class ---
var BuildSpot = Container.expand(function () {
var self = Container.call(this);
self.graphic = self.attachAsset('buildSpot', {
anchorX: 0.5,
@@ -59,44 +60,202 @@
alpha: 0.5
});
self.hasTower = false;
self.tower = null;
- // OLD self.buildTower is REMOVED. Building/upgrading happens via menu.
- self.down = function () {
- if (activeTowerMenu) {
- // If a menu is already open, close it
- activeTowerMenu.close();
- if (activeTowerMenu === self.myMenu) {
- // Clicked same spot again
- self.myMenu = null; // Allow reopening
- return;
+ self.selectionIconContainer = null; // This will hold the icons
+ self.areIconsVisible = false;
+ // Define offsets for icons relative to the BuildSpot's center
+ // (These are local coordinates within the selectionIconContainer if you use one,
+ // or directly relative to the BuildSpot if icons are direct children of BuildSpot)
+ var iconOffsets = [{
+ x: -(self.graphic.width / 2 + 45),
+ y: 0,
+ towerKey: 'stapler'
+ },
+ // Left (SpotRadius + IconRadius/2 + Margin)
+ {
+ x: 0,
+ y: -(self.graphic.height / 2 + 45),
+ towerKey: 'blocker'
+ },
+ // Above
+ {
+ x: self.graphic.width / 2 + 45,
+ y: 0,
+ towerKey: 'placeholder'
+ } // Right
+ ];
+ self.showSelectionIcons = function () {
+ if (self.hasTower || self.areIconsVisible) return;
+ if (currentActiveBuildSpot && currentActiveBuildSpot !== self) {
+ currentActiveBuildSpot.hideSelectionIcons();
+ }
+ currentActiveBuildSpot = self;
+ // Create a container for the icons, positioned AT the BuildSpot's location
+ // This container will be added to the SAME parent as the BuildSpot (e.g., 'level')
+ self.selectionIconContainer = new Container();
+ self.selectionIconContainer.x = self.x; // Position container at buildspot's x (in 'level' space)
+ self.selectionIconContainer.y = self.y; // Position container at buildspot's y (in 'level' space)
+ self.parent.addChild(self.selectionIconContainer); // Add to 'level' (BuildSpot's parent)
+ for (var i = 0; i < iconOffsets.length; i++) {
+ var offsetData = iconOffsets[i];
+ var towerTypeKey = offsetData.towerKey;
+ var towerInfo = TOWER_DATA[towerTypeKey];
+ if (!towerInfo || !towerInfo.levels || !towerInfo.levels[0]) continue;
+ var cost = towerInfo.levels[0].cost;
+ // Create icon button within the selectionIconContainer
+ // Its x,y will be relative to the selectionIconContainer's origin (which is the BuildSpot's center)
+ var iconButton = new Container();
+ iconButton.x = offsetData.x; // Use the defined offset
+ iconButton.y = offsetData.y; // Use the defined offset
+ self.selectionIconContainer.addChild(iconButton);
+ var iconGraphicAsset = towerInfo.iconAsset || 'iconPlaceholder';
+ var iconGraphic = iconButton.attachAsset(iconGraphicAsset, {
+ anchorX: 0.5,
+ anchorY: 0.5
+ });
+ iconGraphic.interactive = true;
+ iconGraphic.towerTypeKey = towerTypeKey;
+ var costText = new Text2("$" + cost, {
+ size: 20,
+ fill: 0xFFFFFF,
+ stroke: 0x000000,
+ strokeThickness: 2
+ });
+ costText.anchor.set(0.5, -0.7); // Position below the icon's center
+ iconButton.addChild(costText);
+ if (currency < cost) {
+ iconButton.alpha = 0.5;
+ iconGraphic.interactive = false;
+ } else {
+ iconButton.alpha = 1.0;
}
+ iconGraphic.down = function () {
+ var currentCost = TOWER_DATA[this.towerTypeKey].levels[0].cost;
+ if (currency >= currentCost) {
+ self.buildSelectedTower(this.towerTypeKey);
+ // hideSelectionIcons will be called by the main game.down logic
+ } else {
+ spawnFloatingText("Need More $!", self.x + offsetData.x, self.y + offsetData.y - 30, {
+ fill: 0xFF0000
+ });
+ }
+ // IMPORTANT: After an icon is clicked, we want to hide the icons.
+ // The main game.down will handle this if the click is "outside".
+ // If the click IS on an icon, the icon's own handler executes, then game.down.
+ // So, we can call hideSelectionIcons here too, or rely on game.down.
+ // For simplicity, let game.down handle closing.
+ };
}
+ self.areIconsVisible = true;
LK.getSound('uiOpenMenu').play();
- // The menu needs to be added to a top-level UI container that doesn't scroll
- // For LK Engine, LK.gui.top (or similar) is usually good for this
- self.myMenu = new TowerSelectionMenu(self); // Pass self (the buildspot) to the menu
- LK.gui.top.addChild(self.myMenu); // Add menu to a fixed UI layer
- activeTowerMenu = self.myMenu;
};
+ self.hideSelectionIcons = function () {
+ if (self.selectionIconContainer) {
+ self.selectionIconContainer.destroy(); // Destroy the container and all its children (icons)
+ self.selectionIconContainer = null;
+ }
+ self.areIconsVisible = false;
+ if (currentActiveBuildSpot === self) {
+ currentActiveBuildSpot = null;
+ }
+ };
+ self.updateAffordability = function () {
+ if (!self.areIconsVisible || !self.selectionIconContainer) return;
+ var iconButtons = self.selectionIconContainer.children;
+ for (var i = 0; i < iconButtons.length; i++) {
+ var iconContainer = iconButtons[i]; // This is the Container for icon+text
+ if (!iconContainer.children || iconContainer.children.length === 0) continue;
+ var iconGraphic = iconContainer.getChildAt(0); // Assuming graphic is first child
+ if (iconGraphic && iconGraphic.towerTypeKey) {
+ // Check if it's one of our tower icons
+ var towerTypeKey = iconGraphic.towerTypeKey;
+ var cost = TOWER_DATA[towerTypeKey] ? TOWER_DATA[towerTypeKey].levels[0].cost : Infinity;
+ if (currency < cost) {
+ iconContainer.alpha = 0.5;
+ iconGraphic.interactive = false;
+ } else {
+ iconContainer.alpha = 1.0;
+ iconGraphic.interactive = true;
+ }
+ }
+ }
+ };
+ self.buildSelectedTower = function (towerTypeKey) {
+ if (self.hasTower || !TOWER_DATA[towerTypeKey]) {
+ return;
+ }
+ var towerLevelData = TOWER_DATA[towerTypeKey].levels[0]; // Always build level 0 initially
+ if (currency < towerLevelData.cost) {
+ return;
+ } // Double check affordability
+ currency -= towerLevelData.cost;
+ currencyText.setText("$: " + currency);
+ var newTower;
+ if (towerTypeKey === 'stapler') {
+ newTower = new StaplerTower();
+ } else if (towerTypeKey === 'blocker') {
+ newTower = new BureaucracyBlockerTower();
+ }
+ // else if (towerTypeKey === 'placeholder' || your_third_type) newTower = new YourThirdTower();
+ if (newTower) {
+ // newTower.init(0); // Constructor should call initializeTowerFromData(self, type, 0)
+ newTower.x = 0;
+ newTower.y = 0; // Relative to this BuildSpot
+ self.addChild(newTower);
+ self.tower = newTower;
+ self.hasTower = true;
+ self.graphic.alpha = 0.1; // Dim the build spot graphic
+ var buildSfx = TOWER_DATA[towerTypeKey].buildSfx || 'buildTower'; // Fallback sfx
+ LK.getSound(buildSfx).play();
+ spawnFloatingText(TOWER_DATA[towerTypeKey].name + " BUILT!", self.x, self.y - 50, {
+ fill: 0x00FF00
+ });
+ // After building, update affordability for ALL visible icons
+ if (currentActiveBuildSpot) {
+ currentActiveBuildSpot.updateAffordability();
+ }
+ // Or iterate all build spots that might have icons visible (if multiple can be open)
+ }
+ };
+ // Main click handler for the BuildSpot itself
+ self.down = function () {
+ // This is the click on the BuildSpot itself
+ if (self.hasTower) {
+ spawnFloatingText("Tower here!", self.x, self.y, {
+ fill: 0xCCCCCC
+ });
+ // If any other spot's menu is open, close it.
+ if (currentActiveBuildSpot && currentActiveBuildSpot !== self) {
+ currentActiveBuildSpot.hideSelectionIcons();
+ }
+ // Also close self's menu if it was somehow open (shouldn't be if tower exists)
+ if (self.areIconsVisible) self.hideSelectionIcons();
+ return;
+ }
+ if (self.areIconsVisible) {
+ self.hideSelectionIcons();
+ } else {
+ self.showSelectionIcons();
+ }
+ };
return self;
});
// Modify BureaucracyBlockerTower
+// --- MODIFY BureaucracyBlockerTower ---
var BureaucracyBlockerTower = Container.expand(function () {
var self = Container.call(this);
- // Start with a placeholder asset that will be replaced during initialization
- self.graphic = self.attachAsset('towerBlockerLvl1', {
+ self.graphic = self.attachAsset(TOWER_DATA['blocker'].levels[0].asset, {
anchorX: 0.5,
anchorY: 0.5
});
- self.range = 250;
- self.slowFactor = 0.5;
+ self.range = 250; // Will be set by init
+ self.slowFactor = 0.5; // Will be set by init
self.enemiesSlowed = [];
self.towerType = 'blocker';
self.currentLevel = 0;
self.init = function (levelIndex) {
initializeTowerFromData(self, 'blocker', levelIndex);
- // Clear existing slows when leveling up to re-evaluate with new range/factor
self.clearAllSlows();
};
self.clearAllSlows = function () {
for (var i = 0; i < self.enemiesSlowed.length; i++) {
@@ -111,29 +270,35 @@
}
self.enemiesSlowed = [];
};
self.update = function () {
- var globalPos = self.parent ? self.parent.toGlobal(self.position) : self.position; // Get global position
+ // --- REPLACE THIS BLOCK for towerGlobalPos ---
+ // var globalPos = self.parent ? self.parent.toGlobal(self.position) : self.position; // OLD WAY
+ // --- WITH THIS ---
+ var buildSpot = self.parent;
+ if (!buildSpot || !buildSpot.parent) {
+ return;
+ }
+ var levelContainer = buildSpot.parent;
+ var towerGlobalPos = levelContainer.toGlobal(buildSpot.position); // Tower is at BuildSpot's origin
+ // --- END REPLACEMENT ---
var newlySlowedThisFrame = [];
for (var i = 0; i < enemies.length; i++) {
var enemy = enemies[i];
if (!enemy || !enemy.parent) {
continue;
- } // Skip if enemy is already gone
- var dx = enemy.x - globalPos.x;
- var dy = enemy.y - globalPos.y;
+ }
+ var dx = enemy.x - towerGlobalPos.x;
+ var dy = enemy.y - towerGlobalPos.y;
var distanceSq = dx * dx + dy * dy;
if (distanceSq < self.range * self.range) {
- // Enemy is in range
if (!enemy.isSlowedByBlocker) {
- // Check a custom flag
- enemy.originalSpeed = enemy.speed; // Store original speed
+ enemy.originalSpeed = enemy.speed;
enemy.speed *= self.slowFactor;
- enemy.isSlowedByBlocker = self; // Mark who slowed it
- // Optional: Visual effect on enemy
+ enemy.isSlowedByBlocker = self;
if (enemy.graphic) {
enemy.graphic.tint = 0xAAAAFF;
- } // Light blue tint
+ }
}
newlySlowedThisFrame.push(enemy);
}
}
@@ -432,65 +597,76 @@
return self;
});
// --- Base Tower Functionality (Conceptual - can be mixed into specific towers) ---
// This isn't a formal class, but concepts to apply
-// Modify StaplerTower
var StaplerTower = Container.expand(function () {
var self = Container.call(this);
- self.graphic = self.attachAsset('towerStaplerLvl1', {
+ self.graphic = self.attachAsset(TOWER_DATA['stapler'].levels[0].asset, {
anchorX: 0.5,
- anchorY: 0.5,
- alpha: 0.5
- }); // Start with placeholder asset that will be replaced during initialization
- self.fireRate = 60;
- self.range = 300;
- self.damage = 1;
+ anchorY: 0.5
+ });
+ self.fireRate = 60; // Set from TOWER_DATA in init
+ self.range = 300; // Set from TOWER_DATA in init
+ self.damage = 1; // Set from TOWER_DATA in init
self.lastFired = 0;
- self.towerType = 'stapler'; // For identification
+ self.towerType = 'stapler';
self.currentLevel = 0;
self.init = function (levelIndex) {
initializeTowerFromData(self, 'stapler', levelIndex);
- // Reset any specific state if needed when leveling up
self.lastFired = 0;
};
self.update = function () {
- self.lastFired++; // Increment timer
+ self.lastFired++;
if (self.lastFired >= self.fireRate) {
- // Find closest enemy
var closestEnemy = null;
- // Use squared distance for efficiency
var minDistanceSq = self.range * self.range;
- // Need global position of the tower for range check
- var globalPos = self.parent.toGlobal(self.position);
+ // --- REPLACE THIS BLOCK for towerGlobalPos ---
+ // var globalPos = self.parent.toGlobal(self.position); // OLD WAY
+ // --- WITH THIS ---
+ var buildSpot = self.parent;
+ if (!buildSpot || !buildSpot.parent) {
+ return;
+ } // Safety: buildSpot should exist and have a parent (level)
+ var levelContainer = buildSpot.parent;
+ var towerGlobalPos = levelContainer.toGlobal(buildSpot.position); // Tower is at BuildSpot's origin
+ // --- END REPLACEMENT ---
for (var i = 0; i < enemies.length; i++) {
var enemy = enemies[i];
- var dx = enemy.x - globalPos.x; // Use global tower X
- var dy = enemy.y - globalPos.y; // Use global tower Y
- var distanceSq = dx * dx + dy * dy; // Squared distance check
+ var dx = enemy.x - towerGlobalPos.x;
+ var dy = enemy.y - towerGlobalPos.y;
+ var distanceSq = dx * dx + dy * dy;
if (distanceSq < minDistanceSq) {
minDistanceSq = distanceSq;
closestEnemy = enemy;
}
}
if (closestEnemy) {
self.shoot(closestEnemy);
- self.lastFired = 0; // Reset timer
+ self.lastFired = 0;
}
}
};
self.shoot = function (target) {
var bullet = new TowerBullet();
- // Use global position for bullet starting point
- var globalPos = self.parent.toGlobal(self.position);
- bullet.x = globalPos.x;
- bullet.y = globalPos.y;
+ // --- REPLACE THIS BLOCK for towerGlobalPos ---
+ // var globalPos = self.parent.toGlobal(self.position); // OLD WAY
+ // --- WITH THIS ---
+ var buildSpot = self.parent;
+ if (!buildSpot || !buildSpot.parent) {
+ return;
+ }
+ var levelContainer = buildSpot.parent;
+ var towerGlobalPos = levelContainer.toGlobal(buildSpot.position); // Tower is at BuildSpot's origin
+ // --- END REPLACEMENT ---
+ bullet.x = towerGlobalPos.x;
+ bullet.y = towerGlobalPos.y;
bullet.target = target;
bullet.damage = self.damage;
- // Add bullet to the main game container, not the tower itself
game.addChild(bullet);
bullets.push(bullet);
LK.getSound('shoot').play();
};
+ initializeTowerFromData(self, 'stapler', 0);
return self;
});
// TowerBullet class remains the same
var TowerBullet = Container.expand(function () {
@@ -524,276 +700,51 @@
self.y += dy;
};
return self;
});
-// UI elements
-// --- Tower Selection Popup Menu ---
-var TowerSelectionMenu = Container.expand(function (buildSpotInstance) {
- var self = Container.call(this);
- self.buildSpot = buildSpotInstance; // Reference to the BuildSpot that opened it
- self.buttons = [];
- var background = new Container();
- var backgroundShape = background.attachAsset('uiButtonBackground', {
- // Simple background for the menu
- width: 150,
- // Adjust as needed
- height: Object.keys(TOWER_DATA).length * 110 + 20,
- // Dynamic height
- alpha: 0.8,
- tint: 0x333333
- });
- background.pivot.set(0, 0); // Anchor top-left for easier button layout
- self.addChild(background);
- var yOffset = 10;
- for (var towerTypeKey in TOWER_DATA) {
- if (TOWER_DATA.hasOwnProperty(towerTypeKey)) {
- var towerTypeInfo = TOWER_DATA[towerTypeKey];
- var currentTowerLevelOnSpot = -1; // -1 if no tower, otherwise level index
- var nextLevelIndex = 0;
- var buttonText = towerTypeInfo.name;
- var cost = towerTypeInfo.levels[0].cost;
- if (self.buildSpot.tower && self.buildSpot.tower.towerType === towerTypeKey) {
- currentTowerLevelOnSpot = self.buildSpot.tower.currentLevel;
- nextLevelIndex = currentTowerLevelOnSpot + 1;
- if (nextLevelIndex < towerTypeInfo.levels.length) {
- cost = towerTypeInfo.levels[nextLevelIndex].cost;
- buttonText = "Upgrade " + towerTypeInfo.name;
- for (var k = 0; k < nextLevelIndex; k++) {
- buttonText += "+";
- } // Add + signs
- } else {
- buttonText = towerTypeInfo.name + " (MAX)"; // Max level
- cost = Infinity; // Can't upgrade further
- }
- }
- var buttonContainer = new Container();
- var buttonIconAsset = towerTypeInfo.iconAsset || 'uiButtonBackground'; // Fallback if icon not defined
- var buttonBg = buttonContainer.attachAsset(buttonIconAsset, {
- // Use specific icon
- anchorX: 0,
- anchorY: 0,
- width: 130,
- height: 100 // Adjust if icons have different native sizes
- });
- buttonBg.interactive = true;
- buttonContainer.x = 10;
- buttonContainer.y = yOffset;
- self.addChild(buttonContainer);
- var buttonBg = buttonContainer.attachAsset('uiButtonBackground', {
- // Use your actual icon asset for the tower type here
- // For now, a generic background
- anchorX: 0,
- anchorY: 0,
- width: 130,
- height: 100
- });
- buttonBg.interactive = true;
- buttonBg.towerTypeKey = towerTypeKey; // Store for click handler
- buttonBg.nextLevelIndex = nextLevelIndex;
- buttonBg.cost = cost;
- var text = new Text2(buttonText + "\n$: " + (cost === Infinity ? "---" : cost), {
- size: 18,
- fill: 0xFFFFFF,
- align: 'center',
- wordWrap: true,
- wordWrapWidth: 120
- });
- text.anchor.set(0.5);
- text.x = buttonBg.width / 2;
- text.y = buttonBg.height / 2;
- buttonContainer.addChild(text);
- // Dim if cannot afford
- if (currency < cost || cost === Infinity) {
- buttonContainer.alpha = 0.5;
- buttonBg.interactive = false; // Can't click if can't afford or maxed
- } else {
- buttonContainer.alpha = 1.0;
- }
- buttonBg.down = function () {
- if (currency >= this.cost) {
- currency -= this.cost;
- currencyText.setText("$: " + currency);
- if (self.buildSpot.tower && self.buildSpot.tower.towerType === this.towerTypeKey) {
- // Upgrade existing tower
- self.buildSpot.tower.init(this.nextLevelIndex); // Re-initialize with new level data
- LK.getSound('towerUpgrade').play();
- spawnFloatingText("UPGRADED!", self.buildSpot.x, self.buildSpot.y - 50, {
- fill: 0x00FF00
- });
- } else {
- // Build new tower
- if (self.buildSpot.tower) {
- // Sell old tower? For now, just replace.
- self.buildSpot.tower.destroy();
- }
- var newTower;
- if (this.towerTypeKey === 'stapler') {
- newTower = new StaplerTower();
- } else if (this.towerTypeKey === 'blocker') {
- newTower = new BureaucracyBlockerTower();
- }
- // Add more tower types here
- if (newTower) {
- newTower.init(this.nextLevelIndex); // Init at level 0 (or selected level)
- newTower.x = 0;
- newTower.y = 0; // Relative to buildSpot
- self.buildSpot.addChild(newTower);
- self.buildSpot.tower = newTower;
- self.buildSpot.hasTower = true;
- self.buildSpot.graphic.alpha = 0.1;
- LK.getSound(TOWER_DATA[this.towerTypeKey].buildSfx).play();
- spawnFloatingText("BUILT!", self.buildSpot.x, self.buildSpot.y - 50, {
- fill: 0x00FF00
- });
- }
- }
- self.close(); // Close menu after action
- }
- };
- self.buttons.push(buttonContainer);
- yOffset += 110; // Spacing for next button
- }
- }
- // Add a close button (optional) or close on outside click
- var closeButton = new Text2("Close [X]", {
- size: 20,
- fill: 0xFF0000
- });
- closeButton.anchor.set(1, 0);
- closeButton.x = background.width - 5;
- closeButton.y = 5;
- closeButton.interactive = true;
- closeButton.down = function () {
- self.close();
- };
- self.addChild(closeButton);
- self.close = function () {
- if (activeTowerMenu === self) {
- activeTowerMenu = null;
- }
- self.destroy();
- };
- // Position the menu next to the build spot
- // This needs buildSpot's GLOBAL position
- var spotGlobalPos = buildSpotInstance.parent.toGlobal(buildSpotInstance.position); // Assuming buildSpot is child of 'level' or similar
- self.x = spotGlobalPos.x + buildSpotInstance.graphic.width / 2 + 10; // To the right
- self.y = spotGlobalPos.y - background.height / 2; // Centered vertically
- // Ensure menu is within screen bounds (simple clamp)
- if (self.x + background.width > SCREEN_WIDTH) {
- self.x = SCREEN_WIDTH - background.width - 10;
- }
- if (self.x < 10) {
- self.x = 10;
- }
- if (self.y + background.height > SCREEN_HEIGHT) {
- self.y = SCREEN_HEIGHT - background.height - 10;
- }
- if (self.y < 10) {
- self.y = 10;
- }
- // --- REVISED MENU POSITIONING LOGIC ---
- var spotGraphic = buildSpotInstance.graphic;
- var spotGlobalPos = buildSpotInstance.parent.toGlobal(buildSpotInstance.position);
- var menuWidth = background.width;
- var menuHeight = background.height;
- var spotRadius = spotGraphic ? spotGraphic.width / 2 : 75; // Approximate radius of the spot
- var margin = 10; // Margin from spot and screen edge
- // Try right of spot
- var potentialX = spotGlobalPos.x + spotRadius + margin;
- var potentialY = spotGlobalPos.y - menuHeight / 2; // Vertically center with spot
- if (potentialX + menuWidth > SCREEN_WIDTH - margin) {
- // Goes off right edge, try left of spot
- potentialX = spotGlobalPos.x - spotRadius - menuWidth - margin;
- }
- // Clamp X to be on screen
- if (potentialX < margin) {
- potentialX = margin;
- }
- if (potentialX + menuWidth > SCREEN_WIDTH - margin) {
- potentialX = SCREEN_WIDTH - menuWidth - margin;
- }
- // Clamp Y to be on screen
- if (potentialY < margin) {
- potentialY = margin;
- }
- if (potentialY + menuHeight > SCREEN_HEIGHT - margin) {
- potentialY = SCREEN_HEIGHT - menuHeight - margin;
- }
- self.x = potentialX;
- self.y = potentialY;
- return self;
-});
/****
* Initialize Game
****/
+// UI elements
var game = new LK.Game({
backgroundColor: 0x558855 // Darker green background
});
/****
* Game Code
****/
-// --- TOWER DEFINITIONS ---
-// Sound for red tape worm
-// Sound for bureaucracy blocker or paperwork
+// This isn't a formal class, but concepts to apply
+// --- Base Tower Functionality (Conceptual - can be mixed into specific towers) ---
+// Specific build sounds for each tower type if desired
+// --- New Sound Effects ---
+// Bureaucracy Blocker
+// Even more so
+// Slightly bigger/cooler
+// Stapler
+// --- Tower Level Assets (Placeholders) ---
+// You'll also need actual tower icons for the buttons eventually
+// For + symbols
+// --- UI Assets for Popup ---
+//LK.init.shape('debugTargetMarker', {width:25, height:25, color:0x00ff00, shape:'ellipse'})
+//LK.init.shape('debugMarker', {width:20, height:20, color:0xff0000, shape:'ellipse'})
+// For drawing lines if needed later
+// <-- NEW DEBUG ASSET
+// Added sound for auto-attack
+// Game constants
+// <-- NEW Green Target Marker
+// --- NEW ASSETS ---
// Placeholder path
// Placeholder path
-// --- NEW ASSETS ---
-// <-- NEW Green Target Marker
-// Game constants
-// Added sound for auto-attack
-// <-- NEW DEBUG ASSET
-// For drawing lines if needed later
-//LK.init.shape('debugMarker', {width:20, height:20, color:0xff0000, shape:'ellipse'})
-//LK.init.shape('debugTargetMarker', {width:25, height:25, color:0x00ff00, shape:'ellipse'})
-// --- UI Assets for Popup ---
-// For + symbols
-// You'll also need actual tower icons for the buttons eventually
-// --- Tower Level Assets (Placeholders) ---
-// Stapler
-// Slightly bigger/cooler
-// Even more so
-// Bureaucracy Blocker
-// --- New Sound Effects ---
-// Specific build sounds for each tower type if desired
-// --- Base Tower Functionality (Conceptual - can be mixed into specific towers) ---
-// This isn't a formal class, but concepts to apply
-function initializeTowerFromData(towerInstance, towerTypeKey, levelIndex) {
- var towerTypeData = TOWER_DATA[towerTypeKey];
- if (!towerTypeData) {
- console.error("Invalid tower type:", towerTypeKey);
- return;
- }
- var levelData = towerTypeData.levels[levelIndex];
- if (!levelData) {
- console.error("Invalid level for tower:", towerTypeKey, levelIndex);
- return;
- }
- towerInstance.towerType = towerTypeKey;
- towerInstance.currentLevel = levelIndex;
- towerInstance.graphic.setAsset(levelData.asset); // Change visual asset
- // Apply stats
- if (levelData.damage !== undefined) {
- towerInstance.damage = levelData.damage;
- }
- if (levelData.range !== undefined) {
- towerInstance.range = levelData.range;
- }
- if (levelData.fireRate !== undefined) {
- towerInstance.fireRate = levelData.fireRate;
- }
- if (levelData.slowFactor !== undefined) {
- towerInstance.slowFactor = levelData.slowFactor;
- }
- // Add any other stats specific to towers
-}
+// Sound for bureaucracy blocker or paperwork
+// Sound for red tape worm
var TOWER_DATA = {
'stapler': {
name: 'Stapler Turret',
iconAsset: 'towerStaplerLvl1',
// Use Lvl1 icon for selection button initially
buildSfx: 'buildStapler',
+ // You'll need to define this sound
levels: [{
asset: 'towerStaplerLvl1',
cost: 50,
damage: 1,
@@ -819,8 +770,9 @@
'blocker': {
name: 'Bureaucracy Blocker',
iconAsset: 'towerBlockerLvl1',
buildSfx: 'buildBlocker',
+ // You'll need to define this sound
levels: [{
asset: 'towerBlockerLvl1',
cost: 75,
slowFactor: 0.5,
@@ -831,20 +783,100 @@
cost: 100,
slowFactor: 0.4,
range: 275,
description: "Wider, stronger slow."
- },
- // 0.4 means 60% speed reduction
- {
+ }, {
asset: 'towerBlockerLvl3',
cost: 150,
slowFactor: 0.3,
range: 300,
description: "Bureaucratic Gridlock!"
}]
}
- // Add more tower types here later
};
+// --- Base Tower Functionality Helper --- // <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< AND initializeTowerFromData
+function initializeTowerFromData(towerInstance, towerTypeKey, levelIndex) {
+ var towerTypeData = TOWER_DATA[towerTypeKey]; // Now TOWER_DATA will exist
+ if (!towerTypeData) {
+ // console.error("Invalid tower type:", towerTypeKey); // LK Engine doesn't show console
+ // You could use a visual error like a Text2 message if this happens
+ var errorText = new Text2("Error: No data for " + towerTypeKey, {
+ fill: 0xFF0000,
+ size: 20
+ });
+ LK.gui.top.addChild(errorText);
+ LK.setTimeout(function () {
+ errorText.destroy();
+ }, 3000);
+ return;
+ }
+ var levelData = towerTypeData.levels[levelIndex];
+ if (!levelData) {
+ // console.error("Invalid level for tower:", towerTypeKey, levelIndex);
+ var errorTextLvl = new Text2("Error: No lvl " + levelIndex + " for " + towerTypeKey, {
+ fill: 0xFF0000,
+ size: 20
+ });
+ LK.gui.top.addChild(errorTextLvl);
+ LK.setTimeout(function () {
+ errorTextLvl.destroy();
+ }, 3000);
+ return;
+ }
+ towerInstance.towerType = towerTypeKey;
+ towerInstance.currentLevel = levelIndex;
+ if (towerInstance.graphic) {
+ towerInstance.graphic.setAsset(levelData.asset);
+ } else {
+ // This should ideally not happen if graphic is pre-attached
+ towerInstance.graphic = towerInstance.attachAsset(levelData.asset, {
+ anchorX: 0.5,
+ anchorY: 0.5
+ });
+ }
+ // Apply stats
+ if (levelData.damage !== undefined) {
+ towerInstance.damage = levelData.damage;
+ }
+ if (levelData.range !== undefined) {
+ towerInstance.range = levelData.range;
+ }
+ if (levelData.fireRate !== undefined) {
+ towerInstance.fireRate = levelData.fireRate;
+ }
+ if (levelData.slowFactor !== undefined) {
+ towerInstance.slowFactor = levelData.slowFactor;
+ }
+}
+var ICON_SELECT_OFFSETS = [{
+ x: -(150 / 2 + 80 / 2 + 10),
+ y: 0,
+ towerKey: 'stapler'
+},
+// Left
+{
+ x: 0,
+ y: -(150 / 2 + 80 / 2 + 10),
+ towerKey: 'blocker'
+},
+// Above
+{
+ x: 150 / 2 + 80 / 2 + 10,
+ y: 0,
+ towerKey: 'placeholder'
+} // Right
+];
+// Ensure 'placeholder' exists in TOWER_DATA with at least a cost and iconAsset
+if (TOWER_DATA && !TOWER_DATA['placeholder']) {
+ TOWER_DATA['placeholder'] = {
+ name: "???",
+ iconAsset: 'iconPlaceholder',
+ levels: [{
+ cost: 9999,
+ asset: 'iconPlaceholder'
+ }]
+ };
+}
var TOWER_COST = 50;
var WAVE_DELAY = 300; // frames between waves
var MAX_WAVES = 10;
// Access screen dimensions directly from LK
@@ -855,8 +887,9 @@
// Game variables
//var clickMarker; // <-- NEW: Reference to the visual click marker
//var targetMarker; // <-- NEW: Reference to the green target marker
var activeTowerMenu = null; // To ensure only one menu is open
+var currentActiveBuildSpot = null; // Track which spot's icons are open
var lastWorldClickX = 0; // <-- NEW: Store last click world X
var lastWorldClickY = 0; // <-- NEW: Store last click world Y
var currency = 100;
var playerLives = 5;
@@ -1055,8 +1088,9 @@
goal.x = pathPoints[pathPoints.length - 1].x;
goal.y = pathPoints[pathPoints.length - 1].y;
game.addChild(goal);
}
+ currentActiveBuildSpot = null;
// Doge
doge = new DogeHero();
doge.x = SCREEN_WIDTH / 2;
doge.y = SCREEN_HEIGHT / 2;
@@ -1151,54 +1185,66 @@
}
// Event Handlers /**** MODIFIED ****/
var dragDoge = false;
game.down = function (x, y, obj) {
- // --- Use Raw x, y as World Coordinates ---
var worldX = x;
- var worldY = y;
- // --- Close active tower menu if clicking outside of it ---
- var clickedOnMenu = false;
- if (activeTowerMenu) {
- // Check if the click (x,y are SCREEN coordinates for UI) was on the menu
- // This requires menu to know its screen bounds or for obj to be part of the menu
- // Simpler: if obj is not one of the menu's interactive children or the menu itself
- if (obj && obj.parent) {
- var currentParent = obj.parent;
- while (currentParent) {
- if (currentParent === activeTowerMenu) {
- clickedOnMenu = true;
- break;
- }
- currentParent = currentParent.parent;
+ var worldY = y; // Using raw world-space coords from event
+ var clickedOnAnInteractiveIcon = false;
+ // Check if the click was on an active selection icon
+ if (currentActiveBuildSpot && currentActiveBuildSpot.selectionIconContainer) {
+ var iconsInMenu = currentActiveBuildSpot.selectionIconContainer.children;
+ for (var i = 0; i < iconsInMenu.length; i++) {
+ var iconButtonContainer = iconsInMenu[i];
+ // obj from game.down is the *topmost* interactive element.
+ // If it's the iconGraphic inside iconButtonContainer, its .down will fire.
+ var iconGraphic = iconButtonContainer.getChildAt(0); // Assuming graphic is first child
+ if (obj === iconGraphic) {
+ clickedOnAnInteractiveIcon = true;
+ // The icon's own .down handler will manage building and closing.
+ // We might still want to hide icons if the build fails (e.g. re-check cost)
+ // but for now, let the icon handler close on success.
+ break;
}
}
- if (obj === activeTowerMenu) {
- clickedOnMenu = true;
- } // Clicked menu background
- if (!clickedOnMenu) {
- activeTowerMenu.close();
+ }
+ // If the click was on an icon, let its handler do the work.
+ if (clickedOnAnInteractiveIcon) {
+ return;
+ }
+ // If click was not on an icon, check if it was on a BuildSpot graphic itself
+ // to open/close its menu.
+ var clickedOnBuildSpotGraphic = false;
+ if (level && level.buildSpots) {
+ for (var i = 0; i < level.buildSpots.length; i++) {
+ var spot = level.buildSpots[i];
+ if (obj === spot.graphic) {
+ // LK engine gives us the target 'obj'
+ clickedOnBuildSpotGraphic = true;
+ // The spot.down() handler will be called by the engine.
+ break;
+ }
}
}
- // If menu was closed by this click, don't process other actions immediately
- if (clickedOnMenu) {
+ // If the click was on a build spot graphic, its .down handler will manage the menu.
+ if (clickedOnBuildSpotGraphic) {
return;
}
- var worldPos = {
- x: worldX,
- y: worldY
- };
- // --- Optional Debug Marker Update ---
- // lastWorldClickX = worldPos.x; lastWorldClickY = worldPos.y;
- // if (clickMarker) { clickMarker.x = worldPos.x; clickMarker.y = worldPos.y; clickMarker.visible = true; LK.setTimeout(function() { if (clickMarker) clickMarker.visible = false; }, 500); }
+ // If we've reached here, the click was not on an active icon and not on a buildspot graphic.
+ // So, if a menu is open, close it.
+ if (currentActiveBuildSpot) {
+ currentActiveBuildSpot.hideSelectionIcons();
+ // currentActiveBuildSpot will be set to null inside hideSelectionIcons
+ }
+ // --- Doge Movement Logic (if click wasn't handled by UI/icons/spots) ---
var targetObjectClicked = obj;
var clickedOnDoge = false;
var clickedOnBuildSpot = null;
// 1. Check for clicking Doge (using worldPos)
if (doge && targetObjectClicked === doge.graphic) {
clickedOnDoge = true;
} else if (doge) {
- var dx = worldPos.x - doge.x;
- var dy = worldPos.y - doge.y;
+ var dx = worldX - doge.x;
+ var dy = worldY - doge.y;
if (Math.sqrt(dx * dx + dy * dy) < doge.width / 2) {
clickedOnDoge = true;
}
}
@@ -1206,10 +1252,10 @@
if (!clickedOnDoge && level && level.buildSpots) {
for (var i = 0; i < level.buildSpots.length; i++) {
var spot = level.buildSpots[i];
if (spot && !spot.hasTower) {
- var spotDx = worldPos.x - spot.x;
- var spotDy = worldPos.y - spot.y;
+ var spotDx = worldX - spot.x;
+ var spotDy = worldY - spot.y;
var spotRadius = spot.graphic ? spot.graphic.width / 2 : 75;
if (Math.sqrt(spotDx * spotDx + spotDy * spotDy) < spotRadius) {
clickedOnBuildSpot = spot;
// Optional highlight
Stapler Turret Sprite Sheet: An office stapler mounted on a simple rotating base images show it opening and closing.. In-Game asset. 2d. High contrast. No shadows
Stapler bullet. In-Game asset. 2d. High contrast. No shadows
Remove the background
A stylized golden fire hydrant labeled "Free Speech" OR a glowing server rack labeled "Meme Archive".. In-Game asset. 2d. High contrast. No shadows
Paperclip. In-Game asset. 2d. High contrast. No shadows
A simple, slightly glowing circular outline indicating where towers can be placed.. In-Game asset. 2d. High contrast. No shadows
More cabinet, More Files
black circle. In-Game asset. 2d. High contrast. No shadows
DOGE Enemy Auditor. In-Game asset. 2d. High contrast. No shadows
grow the image and have papers fall from the folders
Squish the image like the cabinet is squeezing in on itself
Red Tape enemy extends as if bouncing while moving
Envelope flying through the air with wings. In-Game asset. 2d. High contrast. No shadows
"Laser Cat Perch": A cat with laser eyes that "targets" and zaps high-priority enemies with precision. (Internet loves cats).. In-Game asset. 2d. High contrast. No shadows
"Rickroller": A RickAstley tower holding a mic. In-Game asset. 2d. High contrast. No shadows
"'This Is Fine' Fire Pit": A tower resembling the "This is Fine" dog meme.. In-Game asset. 2d. High contrast. No shadows
Sell icon with a money symbol. In-Game asset. 2d. High contrast. No shadows
DOGE Coin. In-Game asset. 2d. High contrast. No shadows
Realistic MEME of Rick Astley dancing with mic. In-Game asset. 2d. High contrast. No shadows
Range Circle. In-Game asset. 2d. High contrast. No shadows
Shape: A tall, sleek, perhaps slightly intimidating rectangular or obelisk-like structure. Think modern skyscraper aesthetics scaled down. Material/Color: Polished chrome, brushed aluminum, dark grey, or a very clean white. Minimalist. Details: Maybe a single, subtly glowing slit or a small, focused lens near the top where the "restructuring energy" will eventually be directed from (though the actual effect happens on the target). Very clean lines, sharp edges. A small, almost unnoticeable corporate logo (maybe a stylized "R" or an abstract "efficiency" symbol). No visible moving parts when idle. It's about quiet, decisive power. Meme Angle: Evokes the feeling of an unapproachable, all-powerful corporate entity or a consultant's "black box" solution.. In-Game asset. 2d. High contrast. No shadows
Beam of disintegration. In-Game asset. 2d. High contrast. No shadows
Intern holding a coffee cup running 3 frames. In-Game asset. 2d. High contrast. No shadows