User prompt
Currently, StaplerTower calls initializeTowerFromData in its constructor. BureaucracyBlockerTower has an init method that calls it, but this init method isn't explicitly called when a new BureaucracyBlockerTower is created in buildSelectedTower. Suggestion: Let's standardize this. In both StaplerTower and BureaucracyBlockerTower constructors: Remove the lines that directly do self.graphic = self.attachAsset(...). Call initializeTowerFromData(self, 'towerTypeKey', 0); at the end of the constructor (e.g., initializeTowerFromData(self, 'stapler', 0); for StaplerTower). Modify initializeTowerFromData to reliably handle creating the graphic if it doesn't exist, or updating it if it does. // Inside initializeTowerFromData function: // ... (towerTypeData, levelData checks) ... towerInstance.towerType = towerTypeKey; towerInstance.currentLevel = levelIndex; // Graphic handling: if (towerInstance.graphic && towerInstance.graphic.parent) { towerInstance.graphic.setAsset(levelData.asset); } else { if (towerInstance.graphic) { towerInstance.graphic.destroy(); // Clean up old if it existed but was detached } towerInstance.graphic = towerInstance.attachAsset(levelData.asset, { anchorX: 0.5, anchorY: 0.5 }); } // ... (set stats) ... // Tower-specific re-initialization logic if (towerTypeKey === 'stapler' && towerInstance.hasOwnProperty('lastFired')) { towerInstance.lastFired = 0; // Reset fire cooldown on init/upgrade } if (towerTypeKey === 'blocker' && typeof towerInstance.clearAllSlows === 'function') { towerInstance.clearAllSlows(); // Ensure slows are reset based on new stats } Remove the separate self.init function from BureaucracyBlockerTower as its job will be done by the constructor calling initializeTowerFromData. In BuildSpot.buildSelectedTower, you would no longer need a special newTower.init(0) call for the blocker.
User prompt
Ava, please copy the update and takeDamage methods from the Enemy class into the RedTapeWorm class, ensuring RedTapeWorm instances can move along the path and react to damage.
User prompt
Please fix the bug: 'Uncaught ReferenceError: initializeTowerFromData is not defined' in or related to this line: 'initializeTowerFromData(self, 'stapler', 0); // Set initial stats & correct L0 asset' Line Number: 690
User prompt
Duplicate Definitions: You have TOWER_DATA defined twice. The second definition (with detailed levels, costs, assets, etc.) is the one that seems to be in use and is more complete. The first one should be removed. Similarly, the function initializeTowerFromData is defined twice. The first, shorter one should be removed, and we should ensure the more complete second one is used everywhere. The game.up function is defined twice with identical code. One of them can be removed.
Code edit (7 edits merged)
Please save this source code
User prompt
Stop dog movement when clicking on buildspot
User prompt
Please fix the bug: 'Uncaught ReferenceError: clickedOnDoge is not defined' in or related to this line: 'if (!clickedOnDoge) if (dragDoge && doge) {' Line Number: 1245
Code edit (5 edits merged)
Please save this source code
User prompt
Please fix the bug: 'Uncaught TypeError: Cannot read properties of undefined (reading 'stopPropagation')' in or related to this line: 'obj.event.stopPropagation();' Line Number: 250
User prompt
Doge shouldn't move towards build spots when they are clicked.
User prompt
Please fix the bug: 'ReferenceError: globalPos is not defined' in or related to this line: 'var dx = enemy.x - globalPos.x; // Use global tower X' Line Number: 660
Code edit (1 edits merged)
Please save this source code
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
/**** * 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; self.duration = 30; // frames self.radius = 100; self.maxRadius = 350; self.enemiesHit = []; // Initialize here self.update = function () { self.radius += 15; graphic.scaleX = self.radius / 100; graphic.scaleY = self.radius / 100; graphic.alpha -= 0.017; for (var i = 0; i < enemies.length; i++) { var enemy = enemies[i]; if (self.enemiesHit.indexOf(enemy) === -1) { // Only hit once 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); } } } self.duration--; if (self.duration <= 0 || self.radius >= self.maxRadius) { self.destroy(); } }; return self; }); 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.down = function () { if (activeTowerMenu) { activeTowerMenu.close(); if (activeTowerMenu === self.myMenu) { self.myMenu = null; return; } } LK.getSound('uiOpenMenu').play(); self.myMenu = new TowerSelectionMenu(self); LK.gui.top.addChild(self.myMenu); activeTowerMenu = self.myMenu; }; return self; }); var BureaucracyBlockerTower = Container.expand(function () { var self = Container.call(this); self.graphic = self.attachAsset('towerBlockerLvl1', { // Placeholder, init will set actual anchorX: 0.5, anchorY: 0.5 }); self.range = 250; // Default, will be overwritten by init self.slowFactor = 0.5; // Default 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 () { var globalPos = self.parent ? self.parent.toGlobal(self.position) : self.position; var newlySlowedThisFrame = []; for (var i = 0; i < enemies.length; i++) { var enemy = enemies[i]; if (!enemy || !enemy.parent) continue; var dx = enemy.x - globalPos.x; var dy = enemy.y - globalPos.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); } } for (var i = self.enemiesSlowed.length - 1; i >= 0; i--) { var previouslySlowedEnemy = self.enemiesSlowed[i]; if (!previouslySlowedEnemy || !previouslySlowedEnemy.parent || newlySlowedThisFrame.indexOf(previouslySlowedEnemy) === -1) { if (previouslySlowedEnemy && previouslySlowedEnemy.isSlowedByBlocker === self) { previouslySlowedEnemy.speed = previouslySlowedEnemy.originalSpeed; previouslySlowedEnemy.isSlowedByBlocker = null; if (previouslySlowedEnemy.graphic) previouslySlowedEnemy.graphic.tint = 0xFFFFFF; } self.enemiesSlowed.splice(i, 1); } } self.enemiesSlowed = newlySlowedThisFrame; }; var originalDestroy = self.destroy; self.destroy = function () { self.clearAllSlows(); // Call the specific clear function if (originalDestroy) { originalDestroy.call(self); } else if (self.parent) { self.parent.removeChild(self); } }; return self; }); var DogeHero = Container.expand(function () { var self = Container.call(this); self.graphic = self.attachAsset('dogeHero', { anchorX: 0.5, anchorY: 0.5 }); self.width = self.graphic.width; self.height = self.graphic.height; self.speed = 5; self.targetX = self.x; self.targetY = self.y; self.autoAttackRange = 180; self.autoAttackDamage = 2; self.autoAttackCooldownTime = 45; self.currentAutoAttackCooldown = 0; self.manualBarkCooldownTime = 300; self.currentManualBarkCooldown = 0; self.update = function () { var dx_move = self.targetX - self.x; var dy_move = self.targetY - self.y; var distance_move = Math.sqrt(dx_move * dx_move + dy_move * dy_move); if (distance_move > self.speed) { self.x += dx_move / distance_move * self.speed; self.y += dy_move / distance_move * self.speed; } else if (distance_move > 0) { self.x = self.targetX; self.y = self.targetY; } if (self.currentAutoAttackCooldown > 0) self.currentAutoAttackCooldown--; if (self.currentManualBarkCooldown > 0) self.currentManualBarkCooldown--; 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); // Add to main game container LK.getSound('dogeBark').play(); self.currentManualBarkCooldown = self.manualBarkCooldownTime; return true; } return false; }; return self; }); // Generic Enemy Class - configured by ENEMY_DATA var Enemy = Container.expand(function (enemyTypeKey, currentWaveNum) { var self = Container.call(this); var data = ENEMY_DATA[enemyTypeKey]; if (!data) { console.error("Enemy data not found for type:", enemyTypeKey); // Fallback to a default paper enemy visual if data is missing self.graphic = self.attachAsset(ENEMY_DATA['paper'].asset, { anchorX: 0.5, anchorY: 0.5 }); self.health = 1; self.speed = 1; self.value = 1; } else { self.graphic = self.attachAsset(data.asset, { anchorX: 0.5, anchorY: 0.5 }); self.health = data.health + (data.healthScalePerWave || 0) * currentWaveNum; self.speed = data.speed; self.value = data.value; } self.enemyType = enemyTypeKey; // Store type self.currentPathIndex = 0; self.originalSpeed = self.speed; // For slow effects self.isSlowedByBlocker = null; // Track who slowed this enemy self.update = function () { if (!pathPoints || pathPoints.length === 0) return; if (self.currentPathIndex < pathPoints.length) { var target = pathPoints[self.currentPathIndex]; if (!target) { self.currentPathIndex++; return; } var dx = target.x - self.x; var dy = target.y - self.y; var distance = Math.sqrt(dx * dx + dy * dy); if (distance < self.speed * 1.5) { // Path progression threshold self.currentPathIndex++; if (self.currentPathIndex >= pathPoints.length) { playerLives--; livesText.setText("Lives: " + playerLives); LK.getSound('enemyReachGoal').play(); if (playerLives <= 0) { if (LK.getScore() > storage.highScore) storage.highScore = LK.getScore(); LK.showGameOver(); } self.destroy(); return; } } else { dx = dx / distance * self.speed; dy = dy / distance * self.speed; self.x += dx; self.y += dy; } } else { console.log("Enemy past end of path, destroying.", self.enemyType); self.destroy(); } }; self.takeDamage = function (amount) { self.health -= amount; LK.effects.flashObject(self, 0xff0000, 200); if (self.health <= 0 && self.parent) { 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; }); // RedTapeWorm - Now primarily data-driven, can be expanded with unique logic if needed var RedTapeWorm = Enemy.expand(function (currentWaveNum) { var self = Enemy.call(this, 'redTapeWorm', currentWaveNum); // This properly calls the Enemy constructor, passing its specific type and the current wave // This reuses all logic from Enemy, but it's configured via 'redTapeWorm' in ENEMY_DATA // If RedTapeWorm has ANY unique behavior, add it here. // For example, overriding destroy (though the original was commented out): // var originalDestroy = self.destroy; // Keep a reference to base destroy from Enemy // self.destroy = function() { // spawnFloatingText("So Bureaucratic!", self.x, self.y - 50, { fill: 0xFF6666 }); // if (originalDestroy) originalDestroy.call(self); // Call the original destroy method from Enemy // else if (self.parent) self.parent.removeChild(self); // }; return self; }); var GameLevel = Container.expand(function () { var self = Container.call(this); var pathGraphics = []; var buildSpotGraphics = []; self.buildSpots = []; // Store logical spots self.createPath = function (pathData) { pathGraphics.forEach(function (tile) { return tile.destroy(); }); pathGraphics = []; for (var i = 0; i < pathData.length - 1; i++) { var start = pathData[i]; var end = pathData[i + 1]; if (!start || !end) continue; var dx = end.x - start.x; var dy = end.y - start.y; var distance = Math.sqrt(dx * dx + dy * dy); var steps = Math.ceil(distance / 90); 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, anchorX: 0.5, anchorY: 0.5 }); self.addChild(tile); pathGraphics.push(tile); } } }; self.createBuildSpots = function (spotsData) { buildSpotGraphics.forEach(function (spot) { return spot.destroy(); }); buildSpotGraphics = []; self.buildSpots = []; for (var i = 0; i < spotsData.length; i++) { if (!spotsData[i]) continue; var spot = new BuildSpot(); spot.x = spotsData[i].x; spot.y = spotsData[i].y; self.addChild(spot); self.buildSpots.push(spot); buildSpotGraphics.push(spot); } }; return self; }); var Goal = Container.expand(function () { var self = Container.call(this); self.attachAsset('goal', { anchorX: 0.5, anchorY: 0.5, alpha: 0.6 }); return self; }); var StaplerTower = Container.expand(function () { var self = Container.call(this); self.graphic = self.attachAsset('towerStaplerLvl1', { anchorX: 0.5, anchorY: 0.5, alpha: 1 }); // Full alpha for tower self.fireRate = 60; // Default self.range = 300; // Default self.damage = 1; // Default 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; var globalPos = self.parent.toGlobal(self.position); for (var i = 0; i < enemies.length; i++) { var enemy = enemies[i]; var dx = enemy.x - globalPos.x; var dy = enemy.y - globalPos.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(); var globalPos = self.parent.toGlobal(self.position); bullet.x = globalPos.x; bullet.y = globalPos.y; bullet.target = target; bullet.damage = self.damage; game.addChild(bullet); // Add to main game container bullets.push(bullet); LK.getSound('shoot').play(); }; return self; }); var TowerBullet = Container.expand(function () { var self = Container.call(this); self.attachAsset('bullet', { anchorX: 0.5, anchorY: 0.5 }); self.speed = 10; self.damage = 1; self.target = null; self.update = function () { 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); if (distance < self.speed) { self.target.takeDamage(self.damage); self.destroy(); return; } dx = dx / distance * self.speed; dy = dy / distance * self.speed; self.x += dx; self.y += dy; }; return self; }); var TowerSelectionMenu = Container.expand(function (buildSpotInstance) { var self = Container.call(this); self.buildSpot = buildSpotInstance; self.buttons = []; var backgroundContainer = new Container(); // Changed variable name for clarity var backgroundShape = backgroundContainer.attachAsset('uiButtonBackground', { width: 150, height: Object.keys(TOWER_DATA).length * 110 + 40, // Dynamic height + close button room alpha: 0.8, tint: 0x333333 }); backgroundContainer.pivot.set(0, 0); self.addChild(backgroundContainer); var yOffset = 10; for (var towerTypeKey in TOWER_DATA) { if (TOWER_DATA.hasOwnProperty(towerTypeKey)) { var towerTypeInfo = TOWER_DATA[towerTypeKey]; var currentTowerLevelOnSpot = -1; 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 += "+"; } else { buttonText = towerTypeInfo.name + " (MAX)"; cost = Infinity; } } var buttonContainer = new Container(); buttonContainer.x = 10; buttonContainer.y = yOffset; self.addChild(buttonContainer); // Use the specific icon as the interactive button graphic var towerButtonGraphic = buttonContainer.attachAsset(towerTypeInfo.iconAsset || 'uiButtonBackground', { anchorX: 0, anchorY: 0, width: 130, height: 100 }); towerButtonGraphic.interactive = true; towerButtonGraphic.towerTypeKey = towerTypeKey; towerButtonGraphic.nextLevelIndex = nextLevelIndex; towerButtonGraphic.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 = towerButtonGraphic.width / 2; text.y = towerButtonGraphic.height / 2; buttonContainer.addChild(text); if (currency < cost || cost === Infinity) { buttonContainer.alpha = 0.5; towerButtonGraphic.interactive = false; } else { buttonContainer.alpha = 1.0; } towerButtonGraphic.down = function () { if (currency >= this.cost) { currency -= this.cost; currencyText.setText("$: " + currency); if (self.buildSpot.tower && self.buildSpot.tower.towerType === this.towerTypeKey) { self.buildSpot.tower.init(this.nextLevelIndex); LK.getSound('towerUpgrade').play(); spawnFloatingText("UPGRADED!", self.buildSpot.x, self.buildSpot.y - 50, { fill: 0x00FF00 }); } else { if (self.buildSpot.tower) self.buildSpot.tower.destroy(); var newTower; if (this.towerTypeKey === 'stapler') newTower = new StaplerTower();else if (this.towerTypeKey === 'blocker') newTower = new BureaucracyBlockerTower(); // Add factory for other tower types here if (newTower) {...} 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; // Make buildspot faint LK.getSound(TOWER_DATA[this.towerTypeKey].buildSfx).play(); spawnFloatingText("BUILT!", self.buildSpot.x, self.buildSpot.y - 50, { fill: 0x00FF00 }); } } self.close(); } }; self.buttons.push(buttonContainer); yOffset += 110; } } var closeButton = new Text2("Close [X]", { size: 20, fill: 0xFF0000 }); closeButton.anchor.set(1, 0); // Anchor top-right of the button itself closeButton.x = backgroundShape.width - 5; // Position relative to background shape closeButton.y = 5; closeButton.interactive = true; closeButton.down = function () { self.close(); }; backgroundContainer.addChild(closeButton); // Add to background so it's part of menu block self.close = function () { if (activeTowerMenu === self) activeTowerMenu = null; if (self.parent) self.destroy(); // Check parent before destroying }; var spotGraphic = buildSpotInstance.graphic; // buildSpotInstance.graphic is the BuildSpot's own graphic var spotGlobalPos = buildSpotInstance.parent.toGlobal(buildSpotInstance.position); // buildSpotInstance is child of 'level' var menuWidth = backgroundShape.width; var menuHeight = backgroundShape.height; var spotRadius = spotGraphic ? spotGraphic.width / 2 : 75; var margin = 10; var potentialX = spotGlobalPos.x + spotRadius + margin; var potentialY = spotGlobalPos.y - menuHeight / 2; if (potentialX + menuWidth > SCREEN_WIDTH - margin) { potentialX = spotGlobalPos.x - spotRadius - menuWidth - margin; } potentialX = Math.max(margin, Math.min(potentialX, SCREEN_WIDTH - menuWidth - margin)); potentialY = Math.max(margin, Math.min(potentialY, SCREEN_HEIGHT - menuHeight - margin)); self.x = potentialX; self.y = potentialY; return self; }); /**** * Initialize Game ****/ var game = new LK.Game({ backgroundColor: 0x558855 }); /**** * Game Code ****/ // Assuming you have these sounds from TOWER_DATA // Assuming you have these sounds from TOWER_DATA // Note: same ID as stapler, towerStaplerLvl1 // Note: same ID as towerBlockerLvl1 // --- TOWER DEFINITIONS --- function _typeof(o) { "@babel/helpers - typeof"; return _typeof = "function" == typeof Symbol && "symbol" == typeof Symbol.iterator ? function (o) { return typeof o; } : function (o) { return o && "function" == typeof Symbol && o.constructor === Symbol && o !== Symbol.prototype ? "symbol" : typeof o; }, _typeof(o); } function ownKeys(e, r) { var t = Object.keys(e); if (Object.getOwnPropertySymbols) { var o = Object.getOwnPropertySymbols(e); r && (o = o.filter(function (r) { return Object.getOwnPropertyDescriptor(e, r).enumerable; })), t.push.apply(t, o); } return t; } function _objectSpread(e) { for (var r = 1; r < arguments.length; r++) { var t = null != arguments[r] ? arguments[r] : {}; r % 2 ? ownKeys(Object(t), !0).forEach(function (r) { _defineProperty(e, r, t[r]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(e, Object.getOwnPropertyDescriptors(t)) : ownKeys(Object(t)).forEach(function (r) { Object.defineProperty(e, r, Object.getOwnPropertyDescriptor(t, r)); }); } return e; } function _defineProperty(e, r, t) { return (r = _toPropertyKey(r)) in e ? Object.defineProperty(e, r, { value: t, enumerable: !0, configurable: !0, writable: !0 }) : e[r] = t, e; } function _toPropertyKey(t) { var i = _toPrimitive(t, "string"); return "symbol" == _typeof(i) ? i : i + ""; } function _toPrimitive(t, r) { if ("object" != _typeof(t) || !t) return t; var e = t[Symbol.toPrimitive]; if (void 0 !== e) { var i = e.call(t, r || "default"); if ("object" != _typeof(i)) return i; throw new TypeError("@@toPrimitive must return a primitive value."); } return ("string" === r ? String : Number)(t); } 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; if (towerInstance.graphic && typeof towerInstance.graphic.setAsset === 'function') { // Check if graphic exists and has setAsset towerInstance.graphic.setAsset(levelData.asset); } else { // Fallback if graphic or setAsset is missing (e.g. if tower didn't attach one yet) if (towerInstance.graphic) towerInstance.graphic.destroy(); // remove old if any towerInstance.graphic = towerInstance.attachAsset(levelData.asset, { anchorX: 0.5, anchorY: 0.5, alpha: 1 }); } 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 TOWER_DATA = { 'stapler': { name: 'Stapler Turret', iconAsset: 'towerStaplerLvl1', buildSfx: 'buildStapler', 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', 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!" }] } }; // --- ENEMY DEFINITIONS --- var ENEMY_DATA = { 'paper': { name: 'Paper Shredder Fodder', asset: 'enemyPaper', health: 3, speed: 2, value: 10, healthScalePerWave: 0.5 // Health increases by this amount * wave number }, 'redTapeWorm': { name: 'Red Tape Worm', asset: 'enemyRedTapeWorm', health: 20, speed: 0.75, value: 25, healthScalePerWave: 2 // Worms get significantly tougher each wave } }; // --- WAVE DEFINITIONS --- var WAVE_DELAY_BETWEEN_WAVES = 300; // Default frames between waves IF not specified in definition var WAVE_DEFINITIONS = [ // Wave 1 (index 0 internally, displayed as 1) { delayBeforeWaveStart: 0, // Delay before this wave starts (after previous ends) groups: [{ type: 'paper', count: 5, spawnIntervalFrames: 60, initialGroupDelayFrames: 0 }] }, // Wave 2 { delayBeforeWaveStart: WAVE_DELAY_BETWEEN_WAVES, groups: [{ type: 'paper', count: 8, spawnIntervalFrames: 50, initialGroupDelayFrames: 0 }] }, // Wave 3 { delayBeforeWaveStart: WAVE_DELAY_BETWEEN_WAVES, groups: [{ type: 'paper', count: 10, spawnIntervalFrames: 45, initialGroupDelayFrames: 0 }, { type: 'redTapeWorm', count: 1, spawnIntervalFrames: 0, initialGroupDelayFrames: 300 } // Worm appears after paper batch ] }, // Wave 4 { delayBeforeWaveStart: WAVE_DELAY_BETWEEN_WAVES, groups: [{ type: 'paper', count: 15, spawnIntervalFrames: 40, initialGroupDelayFrames: 0 }, { type: 'redTapeWorm', count: 2, spawnIntervalFrames: 120, initialGroupDelayFrames: 100 }] }, // Wave 5 - More challenging { delayBeforeWaveStart: WAVE_DELAY_BETWEEN_WAVES, groups: [{ type: 'paper', count: 10, spawnIntervalFrames: 30, initialGroupDelayFrames: 0 }, { type: 'redTapeWorm', count: 3, spawnIntervalFrames: 180, initialGroupDelayFrames: 200 }, { type: 'paper', count: 10, spawnIntervalFrames: 30, initialGroupDelayFrames: 900 } // Second batch of paper ] } // Add more wave definitions here ]; var MAX_WAVES = WAVE_DEFINITIONS.length; // Max waves is now based on definitions // --- LEVEL DEFINITIONS --- var LEVEL_DATA = { 'level1': { pathPoints: [{ x: 0.1, y_abs: 100 }, { x: 0.1, y_abs: 500 }, { x: 0.4, y_abs: 500 }, { x: 0.4, y_abs: 1000 }, { x: 0.7, y_abs: 1000 }, { x: 0.7, y_abs: 1500 }, { x: 0.3, y_abs: 1500 }, { x: 0.3, y_abs: 2000 }, { x: 0.8, y_abs: 2000 }, { x: 0.8, y_abs: 2800 }].map(function (p) { return { x: p.x * SCREEN_WIDTH, y: p.y_abs }; }), // Convert relative X to absolute buildSpots: [{ x: 0.25, y_abs: 300 }, { x: 0.25, y_abs: 750 }, { x: 0.55, y_abs: 750 }, { x: 0.55, y_abs: 1250 }, { x: 0.85, y_abs: 1250 }, { x: 0.50, y_abs: 1750 }, { x: 0.15, y_abs: 1750 }, { x: 0.50, y_abs: 2400 }, { x: 0.90, y_abs: 2400 }].map(function (p) { return { x: p.x * SCREEN_WIDTH, y: p.y_abs }; }), // Convert relative X to absolute mapHeight: 3000, initialCurrency: 150, // Increased starting currency a bit playerLives: 10, // Increased lives a bit music: 'bgmusic' } // 'level2': { ... } }; var currentLevelKey = 'level1'; // The level to play // Game constants from LK or globals var SCREEN_HEIGHT = 2732; var SCREEN_WIDTH = 2048; var PAN_THRESHOLD = SCREEN_HEIGHT * 0.25; var MAP_HEIGHT; // Will be set by level data // Game variables var activeTowerMenu = null; var currency; var playerLives; var currentWaveInternal = 0; // 0-indexed for arrays var waveTimer = 0; var isWaveActive = false; var enemies = []; var bullets = []; var pathPoints = []; // Will be populated by level data var level; // GameLevel instance var doge; var goal; var gameRunning = true; // To stop updates on game over/win // --- HELPER FUNCTION FOR FLOATING TEXT --- function spawnFloatingText(text, x, y, options) { var defaultOptions = { size: 30, fill: 0xFFFFFF, stroke: 0x000000, strokeThickness: 2, duration: 60, velocityY: -1.5, alphaFadeSpeed: 0.015 }; var settings = Object.assign({}, defaultOptions, options); var floatingText = new Text2(text, { size: settings.size, fill: settings.fill, stroke: settings.stroke, strokeThickness: settings.strokeThickness, anchorX: 0.5, anchorY: 0.5 }); floatingText.x = x; floatingText.y = y; floatingText.alpha = 1.0; game.addChild(floatingText); var framesLived = 0; floatingText.update = function () { // Needs to be manually called if not auto-updated by LK if (!gameRunning && this.parent) { // A way to stop floating texts after game ends this.destroy(); return; } floatingText.y += settings.velocityY; floatingText.alpha -= settings.alphaFadeSpeed; framesLived++; if (framesLived >= settings.duration || floatingText.alpha <= 0) { floatingText.destroy(); } }; // If LK doesn't auto-update children with .update, you'll need a list and manual update loop. // For now, assuming it's handled if it's a child of `game` and has `update`. } // UI Text Elements var scoreText = new Text2("Score: 0", { size: 50, fill: 0xFFFFFF }); 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); waveText.x = -100; LK.gui.topRight.addChild(waveText); var currencyText = new Text2("$: 0", { size: 50, fill: 0xFFFFFF }); currencyText.anchor.set(0.5, 0); currencyText.y = 60; LK.gui.top.addChild(currencyText); var livesText = new Text2("Lives: 0", { size: 50, fill: 0xFFFFFF }); livesText.anchor.set(1, 0); livesText.x = -100; livesText.y = 60; LK.gui.topRight.addChild(livesText); var barkButton = new Text2("BARK!", { size: 60, fill: 0xffcc00, stroke: 0x000000, strokeThickness: 4 }); barkButton.anchor.set(0.5, 1); barkButton.y = -50; barkButton.interactive = true; barkButton.down = function () { if (doge && doge.manualBark()) { barkButton.scale.set(1.1); LK.setTimeout(function () { return barkButton.scale.set(1.0); }, 100); } }; LK.gui.bottom.addChild(barkButton); // var debugText ... (Removed for brevity, can be re-added if needed) function initializeGame(levelKeyToLoad) { gameRunning = true; var currentLevelData = LEVEL_DATA[levelKeyToLoad]; if (!currentLevelData) { console.error("FATAL: Level data not found for key:", levelKeyToLoad); // Potentially show an error message to the player or load a default return; } game.y = 0; game.scale.set(1); // Clear previous level entities if any if (level && level.parent) level.destroy(); // Assuming destroy cleans up children enemies.forEach(function (e) { if (e.parent) e.destroy(); }); enemies = []; bullets.forEach(function (b) { if (b.parent) b.destroy(); }); bullets = []; if (doge && doge.parent) doge.destroy(); if (goal && goal.parent) goal.destroy(); // If activeTowerMenu is open, close it if (activeTowerMenu && activeTowerMenu.parent) { activeTowerMenu.close(); } activeTowerMenu = null; level = new GameLevel(); game.addChild(level); pathPoints = currentLevelData.pathPoints; level.createPath(pathPoints); level.createBuildSpots(currentLevelData.buildSpots); MAP_HEIGHT = currentLevelData.mapHeight; goal = new Goal(); if (pathPoints.length > 0) { goal.x = pathPoints[pathPoints.length - 1].x; goal.y = pathPoints[pathPoints.length - 1].y; game.addChild(goal); } doge = new DogeHero(); doge.x = SCREEN_WIDTH / 2; doge.y = SCREEN_HEIGHT / 2; // Start Doge in the middle of the initial view doge.targetX = doge.x; doge.targetY = doge.y; game.addChild(doge); currency = currentLevelData.initialCurrency; playerLives = currentLevelData.playerLives; currentWaveInternal = 0; waveTimer = 0; isWaveActive = false; // Update UI currencyText.setText("$: " + currency); livesText.setText("Lives: " + playerLives); waveText.setText("Wave: 0/" + MAX_WAVES); // Show 0 at start, spawnWave will update to 1 LK.setScore(0); // Reset score on game start/restart scoreText.setText("Score: " + LK.getScore()); barkButton.setText("BARK!", { size: 60, fill: 0xffcc00, stroke: 0x000000, strokeThickness: 4 }); if (currentLevelData.music) LK.playMusic(currentLevelData.music); } function spawnWave() { if (currentWaveInternal >= WAVE_DEFINITIONS.length) { // This condition should ideally be caught by checkWaveComplete if MAX_WAVES is correct console.log("Attempted to spawn wave beyond definitions."); return; } var waveData = WAVE_DEFINITIONS[currentWaveInternal]; waveText.setText("Wave: " + (currentWaveInternal + 1) + "/" + MAX_WAVES); isWaveActive = true; // Mark as active, will be set false by checkWaveComplete var enemiesThisWave = 0; // Counter for enemies that will be spawned in this wave var cumulativeDelayMs = 0; waveData.groups.forEach(function (group) { enemiesThisWave += group.count; var groupDelayMs = cumulativeDelayMs + (group.initialGroupDelayFrames || 0) * (1000 / 60); // Approx ms for (var i = 0; i < group.count; i++) { // IIFE to capture current values of type and delay for LK.setTimeout (function (enemyType, finalSpawnDelayMs) { LK.setTimeout(function () { if (!pathPoints || pathPoints.length === 0 || !gameRunning) return; // Safety check & game ended check var newEnemy; // Basic factory based on type, could be expanded if (enemyType === 'redTapeWorm') { newEnemy = new RedTapeWorm(currentWaveInternal); } else { // Default to generic Enemy, configured by its type newEnemy = new Enemy(enemyType, currentWaveInternal); } newEnemy.x = pathPoints[0].x; newEnemy.y = pathPoints[0].y; game.addChild(newEnemy); enemies.push(newEnemy); }, finalSpawnDelayMs); })(group.type, groupDelayMs); groupDelayMs += (group.spawnIntervalFrames || 60) * (1000 / 60); // Add interval for next enemy in THIS group } // The cumulativeDelayMs for the *start* of the next group should be based on the longest // duration of the current groups, or simply sequential. Here we assume sequential start of groups based on their initial delay. // If groups are meant to overlap, this logic would need to be more complex. // For simplicity now, let's assume initialGroupDelayFrames is relative to wave start or previous group end. // This example uses it as an absolute offset from wave start + previous groups if any. // This might mean very long delays if many groups. A better model might be that initialGroupDelayFrames is *always* from wave start. // Re-evaluating: let each group's initial delay be from wave start for clarity. // No, the `groupDelayMs` correctly handles offsets *within* the loop. The `cumulativeDelayMs` logic as is could lead to large offsets // if group spawn times are long. Let's refine `cumulativeDelayMs` to represent the longest time any prior spawn in *this specific wave* has taken. // Simpler: initialGroupDelayFrames are independent offsets from wave spawn. }); // After scheduling all spawns, currentWaveInternal can be incremented for the *next* wave. // waveTimer will be reset by checkWaveComplete when this wave is actually cleared. } function checkWaveComplete() { if (isWaveActive && enemies.length === 0) { // Check if all enemies for *this wave definition* have been spawned. // This is tricky with timeouts. For now, assume if isWaveActive is true and enemies is 0, the wave is done. // A more robust system might count spawned enemies vs expected. isWaveActive = false; waveTimer = 0; // Reset timer for next wave delay currentWaveInternal++; // Advance to next wave index *after* current is cleared if (currentWaveInternal >= MAX_WAVES) { gameRunning = false; if (LK.getScore() > storage.highScore) storage.highScore = LK.getScore(); LK.showYouWin(); } } } // Event Handlers var dragDoge = false; game.down = function (screenX, screenY, obj) { // screenX, screenY are usually raw from engine if (!gameRunning) return; // For LK, assuming x, y in game.down are ALREADY world coordinates if the game container is clicked // If LK provides game.globalToLocal or similar, that would be better for converting screen clicks. // Based on your original, it seems x,y from game.down ARE world coords if clicking game canvas. var worldX = screenX; var worldY = screenY; var clickedOnMenu = false; if (activeTowerMenu) { if (obj && obj.parent) { // Check if click was ON the menu itself or its children var currentParent = obj; while (currentParent) { if (currentParent === activeTowerMenu) { clickedOnMenu = true; break; } currentParent = currentParent.parent; } } if (!clickedOnMenu) { activeTowerMenu.close(); // Clicked outside // If menu was closed, decide if the click should do anything else. Generally not. return; } else { return; // Click was on the menu, menu handles it. } } // Check for Doge click var clickedOnDoge = false; if (doge) { // Check if Doge exists if (obj === doge.graphic) { // Direct click on Doge's graphic (if obj is the graphic) clickedOnDoge = true; } else { // Hit test based on position and size var dx = worldX - doge.x; var dy = worldY - doge.y; if (Math.sqrt(dx * dx + dy * dy) < (doge.width || 75) / 2) { // Use width or a fallback clickedOnDoge = true; } } } if (clickedOnDoge) { dragDoge = true; doge.setTarget(doge.x, doge.y); // Stop movement when picked up return; // Don't process other clicks if Doge was clicked } // Check for BuildSpot click (only if not clicking Doge or UI handled it) // This check should iterate through logical buildspots (children of `level`) if (level && level.buildSpots) { for (var i = 0; i < level.buildSpots.length; i++) { var spot = level.buildSpots[i]; // Check obj first, assuming 'obj' is the specific graphical element clicked. // This makes hit testing more accurate if buildspots have distinct graphics. if (obj === spot || spot.graphic && obj === spot.graphic) { spot.down(); // Call the BuildSpot's own handler return; // Click handled by buildspot } // Fallback geometric check if obj is not the spot's graphic itself but coords are inside var spotDx = worldX - (spot.parent.x + spot.x); // spot.x is relative to its parent (level) var spotDy = worldY - (spot.parent.y + spot.y); var spotRadius = spot.graphic ? spot.graphic.width / 2 : 75; if (Math.sqrt(spotDx * spotDx + spotDy * spotDy) < spotRadius) { spot.down(); return; } } } // If nothing specific was clicked (Doge, BuildSpot, Menu), set Doge's target if (doge) { dragDoge = false; // Not dragging if just setting target doge.setTarget(worldX, worldY); } }; game.move = function (x, y, obj) { if (!gameRunning) return; if (dragDoge && doge) { var worldX = x; var worldY = y; // Assuming these are world coords doge.x = worldX; doge.y = worldY; doge.setTarget(doge.x, doge.y); } }; game.up = function (x, y, obj) { // No need to check gameRunning for 'up' as it's releasing a prior action if (dragDoge) { dragDoge = false; // Optional: if Doge was dragged onto a build spot, perhaps open menu? // For now, just releases Doge. } }; // Main game loop game.update = function () { if (!gameRunning) { // If game over or won, stop main updates // However, we might still want some things like floating text to fade out // This can be handled in the floating text's own update. // Remove all enemies and bullets to be sure. enemies.forEach(function (e) { if (e.parent) e.destroy(); }); enemies = []; bullets.forEach(function (b) { if (b.parent) b.destroy(); }); bullets = []; return; } if (!isWaveActive && currentWaveInternal < MAX_WAVES) { var _WAVE_DEFINITIONS$cur, _WAVE_DEFINITIONS$cur2; waveTimer++; var nextWaveDelay = (_WAVE_DEFINITIONS$cur = (_WAVE_DEFINITIONS$cur2 = WAVE_DEFINITIONS[currentWaveInternal]) === null || _WAVE_DEFINITIONS$cur2 === void 0 ? void 0 : _WAVE_DEFINITIONS$cur2.delayBeforeWaveStart) !== null && _WAVE_DEFINITIONS$cur !== void 0 ? _WAVE_DEFINITIONS$cur : WAVE_DELAY_BETWEEN_WAVES; if (waveTimer >= nextWaveDelay) { waveTimer = 0; spawnWave(); } } for (var i = bullets.length - 1; i >= 0; i--) { if (!bullets[i].parent) bullets.splice(i, 1); } for (var i = enemies.length - 1; i >= 0; i--) { if (!enemies[i].parent) enemies.splice(i, 1); } if (doge && doge.parent) doge.update(); checkWaveComplete(); if (doge) { var barkButtonBaseStyle = { size: 60, stroke: 0x000000, strokeThickness: 4 }; if (doge.currentManualBarkCooldown > 0) { var secondsLeft = Math.ceil(doge.currentManualBarkCooldown / 60); barkButton.setText("WAIT (" + secondsLeft + ")", _objectSpread(_objectSpread({}, barkButtonBaseStyle), {}, { fill: 0x888888 })); } else { barkButton.setText("BARK!", _objectSpread(_objectSpread({}, barkButtonBaseStyle), {}, { fill: 0xffcc00 })); } } if (doge) { var dogeScreenY = doge.y + game.y; // doge.y is world, game.y is camera offset 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; if (MAP_HEIGHT > SCREEN_HEIGHT) minGameY = -(MAP_HEIGHT - SCREEN_HEIGHT);else minGameY = 0; // Don't scroll if map fits or is smaller than screen 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; } // LK's main loop should be calling .update() on all children that have it, including floating texts. }; // Override default game over / you win to set gameRunning flag var originalShowGameOver = LK.showGameOver; LK.showGameOver = function () { gameRunning = false; if (originalShowGameOver) originalShowGameOver.call(LK); }; var originalShowYouWin = LK.showYouWin; LK.showYouWin = function () { gameRunning = false; if (originalShowYouWin) originalShowYouWin.call(LK); }; // Initialize the game initializeGame(currentLevelKey);
===================================================================
--- original.js
+++ change.js
@@ -15,32 +15,28 @@
anchorX: 0.5,
anchorY: 0.5,
alpha: 0.5
});
- self.damage = 3; // Slightly increased damage for manual ability
+ self.damage = 3;
self.duration = 30; // frames
self.radius = 100;
- self.maxRadius = 350; // Slightly larger radius for manual ability
+ self.maxRadius = 350;
+ self.enemiesHit = []; // Initialize here
self.update = function () {
- self.radius += 15; // Expand slightly faster
+ self.radius += 15;
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) {
+ // Only hit once
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.enemiesHit.push(enemy);
}
}
}
self.duration--;
@@ -49,9 +45,8 @@
}
};
return self;
});
-// In BuildSpot class:
var BuildSpot = Container.expand(function () {
var self = Container.call(this);
self.graphic = self.attachAsset('buildSpot', {
anchorX: 0.5,
@@ -59,44 +54,37 @@
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
+ self.myMenu = null;
return;
}
}
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
+ self.myMenu = new TowerSelectionMenu(self);
+ LK.gui.top.addChild(self.myMenu);
activeTowerMenu = self.myMenu;
};
return self;
});
-// 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', {
+ // Placeholder, init will set actual
anchorX: 0.5,
anchorY: 0.5
});
- self.range = 250;
- self.slowFactor = 0.5;
+ self.range = 250; // Default, will be overwritten by init
+ self.slowFactor = 0.5; // Default
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,115 +99,80 @@
}
self.enemiesSlowed = [];
};
self.update = function () {
- var globalPos = self.parent ? self.parent.toGlobal(self.position) : self.position; // Get global position
+ var globalPos = self.parent ? self.parent.toGlobal(self.position) : self.position;
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
+ if (!enemy || !enemy.parent) continue;
var dx = enemy.x - globalPos.x;
var dy = enemy.y - globalPos.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
- if (enemy.graphic) {
- enemy.graphic.tint = 0xAAAAFF;
- } // Light blue tint
+ 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
+ if (previouslySlowedEnemy.graphic) previouslySlowedEnemy.graphic.tint = 0xFFFFFF;
}
self.enemiesSlowed.splice(i, 1);
}
}
- self.enemiesSlowed = newlySlowedThisFrame; // Update the list of currently slowed enemies
+ self.enemiesSlowed = newlySlowedThisFrame;
};
- // 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 = [];
+ self.clearAllSlows(); // Call the specific clear function
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.width = self.graphic.width;
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) {
+ var dx_move = self.targetX - self.x;
+ var dy_move = self.targetY - self.y;
+ var distance_move = Math.sqrt(dx_move * dx_move + dy_move * dy_move);
+ if (distance_move > self.speed) {
+ self.x += dx_move / distance_move * self.speed;
+ self.y += dy_move / distance_move * self.speed;
+ } else if (distance_move > 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) self.currentAutoAttackCooldown--;
+ if (self.currentManualBarkCooldown > 0) self.currentManualBarkCooldown--;
if (self.currentAutoAttackCooldown <= 0) {
var closestEnemy = null;
var minDistanceSq = self.autoAttackRange * self.autoAttackRange;
for (var i = 0; i < enemies.length; i++) {
@@ -247,81 +200,84 @@
if (self.currentManualBarkCooldown <= 0) {
var wave = new BarkWave();
wave.x = self.x;
wave.y = self.y;
- game.addChild(wave);
+ game.addChild(wave); // Add to main game container
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 () {
+// Generic Enemy Class - configured by ENEMY_DATA
+var Enemy = Container.expand(function (enemyTypeKey, currentWaveNum) {
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
+ var data = ENEMY_DATA[enemyTypeKey];
+ if (!data) {
+ console.error("Enemy data not found for type:", enemyTypeKey);
+ // Fallback to a default paper enemy visual if data is missing
+ self.graphic = self.attachAsset(ENEMY_DATA['paper'].asset, {
+ anchorX: 0.5,
+ anchorY: 0.5
+ });
+ self.health = 1;
+ self.speed = 1;
+ self.value = 1;
+ } else {
+ self.graphic = self.attachAsset(data.asset, {
+ anchorX: 0.5,
+ anchorY: 0.5
+ });
+ self.health = data.health + (data.healthScalePerWave || 0) * currentWaveNum;
+ self.speed = data.speed;
+ self.value = data.value;
+ }
+ self.enemyType = enemyTypeKey; // Store type
self.currentPathIndex = 0;
+ self.originalSpeed = self.speed; // For slow effects
+ self.isSlowedByBlocker = null; // Track who slowed this enemy
self.update = function () {
- // Check if pathPoints is loaded and valid
- if (!pathPoints || pathPoints.length === 0) {
- return;
- }
+ 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
+ // Path progression 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();
- }
+ 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.");
+ console.log("Enemy past end of path, destroying.", self.enemyType);
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());
@@ -330,225 +286,184 @@
}
};
return self;
});
-// GameLevel class remains the same
+// RedTapeWorm - Now primarily data-driven, can be expanded with unique logic if needed
+var RedTapeWorm = Enemy.expand(function (currentWaveNum) {
+ var self = Enemy.call(this, 'redTapeWorm', currentWaveNum);
+ // This properly calls the Enemy constructor, passing its specific type and the current wave
+ // This reuses all logic from Enemy, but it's configured via 'redTapeWorm' in ENEMY_DATA
+ // If RedTapeWorm has ANY unique behavior, add it here.
+ // For example, overriding destroy (though the original was commented out):
+ // var originalDestroy = self.destroy; // Keep a reference to base destroy from Enemy
+ // self.destroy = function() {
+ // spawnFloatingText("So Bureaucratic!", self.x, self.y - 50, { fill: 0xFF6666 });
+ // if (originalDestroy) originalDestroy.call(self); // Call the original destroy method from Enemy
+ // else if (self.parent) self.parent.removeChild(self);
+ // };
+ return self;
+});
var GameLevel = Container.expand(function () {
var self = Container.call(this);
- var pathGraphics = []; // Store path visuals
- var buildSpotGraphics = []; // Store build spot visuals
+ var pathGraphics = [];
+ var buildSpotGraphics = [];
+ self.buildSpots = []; // Store logical spots
self.createPath = function (pathData) {
- // Clear previous path graphics
pathGraphics.forEach(function (tile) {
- tile.destroy();
+ return 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
+ if (!start || !end) continue;
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
+ var steps = Math.ceil(distance / 90);
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
+ pathGraphics.push(tile);
}
}
};
self.createBuildSpots = function (spotsData) {
- // Clear previous build spots
buildSpotGraphics.forEach(function (spot) {
- spot.destroy();
+ return spot.destroy();
});
buildSpotGraphics = [];
- self.buildSpots = []; // Clear the logical array too
+ self.buildSpots = [];
for (var i = 0; i < spotsData.length; i++) {
- if (!spotsData[i]) {
- continue;
- } // Safety check
+ if (!spotsData[i]) continue;
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
+ self.buildSpots.push(spot);
+ buildSpotGraphics.push(spot);
}
};
return self;
});
-// Goal class remains the same
var Goal = Container.expand(function () {
var self = Container.call(this);
- var graphic = self.attachAsset('goal', {
+ 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
-// Modify StaplerTower
var StaplerTower = Container.expand(function () {
var self = Container.call(this);
self.graphic = self.attachAsset('towerStaplerLvl1', {
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;
+ alpha: 1
+ }); // Full alpha for tower
+ self.fireRate = 60; // Default
+ self.range = 300; // Default
+ self.damage = 1; // Default
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);
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 - globalPos.x;
+ var dy = enemy.y - globalPos.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;
bullet.target = target;
bullet.damage = self.damage;
- // Add bullet to the main game container, not the tower itself
- game.addChild(bullet);
+ game.addChild(bullet); // Add to main game container
bullets.push(bullet);
LK.getSound('shoot').play();
};
return self;
});
-// TowerBullet class remains the same
var TowerBullet = Container.expand(function () {
var self = Container.call(this);
- var graphic = self.attachAsset('bullet', {
+ 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;
});
-// 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.buildSpot = buildSpotInstance;
self.buttons = [];
- var background = new Container();
- var backgroundShape = background.attachAsset('uiButtonBackground', {
- // Simple background for the menu
+ var backgroundContainer = new Container(); // Changed variable name for clarity
+ var backgroundShape = backgroundContainer.attachAsset('uiButtonBackground', {
width: 150,
- // Adjust as needed
- height: Object.keys(TOWER_DATA).length * 110 + 20,
- // Dynamic height
+ height: Object.keys(TOWER_DATA).length * 110 + 40,
+ // Dynamic height + close button room
alpha: 0.8,
tint: 0x333333
});
- background.pivot.set(0, 0); // Anchor top-left for easier button layout
- self.addChild(background);
+ backgroundContainer.pivot.set(0, 0);
+ self.addChild(backgroundContainer);
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 currentTowerLevelOnSpot = -1;
var nextLevelIndex = 0;
var buttonText = towerTypeInfo.name;
var cost = towerTypeInfo.levels[0].cost;
if (self.buildSpot.tower && self.buildSpot.tower.towerType === towerTypeKey) {
@@ -556,169 +471,111 @@
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
+ for (var k = 0; k < nextLevelIndex; k++) buttonText += "+";
} else {
- buttonText = towerTypeInfo.name + " (MAX)"; // Max level
- cost = Infinity; // Can't upgrade further
+ buttonText = towerTypeInfo.name + " (MAX)";
+ cost = Infinity;
}
}
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
+ // Use the specific icon as the interactive button graphic
+ var towerButtonGraphic = buttonContainer.attachAsset(towerTypeInfo.iconAsset || 'uiButtonBackground', {
anchorX: 0,
anchorY: 0,
width: 130,
height: 100
});
- buttonBg.interactive = true;
- buttonBg.towerTypeKey = towerTypeKey; // Store for click handler
- buttonBg.nextLevelIndex = nextLevelIndex;
- buttonBg.cost = cost;
+ towerButtonGraphic.interactive = true;
+ towerButtonGraphic.towerTypeKey = towerTypeKey;
+ towerButtonGraphic.nextLevelIndex = nextLevelIndex;
+ towerButtonGraphic.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;
+ text.x = towerButtonGraphic.width / 2;
+ text.y = towerButtonGraphic.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
+ towerButtonGraphic.interactive = false;
} else {
buttonContainer.alpha = 1.0;
}
- buttonBg.down = function () {
+ towerButtonGraphic.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
+ self.buildSpot.tower.init(this.nextLevelIndex);
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();
- }
+ if (self.buildSpot.tower) 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 (this.towerTypeKey === 'stapler') newTower = new StaplerTower();else if (this.towerTypeKey === 'blocker') newTower = new BureaucracyBlockerTower();
+ // Add factory for other tower types here if (newTower) {...}
if (newTower) {
- newTower.init(this.nextLevelIndex); // Init at level 0 (or selected level)
+ 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;
+ self.buildSpot.graphic.alpha = 0.1; // Make buildspot faint
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.close();
}
};
self.buttons.push(buttonContainer);
- yOffset += 110; // Spacing for next button
+ yOffset += 110;
}
}
- // 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.anchor.set(1, 0); // Anchor top-right of the button itself
+ closeButton.x = backgroundShape.width - 5; // Position relative to background shape
closeButton.y = 5;
closeButton.interactive = true;
closeButton.down = function () {
self.close();
};
- self.addChild(closeButton);
+ backgroundContainer.addChild(closeButton); // Add to background so it's part of menu block
self.close = function () {
- if (activeTowerMenu === self) {
- activeTowerMenu = null;
- }
- self.destroy();
+ if (activeTowerMenu === self) activeTowerMenu = null;
+ if (self.parent) self.destroy(); // Check parent before destroying
};
- // 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 spotGraphic = buildSpotInstance.graphic; // buildSpotInstance.graphic is the BuildSpot's own graphic
+ var spotGlobalPos = buildSpotInstance.parent.toGlobal(buildSpotInstance.position); // buildSpotInstance is child of 'level'
+ var menuWidth = backgroundShape.width;
+ var menuHeight = backgroundShape.height;
+ var spotRadius = spotGraphic ? spotGraphic.width / 2 : 75;
+ var margin = 10;
var potentialX = spotGlobalPos.x + spotRadius + margin;
- var potentialY = spotGlobalPos.y - menuHeight / 2; // Vertically center with spot
+ var potentialY = spotGlobalPos.y - menuHeight / 2;
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;
- }
+ potentialX = Math.max(margin, Math.min(potentialX, SCREEN_WIDTH - menuWidth - margin));
+ potentialY = Math.max(margin, Math.min(potentialY, SCREEN_HEIGHT - menuHeight - margin));
self.x = potentialX;
self.y = potentialY;
return self;
});
@@ -726,39 +583,70 @@
/****
* Initialize Game
****/
var game = new LK.Game({
- backgroundColor: 0x558855 // Darker green background
+ backgroundColor: 0x558855
});
/****
* 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
+// Assuming you have these sounds from TOWER_DATA
+// Assuming you have these sounds from TOWER_DATA
+// Note: same ID as stapler, towerStaplerLvl1
+// Note: same ID as towerBlockerLvl1
// --- TOWER DEFINITIONS ---
+function _typeof(o) {
+ "@babel/helpers - typeof";
+ return _typeof = "function" == typeof Symbol && "symbol" == typeof Symbol.iterator ? function (o) {
+ return typeof o;
+ } : function (o) {
+ return o && "function" == typeof Symbol && o.constructor === Symbol && o !== Symbol.prototype ? "symbol" : typeof o;
+ }, _typeof(o);
+}
+function ownKeys(e, r) {
+ var t = Object.keys(e);
+ if (Object.getOwnPropertySymbols) {
+ var o = Object.getOwnPropertySymbols(e);
+ r && (o = o.filter(function (r) {
+ return Object.getOwnPropertyDescriptor(e, r).enumerable;
+ })), t.push.apply(t, o);
+ }
+ return t;
+}
+function _objectSpread(e) {
+ for (var r = 1; r < arguments.length; r++) {
+ var t = null != arguments[r] ? arguments[r] : {};
+ r % 2 ? ownKeys(Object(t), !0).forEach(function (r) {
+ _defineProperty(e, r, t[r]);
+ }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(e, Object.getOwnPropertyDescriptors(t)) : ownKeys(Object(t)).forEach(function (r) {
+ Object.defineProperty(e, r, Object.getOwnPropertyDescriptor(t, r));
+ });
+ }
+ return e;
+}
+function _defineProperty(e, r, t) {
+ return (r = _toPropertyKey(r)) in e ? Object.defineProperty(e, r, {
+ value: t,
+ enumerable: !0,
+ configurable: !0,
+ writable: !0
+ }) : e[r] = t, e;
+}
+function _toPropertyKey(t) {
+ var i = _toPrimitive(t, "string");
+ return "symbol" == _typeof(i) ? i : i + "";
+}
+function _toPrimitive(t, r) {
+ if ("object" != _typeof(t) || !t) return t;
+ var e = t[Symbol.toPrimitive];
+ if (void 0 !== e) {
+ var i = e.call(t, r || "default");
+ if ("object" != _typeof(i)) return i;
+ throw new TypeError("@@toPrimitive must return a primitive value.");
+ }
+ return ("string" === r ? String : Number)(t);
+}
function initializeTowerFromData(towerInstance, towerTypeKey, levelIndex) {
var towerTypeData = TOWER_DATA[towerTypeKey];
if (!towerTypeData) {
console.error("Invalid tower type:", towerTypeKey);
@@ -770,29 +658,29 @@
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 (towerInstance.graphic && typeof towerInstance.graphic.setAsset === 'function') {
+ // Check if graphic exists and has setAsset
+ towerInstance.graphic.setAsset(levelData.asset);
+ } else {
+ // Fallback if graphic or setAsset is missing (e.g. if tower didn't attach one yet)
+ if (towerInstance.graphic) towerInstance.graphic.destroy(); // remove old if any
+ towerInstance.graphic = towerInstance.attachAsset(levelData.asset, {
+ anchorX: 0.5,
+ anchorY: 0.5,
+ alpha: 1
+ });
}
- 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
+ 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 TOWER_DATA = {
'stapler': {
name: 'Stapler Turret',
iconAsset: 'towerStaplerLvl1',
- // Use Lvl1 icon for selection button initially
buildSfx: 'buildStapler',
levels: [{
asset: 'towerStaplerLvl1',
cost: 50,
@@ -831,499 +719,608 @@
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
};
-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 ****/
+// --- ENEMY DEFINITIONS ---
+var ENEMY_DATA = {
+ 'paper': {
+ name: 'Paper Shredder Fodder',
+ asset: 'enemyPaper',
+ health: 3,
+ speed: 2,
+ value: 10,
+ healthScalePerWave: 0.5 // Health increases by this amount * wave number
+ },
+ 'redTapeWorm': {
+ name: 'Red Tape Worm',
+ asset: 'enemyRedTapeWorm',
+ health: 20,
+ speed: 0.75,
+ value: 25,
+ healthScalePerWave: 2 // Worms get significantly tougher each wave
+ }
+};
+// --- WAVE DEFINITIONS ---
+var WAVE_DELAY_BETWEEN_WAVES = 300; // Default frames between waves IF not specified in definition
+var WAVE_DEFINITIONS = [
+// Wave 1 (index 0 internally, displayed as 1)
+{
+ delayBeforeWaveStart: 0,
+ // Delay before this wave starts (after previous ends)
+ groups: [{
+ type: 'paper',
+ count: 5,
+ spawnIntervalFrames: 60,
+ initialGroupDelayFrames: 0
+ }]
+},
+// Wave 2
+{
+ delayBeforeWaveStart: WAVE_DELAY_BETWEEN_WAVES,
+ groups: [{
+ type: 'paper',
+ count: 8,
+ spawnIntervalFrames: 50,
+ initialGroupDelayFrames: 0
+ }]
+},
+// Wave 3
+{
+ delayBeforeWaveStart: WAVE_DELAY_BETWEEN_WAVES,
+ groups: [{
+ type: 'paper',
+ count: 10,
+ spawnIntervalFrames: 45,
+ initialGroupDelayFrames: 0
+ }, {
+ type: 'redTapeWorm',
+ count: 1,
+ spawnIntervalFrames: 0,
+ initialGroupDelayFrames: 300
+ } // Worm appears after paper batch
+ ]
+},
+// Wave 4
+{
+ delayBeforeWaveStart: WAVE_DELAY_BETWEEN_WAVES,
+ groups: [{
+ type: 'paper',
+ count: 15,
+ spawnIntervalFrames: 40,
+ initialGroupDelayFrames: 0
+ }, {
+ type: 'redTapeWorm',
+ count: 2,
+ spawnIntervalFrames: 120,
+ initialGroupDelayFrames: 100
+ }]
+},
+// Wave 5 - More challenging
+{
+ delayBeforeWaveStart: WAVE_DELAY_BETWEEN_WAVES,
+ groups: [{
+ type: 'paper',
+ count: 10,
+ spawnIntervalFrames: 30,
+ initialGroupDelayFrames: 0
+ }, {
+ type: 'redTapeWorm',
+ count: 3,
+ spawnIntervalFrames: 180,
+ initialGroupDelayFrames: 200
+ }, {
+ type: 'paper',
+ count: 10,
+ spawnIntervalFrames: 30,
+ initialGroupDelayFrames: 900
+ } // Second batch of paper
+ ]
+}
+// Add more wave definitions here
+];
+var MAX_WAVES = WAVE_DEFINITIONS.length; // Max waves is now based on definitions
+// --- LEVEL DEFINITIONS ---
+var LEVEL_DATA = {
+ 'level1': {
+ pathPoints: [{
+ x: 0.1,
+ y_abs: 100
+ }, {
+ x: 0.1,
+ y_abs: 500
+ }, {
+ x: 0.4,
+ y_abs: 500
+ }, {
+ x: 0.4,
+ y_abs: 1000
+ }, {
+ x: 0.7,
+ y_abs: 1000
+ }, {
+ x: 0.7,
+ y_abs: 1500
+ }, {
+ x: 0.3,
+ y_abs: 1500
+ }, {
+ x: 0.3,
+ y_abs: 2000
+ }, {
+ x: 0.8,
+ y_abs: 2000
+ }, {
+ x: 0.8,
+ y_abs: 2800
+ }].map(function (p) {
+ return {
+ x: p.x * SCREEN_WIDTH,
+ y: p.y_abs
+ };
+ }),
+ // Convert relative X to absolute
+ buildSpots: [{
+ x: 0.25,
+ y_abs: 300
+ }, {
+ x: 0.25,
+ y_abs: 750
+ }, {
+ x: 0.55,
+ y_abs: 750
+ }, {
+ x: 0.55,
+ y_abs: 1250
+ }, {
+ x: 0.85,
+ y_abs: 1250
+ }, {
+ x: 0.50,
+ y_abs: 1750
+ }, {
+ x: 0.15,
+ y_abs: 1750
+ }, {
+ x: 0.50,
+ y_abs: 2400
+ }, {
+ x: 0.90,
+ y_abs: 2400
+ }].map(function (p) {
+ return {
+ x: p.x * SCREEN_WIDTH,
+ y: p.y_abs
+ };
+ }),
+ // Convert relative X to absolute
+ mapHeight: 3000,
+ initialCurrency: 150,
+ // Increased starting currency a bit
+ playerLives: 10,
+ // Increased lives a bit
+ music: 'bgmusic'
+ }
+ // 'level2': { ... }
+};
+var currentLevelKey = 'level1'; // The level to play
+// Game constants from LK or globals
+var SCREEN_HEIGHT = 2732;
+var SCREEN_WIDTH = 2048;
+var PAN_THRESHOLD = SCREEN_HEIGHT * 0.25;
+var MAP_HEIGHT; // Will be set by level data
// 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 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 activeTowerMenu = null;
+var currency;
+var playerLives;
+var currentWaveInternal = 0; // 0-indexed for arrays
var waveTimer = 0;
var isWaveActive = false;
var enemies = [];
var bullets = [];
-var pathPoints = [];
-var level;
+var pathPoints = []; // Will be populated by level data
+var level; // GameLevel instance
var doge;
var goal;
+var gameRunning = true; // To stop updates on game over/win
// --- 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 settings = Object.assign({}, defaultOptions, 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
+ game.addChild(floatingText);
var framesLived = 0;
floatingText.update = function () {
+ // Needs to be manually called if not auto-updated by LK
+ if (!gameRunning && this.parent) {
+ // A way to stop floating texts after game ends
+ this.destroy();
+ return;
+ }
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.
+ // If LK doesn't auto-update children with .update, you'll need a list and manual update loop.
+ // For now, assuming it's handled if it's a child of `game` and has `update`.
}
+// UI Text Elements
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
+waveText.anchor.set(1, 0);
+waveText.x = -100;
LK.gui.topRight.addChild(waveText);
-var currencyText = new Text2("$: " + currency, {
+var currencyText = new Text2("$: 0", {
size: 50,
fill: 0xFFFFFF
});
currencyText.anchor.set(0.5, 0);
currencyText.y = 60;
LK.gui.top.addChild(currencyText);
-var livesText = new Text2("Lives: " + playerLives, {
+var livesText = new Text2("Lives: 0", {
size: 50,
fill: 0xFFFFFF
});
-livesText.anchor.set(1, 0); // Anchor top-right
+livesText.anchor.set(1, 0);
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.anchor.set(0.5, 1);
+barkButton.y = -50;
+barkButton.interactive = true;
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);
- }
+ if (doge && doge.manualBark()) {
+ barkButton.scale.set(1.1);
+ LK.setTimeout(function () {
+ return 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() {
+// var debugText ... (Removed for brevity, can be re-added if needed)
+function initializeGame(levelKeyToLoad) {
+ gameRunning = true;
+ var currentLevelData = LEVEL_DATA[levelKeyToLoad];
+ if (!currentLevelData) {
+ console.error("FATAL: Level data not found for key:", levelKeyToLoad);
+ // Potentially show an error message to the player or load a default
+ return;
+ }
game.y = 0;
- game.scale.set(1); // Reset position and scale
+ game.scale.set(1);
+ // Clear previous level entities if any
+ if (level && level.parent) level.destroy(); // Assuming destroy cleans up children
+ enemies.forEach(function (e) {
+ if (e.parent) e.destroy();
+ });
+ enemies = [];
+ bullets.forEach(function (b) {
+ if (b.parent) b.destroy();
+ });
+ bullets = [];
+ if (doge && doge.parent) doge.destroy();
+ if (goal && goal.parent) goal.destroy();
+ // If activeTowerMenu is open, close it
+ if (activeTowerMenu && activeTowerMenu.parent) {
+ activeTowerMenu.close();
+ }
+ activeTowerMenu = null;
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
- }];
+ pathPoints = currentLevelData.pathPoints;
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
+ level.createBuildSpots(currentLevelData.buildSpots);
+ MAP_HEIGHT = currentLevelData.mapHeight;
goal = new Goal();
if (pathPoints.length > 0) {
goal.x = pathPoints[pathPoints.length - 1].x;
goal.y = pathPoints[pathPoints.length - 1].y;
game.addChild(goal);
}
- // Doge
doge = new DogeHero();
doge.x = SCREEN_WIDTH / 2;
- doge.y = SCREEN_HEIGHT / 2;
+ doge.y = SCREEN_HEIGHT / 2; // Start Doge in the middle of the initial view
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;
+ currency = currentLevelData.initialCurrency;
+ playerLives = currentLevelData.playerLives;
+ currentWaveInternal = 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);
+ waveText.setText("Wave: 0/" + MAX_WAVES); // Show 0 at start, spawnWave will update to 1
+ LK.setScore(0); // Reset score on game start/restart
scoreText.setText("Score: " + LK.getScore());
- barkButton.setText("BARK!");
- // updateDebugText(); // Remove if debug text removed
- LK.playMusic('bgmusic');
+ barkButton.setText("BARK!", {
+ size: 60,
+ fill: 0xffcc00,
+ stroke: 0x000000,
+ strokeThickness: 4
+ });
+ if (currentLevelData.music) LK.playMusic(currentLevelData.music);
}
-// 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
+ if (currentWaveInternal >= WAVE_DEFINITIONS.length) {
+ // This condition should ideally be caught by checkWaveComplete if MAX_WAVES is correct
+ console.log("Attempted to spawn wave beyond definitions.");
+ return;
}
- isWaveActive = false; // Mark wave as starting (will be set true after first spawn attempt or completion)
- spawnEnemy(enemyCount);
+ var waveData = WAVE_DEFINITIONS[currentWaveInternal];
+ waveText.setText("Wave: " + (currentWaveInternal + 1) + "/" + MAX_WAVES);
+ isWaveActive = true; // Mark as active, will be set false by checkWaveComplete
+ var enemiesThisWave = 0; // Counter for enemies that will be spawned in this wave
+ var cumulativeDelayMs = 0;
+ waveData.groups.forEach(function (group) {
+ enemiesThisWave += group.count;
+ var groupDelayMs = cumulativeDelayMs + (group.initialGroupDelayFrames || 0) * (1000 / 60); // Approx ms
+ for (var i = 0; i < group.count; i++) {
+ // IIFE to capture current values of type and delay for LK.setTimeout
+ (function (enemyType, finalSpawnDelayMs) {
+ LK.setTimeout(function () {
+ if (!pathPoints || pathPoints.length === 0 || !gameRunning) return; // Safety check & game ended check
+ var newEnemy;
+ // Basic factory based on type, could be expanded
+ if (enemyType === 'redTapeWorm') {
+ newEnemy = new RedTapeWorm(currentWaveInternal);
+ } else {
+ // Default to generic Enemy, configured by its type
+ newEnemy = new Enemy(enemyType, currentWaveInternal);
+ }
+ newEnemy.x = pathPoints[0].x;
+ newEnemy.y = pathPoints[0].y;
+ game.addChild(newEnemy);
+ enemies.push(newEnemy);
+ }, finalSpawnDelayMs);
+ })(group.type, groupDelayMs);
+ groupDelayMs += (group.spawnIntervalFrames || 60) * (1000 / 60); // Add interval for next enemy in THIS group
+ }
+ // The cumulativeDelayMs for the *start* of the next group should be based on the longest
+ // duration of the current groups, or simply sequential. Here we assume sequential start of groups based on their initial delay.
+ // If groups are meant to overlap, this logic would need to be more complex.
+ // For simplicity now, let's assume initialGroupDelayFrames is relative to wave start or previous group end.
+ // This example uses it as an absolute offset from wave start + previous groups if any.
+ // This might mean very long delays if many groups. A better model might be that initialGroupDelayFrames is *always* from wave start.
+ // Re-evaluating: let each group's initial delay be from wave start for clarity.
+ // No, the `groupDelayMs` correctly handles offsets *within* the loop. The `cumulativeDelayMs` logic as is could lead to large offsets
+ // if group spawn times are long. Let's refine `cumulativeDelayMs` to represent the longest time any prior spawn in *this specific wave* has taken.
+ // Simpler: initialGroupDelayFrames are independent offsets from wave spawn.
+ });
+ // After scheduling all spawns, currentWaveInternal can be incremented for the *next* wave.
+ // waveTimer will be reset by checkWaveComplete when this wave is actually cleared.
}
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
+ // Check if all enemies for *this wave definition* have been spawned.
+ // This is tricky with timeouts. For now, assume if isWaveActive is true and enemies is 0, the wave is done.
+ // A more robust system might count spawned enemies vs expected.
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();
- }
+ currentWaveInternal++; // Advance to next wave index *after* current is cleared
+ if (currentWaveInternal >= MAX_WAVES) {
+ gameRunning = false;
+ if (LK.getScore() > storage.highScore) storage.highScore = LK.getScore();
LK.showYouWin();
}
}
}
-// Event Handlers /**** MODIFIED ****/
+// Event Handlers
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 ---
+game.down = function (screenX, screenY, obj) {
+ // screenX, screenY are usually raw from engine
+ if (!gameRunning) return;
+ // For LK, assuming x, y in game.down are ALREADY world coordinates if the game container is clicked
+ // If LK provides game.globalToLocal or similar, that would be better for converting screen clicks.
+ // Based on your original, it seems x,y from game.down ARE world coords if clicking game canvas.
+ var worldX = screenX;
+ var worldY = screenY;
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;
+ // Check if click was ON the menu itself or its children
+ var currentParent = obj;
while (currentParent) {
if (currentParent === activeTowerMenu) {
clickedOnMenu = true;
break;
}
currentParent = currentParent.parent;
}
}
- if (obj === activeTowerMenu) {
- clickedOnMenu = true;
- } // Clicked menu background
if (!clickedOnMenu) {
- activeTowerMenu.close();
+ activeTowerMenu.close(); // Clicked outside
+ // If menu was closed, decide if the click should do anything else. Generally not.
+ return;
+ } else {
+ return; // Click was on the menu, menu handles it.
}
}
- // If menu was closed by this click, don't process other actions immediately
- if (clickedOnMenu) {
- 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); }
- var targetObjectClicked = obj;
+ // Check for Doge click
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;
- if (Math.sqrt(dx * dx + dy * dy) < doge.width / 2) {
+ if (doge) {
+ // Check if Doge exists
+ if (obj === doge.graphic) {
+ // Direct click on Doge's graphic (if obj is the graphic)
clickedOnDoge = true;
+ } else {
+ // Hit test based on position and size
+ var dx = worldX - doge.x;
+ var dy = worldY - doge.y;
+ if (Math.sqrt(dx * dx + dy * dy) < (doge.width || 75) / 2) {
+ // Use width or a fallback
+ clickedOnDoge = true;
+ }
}
}
- // 2. Check for clicking BuildSpot (using worldPos)
- if (!clickedOnDoge && level && level.buildSpots) {
+ if (clickedOnDoge) {
+ dragDoge = true;
+ doge.setTarget(doge.x, doge.y); // Stop movement when picked up
+ return; // Don't process other clicks if Doge was clicked
+ }
+ // Check for BuildSpot click (only if not clicking Doge or UI handled it)
+ // This check should iterate through logical buildspots (children of `level`)
+ if (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 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;
- }
+ // Check obj first, assuming 'obj' is the specific graphical element clicked.
+ // This makes hit testing more accurate if buildspots have distinct graphics.
+ if (obj === spot || spot.graphic && obj === spot.graphic) {
+ spot.down(); // Call the BuildSpot's own handler
+ return; // Click handled by buildspot
}
+ // Fallback geometric check if obj is not the spot's graphic itself but coords are inside
+ var spotDx = worldX - (spot.parent.x + spot.x); // spot.x is relative to its parent (level)
+ var spotDy = worldY - (spot.parent.y + spot.y);
+ var spotRadius = spot.graphic ? spot.graphic.width / 2 : 75;
+ if (Math.sqrt(spotDx * spotDx + spotDy * spotDy) < spotRadius) {
+ spot.down();
+ return;
+ }
}
}
- // 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
+ // If nothing specific was clicked (Doge, BuildSpot, Menu), set Doge's target
+ if (doge) {
+ dragDoge = false; // Not dragging if just setting target
+ doge.setTarget(worldX, worldY);
+ }
};
game.move = function (x, y, obj) {
+ if (!gameRunning) return;
if (dragDoge && doge) {
- // --- Use Raw x, y as World Coordinates ---
var worldX = x;
- var worldY = y;
+ var worldY = y; // Assuming these are world coords
doge.x = worldX;
doge.y = worldY;
- doge.setTarget(doge.x, doge.y); // Update target while dragging
- // --- Optional Debug Update ---
- // lastWorldClickX = worldX; lastWorldClickY = worldY;
+ doge.setTarget(doge.x, doge.y);
}
};
game.up = function (x, y, obj) {
+ // No need to check gameRunning for 'up' as it's releasing a prior action
if (dragDoge) {
dragDoge = false;
+ // Optional: if Doge was dragged onto a build spot, perhaps open menu?
+ // For now, just releases Doge.
}
};
-game.up = function (x, y, obj) {
- if (dragDoge) {
- dragDoge = false;
- }
-};
-// Main game loop /**** MODIFIED ****/
+// Main game loop
game.update = function () {
- // --- Wave Management ---
- if (!isWaveActive && currentWave < MAX_WAVES) {
- // Only increment timer if not won yet
+ if (!gameRunning) {
+ // If game over or won, stop main updates
+ // However, we might still want some things like floating text to fade out
+ // This can be handled in the floating text's own update.
+ // Remove all enemies and bullets to be sure.
+ enemies.forEach(function (e) {
+ if (e.parent) e.destroy();
+ });
+ enemies = [];
+ bullets.forEach(function (b) {
+ if (b.parent) b.destroy();
+ });
+ bullets = [];
+ return;
+ }
+ if (!isWaveActive && currentWaveInternal < MAX_WAVES) {
+ var _WAVE_DEFINITIONS$cur, _WAVE_DEFINITIONS$cur2;
waveTimer++;
- if (waveTimer >= WAVE_DELAY) {
- waveTimer = 0; // Reset timer immediately
+ var nextWaveDelay = (_WAVE_DEFINITIONS$cur = (_WAVE_DEFINITIONS$cur2 = WAVE_DEFINITIONS[currentWaveInternal]) === null || _WAVE_DEFINITIONS$cur2 === void 0 ? void 0 : _WAVE_DEFINITIONS$cur2.delayBeforeWaveStart) !== null && _WAVE_DEFINITIONS$cur !== void 0 ? _WAVE_DEFINITIONS$cur : WAVE_DELAY_BETWEEN_WAVES;
+ if (waveTimer >= nextWaveDelay) {
+ waveTimer = 0;
spawnWave();
}
}
- // --- Update Bullets --- (Remove destroyed ones)
for (var i = bullets.length - 1; i >= 0; i--) {
- if (!bullets[i].parent) {
- bullets.splice(i, 1);
- }
+ 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);
- }
+ 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 ---
+ if (doge && doge.parent) doge.update();
checkWaveComplete();
- // --- Update Bark Button UI --- /**** NEW ****/
if (doge) {
- // Check if doge exists
+ var barkButtonBaseStyle = {
+ size: 60,
+ stroke: 0x000000,
+ strokeThickness: 4
+ };
if (doge.currentManualBarkCooldown > 0) {
- var secondsLeft = Math.ceil(doge.currentManualBarkCooldown / 60); // Approx seconds
- barkButton.setText("WAIT (" + secondsLeft + ")");
- barkButton.setText("WAIT (" + secondsLeft + ")", {
+ var secondsLeft = Math.ceil(doge.currentManualBarkCooldown / 60);
+ barkButton.setText("WAIT (" + secondsLeft + ")", _objectSpread(_objectSpread({}, barkButtonBaseStyle), {}, {
fill: 0x888888
- }); // Grey out text
+ }));
} else {
- barkButton.setText("BARK!");
- barkButton.setText("BARK!", {
+ barkButton.setText("BARK!", _objectSpread(_objectSpread({}, barkButtonBaseStyle), {}, {
fill: 0xffcc00
- }); // Restore color
+ }));
}
}
- // --- Camera Panning Logic --- /**** NEW ****/
if (doge) {
- var dogeScreenY = doge.y + game.y;
+ var dogeScreenY = doge.y + game.y; // doge.y is world, game.y is camera offset
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;
- }
+ 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;
+ if (MAP_HEIGHT > SCREEN_HEIGHT) minGameY = -(MAP_HEIGHT - SCREEN_HEIGHT);else minGameY = 0; // Don't scroll if map fits or is smaller than screen
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;
- }
+ 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();
+ // LK's main loop should be calling .update() on all children that have it, including floating texts.
};
+// Override default game over / you win to set gameRunning flag
+var originalShowGameOver = LK.showGameOver;
+LK.showGameOver = function () {
+ gameRunning = false;
+ if (originalShowGameOver) originalShowGameOver.call(LK);
+};
+var originalShowYouWin = LK.showYouWin;
+LK.showYouWin = function () {
+ gameRunning = false;
+ if (originalShowYouWin) originalShowYouWin.call(LK);
+};
// Initialize the game
-LK.setScore(0); // Reset score on start
-initializeGame();
\ No newline at end of file
+initializeGame(currentLevelKey);
\ No newline at end of file
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