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; // Slightly increased damage for manual ability
self.duration = 30; // frames
self.radius = 100;
self.maxRadius = 350; // Slightly larger radius for manual ability
self.update = function () {
self.radius += 15; // Expand slightly faster
graphic.scaleX = self.radius / 100;
graphic.scaleY = self.radius / 100;
graphic.alpha -= 0.017;
// Check for enemies in range ONCE per enemy per wave activation
// To avoid hitting the same enemy multiple times with one wave
if (!self.enemiesHit) {
self.enemiesHit = [];
} // Initialize if needed
for (var i = 0; i < enemies.length; i++) {
var enemy = enemies[i];
// Only check if enemy hasn't been hit by this wave yet
if (self.enemiesHit.indexOf(enemy) === -1) {
var dx = enemy.x - self.x;
var dy = enemy.y - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance < self.radius) {
enemy.takeDamage(self.damage);
self.enemiesHit.push(enemy); // Mark enemy as hit
}
}
}
self.duration--;
if (self.duration <= 0 || self.radius >= self.maxRadius) {
self.destroy();
}
};
return self;
});
// --- REPLACE THE ENTIRE BuildSpot CLASS DEFINITION WITH THIS ---
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; // Reference to the tower built on this spot
self.selectionIconContainer = null;
self.areIconsVisible = false;
self.showSelectionIcons = function () {
if (self.hasTower || self.areIconsVisible) {
return;
}
if (currentActiveBuildSpot && currentActiveBuildSpot !== self) {
currentActiveBuildSpot.hideSelectionIcons();
}
currentActiveBuildSpot = self;
self.selectionIconContainer = new Container();
self.selectionIconContainer.x = self.x; // Position container AT the BuildSpot's x (in 'level' space)
self.selectionIconContainer.y = self.y; // Position container AT the BuildSpot's y (in 'level' space)
// Add to the SAME parent as the BuildSpot (e.g., 'level').
// This ensures icons scroll with the game world.
if (self.parent) {
// BuildSpot should have a parent (level)
self.parent.addChild(self.selectionIconContainer);
} else {
game.addChild(self.selectionIconContainer); // Fallback to game, but level is better
}
for (var i = 0; i < ICON_SELECT_OFFSETS.length; i++) {
var offsetData = ICON_SELECT_OFFSETS[i];
var towerTypeKey = offsetData.towerKey;
var towerInfo = TOWER_DATA[towerTypeKey];
if (!towerInfo || !towerInfo.levels || !towerInfo.levels[0] || !towerInfo.iconAsset) {
// Visual error or skip
var err = new Text2("Data? " + towerTypeKey, {
size: 10,
fill: 0xff0000
});
err.x = offsetData.x;
err.y = offsetData.y;
self.selectionIconContainer.addChild(err);
continue;
}
var cost = towerInfo.levels[0].cost;
// Create the icon graphic directly (no extra container per icon needed)
var iconGraphic = self.selectionIconContainer.attachAsset(towerInfo.iconAsset, {
anchorX: 0.5,
anchorY: 0.5
});
iconGraphic.x = offsetData.x; // Position relative to selectionIconContainer's origin
iconGraphic.y = offsetData.y; // (which is the BuildSpot's center)
iconGraphic.interactive = true;
iconGraphic.towerTypeKey = towerTypeKey; // Store for click
// Add cost text AS A CHILD of the iconGraphic (or selectionIconContainer)
var costText = new Text2("$" + cost, {
size: 20,
fill: 0xFFFFFF,
stroke: 0x000000,
strokeThickness: 2
});
costText.anchor.set(0.5, -0.7); // Position below the icon's center
iconGraphic.addChild(costText); // Text is child of icon, moves with it.
if (currency < cost) {
iconGraphic.alpha = 0.4;
iconGraphic.interactive = false;
} else {
iconGraphic.alpha = 1.0;
}
iconGraphic.down = function () {
var buildCost = TOWER_DATA[this.towerTypeKey].levels[0].cost;
if (currency >= buildCost) {
self.buildSelectedTower(this.towerTypeKey);
// No need to call hideSelectionIcons here, game.down will handle it
// because the click was on an interactive element.
} else {
var tempIconForPos = this; // 'this' is the iconGraphic
var tempParentPos = tempIconForPos.parent.localToGlobal({
x: tempIconForPos.x,
y: tempIconForPos.y
});
var screenY = tempParentPos.y + game.y;
spawnFloatingText("Need More $!", tempParentPos.x, screenY - 30, {
fill: 0xFF0000
});
}
// Always hide icons after a choice or attempted choice on an icon
self.hideSelectionIcons();
};
}
self.areIconsVisible = true;
// LK.getSound('uiOpenMenu').play(); // Sound was moved to BuildSpot.down
};
self.hideSelectionIcons = function () {
if (self.selectionIconContainer) {
self.selectionIconContainer.destroy();
self.selectionIconContainer = null;
}
self.areIconsVisible = false;
if (currentActiveBuildSpot === self) {
currentActiveBuildSpot = null;
}
};
self.updateAffordability = function () {
if (!self.areIconsVisible || !self.selectionIconContainer) {
return;
}
var icons = self.selectionIconContainer.children;
for (var i = 0; i < icons.length; i++) {
var iconGraphic = icons[i]; // Now iconGraphic is the direct child
if (iconGraphic && iconGraphic.towerTypeKey && TOWER_DATA[iconGraphic.towerTypeKey]) {
var cost = TOWER_DATA[iconGraphic.towerTypeKey].levels[0].cost;
if (currency < cost) {
iconGraphic.alpha = 0.4;
iconGraphic.interactive = false;
} else {
iconGraphic.alpha = 1.0;
iconGraphic.interactive = true;
}
}
}
};
self.buildSelectedTower = function (towerTypeKey) {
if (self.hasTower || !TOWER_DATA[towerTypeKey]) {
return;
}
var towerLevelData = TOWER_DATA[towerTypeKey].levels[0];
if (currency < towerLevelData.cost) {
return;
}
currency -= towerLevelData.cost;
currencyText.setText("$: " + currency);
var newTower;
if (towerTypeKey === 'stapler') {
newTower = new StaplerTower();
} else if (towerTypeKey === 'blocker') {
newTower = new BureaucracyBlockerTower();
}
// No need for special newTower.init(0) call - constructor handles initialization
// else if (towerTypeKey === 'placeholder') { /* Do nothing or build a dummy */ return; }
if (newTower) {
// The tower's constructor now calls initializeTowerFromData(self, type, 0)
newTower.x = 0; // Place tower at BuildSpot's origin (local to BuildSpot)
newTower.y = 0;
self.addChild(newTower); // Tower becomes child of BuildSpot
self.tower = newTower;
self.hasTower = true;
self.graphic.alpha = 0.1; // Dim the build spot graphic itself
var buildSfx = TOWER_DATA[towerTypeKey].buildSfx || 'uiSelectTower'; // Fallback
// Ensure these sounds exist: 'buildStapler', 'buildBlocker'
// LK.getSound(buildSfx).play(); // Play specific build sound
// Floating text relative to build spot in world space
spawnFloatingText(TOWER_DATA[towerTypeKey].name + " BUILT!", self.x, self.y - self.graphic.height / 2, {
fill: 0x00FF00
});
// Update affordability for any other spot's menu that might be open (shouldn't be with current logic)
if (currentActiveBuildSpot && currentActiveBuildSpot !== self) {
currentActiveBuildSpot.updateAffordability();
}
}
// Icons are hidden by the icon's down handler OR by game.down clicking elsewhere
};
self.down = function () {
// This is the click on the BuildSpot graphic itself
if (self.hasTower) {
// Placeholder for future upgrade menu
spawnFloatingText("Tower here!", self.x, self.y - self.graphic.height / 2, {
fill: 0xCCCCCC
});
if (currentActiveBuildSpot && currentActiveBuildSpot !== self) {
// If another spot's menu is open
currentActiveBuildSpot.hideSelectionIcons();
}
if (self.areIconsVisible) {
self.hideSelectionIcons();
} // Close own if open (e.g. bug)
return;
}
// If this spot's icons are already visible, clicking it again should close them.
if (self.areIconsVisible) {
self.hideSelectionIcons();
} else {
// If another spot's icons are visible, hide them first.
if (currentActiveBuildSpot && currentActiveBuildSpot !== self) {
currentActiveBuildSpot.hideSelectionIcons();
}
LK.getSound('uiOpenMenu').play(); // Play sound when opening
self.showSelectionIcons();
}
};
return self;
});
// --- MODIFY BureaucracyBlockerTower ---
var BureaucracyBlockerTower = Container.expand(function () {
var self = Container.call(this);
// --- MODIFIED: No graphic initialization in constructor ---
self.enemiesSlowed = [];
// init method removed - functionality moved to constructor and initializeTowerFromData
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 buildSpot = self.parent;
if (!buildSpot || !buildSpot.parent) {
return;
}
var levelContainer = buildSpot.parent;
var towerGlobalPos = levelContainer.toGlobal(buildSpot.position);
var globalPos = self.parent ? self.parent.toGlobal(self.position) : self.position; // Get global 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
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.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
}
newlySlowedThisFrame.push(enemy);
}
}
// Check enemies that were slowed last frame but might be out of range now
for (var i = self.enemiesSlowed.length - 1; i >= 0; i--) {
var previouslySlowedEnemy = self.enemiesSlowed[i];
if (!previouslySlowedEnemy || !previouslySlowedEnemy.parent || newlySlowedThisFrame.indexOf(previouslySlowedEnemy) === -1) {
// Enemy is gone or no longer in range by this tower
if (previouslySlowedEnemy && previouslySlowedEnemy.isSlowedByBlocker === self) {
// Only unslow if WE slowed it
previouslySlowedEnemy.speed = previouslySlowedEnemy.originalSpeed;
previouslySlowedEnemy.isSlowedByBlocker = null;
if (previouslySlowedEnemy.graphic) {
previouslySlowedEnemy.graphic.tint = 0xFFFFFF;
} // Reset tint
}
self.enemiesSlowed.splice(i, 1);
}
}
self.enemiesSlowed = newlySlowedThisFrame; // Update the list of currently slowed enemies
};
// When tower is destroyed, make sure to unslow any enemies it was affecting
var originalDestroy = self.destroy;
self.destroy = function () {
for (var i = 0; i < self.enemiesSlowed.length; i++) {
var enemy = self.enemiesSlowed[i];
if (enemy && enemy.parent && enemy.isSlowedByBlocker === self) {
enemy.speed = enemy.originalSpeed;
enemy.isSlowedByBlocker = null;
if (enemy.graphic) {
enemy.graphic.tint = 0xFFFFFF;
}
}
}
self.enemiesSlowed = [];
if (originalDestroy) {
originalDestroy.call(self);
} else if (self.parent) {
self.parent.removeChild(self);
} // Basic destroy
};
// Initialize tower with level 0 stats and graphics
initializeTowerFromData(self, 'blocker', 0); // Set initial stats & correct L0 asset
return self;
});
// DogeHero - Now includes auto-attack and manual bark ability logic
var DogeHero = Container.expand(function () {
var self = Container.call(this);
self.graphic = self.attachAsset('dogeHero', {
anchorX: 0.5,
anchorY: 0.5
}); // Expose graphic if needed for hit check
self.width = self.graphic.width; // Store size for hit checks
self.height = self.graphic.height;
self.speed = 5;
self.targetX = self.x;
self.targetY = self.y;
// Auto Attack Stats
self.autoAttackRange = 180;
self.autoAttackDamage = 2;
self.autoAttackCooldownTime = 45;
self.currentAutoAttackCooldown = 0;
// Manual Bark Ability Stats
self.manualBarkCooldownTime = 300;
self.currentManualBarkCooldown = 0;
self.update = function () {
// Movement
var dx = self.targetX - self.x;
var dy = self.targetY - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance > self.speed) {
dx = dx / distance * self.speed;
dy = dy / distance * self.speed;
self.x += dx;
self.y += dy;
} else if (distance > 0) {
self.x = self.targetX;
self.y = self.targetY;
}
// Cooldowns
if (self.currentAutoAttackCooldown > 0) {
self.currentAutoAttackCooldown--;
}
if (self.currentManualBarkCooldown > 0) {
self.currentManualBarkCooldown--;
}
// Auto Attack Logic
if (self.currentAutoAttackCooldown <= 0) {
var closestEnemy = null;
var minDistanceSq = self.autoAttackRange * self.autoAttackRange;
for (var i = 0; i < enemies.length; i++) {
var enemy = enemies[i];
var ex = enemy.x - self.x;
var ey = enemy.y - self.y;
var distSq = ex * ex + ey * ey;
if (distSq < minDistanceSq) {
minDistanceSq = distSq;
closestEnemy = enemy;
}
}
if (closestEnemy) {
closestEnemy.takeDamage(self.autoAttackDamage);
LK.getSound('dogeAutoAttack').play();
self.currentAutoAttackCooldown = self.autoAttackCooldownTime;
}
}
};
self.setTarget = function (x, y) {
self.targetX = x;
self.targetY = y;
};
self.manualBark = function () {
if (self.currentManualBarkCooldown <= 0) {
var wave = new BarkWave();
wave.x = self.x;
wave.y = self.y;
game.addChild(wave);
LK.getSound('dogeBark').play();
self.currentManualBarkCooldown = self.manualBarkCooldownTime;
return true;
}
return false;
};
return self;
});
// Enemy class remains largely the same
var Enemy = Container.expand(function () {
var self = Container.call(this);
var graphic = self.attachAsset('enemyPaper', {
anchorX: 0.5,
anchorY: 0.5
});
self.health = 3;
self.speed = 2;
self.value = 10; // Currency earned when killed
self.currentPathIndex = 0;
self.update = function () {
// Check if pathPoints is loaded and valid
if (!pathPoints || pathPoints.length === 0) {
return;
}
if (self.currentPathIndex < pathPoints.length) {
var target = pathPoints[self.currentPathIndex];
if (!target) {
// Safety check
self.currentPathIndex++;
return;
}
var dx = target.x - self.x;
var dy = target.y - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
// Use a slightly larger threshold for path progression
if (distance < self.speed * 1.5) {
// Adjusted threshold
self.currentPathIndex++;
// Check if enemy reached the goal
if (self.currentPathIndex >= pathPoints.length) {
playerLives--;
livesText.setText("Lives: " + playerLives);
LK.getSound('enemyReachGoal').play();
if (playerLives <= 0) {
// Game over
if (LK.getScore() > storage.highScore) {
storage.highScore = LK.getScore();
}
LK.showGameOver();
}
self.destroy();
return;
}
} else {
// Move towards the target point
dx = dx / distance * self.speed;
dy = dy / distance * self.speed;
self.x += dx;
self.y += dy;
}
} else {
// If somehow past the end of path but not destroyed, remove it
console.log("Enemy past end of path, destroying.");
self.destroy();
}
};
self.takeDamage = function (amount) {
self.health -= amount;
// Flash red when taking damage
LK.effects.flashObject(self, 0xff0000, 200);
if (self.health <= 0 && self.parent) {
// Added check for self.parent before accessing currency
currency += self.value;
currencyText.setText("$: " + currency);
LK.setScore(LK.getScore() + self.value);
scoreText.setText("Score: " + LK.getScore());
LK.getSound('enemyDeath').play();
self.destroy();
}
};
return self;
});
// GameLevel class remains the same
var GameLevel = Container.expand(function () {
var self = Container.call(this);
var pathGraphics = []; // Store path visuals
var buildSpotGraphics = []; // Store build spot visuals
self.createPath = function (pathData) {
// Clear previous path graphics
pathGraphics.forEach(function (tile) {
tile.destroy();
});
pathGraphics = [];
// Assume pathData is the pathPoints array
for (var i = 0; i < pathData.length - 1; i++) {
var start = pathData[i];
var end = pathData[i + 1];
if (!start || !end) {
continue;
} // Safety check
// Calculate direction and distance
var dx = end.x - start.x;
var dy = end.y - start.y;
var distance = Math.sqrt(dx * dx + dy * dy);
// Adjust tile spacing slightly
var steps = Math.ceil(distance / 90); // Slightly closer tiles
for (var j = 0; j < steps; j++) {
var ratio = j / steps;
var x = start.x + dx * ratio;
var y = start.y + dy * ratio;
var tile = LK.getAsset('pathTile', {
x: x,
y: y,
alpha: 0.3,
// Make path fainter
anchorX: 0.5,
anchorY: 0.5
});
self.addChild(tile);
pathGraphics.push(tile); // Store reference
}
}
};
self.createBuildSpots = function (spotsData) {
// Clear previous build spots
buildSpotGraphics.forEach(function (spot) {
spot.destroy();
});
buildSpotGraphics = [];
self.buildSpots = []; // Clear the logical array too
for (var i = 0; i < spotsData.length; i++) {
if (!spotsData[i]) {
continue;
} // Safety check
var spot = new BuildSpot();
spot.x = spotsData[i].x;
spot.y = spotsData[i].y;
self.addChild(spot);
self.buildSpots.push(spot); // Store logical spot
buildSpotGraphics.push(spot); // Store graphical spot
}
};
return self;
});
// Goal class remains the same
var Goal = Container.expand(function () {
var self = Container.call(this);
var graphic = self.attachAsset('goal', {
anchorX: 0.5,
anchorY: 0.5,
alpha: 0.6
});
return self;
});
// --- NEW ENEMY: RedTapeWorm ---
var RedTapeWorm = Container.expand(function () {
var self = Container.call(this);
// Inherit from Enemy - this copies properties and methods if Enemy is set up for it.
// If Enemy is not a true prototypal base, we'll redefine common things.
// For simplicity, let's assume Enemy provides a good base or we'll set manually.
// Call parent constructor if applicable
// Override or set specific properties
self.graphic = self.attachAsset('enemyRedTapeWorm', {
anchorX: 0.5,
anchorY: 0.5
}); // Use new asset
self.health = 20; // High health
self.speed = 0.75; // Very slow speed
self.value = 25; // More currency
// Optional: Custom sound on spawn or movement
// LK.getSound('tapeStretch').play(); // (Could be spammy if played on update)
// The base Enemy.update() and Enemy.takeDamage() should work if inherited.
// If not, you'd copy and paste that logic here, adjusting as needed.
// For now, assume base Enemy update handles pathing and goal reaching.
// Custom onDefeat behavior if needed (e.g., spawn smaller tapes - too complex for now)
self.update = function () {
// Check if pathPoints is loaded and valid
if (!pathPoints || pathPoints.length === 0) {
return;
}
if (self.currentPathIndex < pathPoints.length) {
var target = pathPoints[self.currentPathIndex];
if (!target) {
// Safety check
self.currentPathIndex++;
return;
}
var dx = target.x - self.x;
var dy = target.y - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
// Use a slightly larger threshold for path progression
if (distance < self.speed * 1.5) {
// Adjusted threshold
self.currentPathIndex++;
// Check if enemy reached the goal
if (self.currentPathIndex >= pathPoints.length) {
playerLives--;
livesText.setText("Lives: " + playerLives);
LK.getSound('enemyReachGoal').play();
if (playerLives <= 0) {
// Game over
if (LK.getScore() > storage.highScore) {
storage.highScore = LK.getScore();
}
LK.showGameOver();
}
self.destroy();
return;
}
} else {
// Move towards the target point
dx = dx / distance * self.speed;
dy = dy / distance * self.speed;
self.x += dx;
self.y += dy;
}
} else {
// If somehow past the end of path but not destroyed, remove it
console.log("Enemy past end of path, destroying.");
self.destroy();
}
};
self.takeDamage = function (amount) {
self.health -= amount;
// Flash red when taking damage
LK.effects.flashObject(self, 0xff0000, 200);
if (self.health <= 0 && self.parent) {
// Added check for self.parent before accessing currency
currency += self.value;
currencyText.setText("$: " + currency);
LK.setScore(LK.getScore() + self.value);
scoreText.setText("Score: " + LK.getScore());
LK.getSound('enemyDeath').play();
self.destroy();
}
};
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
// --- MODIFY StaplerTower ---
var StaplerTower = Container.expand(function () {
var self = Container.call(this);
// --- MODIFIED: No graphic initialization in constructor ---
self.lastFired = 0;
// init method removed - functionality moved to constructor and initializeTowerFromData
self.update = function () {
self.lastFired++;
if (self.lastFired >= self.fireRate) {
var closestEnemy = null;
var minDistanceSq = self.range * self.range;
var buildSpot = self.parent; // Tower is child of BuildSpot
if (!buildSpot || !buildSpot.parent) {
return;
}
var levelContainer = buildSpot.parent; // BuildSpot is child of Level
var towerGlobalPos = levelContainer.toGlobal(buildSpot.position); // World pos of tower's base
for (var i = 0; i < enemies.length; i++) {
var enemy = enemies[i];
var dx = enemy.x - towerGlobalPos.x; // Use global tower X
var dy = enemy.y - towerGlobalPos.y; // Use global tower Y
var distanceSq = dx * dx + dy * dy; // Squared distance check
if (distanceSq < minDistanceSq) {
minDistanceSq = distanceSq;
closestEnemy = enemy;
}
}
if (closestEnemy) {
self.shoot(closestEnemy);
self.lastFired = 0;
}
}
};
self.shoot = function (target) {
var bullet = new TowerBullet();
var buildSpot = self.parent;
if (!buildSpot || !buildSpot.parent) {
return;
}
var levelContainer = buildSpot.parent;
var towerGlobalPos = levelContainer.toGlobal(buildSpot.position);
bullet.x = towerGlobalPos.x;
bullet.y = towerGlobalPos.y;
bullet.target = target;
bullet.damage = self.damage;
game.addChild(bullet);
bullets.push(bullet);
LK.getSound('shoot').play();
};
// Initialize tower with level 0 stats and graphics
initializeTowerFromData(self, 'stapler', 0); // Set initial stats & correct L0 asset
return self;
});
// TowerBullet class remains the same
var TowerBullet = Container.expand(function () {
var self = Container.call(this);
var graphic = self.attachAsset('bullet', {
anchorX: 0.5,
anchorY: 0.5
});
self.speed = 10;
self.damage = 1;
self.target = null;
self.update = function () {
// Check if target exists and is still in the game
if (!self.target || !self.target.parent) {
self.destroy();
return;
}
var dx = self.target.x - self.x;
var dy = self.target.y - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
// Use speed as collision threshold
if (distance < self.speed) {
self.target.takeDamage(self.damage);
self.destroy();
return;
}
// Normalize and multiply by speed
dx = dx / distance * self.speed;
dy = dy / distance * self.speed;
self.x += dx;
self.y += dy;
};
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0x558855 // Darker green background
});
/****
* Game Code
****/
// This isn't a formal class, but concepts to apply
// --- Base Tower Functionality (Conceptual - can be mixed into specific towers) ---
// Specific build sounds for each tower type if desired
// --- New Sound Effects ---
// Bureaucracy Blocker
// Even more so
// Slightly bigger/cooler
// Stapler
// --- Tower Level Assets (Placeholders) ---
// You'll also need actual tower icons for the buttons eventually
// For + symbols
// --- UI Assets for Popup ---
// 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
// --- TOWER DEFINITIONS ---
// Tower data is defined below
// Function to initialize or upgrade tower with data from TOWER_DATA
function initializeTowerFromData(towerInstance, towerTypeKey, levelIndex) {
if (!towerInstance || !TOWER_DATA[towerTypeKey] || !TOWER_DATA[towerTypeKey].levels || !TOWER_DATA[towerTypeKey].levels[levelIndex]) {
console.error("Invalid tower, type, or level data for initialization");
return;
}
var levelData = TOWER_DATA[towerTypeKey].levels[levelIndex];
// Store tower type and level
towerInstance.towerType = towerTypeKey;
towerInstance.currentLevel = levelIndex;
// Assign all properties from levelData to the tower
for (var key in levelData) {
if (key !== 'asset' && key !== 'cost' && key !== 'description') {
towerInstance[key] = levelData[key];
}
}
// Graphic handling:
if (towerInstance.graphic && towerInstance.graphic.parent) {
// If graphic exists and is attached, update it
towerInstance.graphic.parent.removeChild(towerInstance.graphic);
towerInstance.graphic = towerInstance.attachAsset(levelData.asset, {
anchorX: 0.5,
anchorY: 0.5
});
} else {
// If graphic doesn't exist or is detached, create a new one
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
});
}
// 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
}
}
// --- GLOBAL DEFINITION FOR ICON OFFSETS ---
// buildSpot graphic width is 150, icon width is ~80-100. Adjust offsets as needed.
var ICON_SELECT_OFFSETS = [{
x: -120,
y: 0,
towerKey: 'stapler'
},
// Left
{
x: 0,
y: -120,
towerKey: 'blocker'
},
// Above
{
x: 120,
y: 0,
towerKey: 'placeholder'
} // Right
];
var TOWER_DATA = {
'stapler': {
name: 'Stapler Turret',
iconAsset: 'towerStaplerLvl1',
// Use Lvl1 icon for selection button initially
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."
},
// 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 ****/
// Game variables
var currency = 100;
var playerLives = 5;
var currentWave = 0;
var waveTimer = 0;
var isWaveActive = false;
var enemies = [];
var bullets = [];
var pathPoints = [];
var currentActiveBuildSpot = null;
var level;
var doge;
var goal;
// --- HELPER FUNCTION FOR FLOATING TEXT ---
function spawnFloatingText(text, x, y, options) {
var defaultOptions = {
size: 30,
fill: 0xFFFFFF,
// White text
stroke: 0x000000,
strokeThickness: 2,
duration: 60,
// frames (1 second at 60fps)
velocityY: -1.5,
// Pixels per frame upwards
alphaFadeSpeed: 0.015
};
var settings = Object.assign({}, defaultOptions, options); // Merge user options
var floatingText = new Text2(text, {
size: settings.size,
fill: settings.fill,
stroke: settings.stroke,
strokeThickness: settings.strokeThickness,
anchorX: 0.5,
// Center the text
anchorY: 0.5
});
floatingText.x = x;
floatingText.y = y;
floatingText.alpha = 1.0;
game.addChild(floatingText); // Add to main game container to scroll with world
var framesLived = 0;
floatingText.update = function () {
floatingText.y += settings.velocityY;
floatingText.alpha -= settings.alphaFadeSpeed;
framesLived++;
if (framesLived >= settings.duration || floatingText.alpha <= 0) {
floatingText.destroy();
}
};
// Add this to a list of updatable text objects if your engine doesn't auto-update children with .update
// For LK Engine, if it's a child of `game` and has an `update` method, it should be called.
// If not, you'll need a global array like `activeFloatingTexts` and iterate it in `game.update`.
// Let's assume LK.Game handles child updates for now.
}
var scoreText = new Text2("Score: 0", {
size: 50,
fill: 0xFFFFFF
}); // White text
scoreText.anchor.set(0.5, 0);
LK.gui.top.addChild(scoreText);
var waveText = new Text2("Wave: 0/" + MAX_WAVES, {
size: 50,
fill: 0xFFFFFF
});
waveText.anchor.set(1, 0); // Anchor top-right
waveText.x = -100; // Position from right edge
LK.gui.topRight.addChild(waveText);
var currencyText = new Text2("$: " + currency, {
size: 50,
fill: 0xFFFFFF
});
currencyText.anchor.set(0.5, 0);
currencyText.y = 60;
LK.gui.top.addChild(currencyText);
var livesText = new Text2("Lives: " + playerLives, {
size: 50,
fill: 0xFFFFFF
});
livesText.anchor.set(1, 0); // Anchor top-right
livesText.x = -100;
livesText.y = 60;
LK.gui.topRight.addChild(livesText);
// Removed old info text, replaced by Bark button
// var infoText = new Text2(...)
// Bark Button /**** NEW ****/
var barkButton = new Text2("BARK!", {
size: 60,
fill: 0xffcc00,
// Doge color
stroke: 0x000000,
strokeThickness: 4
});
barkButton.anchor.set(0.5, 1); // Anchor bottom-center
barkButton.y = -50; // Position from bottom edge
barkButton.interactive = true; // Make it clickable
barkButton.down = function () {
if (doge) {
var success = doge.manualBark();
if (success) {
// Optional: visual feedback on button press
barkButton.scale.set(1.1);
LK.setTimeout(function () {
barkButton.scale.set(1.0);
}, 100);
}
}
};
LK.gui.bottom.addChild(barkButton);
// Initialize game level and path
function initializeGame() {
game.y = 0;
game.scale.set(1); // Reset position and scale
level = new GameLevel();
game.addChild(level);
// Path Points (Adjust Y values for map height)
pathPoints = [{
x: SCREEN_WIDTH * 0.1,
y: 100
}, {
x: SCREEN_WIDTH * 0.1,
y: 500
}, {
x: SCREEN_WIDTH * 0.4,
y: 500
}, {
x: SCREEN_WIDTH * 0.4,
y: 1000
}, {
x: SCREEN_WIDTH * 0.7,
y: 1000
}, {
x: SCREEN_WIDTH * 0.7,
y: 1500
}, {
x: SCREEN_WIDTH * 0.3,
y: 1500
}, {
x: SCREEN_WIDTH * 0.3,
y: 2000
}, {
x: SCREEN_WIDTH * 0.8,
y: 2000
}, {
x: SCREEN_WIDTH * 0.8,
y: 2800
}];
level.createPath(pathPoints);
// Build Spots (Adjust Y values for map height)
var buildSpots = [{
x: SCREEN_WIDTH * 0.25,
y: 300
}, {
x: SCREEN_WIDTH * 0.25,
y: 750
}, {
x: SCREEN_WIDTH * 0.55,
y: 750
}, {
x: SCREEN_WIDTH * 0.55,
y: 1250
}, {
x: SCREEN_WIDTH * 0.85,
y: 1250
}, {
x: SCREEN_WIDTH * 0.50,
y: 1750
}, {
x: SCREEN_WIDTH * 0.15,
y: 1750
}, {
x: SCREEN_WIDTH * 0.50,
y: 2400
}, {
x: SCREEN_WIDTH * 0.90,
y: 2400
}];
level.createBuildSpots(buildSpots);
// Goal
goal = new Goal();
if (pathPoints.length > 0) {
goal.x = pathPoints[pathPoints.length - 1].x;
goal.y = pathPoints[pathPoints.length - 1].y;
game.addChild(goal);
}
// Doge
doge = new DogeHero();
doge.x = SCREEN_WIDTH / 2;
doge.y = SCREEN_HEIGHT / 2;
doge.targetX = doge.x;
doge.targetY = doge.y;
game.addChild(doge);
// Reset Variables
currency = 100;
playerLives = 5;
currentWave = 0;
waveTimer = 0;
isWaveActive = false;
enemies.forEach(function (e) {
if (e.parent) {
e.destroy();
}
});
enemies = [];
bullets.forEach(function (b) {
if (b.parent) {
b.destroy();
}
});
bullets = [];
// Update UI
currencyText.setText("$: " + currency);
livesText.setText("Lives: " + playerLives);
waveText.setText("Wave: " + currentWave + "/" + MAX_WAVES);
scoreText.setText("Score: " + LK.getScore());
barkButton.setText("BARK!");
LK.playMusic('bgmusic');
}
currentActiveBuildSpot = null;
function spawnWave() {
currentWave++;
waveText.setText("Wave: " + currentWave + "/" + MAX_WAVES);
var enemyCount = 5 + currentWave * 2;
var spawnInterval = 60; // frames between enemy spawns
function spawnEnemy(count) {
if (count <= 0 || !pathPoints || pathPoints.length === 0) {
// Added check for pathPoints
isWaveActive = true; // Mark wave active even if spawning failed/finished
return;
}
var enemy = new Enemy();
enemy.x = pathPoints[0].x;
enemy.y = pathPoints[0].y;
// Increase difficulty with each wave
enemy.health = 2 + Math.floor(currentWave / 2);
if (currentWave > 5) {
enemy.speed = 2.5;
}
if (currentWave > 8) {
enemy.speed = 3;
}
game.addChild(enemy); // Add enemy to the main game container
enemies.push(enemy);
// Use LK.setTimeout for delays
LK.setTimeout(function () {
spawnEnemy(count - 1);
}, spawnInterval * 16.67); // Use ~16.67ms for 60fps
}
isWaveActive = false; // Mark wave as starting (will be set true after first spawn attempt or completion)
spawnEnemy(enemyCount);
}
function checkWaveComplete() {
if (isWaveActive && enemies.length === 0) {
// Double check if we really spawned everything for the wave before proceeding
// This basic check assumes spawning finishes before all enemies are killed
isWaveActive = false;
waveTimer = 0; // Reset timer for next wave delay
if (currentWave >= MAX_WAVES) {
// Player wins
if (LK.getScore() > storage.highScore) {
storage.highScore = LK.getScore();
}
LK.showYouWin();
}
}
}
// Event Handlers /**** MODIFIED ****/
// --- NEW APPROACH game.down ---
var dragDoge = false;
game.down = function (x, y, obj) {
var worldX = x;
var worldY = y;
// --- Step 1: Was an active UI selection icon clicked? ---
if (currentActiveBuildSpot && currentActiveBuildSpot.selectionIconContainer && currentActiveBuildSpot.areIconsVisible) {
var icons = currentActiveBuildSpot.selectionIconContainer.children;
for (var i = 0; i < icons.length; i++) {
var iconGraphic = icons[i]; // Icon graphic is direct child of selectionIconContainer
if (obj === iconGraphic && iconGraphic.interactive) {
// Click was on an active, interactive selection icon.
// The icon's own .down handler (which you'd add to iconGraphic) will fire.
// Assume the icon's handler will call currentActiveBuildSpot.buildSelectedTower(towerKey)
// and currentActiveBuildSpot.hideSelectionIcons().
// NO DOGE MOVEMENT.
return;
}
}
}
// --- Step 2: Was a BuildSpot graphic clicked? ---
// This is to toggle its menu.
if (level && level.buildSpots) {
for (var i = 0; i < level.buildSpots.length; i++) {
var spot = level.buildSpots[i];
if (obj === spot.graphic) {
// Click was directly on a BuildSpot graphic.
// Its .down handler (spot.down()) will be called by the engine to manage its menu.
// NO DOGE MOVEMENT.
return;
}
}
}
// --- Step 3: Was Doge clicked (for dragging)? ---
if (doge && obj === doge.graphic) {
dragDoge = true;
doge.setTarget(doge.x, doge.y); // Stop any current auto-movement
// NO FURTHER DOGE MOVEMENT TO CLICK LOCATION.
return;
}
// --- Step 4: If a menu is open and the click was on "empty space" (not an icon, not a spot, not Doge), close the menu. ---
if (currentActiveBuildSpot && currentActiveBuildSpot.areIconsVisible) {
// Since we already checked for clicks on icons (Step 1),
// and BuildSpots (Step 2), and Doge (Step 3),
// any other click while a menu is open means "close the menu".
currentActiveBuildSpot.hideSelectionIcons();
// NO DOGE MOVEMENT.
return;
}
// --- Step 5: If we reach here, no UI element was specifically targeted, and no menu was open to be closed.
// This means the click was on empty space, and it's a command for Doge to move.
if (doge) {
dragDoge = false; // Ensure not dragging if we just clicked empty space
doge.setTarget(worldX, worldY);
}
};
game.move = function (x, y, obj) {
if (dragDoge && doge) {
// --- Use Raw x, y as World Coordinates ---
var worldX = x;
var worldY = y;
doge.x = worldX;
doge.y = worldY;
doge.setTarget(doge.x, doge.y); // Update target while dragging
}
};
game.up = function (x, y, obj) {
if (dragDoge) {
dragDoge = false;
}
};
// Only one game.up function is needed
// Main game loop /**** MODIFIED ****/
game.update = function () {
// --- Wave Management ---
if (!isWaveActive && currentWave < MAX_WAVES) {
// Only increment timer if not won yet
waveTimer++;
if (waveTimer >= WAVE_DELAY) {
waveTimer = 0; // Reset timer immediately
spawnWave();
}
}
// --- Update Bullets --- (Remove destroyed ones)
for (var i = bullets.length - 1; i >= 0; i--) {
if (!bullets[i].parent) {
bullets.splice(i, 1);
}
}
// --- Update Enemies --- (Remove destroyed ones)
for (var i = enemies.length - 1; i >= 0; i--) {
if (!enemies[i].parent) {
enemies.splice(i, 1);
}
}
// --- Update Doge (handles its own cooldowns and attacks) ---
if (doge && doge.parent) {
// Check if doge exists
doge.update(); // Make sure Doge's update runs
}
// --- Check Wave Completion ---
checkWaveComplete();
// --- Update Bark Button UI --- /**** NEW ****/
if (doge) {
// Check if doge exists
if (doge.currentManualBarkCooldown > 0) {
var secondsLeft = Math.ceil(doge.currentManualBarkCooldown / 60); // Approx seconds
barkButton.setText("WAIT (" + secondsLeft + ")");
barkButton.setText("WAIT (" + secondsLeft + ")", {
fill: 0x888888
}); // Grey out text
} else {
barkButton.setText("BARK!");
barkButton.setText("BARK!", {
fill: 0xffcc00
}); // Restore color
}
}
// --- Camera Panning Logic --- /**** NEW ****/
if (doge) {
var dogeScreenY = doge.y + game.y;
var targetGameY = game.y;
if (dogeScreenY < PAN_THRESHOLD) {
targetGameY = -(doge.y - PAN_THRESHOLD);
} else if (dogeScreenY > SCREEN_HEIGHT - PAN_THRESHOLD) {
targetGameY = -(doge.y - (SCREEN_HEIGHT - PAN_THRESHOLD));
}
var minGameY = -(MAP_HEIGHT - SCREEN_HEIGHT);
if (isNaN(minGameY)) {
minGameY = 0;
}
var maxGameY = 0;
targetGameY = Math.max(minGameY, Math.min(maxGameY, targetGameY));
var camSmoothFactor = 0.1;
game.y += (targetGameY - game.y) * camSmoothFactor;
if (Math.abs(game.y - targetGameY) < 1) {
game.y = targetGameY;
}
}
// --- Dynamically Update Icon Affordability ---
if (currentActiveBuildSpot && currentActiveBuildSpot.areIconsVisible) {
currentActiveBuildSpot.updateAffordability();
}
};
// Initialize the game
LK.setScore(0); // Reset score on start
initializeGame(); ===================================================================
--- original.js
+++ change.js
@@ -189,8 +189,9 @@
newTower = new StaplerTower();
} else if (towerTypeKey === 'blocker') {
newTower = new BureaucracyBlockerTower();
}
+ // No need for special newTower.init(0) call - constructor handles initialization
// else if (towerTypeKey === 'placeholder') { /* Do nothing or build a dummy */ return; }
if (newTower) {
// The tower's constructor now calls initializeTowerFromData(self, type, 0)
newTower.x = 0; // Place tower at BuildSpot's origin (local to BuildSpot)
@@ -245,19 +246,11 @@
});
// --- MODIFY BureaucracyBlockerTower ---
var BureaucracyBlockerTower = Container.expand(function () {
var self = Container.call(this);
- // --- MODIFIED: Graphic initialized in constructor ---
- self.graphic = self.attachAsset(TOWER_DATA['blocker'].levels[0].asset, {
- anchorX: 0.5,
- anchorY: 0.5
- });
+ // --- MODIFIED: No graphic initialization in constructor ---
self.enemiesSlowed = [];
- // Stats like range, slowFactor will be set by initializeTowerFromData
- self.init = function (levelIndex) {
- initializeTowerFromData(self, 'blocker', levelIndex);
- self.clearAllSlows(); // Important if range/factor changes
- };
+ // init method removed - functionality moved to constructor and initializeTowerFromData
self.clearAllSlows = function () {
for (var i = 0; i < self.enemiesSlowed.length; i++) {
var enemy = self.enemiesSlowed[i];
if (enemy && enemy.parent && enemy.isSlowedByBlocker === self) {
@@ -339,8 +332,10 @@
} else if (self.parent) {
self.parent.removeChild(self);
} // Basic destroy
};
+ // Initialize tower with level 0 stats and graphics
+ initializeTowerFromData(self, 'blocker', 0); // Set initial stats & correct L0 asset
return self;
});
// DogeHero - Now includes auto-attack and manual bark ability logic
var DogeHero = Container.expand(function () {
@@ -662,19 +657,11 @@
// Modify StaplerTower
// --- MODIFY StaplerTower ---
var StaplerTower = Container.expand(function () {
var self = Container.call(this);
- // --- MODIFIED: Graphic initialized in constructor ---
- self.graphic = self.attachAsset(TOWER_DATA['stapler'].levels[0].asset, {
- anchorX: 0.5,
- anchorY: 0.5
- });
+ // --- MODIFIED: No graphic initialization in constructor ---
self.lastFired = 0;
- // Stats like fireRate, range, damage will be set by initializeTowerFromData
- self.init = function (levelIndex) {
- initializeTowerFromData(self, 'stapler', levelIndex);
- self.lastFired = 0; // Reset fire cooldown on upgrade
- };
+ // init method removed - functionality moved to constructor and initializeTowerFromData
self.update = function () {
self.lastFired++;
if (self.lastFired >= self.fireRate) {
var closestEnemy = null;
@@ -716,8 +703,9 @@
game.addChild(bullet);
bullets.push(bullet);
LK.getSound('shoot').play();
};
+ // Initialize tower with level 0 stats and graphics
initializeTowerFromData(self, 'stapler', 0); // Set initial stats & correct L0 asset
return self;
});
// TowerBullet class remains the same
@@ -786,32 +774,48 @@
// Sound for red tape worm
// --- TOWER DEFINITIONS ---
// Tower data is defined below
// Function to initialize or upgrade tower with data from TOWER_DATA
-function initializeTowerFromData(tower, towerType, levelIndex) {
- if (!tower || !TOWER_DATA[towerType] || !TOWER_DATA[towerType].levels || !TOWER_DATA[towerType].levels[levelIndex]) {
+function initializeTowerFromData(towerInstance, towerTypeKey, levelIndex) {
+ if (!towerInstance || !TOWER_DATA[towerTypeKey] || !TOWER_DATA[towerTypeKey].levels || !TOWER_DATA[towerTypeKey].levels[levelIndex]) {
console.error("Invalid tower, type, or level data for initialization");
return;
}
- var levelData = TOWER_DATA[towerType].levels[levelIndex];
+ var levelData = TOWER_DATA[towerTypeKey].levels[levelIndex];
+ // Store tower type and level
+ towerInstance.towerType = towerTypeKey;
+ towerInstance.currentLevel = levelIndex;
// Assign all properties from levelData to the tower
for (var key in levelData) {
if (key !== 'asset' && key !== 'cost' && key !== 'description') {
- tower[key] = levelData[key];
+ towerInstance[key] = levelData[key];
}
}
- // Update graphic if asset is defined
- if (levelData.asset && tower.graphic) {
- // Remove old graphic if it exists
- if (tower.graphic.parent) {
- tower.graphic.parent.removeChild(tower.graphic);
+ // Graphic handling:
+ if (towerInstance.graphic && towerInstance.graphic.parent) {
+ // If graphic exists and is attached, update it
+ towerInstance.graphic.parent.removeChild(towerInstance.graphic);
+ towerInstance.graphic = towerInstance.attachAsset(levelData.asset, {
+ anchorX: 0.5,
+ anchorY: 0.5
+ });
+ } else {
+ // If graphic doesn't exist or is detached, create a new one
+ if (towerInstance.graphic) {
+ towerInstance.graphic.destroy(); // Clean up old if it existed but was detached
}
- // Create new graphic with the appropriate asset
- tower.graphic = tower.attachAsset(levelData.asset, {
+ towerInstance.graphic = towerInstance.attachAsset(levelData.asset, {
anchorX: 0.5,
anchorY: 0.5
});
}
+ // 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
+ }
}
// --- GLOBAL DEFINITION FOR ICON OFFSETS ---
// buildSpot graphic width is 150, icon width is ~80-100. Adjust offsets as needed.
var ICON_SELECT_OFFSETS = [{
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