User prompt
çapraz ilerlerken ve düz ilerlerken aset yok oluyor düzeltt
User prompt
düşman zırhlı araç animasyon çalışmıyor
User prompt
zırhlı araç hasarını 15 yap
User prompt
çöl haydutu düşmanı silah sıkamıyor sniper kulesine duvarlara yaklaştığında uzaktan duruyor ama silah ateşlemiyor lütfen bunu düzelt, ayrıca 100 score dan sonra zırhlı düşman aracı gelmedi bunu düzelt düşman max aynan spawn sayısı 10 olsun
User prompt
çölhaydutu biraz daha geride dursun. ve çöl haydutu silah sıkamıyor sniper kulesine duvarlara yaklaştığında uzaktan duruyor ama silah ateşlemiyor lütfen bunu düzelt
User prompt
zırhlı araç düşman hızlı ve güçlü olsun canı 100 olsun hasarı 5 olsun score 500 den sonra her oyun 1 kez doğar
User prompt
yeni zırhlı araç düşmanına animasyon eklicem sağa giderken sola giderken çapraz ilerleyişleride aşşağa giderlen yukarı giderken asetlerini oluştur ve asetlerde çapraz ön sağ sol arka gibi belirtiler olsun
User prompt
HADİ ANİMASYONLARIN ASETLERİNİ OLUŞTUR
User prompt
en fazla doğma sayısını 15 çıkart
User prompt
düşman doğma süresi score göre hızlanır ama 2 saniyenin altında olmaz
User prompt
düşman asla 8 kişiden fazla aynanda doğamaz
User prompt
Çöl haydutuna silahla sıktığımız zaman silah ona hasar vermiyor bunu hatayı düzelt
User prompt
Köyün haydutuna silah sıktığında hasar almıyor bu sorunu düzelt
User prompt
Çöl haydutları uzaktan ölüyor hayır böyle olmaması lazım uzaktan hasar yemesi olan kişi o değil saldırılı düşmanlardır bu hatayı düzelt
User prompt
çok aşşağda olmuş birazcık yukarda olsun
User prompt
silah mağzasına tıkladığımızda açılan silah bölmeleri biraz daha aşşağda olsun yapı mağazalarıda aynı şekilde ayarla
User prompt
düşman max sayısı 25 olsun
User prompt
çöl haydutu uzaktan ateş ediyor ve düşmanla karşılağtığında durup uzaktan ateş etmesi lazım
User prompt
SORUNU ANLADIM SORUN DÜŞMAN MÜTTEFİĞE SALDIRIRKEN SAĞ SOLA HARAKET ETTİĞİ İÇİN ANİMASYON KAYBOLUYORMUŞ GİBİ GÖZÜKÜYOR BUNU DÜZELT
User prompt
düzelmedi müttefiği oluşturduğumda düşmanların yürüme animasyonu kayboluyor veya bozulup çok küçük hale geliyor onu düzelt
User prompt
ayrıca müttefik doğduğunda düşmanların yürüme animasyonu kayboluyor bu sorunu düzelt
User prompt
düşman sayı sını 35 olacak yeni düşman olarak çöl haydutu eklenecek olayı ise menzilindeki düşmanlara yani: duvarlar sineper kuleleri veya ana oyuncu. ama müttfeik ona doğru ilerlerse müttefikten uzazklaşarak sıkar ve uzakta durmaya çalışır
User prompt
Diğer hataları da düzelt
User prompt
Please fix the bug: 'Sniper is not defined' in or related to this line: 'var sniper = new Sniper();' Line Number: 182
User prompt
Please fix the bug: 'Bunker is not defined' in or related to this line: 'var bunker = new Bunker();' Line Number: 133
/****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
var storage = LK.import("@upit/storage.v1", {
highScore: 0,
currency: 0
});
/****
* Classes
****/
// Ally: Semi-transparent melee ally that follows sniper and attacks nearest enemy
var Ally = Container.expand(function () {
var self = Container.call(this);
// Use a unique ally soldier image as the ally's body
self.graphics = self.attachAsset('allySoldier', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 1.2,
scaleY: 1.2
});
self.graphics.alpha = 0.85; // Slightly transparent for distinction
self.maxHealth = 100;
self.health = self.maxHealth;
self.damage = 1;
self.speed = 3.2; // Faster than regular enemy speed
self.attackRange = 90; // Melee range
self.attackCooldown = 600; // ms between attacks
// Use regular enemy walk animation for Ally
self.walkAnimTick = 0;
self.lastMoveDirection = 1;
self.updateWalkAnim = function () {
// Diagonal walk animation: rotate left and right while moving
if (self.active) {
// Determine direction (toward target or following sniper)
var dir = 1;
if (self.targetEnemy) {
dir = self.targetEnemy.x > self.x ? 1 : -1;
} else if (sniper) {
dir = sniper.x > self.x ? 1 : -1;
}
self.lastMoveDirection = dir;
// Diagonal walk: oscillate rotation left and right as walking
// Use a sine wave for smooth left-right rotation
var walkOsc = Math.sin(LK.ticks * 0.18) * 0.28; // amplitude controls max angle (radians)
tween(self.graphics, {
rotation: walkOsc
}, {
duration: 180,
easing: tween.easeInOut
});
}
// Add up/down bobbing for walk animation (like enemy)
var walkCycle = Math.sin(LK.ticks * 0.15) * 6;
self.graphics.y = walkCycle;
};
self.lastAttack = 0;
self.targetEnemy = null;
self.followDistance = 80; // Distance to keep from sniper
self.active = true;
// Health bar background
self.healthBarBg = LK.getAsset('bullet', {
anchorX: 0.5,
anchorY: 0.5,
tint: 0x000000,
scaleX: 1.6,
scaleY: 0.22
});
self.healthBarBg.y = 60;
self.addChild(self.healthBarBg);
// Health bar fill
self.healthBar = LK.getAsset('bullet', {
anchorX: 0,
anchorY: 0.5,
tint: 0x00FF00,
scaleX: 1.5,
scaleY: 0.18
});
self.healthBar.y = 60;
self.healthBar.x = -self.healthBarBg.width / 2;
self.addChild(self.healthBar);
// Update health bar
self.updateHealthBar = function () {
var percent = Math.max(0, self.health / self.maxHealth);
self.healthBar.scale.x = 1.5 * percent;
if (percent < 0.3) {
self.healthBar.tint = 0xFF0000;
} else if (percent < 0.6) {
self.healthBar.tint = 0xFFFF00;
} else {
self.healthBar.tint = 0x00FF00;
}
self.healthBar.visible = percent < 1;
self.healthBarBg.visible = percent < 1;
};
// Take damage
self.takeDamage = function (amount) {
self.health -= amount;
self.updateHealthBar();
LK.effects.flashObject(self, 0xff0000, 200);
if (self.health <= 0) {
self.active = false;
tween(self, {
alpha: 0
}, {
duration: 400
});
self.visible = false;
}
};
// Ally update: follow sniper or attack nearest enemy
self.update = function () {
if (!self.active) return;
self.updateHealthBar();
self.updateWalkAnim();
// Find nearest enemy in range
var nearest = null;
var minDist = 99999;
for (var i = 0; i < enemies.length; i++) {
var e = enemies[i];
if (!e.active) continue;
var dx = e.x - self.x;
var dy = e.y - self.y;
var dist = Math.sqrt(dx * dx + dy * dy);
if (dist < minDist) {
minDist = dist;
nearest = e;
}
}
var now = Date.now();
if (nearest) {
// Always move toward nearest enemy if any exist
self.targetEnemy = nearest;
var angle = Math.atan2(nearest.y - self.y, nearest.x - self.x);
// If not in attack range, move toward enemy
if (minDist > self.attackRange * 0.7) {
self.x += Math.cos(angle) * self.speed;
self.y += Math.sin(angle) * self.speed;
}
// Attack if in range and cooldown ready
if (minDist <= self.attackRange && now - self.lastAttack > self.attackCooldown) {
self.lastAttack = now;
nearest.takeDamage(self.damage);
LK.effects.flashObject(nearest, 0x00FFFF, 120);
}
} else {
// No enemy present, follow sniper
self.targetEnemy = null;
var dx = sniper.x - self.x;
var dy = sniper.y - self.y;
var dist = Math.sqrt(dx * dx + dy * dy);
if (dist > self.followDistance) {
var angle = Math.atan2(dy, dx);
self.x += Math.cos(angle) * self.speed * 0.7;
self.y += Math.sin(angle) * self.speed * 0.7;
}
}
};
return self;
});
var BuildingShop = Container.expand(function () {
var self = Container.call(this);
self.isOpen = false;
self.buildings = [{
name: "Basic Wall",
price: 10,
health: 100,
owned: false
}, {
name: "Reinforced Wall",
price: 20,
health: 200,
owned: false
}, {
name: "Sniper Tower",
price: 30,
health: 300,
damage: 2,
fireRate: 2000,
owned: false
}];
// Shop button
self.shopButton = new Text2("BUILDINGS", {
size: 80,
fill: 0xFFFF00
});
self.shopButton.anchor.set(0.5, 0);
// Add background to make button more visible
var buttonBg = LK.getAsset('bullet', {
width: 300,
height: 100,
anchorX: 0.5,
anchorY: 0.5,
tint: 0x333333
});
buttonBg.alpha = 0.8;
buttonBg.y = self.shopButton.height / 2;
self.addChild(buttonBg);
self.addChild(self.shopButton);
// Shop panel (hidden by default)
self.panel = new Container();
self.panel.visible = false;
self.addChild(self.panel);
// Create building options
self.buildingItems = [];
for (var i = 0; i < self.buildings.length; i++) {
var building = self.buildings[i];
var item = new Container();
var bg = LK.getAsset('bullet', {
width: 400,
height: 150,
anchorX: 0.5,
anchorY: 0.5,
tint: 0x333333
});
bg.alpha = 0.8;
item.addChild(bg);
var title = new Text2(building.name, {
size: 40,
fill: 0xFFFFFF
});
title.anchor.set(0.5, 0);
title.y = -50;
item.addChild(title);
var info;
if (building.name === "Sniper Tower") {
info = new Text2("Health: " + building.health + " | Damage: " + building.damage, {
size: 30,
fill: 0xFFFFFF
});
} else {
info = new Text2("Health: " + building.health, {
size: 30,
fill: 0xFFFFFF
});
}
info.anchor.set(0.5, 0);
info.y = 0;
item.addChild(info);
var priceText = new Text2(building.owned ? "OWNED" : "$" + building.price, {
size: 35,
fill: building.owned ? 0x00FF00 : 0xFFFF00
});
priceText.anchor.set(0.5, 0);
priceText.y = 40;
item.addChild(priceText);
item.y = i * 200;
item.buildingIndex = i;
self.buildingItems.push(item);
self.panel.addChild(item);
}
// Position panel
self.panel.y = 100;
// Handle shop button press
self.shopButton.down = function (x, y, obj) {
self.toggleShop();
};
// Toggle shop visibility
self.toggleShop = function () {
self.isOpen = !self.isOpen;
self.panel.visible = self.isOpen;
};
// Handle building selection/purchase
self.selectBuilding = function (index) {
var building = self.buildings[index];
if (currency >= building.price) {
// Always allow purchase, deduct currency every time
currency -= building.price;
updateUI();
// Update UI to show price (never "OWNED")
var priceText = self.buildingItems[index].children[3];
priceText.setText("$" + building.price, {
fill: 0xFFFF00
});
LK.getSound('upgrade').play();
// After purchase, immediately select it for placement
var buildingType;
var buildingHealth = building.health;
if (index === 0) {
buildingType = 'basic';
} else if (index === 1) {
buildingType = 'reinforced';
} else if (index === 2) {
buildingType = 'sniper';
}
// Create and add placeable wall
game.placeableWall = new PlaceableWall(buildingType, buildingHealth);
game.addChild(game.placeableWall);
game.isPlacing = true;
// Close shop
self.toggleShop();
return true;
} else if (building.owned) {
// (Legacy: If owned, allow placement without payment, but this should never be true now)
var buildingType;
var buildingHealth = building.health;
if (index === 0) {
buildingType = 'basic';
} else if (index === 1) {
buildingType = 'reinforced';
} else if (index === 2) {
buildingType = 'sniper';
}
game.placeableWall = new PlaceableWall(buildingType, buildingHealth);
game.addChild(game.placeableWall);
game.isPlacing = true;
self.toggleShop();
return true;
} else {
// Not enough currency
LK.effects.flashScreen(0xFF0000, 300);
return false;
}
};
// Check if an item was clicked
self.checkItemClick = function (x, y) {
if (!self.isOpen) return false;
var pos = self.toLocal({
x: x,
y: y
});
for (var i = 0; i < self.buildingItems.length; i++) {
var item = self.buildingItems[i];
// Use item's actual bounds for hit detection
var bounds = item.children[0].getBounds(); // children[0] is the background
// Convert bounds to local coordinates relative to the panel
var itemLeft = item.x + bounds.x - bounds.width * item.children[0].anchorX;
var itemRight = itemLeft + bounds.width;
var itemTop = item.y + bounds.y - bounds.height * item.children[0].anchorY;
var itemBottom = itemTop + bounds.height;
if (pos.x >= itemLeft && pos.x <= itemRight && pos.y >= itemTop && pos.y <= itemBottom) {
return self.selectBuilding(item.buildingIndex);
}
}
return false;
};
// Add down event handlers to each building item
for (var i = 0; i < self.buildingItems.length; i++) {
var item = self.buildingItems[i];
item.interactive = true;
item.index = i; // Store the index
// Using custom event handler for each item
(function (index) {
item.down = function (x, y, obj) {
self.selectBuilding(index);
};
})(i);
}
return self;
});
var Bullet = Container.expand(function () {
var self = Container.call(this);
self.graphics = self.attachAsset('bullet', {
anchorX: 0.5,
anchorY: 0.5
});
self.speed = 15; // Default speed, will be overridden
self.damage = 1; // Default damage, will be overridden
self.active = true;
self.update = function () {
if (!self.active) return;
// Use directional movement if speedX and speedY are defined
if (self.speedX !== undefined && self.speedY !== undefined) {
self.x += self.speedX;
self.y += self.speedY;
} else {
// Fallback to original vertical-only movement
self.y -= self.speed;
}
// Bullet goes off screen (check all edges)
if (self.y < -50 || self.y > 2732 + 50 || self.x < -50 || self.x > 2048 + 50) {
self.active = false;
}
};
self.hit = function () {
self.active = false;
LK.getSound('enemyHit').play();
LK.effects.flashObject(self, 0xffffff, 200);
};
return self;
});
var Bunker = Container.expand(function () {
var self = Container.call(this);
self.graphics = self.attachAsset('bunker', {
anchorX: 0.5,
anchorY: 0.5
});
self.damageIndicator = self.attachAsset('damageIndicator', {
anchorX: 0.5,
anchorY: 0.5
});
self.damageIndicator.alpha = 0;
self.damageIndicator.originalX = 0;
self.damageIndicator.originalY = 0;
self.showDamage = function (percentage) {
// Make indicator more visible as health decreases
self.damageIndicator.alpha = 1 - percentage;
// Move the indicator based on health percentage
// Lower health = more movement
var moveFactor = (1 - percentage) * 10;
self.damageIndicator.x = self.damageIndicator.originalX + (Math.random() * 2 - 1) * moveFactor;
self.damageIndicator.y = self.damageIndicator.originalY + (Math.random() * 2 - 1) * moveFactor;
// Change tint to become redder as health decreases
var healthColor = Math.floor(percentage * 255);
self.damageIndicator.tint = 255 << 16 | healthColor << 8 | healthColor;
};
// Store original position
self.onAddedToStage = function () {
self.damageIndicator.originalX = self.damageIndicator.x;
self.damageIndicator.originalY = self.damageIndicator.y;
};
return self;
});
var Bush = Container.expand(function () {
var self = Container.call(this);
// Random bush appearance with slight variation
var type = Math.floor(Math.random() * 3);
var size = Math.random() * 0.5 + 0.8; // Size between 0.8 and 1.3
var rotation = Math.random() * 0.3 - 0.15; // Slight rotation
// Create the bush using bullet shape with green tint
self.graphics = self.attachAsset('bullet', {
anchorX: 0.5,
anchorY: 0.5,
tint: 0x2E8B57,
// Sea green color
scaleX: size,
scaleY: size
});
// Apply random rotation
self.graphics.rotation = rotation;
// Add some details to make bushes look different from each other
if (type === 0) {
// First type - taller bush
self.graphics.scale.y *= 1.3;
self.graphics.tint = 0x228B22; // Forest green
} else if (type === 1) {
// Second type - wider bush
self.graphics.scale.x *= 1.2;
self.graphics.tint = 0x006400; // Dark green
} else {
// Third type - round bush
self.graphics.tint = 0x3CB371; // Medium sea green
}
return self;
});
// DesertBandit: Ranged enemy that keeps distance from allies and attacks from range
var DesertBandit = Container.expand(function () {
var self = Container.call(this);
// Use a new image asset for Desert Bandit (add to Assets if not present)
self.graphics = self.attachAsset('desertBandit', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 2.5,
scaleY: 2.5
});
self.shadow = new Shadow();
self.shadow.y = 30;
var baseWidth = self.graphics.width * self.graphics.scale.x;
self.shadow.updateSize(baseWidth);
self.addChildAt(self.shadow, 0);
self.shadow.x = 0;
self.shadow.graphics.scale.x = baseWidth * 0.8 / 100;
self.shadow.graphics.alpha = 0.5;
self.type = 'desertBandit';
self.speed = 2.2;
self.hp = 3;
self.damage = 12;
self.points = 20;
self.currency = 4;
self.active = true;
self.attackRange = 420; // Bandit shooting range
self.minDistance = 220; // Minimum distance to keep from target
self.attackCooldown = 1200; // ms between shots
self.lastAttack = 0;
self.target = null;
self.isAttacking = false;
self.moveDirection = Math.random() > 0.5 ? 1 : -1;
self.moveCounter = 0;
self.maxMoveDistance = Math.random() * 30 + 20;
// Walk animation: oscillate left/right
self.updateWalkAnim = function () {
var walkOsc = Math.sin(LK.ticks * 0.18) * 0.28;
tween(self.graphics, {
rotation: walkOsc
}, {
duration: 180,
easing: tween.easeInOut
});
var walkCycle = Math.sin(LK.ticks * 0.15) * 6;
self.graphics.y = walkCycle;
};
// Bandit fires a bullet at the target
self.shootAt = function (target) {
var now = Date.now();
if (now - self.lastAttack < self.attackCooldown) return;
self.lastAttack = now;
if (!target || !target.active) return;
var bullet = new Bullet();
bullet.x = self.x;
bullet.y = self.y;
var dx = target.x - self.x;
var dy = target.y - self.y;
var dist = Math.sqrt(dx * dx + dy * dy);
if (dist === 0) dist = 0.0001;
bullet.speedX = dx / dist * 13;
bullet.speedY = dy / dist * 13;
bullet.damage = self.damage;
bullet.graphics.tint = 0xC2B280; // Sand color
bullet.graphics.scale.set(0.7, 1.2);
bullets.push(bullet);
if (game && typeof game.addChild === "function") game.addChild(bullet);
// Visual feedback
LK.effects.flashObject(self, 0xFFD700, 120);
};
self.update = function () {
if (!self.active) return;
self.updateWalkAnim();
// Find nearest target: prioritize wall, sniper tower, or main player, then ally
var bestTarget = null;
var minDist = 99999;
// 1. Walls (sniper towers and regular)
if (game && game.walls && game.walls.length > 0) {
for (var i = 0; i < game.walls.length; i++) {
var wall = game.walls[i];
if (wall.health > 0) {
var dx = wall.x - self.x;
var dy = wall.y - self.y;
var dist = Math.sqrt(dx * dx + dy * dy);
if (dist < minDist) {
minDist = dist;
bestTarget = wall;
}
}
}
}
// 2. Sniper (main player)
if (sniper && sniper.active) {
var dxS = sniper.x - self.x;
var dyS = sniper.y - self.y;
var distS = Math.sqrt(dxS * dxS + dyS * dyS);
if (distS < minDist) {
minDist = distS;
bestTarget = sniper;
}
}
// 3. Ally
if (ally && ally.active) {
var dxA = ally.x - self.x;
var dyA = ally.y - self.y;
var distA = Math.sqrt(dxA * dxA + dyA * dyA);
if (distA < minDist) {
minDist = distA;
bestTarget = ally;
}
}
self.target = bestTarget;
// If ally is approaching, keep distance and shoot
if (self.target && self.target === ally && minDist < self.attackRange) {
// Move away from ally if too close
if (minDist < self.minDistance) {
var angleAway = Math.atan2(self.y - ally.y, self.x - ally.x);
self.x += Math.cos(angleAway) * self.speed * 1.2;
self.y += Math.sin(angleAway) * self.speed * 1.2;
} else if (minDist > self.attackRange * 0.7) {
// Move closer if too far
var angleTo = Math.atan2(ally.y - self.y, ally.x - self.x);
self.x += Math.cos(angleTo) * self.speed;
self.y += Math.sin(angleTo) * self.speed;
}
// Shoot at ally if in range
if (minDist <= self.attackRange) {
self.shootAt(ally);
}
return;
}
// If target is wall, sniper tower, or player, attack from range
if (self.target && minDist < self.attackRange) {
// Keep distance: move away if too close, else shoot
if (minDist < self.minDistance) {
var angleAway = Math.atan2(self.y - self.target.y, self.x - self.target.x);
self.x += Math.cos(angleAway) * self.speed * 1.1;
self.y += Math.sin(angleAway) * self.speed * 1.1;
} else if (minDist > self.attackRange * 0.7) {
// Move closer if too far
var angleTo = Math.atan2(self.target.y - self.y, self.target.x - self.x);
self.x += Math.cos(angleTo) * self.speed;
self.y += Math.sin(angleTo) * self.speed;
}
// Shoot at target if in range
if (minDist <= self.attackRange) {
self.shootAt(self.target);
}
return;
}
// Default movement: move diagonally down
self.y += self.speed;
self.x += self.moveDirection * (self.speed * 0.5);
self.moveCounter += self.speed;
if (self.moveCounter >= self.maxMoveDistance) {
self.moveDirection *= -1;
self.moveCounter = 0;
self.maxMoveDistance = Math.random() * 30 + 20;
}
// Stay within screen bounds
if (self.x < 50) {
self.x = 50;
self.moveDirection = 1;
} else if (self.x > 2048 - 50) {
self.x = 2048 - 50;
self.moveDirection = -1;
}
// If reached bunker, attack it
if (self.y > bunkerY - 50) {
self.attackBunker();
}
};
self.takeDamage = function (amount) {
self.hp -= amount;
if (self.hp <= 0) {
self.die();
} else {
LK.effects.flashObject(self, 0xffffff, 200);
}
};
self.die = function () {
tween(self.shadow.graphics, {
alpha: 0
}, {
duration: 300
});
tween(self.graphics, {
rotation: Math.PI / 2,
alpha: 0.2
}, {
duration: 500,
easing: tween.easeOut
});
LK.setTimeout(function () {
if (self.graphics) {
tween(self.graphics, {
alpha: 0
}, {
duration: 800,
easing: tween.easeIn
});
}
}, 400);
self.active = false;
var currentScore = LK.getScore();
var scoreMultiplier = 1;
if (currentScore > 300) {
scoreMultiplier = 2.5;
} else if (currentScore > 200) {
scoreMultiplier = 2;
} else if (currentScore > 100) {
scoreMultiplier = 1.5;
}
LK.setScore(currentScore + self.points);
currency += Math.ceil(self.currency * scoreMultiplier);
updateUI();
};
self.attackBunker = function () {
bunkerHealth -= self.damage;
updateBunkerHealth();
LK.getSound('bunkerHit').play();
LK.effects.flashObject(bunker, 0xff0000, 500);
bunker.damageIndicator.alpha = 1;
self.active = false;
if (bunkerHealth <= 0) {
gameOver();
}
};
return self;
});
var Enemy = Container.expand(function (type) {
var self = Container.call(this);
self.type = type || 'regular';
// Set properties based on enemy type
switch (self.type) {
case 'fast':
self.graphics = self.attachAsset('fastEnemy', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 2.5,
scaleY: 2.5
});
self.speed = 3;
self.hp = 1;
self.damage = 10;
self.points = 15;
self.currency = 2;
break;
case 'tank':
self.graphics = self.attachAsset('tankEnemy', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 2.5,
scaleY: 2.5
});
self.speed = 1;
self.hp = 5;
self.damage = 25;
self.points = 30;
self.currency = 5;
break;
default:
// regular
self.graphics = self.attachAsset('regularEnemy', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 2.5,
scaleY: 2.5
});
self.speed = 2;
self.hp = 2;
self.damage = 15;
self.points = 10;
self.currency = 1;
}
// Create and add shadow beneath enemy
self.shadow = new Shadow();
self.shadow.y = 30; // Position shadow slightly below enemy
// Update shadow size based on enemy type
var baseWidth = self.graphics.width * self.graphics.scale.x;
self.shadow.updateSize(baseWidth);
self.addChildAt(self.shadow, 0); // Add shadow behind the enemy
// Ensure the shadow is visible and properly positioned
self.shadow.x = 0;
self.shadow.graphics.scale.x = baseWidth * 0.8 / 100;
self.shadow.graphics.alpha = 0.5;
self.active = true;
self.update = function () {
if (!self.active) return;
// Basic diagonal movement pattern
if (!self.moveDirection) {
self.moveDirection = Math.random() > 0.5 ? 1 : -1; // Random initial direction
self.moveCounter = 0;
self.maxMoveDistance = Math.random() * 30 + 20; // Random move distance
self.isAttackingWall = false;
self.attackTarget = null;
self.attackAnimationTicks = 0;
}
// If enemy is attacking a wall or ally, perform attack animation
if (self.isAttackingWall && self.attackTarget) {
// Increment animation counter
self.attackAnimationTicks++;
// Every 45 frames complete a full attack cycle
if (self.attackAnimationTicks < 20) {
// Pull back for attack - move head backwards
if (self.attackAnimationTicks === 1) {
tween(self.graphics, {
rotation: -0.3,
y: -10
}, {
duration: 300,
easing: tween.easeOut
});
}
} else if (self.attackAnimationTicks < 25) {
// Forward strike - move head forward quickly
if (self.attackAnimationTicks === 20) {
tween(self.graphics, {
rotation: 0.2,
y: 10
}, {
duration: 150,
easing: tween.easeIn
});
}
} else {
// Reset animation counter and damage the wall or ally
self.attackAnimationTicks = 0;
// Apply damage to wall or ally
if (self.attackTarget) {
if (self.attackTarget.takeDamage) {
self.attackTarget.takeDamage(self.damage / 5);
LK.effects.flashObject(self.attackTarget, 0xFF0000, 200);
}
}
}
// When wall or ally is destroyed, resume movement
if (!self.attackTarget || self.attackTarget.health !== undefined && self.attackTarget.health <= 0 || self.attackTarget.active !== undefined && self.attackTarget.active === false) {
self.isAttackingWall = false;
self.attackTarget = null;
// Reset position and rotation
tween(self.graphics, {
rotation: 0,
y: 0
}, {
duration: 200,
easing: tween.easeOut
});
}
return; // Don't move while attacking
}
// --- Begin: Sniper Tower targeting logic ---
// Priority: Attack sniper tower if present and in range, else ally, else bunker
var sniperTowerWall = null;
var minTowerDist = 99999;
if (typeof game !== "undefined" && game.walls && game.walls.length > 0) {
for (var i = 0; i < game.walls.length; i++) {
var wall = game.walls[i];
if (wall.type === "sniper" && wall.health > 0) {
var dxTower = wall.x - self.x;
var dyTower = wall.y - self.y;
var distTower = Math.sqrt(dxTower * dxTower + dyTower * dyTower);
if (distTower < minTowerDist) {
minTowerDist = distTower;
sniperTowerWall = wall;
}
}
}
}
if (sniperTowerWall) {
// Move toward sniper tower and attack if in range
var dxTower = sniperTowerWall.x - self.x;
var dyTower = sniperTowerWall.y - self.y;
var distTower = Math.sqrt(dxTower * dxTower + dyTower * dyTower);
if (distTower < 60) {
if (!self.isAttackingWall || self.attackTarget !== sniperTowerWall) {
self.isAttackingWall = true;
self.attackTarget = sniperTowerWall;
self.attackAnimationTicks = 0;
}
// Do not move if attacking tower
return;
} else {
// Move toward sniper tower if not in attack range
var angleToTower = Math.atan2(dyTower, dxTower);
self.x += Math.cos(angleToTower) * self.speed;
self.y += Math.sin(angleToTower) * self.speed;
// Add walking animation for following tower
if (LK.ticks % 10 === 0 && !self.isAttackingWall) {
tween(self.graphics, {
rotation: (dxTower > 0 ? 1 : -1) * 0.1
}, {
duration: 250,
easing: tween.easeInOut
});
}
return;
}
}
// --- End: Sniper Tower targeting logic ---
// Check for ally in range and attack if present and active
if (typeof ally !== "undefined" && ally && ally.active) {
var dxAlly = ally.x - self.x;
var dyAlly = ally.y - self.y;
var distAlly = Math.sqrt(dxAlly * dxAlly + dyAlly * dyAlly);
// If ally is present, always follow and attack ally
if (distAlly < 60) {
if (!self.isAttackingWall || self.attackTarget !== ally) {
self.isAttackingWall = true;
self.attackTarget = ally;
self.attackAnimationTicks = 0;
}
// Do not move if attacking ally
return;
} else {
// Move toward ally if not in attack range
var angleToAlly = Math.atan2(dyAlly, dxAlly);
self.x += Math.cos(angleToAlly) * self.speed;
self.y += Math.sin(angleToAlly) * self.speed;
// Add walking animation for following ally
if (LK.ticks % 10 === 0 && !self.isAttackingWall) {
tween(self.graphics, {
rotation: (dxAlly > 0 ? 1 : -1) * 0.1
}, {
duration: 250,
easing: tween.easeInOut
});
}
return;
}
}
// Move diagonally
self.y += self.speed;
self.x += self.moveDirection * (self.speed * 0.5);
// Switch direction after a certain distance
self.moveCounter += self.speed;
if (self.moveCounter >= self.maxMoveDistance) {
self.moveDirection *= -1; // Reverse direction
self.moveCounter = 0;
self.maxMoveDistance = Math.random() * 30 + 20; // New random distance
}
// Make the shadow grow or shrink slightly with height simulation
// Calculate height simulation based on movement cycle
var heightSimulation = Math.sin(LK.ticks * 0.05) * 0.1;
// Update shadow properties to create floating effect
self.shadow.graphics.alpha = 0.5 - heightSimulation * 0.1; // Vary opacity with more baseline visibility
self.shadow.graphics.scale.x = self.graphics.width * self.graphics.scale.x * 0.8 / 100 * (1 - heightSimulation); // Vary size
// Ensure shadow follows enemy even with movement
self.shadow.x = 0;
// Add walking animation
if (LK.ticks % 10 === 0 && !self.isAttackingWall) {
// Tilt the enemy slightly in the direction of movement
tween(self.graphics, {
rotation: self.moveDirection * 0.1
}, {
duration: 250,
easing: tween.easeInOut
});
}
// Ensure enemy stays within screen bounds
if (self.x < 50) {
self.x = 50;
self.moveDirection = 1;
} else if (self.x > 2048 - 50) {
self.x = 2048 - 50;
self.moveDirection = -1;
}
// Check if enemy reached the bunker
if (self.y > bunkerY - 50) {
self.attackBunker();
}
};
self.takeDamage = function (amount) {
self.hp -= amount;
if (self.hp <= 0) {
self.die();
} else {
LK.effects.flashObject(self, 0xffffff, 200);
}
};
self.die = function () {
// Fade out shadow when enemy dies
tween(self.shadow.graphics, {
alpha: 0
}, {
duration: 300
});
// Rotate enemy to make it lie on its side (90 degrees in radians)
tween(self.graphics, {
rotation: Math.PI / 2,
alpha: 0.2
}, {
duration: 500,
easing: tween.easeOut
});
// After rotation, fade out completely
LK.setTimeout(function () {
if (self.graphics) {
tween(self.graphics, {
alpha: 0
}, {
duration: 800,
easing: tween.easeIn
});
}
}, 400);
self.active = false;
var currentScore = LK.getScore();
var scoreMultiplier = 1;
// Increase rewards based on score progression
if (currentScore > 300) {
scoreMultiplier = 2.5;
} else if (currentScore > 200) {
scoreMultiplier = 2;
} else if (currentScore > 100) {
scoreMultiplier = 1.5;
}
LK.setScore(currentScore + self.points);
// Set different currency values based on enemy type with score multiplier
if (self.type === 'regular') {
currency += Math.ceil(3 * scoreMultiplier);
} else if (self.type === 'fast') {
currency += Math.ceil(4 * scoreMultiplier);
} else if (self.type === 'tank') {
currency += Math.ceil(6 * scoreMultiplier);
} else {
currency += Math.ceil(self.currency * scoreMultiplier); // Fallback
}
updateUI();
};
self.startAttackingWall = function (wall) {
self.isAttackingWall = true;
self.attackTarget = wall;
self.attackAnimationTicks = 0;
};
self.attackBunker = function () {
bunkerHealth -= self.damage;
updateBunkerHealth();
LK.getSound('bunkerHit').play();
LK.effects.flashObject(bunker, 0xff0000, 500);
// Make damage indicator visible when taking damage
bunker.damageIndicator.alpha = 1;
self.active = false;
// Check if bunker is destroyed
if (bunkerHealth <= 0) {
gameOver();
}
};
return self;
});
var MuzzleFlash = Container.expand(function () {
var self = Container.call(this);
// Create the flash using bullet shape with yellow-white tint
self.graphics = self.attachAsset('bullet', {
anchorX: 0.5,
anchorY: 0.5,
tint: 0xFFFF99 // Bright yellow-white color for flash
});
// Initially scale small
self.graphics.scale.set(0.3, 0.3);
self.graphics.alpha = 0.9;
// Animation lifetime
self.duration = 100; // ms
self.startTime = 0;
self.active = false;
// Start the flash effect
self.flash = function () {
self.active = true;
self.startTime = Date.now();
self.visible = true;
// Reset and start animation
self.graphics.scale.set(0.8, 0.8);
self.graphics.alpha = 1;
};
// Update the flash animation
self.update = function () {
if (!self.active) return;
var elapsed = Date.now() - self.startTime;
var progress = elapsed / self.duration;
if (progress >= 1) {
// Animation complete
self.active = false;
self.visible = false;
return;
}
// Animate scale and alpha
self.graphics.scale.set(0.8 * (1 - progress), 0.8 * (1 - progress));
self.graphics.alpha = 1 - progress;
};
// Hide initially
self.visible = false;
return self;
});
var PlaceableWall = Container.expand(function (buildingType, buildingHealth) {
var self = Container.call(this);
// Create semi-transparent wall based on type
if (buildingType === 'reinforced') {
self.graphics = self.attachAsset('reinforcedWall', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 1.3,
scaleY: 1.3
});
} else if (buildingType === 'sniper') {
// Create a tower
self.graphics = self.attachAsset('sniperTower', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 1.5,
scaleY: 1.5
});
// No need to add extra sniper on top since it's included in the image
} else {
// Basic wall
self.graphics = self.attachAsset('basicWall', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 1.3,
scaleY: 1.3
});
}
// Make it semi-transparent
self.alpha = 0.7;
// Store building properties for when it's placed
self.buildingType = buildingType;
self.buildingHealth = buildingHealth;
return self;
});
// Security: Melee security guard with walk animation (like Ally/Enemy)
var Security = Container.expand(function () {
var self = Container.call(this);
// Use ally soldier image for security for now (replace with unique asset if available)
self.graphics = self.attachAsset('allySoldier', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 1.2,
scaleY: 1.2
});
self.graphics.alpha = 1;
self.maxHealth = 120;
self.health = self.maxHealth;
self.damage = 2;
self.speed = 2.8;
self.attackRange = 90;
self.attackCooldown = 700;
self.walkAnimTick = 0;
self.lastMoveDirection = 1;
self.updateWalkAnim = function () {
// Simulate walk animation (same as Ally/Enemy)
if (LK.ticks % 10 === 0 && self.active) {
var dir = 1;
if (self.targetEnemy) {
dir = self.targetEnemy.x > self.x ? 1 : -1;
}
self.lastMoveDirection = dir;
tween(self.graphics, {
rotation: dir * 0.1
}, {
duration: 250,
easing: tween.easeInOut
});
}
var walkCycle = Math.sin(LK.ticks * 0.15) * 6;
self.graphics.y = walkCycle;
};
self.lastAttack = 0;
self.targetEnemy = null;
self.followDistance = 100;
self.active = true;
// Health bar background
self.healthBarBg = LK.getAsset('bullet', {
anchorX: 0.5,
anchorY: 0.5,
tint: 0x000000,
scaleX: 1.6,
scaleY: 0.22
});
self.healthBarBg.y = 60;
self.addChild(self.healthBarBg);
// Health bar fill
self.healthBar = LK.getAsset('bullet', {
anchorX: 0,
anchorY: 0.5,
tint: 0x00FF00,
scaleX: 1.5,
scaleY: 0.18
});
self.healthBar.y = 60;
self.healthBar.x = -self.healthBarBg.width / 2;
self.addChild(self.healthBar);
// Update health bar
self.updateHealthBar = function () {
var percent = Math.max(0, self.health / self.maxHealth);
self.healthBar.scale.x = 1.5 * percent;
if (percent < 0.3) {
self.healthBar.tint = 0xFF0000;
} else if (percent < 0.6) {
self.healthBar.tint = 0xFFFF00;
} else {
self.healthBar.tint = 0x00FF00;
}
self.healthBar.visible = percent < 1;
self.healthBarBg.visible = percent < 1;
};
// Take damage
self.takeDamage = function (amount) {
self.health -= amount;
self.updateHealthBar();
LK.effects.flashObject(self, 0xff0000, 200);
if (self.health <= 0) {
self.active = false;
tween(self, {
alpha: 0
}, {
duration: 400
});
self.visible = false;
}
};
// Security update: follow sniper or attack nearest enemy
self.update = function () {
if (!self.active) return;
self.updateHealthBar();
self.updateWalkAnim();
// Find nearest enemy in range
var nearest = null;
var minDist = 99999;
for (var i = 0; i < enemies.length; i++) {
var e = enemies[i];
if (!e.active) continue;
var dx = e.x - self.x;
var dy = e.y - self.y;
var dist = Math.sqrt(dx * dx + dy * dy);
if (dist < minDist) {
minDist = dist;
nearest = e;
}
}
var now = Date.now();
if (nearest) {
self.targetEnemy = nearest;
var angle = Math.atan2(nearest.y - self.y, nearest.x - self.x);
if (minDist > self.attackRange * 0.7) {
self.x += Math.cos(angle) * self.speed;
self.y += Math.sin(angle) * self.speed;
}
if (minDist <= self.attackRange && now - self.lastAttack > self.attackCooldown) {
self.lastAttack = now;
nearest.takeDamage(self.damage);
LK.effects.flashObject(nearest, 0x00FFFF, 120);
}
} else {
self.targetEnemy = null;
var dx = sniper.x - self.x;
var dy = sniper.y - self.y;
var dist = Math.sqrt(dx * dx + dy * dy);
if (dist > self.followDistance) {
var angle = Math.atan2(dy, dx);
self.x += Math.cos(angle) * self.speed * 0.7;
self.y += Math.sin(angle) * self.speed * 0.7;
}
}
};
return self;
});
var Shadow = Container.expand(function () {
var self = Container.call(this);
// Create shadow using bullet shape
self.graphics = self.attachAsset('bullet', {
anchorX: 0.5,
anchorY: 0.5,
tint: 0x000000
});
// Set shadow properties
self.graphics.alpha = 0.5; // More visible shadow
// Squish the shadow to appear flat on the ground
self.graphics.scale.y = 0.3;
// Ensure the shadow is visible with default size
self.graphics.scale.x = 0.8;
// Method to update shadow size based on parent
self.updateSize = function (parentWidth) {
// Make shadow slightly smaller than parent
self.graphics.scale.x = parentWidth * 0.8 / 100;
};
return self;
});
var Sniper = Container.expand(function () {
var self = Container.call(this);
self.graphics = self.attachAsset('sniper', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 0.8,
scaleY: 0.8
});
// Add rifle that will rotate toward cursor
self.rifle = self.attachAsset('rifle', {
anchorX: 0,
anchorY: 0.5,
scaleX: 1.7,
scaleY: 1.7
});
// Create references to all weapon graphics but only show the active one
self.weapons = {
basic: self.rifle,
sniper: LK.getAsset('sniper_rifle', {
anchorX: 0,
anchorY: 0.5,
scaleX: 1.2,
scaleY: 1.2
}),
"super": LK.getAsset('sniper_rifle', {
anchorX: 0,
anchorY: 0.5,
scaleX: 1.5,
scaleY: 1.5,
tint: 0xFF00FF // Magenta tint for Super Sniper
})
};
// Add other weapons but hide them initially
self.weapons.sniper.visible = false;
self.weapons.sniper.x = 10;
self.addChild(self.weapons.sniper);
self.weapons["super"].visible = false;
self.weapons["super"].x = 10;
self.addChild(self.weapons["super"]);
// Position rifle to appear like it's being held by the sniper
self.rifle.x = 10;
// Initialize weapon positions
Object.keys(self.weapons).forEach(function (key) {
// Standard positioning
self.weapons[key].x = 10;
self.weapons[key].y = 0;
if (key === 'basic') {
self.weapons[key].scale.x = 1.7;
self.weapons[key].scale.y = 1.7;
} else if (key === 'sniper') {
self.weapons[key].scale.x = 1.2;
self.weapons[key].scale.y = 1.2;
} else {
self.weapons[key].scale.x = 1.7;
self.weapons[key].scale.y = 1.7;
}
});
// Create muzzle flash effects for each weapon
self.muzzleFlashes = {};
Object.keys(self.weapons).forEach(function (key) {
var flash = new MuzzleFlash();
self.weapons[key].addChild(flash);
// Position at the end of each weapon
flash.x = self.weapons[key].width - 10;
flash.y = 0;
if (key === 'super') {
flash.graphics.tint = 0xFF00FF; // Magenta flash for super sniper
}
self.muzzleFlashes[key] = flash;
});
self.fireRate = 1000; // ms between shots
self.lastShot = 0;
self.bulletDamage = 1;
self.bulletSpeed = 15; // Default bullet speed
self.currentWeapon = 'basic'; // Track current weapon type
// Method to update rifle rotation based on cursor position
self.updateAim = function (targetX, targetY) {
// Calculate angle to target
var angle = Math.atan2(targetY - self.y, targetX - self.x);
// Determine if target is on left or right side of the screen
var isTargetOnLeftSide = targetX < 2048 / 2;
// Update sniper graphics based on which side target is on
if (isTargetOnLeftSide) {
// Target is on left side - character faces left
self.graphics.scale.x = -1; // Flip character horizontally
// Adjust weapon positions for left-facing stance
Object.keys(self.weapons).forEach(function (key) {
self.weapons[key].x = -10; // Offset for left side
self.weapons[key].scale.x = -1; // Flip weapon horizontally
});
} else {
// Target is on right side - character faces right
self.graphics.scale.x = 1; // Normal orientation
// Adjust weapon positions for right-facing stance
Object.keys(self.weapons).forEach(function (key) {
self.weapons[key].x = 10; // Normal position on right side
self.weapons[key].scale.x = 1; // Normal weapon orientation
});
}
// Set all weapons rotation to aim at target, properly adjusted for direction
if (isTargetOnLeftSide) {
// When facing left, we need to adjust the angle
self.weapons.basic.rotation = angle + Math.PI;
self.weapons.sniper.rotation = angle + Math.PI;
self.weapons["super"].rotation = angle + Math.PI;
} else {
self.weapons.basic.rotation = angle;
self.weapons.sniper.rotation = angle;
self.weapons["super"].rotation = angle;
}
return angle;
};
self.canShoot = function () {
var now = Date.now();
if (now - self.lastShot >= self.fireRate) {
self.lastShot = now;
return true;
}
return false;
};
self.switchWeapon = function (weaponIndex) {
// Hide all weapons first
self.weapons.basic.visible = false;
self.weapons.sniper.visible = false;
self.weapons["super"].visible = false;
// Show selected weapon based on index
switch (weaponIndex) {
case 0:
self.weapons.basic.visible = true;
self.currentWeapon = 'basic';
break;
case 1:
self.weapons.sniper.visible = true;
self.currentWeapon = 'sniper';
break;
case 2:
self.weapons["super"].visible = true;
self.currentWeapon = 'super';
break;
default:
self.weapons.basic.visible = true;
self.currentWeapon = 'basic';
}
// Reset magazine if switching weapon
var mag = magazines[self.currentWeapon];
if (mag && mag.current > mag.max) mag.current = mag.max;
updateAmmoUI();
};
self.shoot = function (targetX, targetY) {
// Magazine and reload logic
var weapon = self.currentWeapon;
var mag = magazines[weapon];
if (mag.reloading) return null;
if (mag.current <= 0) {
// Start reload
mag.reloading = true;
updateAmmoUI();
// Animate reload: move weapon down and up, and show "reloading..." in UI
tween(self.weapons[weapon], {
y: 60,
alpha: 0.5
}, {
duration: Math.floor(mag.reloadTime * 0.4),
easing: tween.easeIn,
onFinish: function onFinish() {
tween(self.weapons[weapon], {
y: 0,
alpha: 1
}, {
duration: Math.floor(mag.reloadTime * 0.6),
easing: tween.easeOut
});
}
});
LK.setTimeout(function () {
mag.current = mag.max;
mag.reloading = false;
updateAmmoUI();
}, mag.reloadTime);
return null;
}
if (!self.canShoot()) return null;
mag.current--;
updateAmmoUI();
// Update rifle aim
var angle = self.updateAim(targetX, targetY);
var bullet = new Bullet();
// Determine if target is on left or right side of the screen
var isTargetOnLeftSide = targetX < 2048 / 2;
var activeWeapon = self.weapons[self.currentWeapon];
var rifleLength = activeWeapon.width;
// Calculate bullet spawn position at the muzzle, always at the tip of the rifle in world space
// Get the local muzzle position (rifle tip) in weapon's local space
var muzzleLocalX = rifleLength;
var muzzleLocalY = 0;
// Transform muzzle position to world space
// 1. Get weapon's rotation and scale.x (for left/right flip)
var weaponRotation = activeWeapon.rotation;
var weaponScaleX = activeWeapon.scale.x;
// 2. Calculate rotated and flipped muzzle position
var cosR = Math.cos(weaponRotation);
var sinR = Math.sin(weaponRotation);
var muzzleWorldX = self.x + (activeWeapon.x + muzzleLocalX * weaponScaleX) * cosR - muzzleLocalY * sinR;
var muzzleWorldY = self.y + (activeWeapon.y + muzzleLocalX * weaponScaleX) * sinR + muzzleLocalY * cosR;
// Place bullet at the muzzle tip
bullet.x = muzzleWorldX;
bullet.y = muzzleWorldY;
// Set bullet direction - always use the original angle for bullet direction
bullet.speedX = Math.cos(angle) * (self.currentWeapon === "super" ? self.bulletSpeed : bullet.speed);
bullet.speedY = Math.sin(angle) * (self.currentWeapon === "super" ? self.bulletSpeed : bullet.speed);
bullet.damage = self.bulletDamage;
bullet.speed = self.bulletSpeed; // Set bullet speed from sniper
// Customize bullet appearance based on weapon type
if (self.currentWeapon === 'sniper') {
bullet.graphics.tint = 0x33CCFF; // Blue tint for sniper bullets
bullet.graphics.scale.set(0.8, 1.5); // Thinner, longer bullets
} else if (self.currentWeapon === 'super') {
bullet.graphics.tint = 0xFF00FF; // Magenta tint for super sniper bullets
bullet.graphics.scale.set(1.2, 2.2); // Even longer, more powerful look
bullet.damage = self.bulletDamage; // Ensure correct damage
bullet.speed = self.bulletSpeed; // Ensure correct speed
}
// Trigger muzzle flash for current weapon
var muzzleFlash = self.muzzleFlashes[self.currentWeapon];
if (muzzleFlash) {
muzzleFlash.flash();
}
// Add weapon recoil effect based on weapon type
var recoilDistance = 10; // Default recoil for rifle
var recoilDuration = 100; // Default recoil recovery time
var originX = activeWeapon.x;
// Set different recoil parameters based on weapon type
if (self.currentWeapon === 'sniper') {
recoilDistance = 20; // Stronger recoil for sniper
recoilDuration = 150;
} else if (self.currentWeapon === 'super') {
recoilDistance = 35; // Even stronger recoil for super sniper
recoilDuration = 200;
}
// Calculate recoil direction (opposite to shot direction)
// For recoil, we need to consider which direction the weapon is facing
var recoilDirection = isTargetOnLeftSide ? 1 : -1; // Reverse for left-facing
var bulletAngle = angle; // Use the same angle as bullet direction
var recoilX = recoilDirection * Math.cos(bulletAngle) * recoilDistance;
var recoilY = -Math.sin(bulletAngle) * recoilDistance;
// Apply recoil to the active weapon
tween(activeWeapon, {
x: originX + recoilX,
y: recoilY
}, {
duration: 50,
// Quick recoil
easing: tween.easeOut,
onFinish: function onFinish() {
// Recover from recoil
tween(activeWeapon, {
x: originX,
y: 0
}, {
duration: recoilDuration,
easing: tween.easeInOut
});
}
});
LK.getSound('shoot').play();
return bullet;
};
return self;
});
var Tree = Container.expand(function () {
var self = Container.call(this);
// Random tree appearance with variation
var type = Math.floor(Math.random() * 3);
var size = Math.random() * 0.7 + 1.2; // Size between 1.2 and 1.9
var rotation = Math.random() * 0.2 - 0.1; // Slight rotation
// Create the tree trunk using bullet shape with brown tint
var treeTrunk = self.attachAsset('bullet', {
anchorX: 0.5,
anchorY: 0.5,
tint: 0x8B4513,
// Saddle brown color
scaleX: size * 0.3,
scaleY: size * 0.8
});
// Create the tree crown using bullet shape with green tint
var treeCrown = self.attachAsset('bullet', {
anchorX: 0.5,
anchorY: 0.5,
tint: 0x228B22,
// Forest green color
scaleX: size * 1.2,
scaleY: size * 1.0
});
// Position crown on top of trunk
treeCrown.y = -treeTrunk.height * 0.5;
// Apply random rotation
self.rotation = rotation;
// Add some details to make trees look different from each other
if (type === 0) {
// First type - taller tree with darker green
treeCrown.scale.y *= 1.4;
treeCrown.tint = 0x006400; // Dark green
} else if (type === 1) {
// Second type - wider tree with lighter green
treeCrown.scale.x *= 1.3;
treeCrown.tint = 0x32CD32; // Lime green
} else {
// Third type - autumn tree with different color
treeCrown.tint = 0xFF8C00; // Dark orange
}
return self;
});
var Wall = Container.expand(function (type, health) {
var self = Container.call(this);
// Set properties based on wall type
self.type = type || 'basic';
self.maxHealth = health || 100;
self.health = self.maxHealth;
// Create wall graphic based on type
if (self.type === 'reinforced') {
self.graphics = self.attachAsset('reinforcedWall', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 1.3,
scaleY: 1.3
});
} else if (self.type === 'sniper') {
// Create a tower
self.graphics = self.attachAsset('sniperTower', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 1.5,
scaleY: 1.5
});
// Sniper tower stats
self.maxHealth = 300;
self.health = self.maxHealth;
self.towerDamage = 2;
self.towerRange = 700;
self.towerFireRate = 1200; // ms between shots
self.towerLastShot = 0;
self.towerTarget = null;
self.towerBulletSpeed = 18;
self.towerBullet = function (target) {
var bullet = new Bullet();
bullet.x = self.x;
bullet.y = self.y - self.graphics.height / 2;
var dx = target.x - bullet.x;
var dy = target.y - bullet.y;
var dist = Math.sqrt(dx * dx + dy * dy);
// Prevent division by zero and ensure bullet always moves
if (dist === 0) dist = 0.0001;
bullet.speedX = dx / dist * self.towerBulletSpeed;
bullet.speedY = dy / dist * self.towerBulletSpeed;
bullet.damage = self.towerDamage;
bullet.graphics.tint = 0x33CCFF;
bullet.graphics.scale.set(0.7, 1.3);
return bullet;
};
// No need to add extra sniper on top since it's included in the image
} else {
// Basic wall
self.graphics = self.attachAsset('basicWall', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 1.3,
scaleY: 1.3
});
}
// Add health bar
self.healthBar = new Container();
self.addChild(self.healthBar);
// Health bar background
self.healthBarBg = LK.getAsset('bullet', {
anchorX: 0.5,
anchorY: 0.5,
tint: 0x000000,
scaleX: 3,
scaleY: 0.3
});
self.healthBarBg.y = -40;
self.healthBar.addChild(self.healthBarBg);
// Health bar fill
self.healthBarFill = LK.getAsset('bullet', {
anchorX: 0,
anchorY: 0.5,
tint: 0x00FF00,
scaleX: 3,
scaleY: 0.2
});
self.healthBarFill.y = -40;
self.healthBarFill.x = -self.healthBarBg.width / 2;
self.healthBar.addChild(self.healthBarFill);
// Hide health bar initially
self.healthBar.visible = false;
self.update = function () {
// Show health bar if damaged, and always show for sniper tower if not full health
if (self.health < self.maxHealth || self.type === 'sniper' && self.health < self.maxHealth) {
self.healthBar.visible = true;
// Update health bar fill
var healthPercent = self.health / self.maxHealth;
self.healthBarFill.scale.x = 3 * healthPercent;
// Change color based on health
if (healthPercent < 0.3) {
self.healthBarFill.tint = 0xFF0000; // Red
} else if (healthPercent < 0.6) {
self.healthBarFill.tint = 0xFFFF00; // Yellow
} else {
self.healthBarFill.tint = 0x00FF00; // Green
}
} else {
self.healthBar.visible = false;
}
// Sniper tower AI: shoot at nearest enemy in range
if (self.type === 'sniper' && self.health > 0) {
var now = Date.now();
// Find nearest enemy in range
var nearest = null;
var minDist = 99999;
for (var i = 0; i < enemies.length; i++) {
var e = enemies[i];
if (!e.active) continue;
var dx = e.x - self.x;
var dy = e.y - self.y;
var dist = Math.sqrt(dx * dx + dy * dy);
if (dist < minDist && dist < self.towerRange) {
minDist = dist;
nearest = e;
}
}
if (nearest && now - self.towerLastShot > self.towerFireRate) {
self.towerLastShot = now;
var bullet = self.towerBullet(nearest);
if (bullet) {
bullets.push(bullet);
if (game && typeof game.addChild === "function") game.addChild(bullet);
}
}
}
};
self.takeDamage = function (amount) {
self.health -= amount;
if (self.health <= 0) {
self.destroy();
return true; // Wall destroyed
}
return false; // Wall still standing
};
return self;
});
var WaveCountdown = Container.expand(function () {
var self = Container.call(this);
self.countdownTime = 60; // 60 seconds default
self.active = false;
// Create countdown text
self.countdownText = new Text2(self.countdownTime, {
size: 150,
fill: 0xFFFFFF
});
self.countdownText.anchor.set(0.5, 0.5);
self.addChild(self.countdownText);
// Create skip button
self.skipButton = new Text2("SKIP", {
size: 80,
fill: 0xFFFF00
});
self.skipButton.anchor.set(0.5, 0.5);
self.skipButton.y = 100;
self.addChild(self.skipButton);
// Skip button background for better visibility
var skipButtonBg = LK.getAsset('bullet', {
width: 200,
height: 100,
anchorX: 0.5,
anchorY: 0.5,
tint: 0x333333
});
skipButtonBg.alpha = 0.8;
skipButtonBg.y = 100;
self.addChild(skipButtonBg);
// Custom swap implementation since swapChildren is not available
var parent = self.skipButton.parent;
var skipButtonIndex = parent.children.indexOf(skipButtonBg);
var buttonIndex = parent.children.indexOf(self.skipButton);
// Manual reordering of children to create swap effect
if (skipButtonIndex !== -1 && buttonIndex !== -1) {
// Remove both from parent
parent.removeChild(skipButtonBg);
parent.removeChild(self.skipButton);
// Add them back in reverse order
parent.addChild(self.skipButton);
parent.addChild(skipButtonBg);
}
// Timer for countdown
self.timer = null;
// Start countdown
self.startCountdown = function (onComplete) {
self.active = true;
self.visible = true;
self.countdownTime = 60;
self.countdownText.setText(self.countdownTime);
self.onComplete = onComplete;
// Update countdown every second
self.timer = LK.setInterval(function () {
self.countdownTime--;
self.countdownText.setText(self.countdownTime);
if (self.countdownTime <= 0) {
self.stopCountdown();
if (self.onComplete) self.onComplete();
}
}, 1000);
};
// Stop countdown
self.stopCountdown = function () {
if (self.timer) {
LK.clearInterval(self.timer);
self.timer = null;
}
self.active = false;
self.visible = false;
};
// Skip button event handler
self.skipButton.down = function (x, y, obj) {
if (self.active) {
self.stopCountdown();
if (self.onComplete) self.onComplete();
}
};
return self;
});
var WeaponShop = Container.expand(function () {
var self = Container.call(this);
self.isOpen = false;
self.weapons = [{
name: "Basic Rifle",
price: 0,
damage: 1,
speed: 15,
owned: true
}, {
name: "Sniper Rifle",
price: 50,
damage: 3,
speed: 30,
owned: false
}, {
name: "Super Sniper",
price: 150,
damage: 7,
speed: 40,
owned: false
}];
// Shop button
self.shopButton = new Text2("WEAPONS", {
size: 80,
fill: 0xFFFF00
});
self.shopButton.anchor.set(0.5, 0);
// Add background to make button more visible
var buttonBg = LK.getAsset('bullet', {
width: 300,
height: 100,
anchorX: 0.5,
anchorY: 0.5,
tint: 0x333333
});
buttonBg.alpha = 0.8;
buttonBg.y = self.shopButton.height / 2;
self.addChild(buttonBg);
self.addChild(self.shopButton);
// Shop panel (hidden by default)
self.panel = new Container();
self.panel.visible = false;
self.addChild(self.panel);
// Create weapon options
self.weaponItems = [];
for (var i = 0; i < self.weapons.length; i++) {
var weapon = self.weapons[i];
var item = new Container();
var bg = LK.getAsset('bullet', {
width: 400,
height: 150,
anchorX: 0.5,
anchorY: 0.5,
tint: 0x333333
});
bg.alpha = 0.8;
item.addChild(bg);
var title = new Text2(weapon.name, {
size: 40,
fill: 0xFFFFFF
});
title.anchor.set(0.5, 0);
title.y = -50;
item.addChild(title);
var info = new Text2("Damage: " + weapon.damage + " | Speed: " + weapon.speed, {
size: 30,
fill: 0xFFFFFF
});
info.anchor.set(0.5, 0);
info.y = 0;
item.addChild(info);
var priceText = new Text2(weapon.owned ? "OWNED" : "$" + weapon.price, {
size: 35,
fill: weapon.owned ? 0x00FF00 : 0xFFFF00
});
priceText.anchor.set(0.5, 0);
priceText.y = 40;
item.addChild(priceText);
item.y = i * 200;
item.weaponIndex = i;
self.weaponItems.push(item);
self.panel.addChild(item);
}
// Position panel
self.panel.y = 100;
// Handle shop button press
self.shopButton.down = function (x, y, obj) {
self.toggleShop();
};
// Toggle shop visibility
self.toggleShop = function () {
self.isOpen = !self.isOpen;
self.panel.visible = self.isOpen;
};
// Handle weapon selection
self.selectWeapon = function (index) {
var weapon = self.weapons[index];
if (weapon.owned) {
// Equip weapon
sniper.bulletDamage = weapon.damage;
sniper.bulletSpeed = weapon.speed;
sniper.switchWeapon(index); // Switch to selected weapon appearance
// Visual feedback for equipped weapon
for (var i = 0; i < self.weaponItems.length; i++) {
var priceText = self.weaponItems[i].children[3];
if (i === index) {
priceText.setText("EQUIPPED", {
fill: i === 2 ? 0xFF00FF : 0x00FFFF // Magenta for super sniper, cyan for others
});
} else if (self.weapons[i].owned) {
priceText.setText("OWNED", {
fill: i === 2 ? 0xFF00FF : 0x00FF00 // Magenta for super sniper, green for others
});
}
}
LK.getSound('upgrade').play();
return true;
} else if (currency >= weapon.price) {
// Purchase weapon
currency -= weapon.price;
weapon.owned = true;
// Update UI
var priceText = self.weaponItems[index].children[3];
priceText.setText("EQUIPPED", {
fill: index === 2 ? 0xFF00FF : 0x00FFFF // Magenta for super sniper, cyan for others
});
// Reset other weapon texts to "OWNED"
for (var i = 0; i < self.weaponItems.length; i++) {
if (i !== index && self.weapons[i].owned) {
self.weaponItems[i].children[3].setText("OWNED", {
fill: i === 2 ? 0xFF00FF : 0x00FF00 // Magenta for super sniper, green for others
});
}
}
// Equip weapon
sniper.bulletDamage = weapon.damage;
sniper.bulletSpeed = weapon.speed;
sniper.switchWeapon(index); // Switch to selected weapon appearance
LK.getSound('upgrade').play();
updateUI();
return true;
} else {
// Not enough currency
LK.effects.flashScreen(0xFF0000, 300);
return false;
}
};
// Check if an item was clicked
self.checkItemClick = function (x, y) {
if (!self.isOpen) return false;
var pos = self.toLocal({
x: x,
y: y
});
for (var i = 0; i < self.weaponItems.length; i++) {
var item = self.weaponItems[i];
// Use item's actual bounds for hit detection
var bounds = item.children[0].getBounds(); // children[0] is the background
// Convert bounds to local coordinates relative to the panel
var itemLeft = item.x + bounds.x - bounds.width * item.children[0].anchorX;
var itemRight = itemLeft + bounds.width;
var itemTop = item.y + bounds.y - bounds.height * item.children[0].anchorY;
var itemBottom = itemTop + bounds.height;
if (pos.x >= itemLeft && pos.x <= itemRight && pos.y >= itemTop && pos.y <= itemBottom) {
return self.selectWeapon(item.weaponIndex);
}
}
return false;
};
// Add down event handlers to each weapon item
for (var i = 0; i < self.weaponItems.length; i++) {
var item = self.weaponItems[i];
item.interactive = true;
item.index = i; // Store the index
// Using custom event handler for each item
(function (index) {
item.down = function (x, y, obj) {
self.selectWeapon(index);
};
})(i);
}
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
// No title, no description
// Always backgroundColor is black
backgroundColor: 0x000000
});
/****
* Game Code
****/
// Create background
var background = game.attachAsset('background', {
width: 2048,
height: 2732,
anchorX: 0,
anchorY: 0,
tint: 0xC2B280 // Apply dry land tint to the background
});
// Game variables
var bunkerHealth = 100;
var maxBunkerHealth = 100;
var bunkerY = 2732 - 100; // Position near bottom of screen
var currency = 500;
var difficulty = 1;
var wave = 1;
var enemySpawnRate = 3000; // ms between enemy spawns
var lastEnemySpawn = 0;
var gameActive = true;
var waveInProgress = false;
var enemyIncreasePerWave = 0.25; // 25% more enemies per wave
var enemiesPerWave = 35; // Starting with 35 enemies
var enemiesSpawned = 0; // Track enemies spawned in current wave
var enemiesRequired = 10; // Initial enemies required for first wave
// Magazine system for each weapon type
var magazines = {
basic: {
max: 15,
current: 15,
reloading: false,
reloadTime: 1200
},
sniper: {
max: 10,
current: 10,
reloading: false,
reloadTime: 1800
},
"super": {
max: 5,
current: 5,
reloading: false,
reloadTime: 2500
}
};
// UI for ammo display
var ammoTxt = new Text2('Ammo: 5/5', {
size: 50,
fill: 0xFFFF00
});
ammoTxt.anchor.set(1, 0);
ammoTxt.x = 2048 - 80;
ammoTxt.y = 60;
LK.gui.top.addChild(ammoTxt);
// Helper to update ammo UI
function updateAmmoUI() {
var weapon = sniper.currentWeapon;
var mag = magazines[weapon];
if (mag.reloading) {
ammoTxt.setText('Reloading...');
} else {
ammoTxt.setText('Ammo: ' + mag.current + '/' + mag.max);
}
}
// Upgrade costs and values
var upgrades = {
fireRate: {
level: 1,
cost: 10,
value: 1000,
// ms between shots
increment: -100 // decrease time between shots
},
bulletDamage: {
level: 1,
cost: 15,
value: 1,
increment: 1 // increase damage
}
};
// Arrays for tracking game objects
var bullets = [];
var enemies = [];
var walls = [];
// Ally instance (semi-transparent melee ally)
var ally = null;
// Create bunker
var bunker = new Bunker();
bunker.x = 2048 / 2;
bunker.y = bunkerY;
game.addChild(bunker);
// Create sniper
var sniper = new Sniper();
sniper.x = 2048 / 2;
sniper.y = bunkerY - 50;
game.addChild(sniper);
// Create weapon shop
var weaponShop = new WeaponShop();
weaponShop.x = 350; // Moved more to the right for better visibility
weaponShop.y = 1800; // Position higher on the screen for better visibility
game.addChild(weaponShop); // Add to game instead of GUI for better positioning
// Create building shop
var buildingShop = new BuildingShop();
buildingShop.x = 400; // Moved further right to improve visibility and hit detection
buildingShop.y = 600; // Repositioned lower on the screen for better accessibility
game.addChild(buildingShop);
// Create wave countdown timer
var waveCountdown = new WaveCountdown();
waveCountdown.x = 2048 / 2; // Center horizontally
waveCountdown.y = 2732 / 2; // Center vertically
waveCountdown.visible = false; // Hide initially
game.addChild(waveCountdown);
// UI Elements
// Score display
var scoreTxt = new Text2('Score: 0', {
size: 70,
fill: 0xFFFFFF
});
scoreTxt.anchor.set(0.5, 0);
scoreTxt.y = 50;
LK.gui.top.addChild(scoreTxt);
// Ally purchase button (bottom left corner, visible)
var allyBtnBg = LK.getAsset('bullet', {
width: 260,
height: 110,
anchorX: 0.5,
anchorY: 0.5,
tint: 0x333333
});
allyBtnBg.alpha = 0.8;
var allyBtn = new Text2("ALLY\n$100", {
size: 55,
fill: 0x00FFFF
});
allyBtn.anchor.set(0.5, 0.5);
var allyBtnContainer = new Container();
allyBtnContainer.addChild(allyBtnBg);
allyBtnContainer.addChild(allyBtn);
allyBtnContainer.x = 140;
allyBtnContainer.y = 2732 - 140;
allyBtnContainer.interactive = true;
allyBtnContainer.visible = true;
game.addChild(allyBtnContainer);
// Ally button event
allyBtnContainer.down = function (x, y, obj) {
if (currency >= 100 && (!ally || !ally.active)) {
currency -= 100;
updateUI();
if (ally && !ally.active) {
game.removeChild(ally);
}
ally = new Ally();
ally.x = sniper.x + 80;
ally.y = sniper.y;
game.addChild(ally);
LK.effects.flashObject(ally, 0x00FFFF, 400);
} else if (currency < 100) {
LK.effects.flashObject(allyBtnContainer, 0xFF0000, 200);
}
};
// Wave display
var waveTxt = new Text2('Wave: 1', {
size: 50,
fill: 0xFFFFFF
});
waveTxt.anchor.set(1, 0);
LK.gui.topLeft.addChild(waveTxt);
waveTxt.x = 150; // Move away from the top left corner
// Health display
var healthTxt = new Text2('Bunker: 100%', {
size: 50,
fill: 0xFFFFFF
});
healthTxt.anchor.set(0.5, 0);
healthTxt.y = 130;
LK.gui.top.addChild(healthTxt);
// Currency display
var currencyTxt = new Text2('$: 0', {
size: 50,
fill: 0x00FF00
});
currencyTxt.anchor.set(0, 0);
LK.gui.topLeft.addChild(currencyTxt);
currencyTxt.x = 150; // Move away from top left corner
currencyTxt.y = 60; // Position below wave display
// Upgrade buttons removed as requested
// Update UI elements
function updateUI() {
scoreTxt.setText('Score: ' + LK.getScore());
currencyTxt.setText('$: ' + currency);
waveTxt.setText('Wave: ' + wave);
updateUpgradeButtons();
}
function updateBunkerHealth() {
var healthPercentage = Math.max(0, Math.min(100, Math.round(bunkerHealth / maxBunkerHealth * 100)));
healthTxt.setText('Bunker: ' + healthPercentage + '%');
bunker.showDamage(bunkerHealth / maxBunkerHealth);
// Only make damage indicator visible and shake when actually taking damage
// Don't show it during initialization
// Only proceed with damage indication animations if bunker is actually taking damage
if (bunkerHealth < maxBunkerHealth) {
// Make damage indicator visible and shake when damage is taken
bunker.damageIndicator.alpha = 1;
// Shake effect
tween(bunker.damageIndicator, {
x: bunker.damageIndicator.originalX + (Math.random() * 10 - 5),
y: bunker.damageIndicator.originalY + (Math.random() * 10 - 5)
}, {
duration: 50,
repeat: 5,
yoyo: true,
onFinish: function onFinish() {
// Hide damage indicator after shake completes (1 second)
LK.setTimeout(function () {
if (gameActive) {
// Fade out damage indicator
tween(bunker.damageIndicator, {
alpha: 0
}, {
duration: 300
});
}
}, 1000);
}
});
}
// Create a visual indicator effect when health is low
if (bunkerHealth / maxBunkerHealth < 0.5) {
// Add continuous damage indicator update for low health
LK.setTimeout(function () {
if (gameActive) bunker.showDamage(bunkerHealth / maxBunkerHealth);
}, 100);
}
}
function updateUpgradeButtons() {
// Upgrade buttons removed as requested
}
// Game mechanics functions
function spawnEnemy() {
var now = Date.now();
if (!waveInProgress || now - lastEnemySpawn < enemySpawnRate) return;
// Check if we've already spawned enough enemies for this wave
if (enemiesSpawned >= enemiesRequired) return;
lastEnemySpawn = now;
// Calculate number of enemies to spawn based on score
var currentScore = LK.getScore();
var enemiesToSpawnAtOnce = 1; // Default spawn one enemy at a time
// Increase enemies spawned at once based on score thresholds
if (currentScore >= 500) {
enemiesToSpawnAtOnce = 5; // Spawn 5 enemies at once at high scores
} else if (currentScore >= 300) {
enemiesToSpawnAtOnce = 4; // Spawn 4 enemies at once
} else if (currentScore >= 200) {
enemiesToSpawnAtOnce = 3; // Spawn 3 enemies at once
} else if (currentScore >= 100) {
enemiesToSpawnAtOnce = 2; // Spawn 2 enemies at once
}
// Make sure we don't spawn more enemies than required for this wave
enemiesToSpawnAtOnce = Math.min(enemiesToSpawnAtOnce, enemiesRequired - enemiesSpawned);
// Spawn multiple enemies at once
for (var i = 0; i < enemiesToSpawnAtOnce; i++) {
// Determine enemy type based on score and wave progression
var enemyType = 'regular';
var random = Math.random();
// More advanced enemies appear based on score thresholds
if (currentScore >= 350 && random < 0.25) {
enemyType = 'desertBandit';
} else if (currentScore >= 300 && random < 0.3) {
enemyType = 'tank';
} else if (currentScore >= 150 && random < 0.25) {
enemyType = 'tank';
} else if (currentScore >= 100 && random < 0.35) {
enemyType = 'fast';
} else if (currentScore >= 50 && random < 0.25) {
enemyType = 'fast';
}
var enemy;
if (enemyType === 'desertBandit') {
enemy = new DesertBandit();
} else {
enemy = new Enemy(enemyType);
}
// Distribute enemies across the width of the screen
if (enemiesToSpawnAtOnce > 1) {
// Distribute evenly but with some randomness
var segment = 2048 / enemiesToSpawnAtOnce;
enemy.x = i * segment + Math.random() * (segment - 100) + 50;
} else {
enemy.x = Math.random() * (2048 - 100) + 50; // Random x position
}
enemy.y = -50; // Start above the screen
enemies.push(enemy);
game.addChild(enemy);
// Increment enemies spawned counter
enemiesSpawned++;
}
}
function checkCollisions() {
for (var i = bullets.length - 1; i >= 0; i--) {
var bullet = bullets[i];
if (!bullet.active) {
game.removeChild(bullet);
bullets.splice(i, 1);
continue;
}
for (var j = enemies.length - 1; j >= 0; j--) {
var enemy = enemies[j];
if (!enemy.active) {
game.removeChild(enemy);
enemies.splice(j, 1);
continue;
}
if (bullet.active && enemy.active && bullet.intersects(enemy)) {
enemy.takeDamage(bullet.damage);
bullet.hit();
break;
}
}
}
}
function upgradeFireRate() {
if (currency >= upgrades.fireRate.cost) {
currency -= upgrades.fireRate.cost;
upgrades.fireRate.level++;
upgrades.fireRate.value += upgrades.fireRate.increment;
// Ensure fire rate doesn't go below minimum
upgrades.fireRate.value = Math.max(200, upgrades.fireRate.value);
sniper.fireRate = upgrades.fireRate.value;
// Increase cost for next upgrade
upgrades.fireRate.cost = Math.floor(upgrades.fireRate.cost * 1.5);
LK.getSound('upgrade').play();
updateUI();
}
}
function upgradeBulletDamage() {
if (currency >= upgrades.bulletDamage.cost) {
currency -= upgrades.bulletDamage.cost;
upgrades.bulletDamage.level++;
upgrades.bulletDamage.value += upgrades.bulletDamage.increment;
sniper.bulletDamage = upgrades.bulletDamage.value;
// Increase cost for next upgrade
upgrades.bulletDamage.cost = Math.floor(upgrades.bulletDamage.cost * 1.5);
LK.getSound('upgrade').play();
updateUI();
}
}
function increaseDifficulty() {
// Check if all enemies for this wave are spawned and eliminated
if (waveInProgress && enemiesSpawned >= enemiesRequired && enemies.length === 0) {
// All enemies in current wave defeated, prepare for next wave
wave++;
// Calculate new enemies required for next wave (increase by 25%)
enemiesRequired = Math.ceil(enemiesPerWave * Math.pow(1 + 0.25, wave - 1));
// Reset enemies spawned counter
enemiesSpawned = 0;
// Decrease spawn rate with each wave (faster spawns) based on score and wave
var currentScore = LK.getScore();
// More aggressive spawn rate reduction based on score
var scoreBasedReduction = Math.min(2000, Math.floor(currentScore / 8) * 25);
// Use an exponential reduction for higher scores to make spawns much faster
if (currentScore > 300) {
scoreBasedReduction += Math.min(1000, Math.pow(currentScore - 300, 1.2));
}
// Set a lower minimum spawn rate for higher scores
var minSpawnRate = currentScore > 400 ? 100 : currentScore > 200 ? 200 : 300;
enemySpawnRate = Math.max(minSpawnRate, 3000 - wave * 200 - scoreBasedReduction);
// Pause wave progression and show countdown
waveInProgress = false;
// Start countdown for next wave
waveCountdown.startCountdown(function () {
// When countdown completes, start the new wave
startNewWave();
});
}
}
function startNewWave() {
// Clear all existing enemies
for (var i = enemies.length - 1; i >= 0; i--) {
game.removeChild(enemies[i]);
}
enemies = [];
// Reset enemies spawned counter
enemiesSpawned = 0;
// Update UI to show new wave and enemy count
waveTxt.setText('Wave: ' + wave + ' (' + enemiesRequired + ' enemies)');
// Start spawning enemies again
waveInProgress = true;
// Flash screen to indicate new wave
LK.effects.flashScreen(0x00FF00, 500);
// Adjust difficulty based on score
var currentScore = LK.getScore();
if (currentScore > 300) {
difficulty = 5;
} else if (currentScore > 200) {
difficulty = 4;
} else if (currentScore > 100) {
difficulty = 3;
} else if (currentScore > 50) {
difficulty = 2;
} else {
difficulty = 1;
}
}
function gameOver() {
gameActive = false;
// Save high score
if (LK.getScore() > storage.highScore) {
storage.highScore = LK.getScore();
}
// Save currency for next game
storage.currency = currency;
// Show game over screen
LK.showGameOver();
}
// Event handlers
game.move = function (x, y, obj) {
if (gameActive) {
// If placing a wall, move it with cursor
if (game.isPlacing && game.placeableWall) {
game.placeableWall.x = x;
game.placeableWall.y = y;
} else {
// Otherwise update rifle aim to follow cursor
var angle = sniper.updateAim(x, y);
// Let the updateAim method handle the character orientation
// We don't rotate the sniper character itself anymore
// This prevents the character from being upside down when aiming
}
}
};
game.down = function (x, y, obj) {
if (!gameActive) return;
// Check if weapon shop item was clicked
if (weaponShop.checkItemClick(x, y)) {
return;
}
// Check if building shop item was clicked
if (buildingShop.checkItemClick(x, y)) {
return;
}
// If placing a wall, place it at the clicked position
if (game.isPlacing && game.placeableWall) {
// Don't place near bunker or too close to top-left corner
var distToBunker = Math.sqrt(Math.pow(x - bunker.x, 2) + Math.pow(y - bunker.y, 2));
var distToTopLeft = Math.sqrt(Math.pow(x - 100, 2) + Math.pow(y - 100, 2));
if (distToBunker < 150 || distToTopLeft < 100 || y > bunkerY - 50) {
// Can't place here - flash red
LK.effects.flashObject(game.placeableWall, 0xFF0000, 300);
return;
}
// Create an actual wall at this position
var wall = new Wall(game.placeableWall.buildingType, game.placeableWall.buildingHealth);
wall.x = x;
wall.y = y;
game.addChild(wall);
// Add to walls array if we don't have one
if (!game.walls) {
game.walls = [];
}
game.walls.push(wall);
// Remove placeable wall
game.removeChild(game.placeableWall);
game.placeableWall = null;
game.isPlacing = false;
return;
}
// Fire at touch location (single shot for all weapons)
var bullet = sniper.shoot(x, y);
if (bullet) {
bullets.push(bullet);
game.addChild(bullet);
}
};
game.up = function (x, y, obj) {
// No machine gun auto fire to stop
};
// Update function called every frame
game.update = function () {
if (!gameActive) return;
// Only spawn enemies if a wave is in progress
if (waveInProgress) {
// Spawn enemies
spawnEnemy();
// Update enemies
for (var i = 0; i < enemies.length; i++) {
if (enemies[i].active) {
enemies[i].update();
}
}
// Check for collisions
checkCollisions();
}
// Update bullets regardless of wave status
for (var i = 0; i < bullets.length; i++) {
if (bullets[i].active) {
bullets[i].update();
}
}
// Update walls and check for wall-enemy collisions
if (game.walls && game.walls.length > 0) {
for (var i = game.walls.length - 1; i >= 0; i--) {
var wall = game.walls[i];
// Update wall health bar
wall.update();
// Check for collisions with enemies
for (var j = enemies.length - 1; j >= 0; j--) {
var enemy = enemies[j];
if (enemy.active && wall.intersects(enemy)) {
// Stop enemy and set it to attack the wall
if (!enemy.isAttackingWall) {
enemy.isAttackingWall = true;
enemy.attackTarget = wall;
enemy.attackAnimationTicks = 0;
}
// Check if wall was destroyed after the attack
if (wall.health <= 0) {
// Remove wall if destroyed
game.removeChild(wall);
game.walls.splice(i, 1);
// Reset any enemies attacking this wall
for (var k = 0; k < enemies.length; k++) {
if (enemies[k].attackTarget === wall) {
enemies[k].isAttackingWall = false;
enemies[k].attackTarget = null;
// Reset enemy position and rotation
tween(enemies[k].graphics, {
rotation: 0,
y: 0
}, {
duration: 200,
easing: tween.easeOut
});
}
}
break;
}
}
}
}
}
// Update muzzle flashes
Object.keys(sniper.muzzleFlashes).forEach(function (key) {
if (sniper.muzzleFlashes[key].active) {
sniper.muzzleFlashes[key].update();
}
});
// Update ally if present
if (ally && ally.active) {
ally.update();
} else if (ally && !ally.active) {
// Remove dead ally from game
game.removeChild(ally);
ally = null;
}
// Update difficulty (handles wave transitions)
increaseDifficulty();
// Continuously update damage indicator when health is low
if (bunkerHealth / maxBunkerHealth < 0.4) {
// Move damage indicator more frequently as health gets lower
if (LK.ticks % Math.max(5, Math.floor(bunkerHealth / maxBunkerHealth * 20)) === 0) {
bunker.showDamage(bunkerHealth / maxBunkerHealth);
}
}
// Update UI
if (LK.ticks % 30 === 0) {
updateUI();
}
};
// Make sure the weapon shop starts with the appropriate weapon equipped
weaponShop.selectWeapon(0); // Start with basic rifle selected
updateAmmoUI(); // Show initial ammo
// Initialize UI
updateUI();
// Ensure damage indicator is invisible at start
bunker.damageIndicator.alpha = 0;
updateBunkerHealth();
// Calculate initial enemies required for first wave
enemiesRequired = enemiesPerWave;
// Start the first wave with countdown
waveInProgress = false;
waveCountdown.startCountdown(function () {
startNewWave();
});
// Place random bushes and trees around the game area
function placeBushes() {
// Number of decorative elements to place
var bushCount = 25;
var treeCount = 15;
// Positions to avoid (bunker and sniper area)
var avoidX = 2048 / 2;
var avoidY = bunkerY;
var avoidRadius = 200;
// Also avoid the top-left corner where menu icon is located
var topLeftX = 50;
var topLeftY = 50;
var topLeftRadius = 100;
// Add bushes
for (var i = 0; i < bushCount; i++) {
var bush = new Bush();
// Keep generating positions until we find a suitable one
var validPosition = false;
var attempts = 0;
while (!validPosition && attempts < 10) {
// Generate random position
bush.x = Math.random() * 2048;
bush.y = Math.random() * 2732;
// Check distance from bunker area
var distToBunker = Math.sqrt(Math.pow(bush.x - avoidX, 2) + Math.pow(bush.y - avoidY, 2));
// Check distance from top-left corner
var distToTopLeft = Math.sqrt(Math.pow(bush.x - topLeftX, 2) + Math.pow(bush.y - topLeftY, 2));
// Position is valid if it's away from both areas to avoid
if (distToBunker > avoidRadius && distToTopLeft > topLeftRadius) {
validPosition = true;
}
attempts++;
}
// Add bush behind other game elements (insert at the beginning of children array)
game.addChildAt(bush, 0);
}
// Add trees
for (var i = 0; i < treeCount; i++) {
var tree = new Tree();
// Keep generating positions until we find a suitable one
var validPosition = false;
var attempts = 0;
while (!validPosition && attempts < 10) {
// Generate random position
tree.x = Math.random() * 2048;
tree.y = Math.random() * 2732;
// Check distance from bunker area
var distToBunker = Math.sqrt(Math.pow(tree.x - avoidX, 2) + Math.pow(tree.y - avoidY, 2));
// Check distance from top-left corner
var distToTopLeft = Math.sqrt(Math.pow(tree.x - topLeftX, 2) + Math.pow(tree.y - topLeftY, 2));
// Position is valid if it's away from both areas to avoid
if (distToBunker > avoidRadius && distToTopLeft > topLeftRadius) {
validPosition = true;
}
attempts++;
}
// Add tree behind other game elements (insert at the beginning of children array)
game.addChildAt(tree, 0);
}
}
// Add bushes to the game
placeBushes();
// Start background music
LK.playMusic('gameBgMusic', {
fade: {
start: 0,
end: 0.3,
duration: 1000
}
}); ===================================================================
--- original.js
+++ change.js
@@ -9,321 +9,1939 @@
/****
* Classes
****/
-// Ally class (simple melee ally)
+// Ally: Semi-transparent melee ally that follows sniper and attacks nearest enemy
var Ally = Container.expand(function () {
var self = Container.call(this);
- self.active = true;
- self.health = 3;
+ // Use a unique ally soldier image as the ally's body
self.graphics = self.attachAsset('allySoldier', {
anchorX: 0.5,
anchorY: 0.5,
- alpha: 0.7
+ scaleX: 1.2,
+ scaleY: 1.2
});
- self.x = 0;
- self.y = 0;
+ self.graphics.alpha = 0.85; // Slightly transparent for distinction
+ self.maxHealth = 100;
+ self.health = self.maxHealth;
+ self.damage = 1;
+ self.speed = 3.2; // Faster than regular enemy speed
+ self.attackRange = 90; // Melee range
+ self.attackCooldown = 600; // ms between attacks
+ // Use regular enemy walk animation for Ally
+ self.walkAnimTick = 0;
+ self.lastMoveDirection = 1;
+ self.updateWalkAnim = function () {
+ // Diagonal walk animation: rotate left and right while moving
+ if (self.active) {
+ // Determine direction (toward target or following sniper)
+ var dir = 1;
+ if (self.targetEnemy) {
+ dir = self.targetEnemy.x > self.x ? 1 : -1;
+ } else if (sniper) {
+ dir = sniper.x > self.x ? 1 : -1;
+ }
+ self.lastMoveDirection = dir;
+ // Diagonal walk: oscillate rotation left and right as walking
+ // Use a sine wave for smooth left-right rotation
+ var walkOsc = Math.sin(LK.ticks * 0.18) * 0.28; // amplitude controls max angle (radians)
+ tween(self.graphics, {
+ rotation: walkOsc
+ }, {
+ duration: 180,
+ easing: tween.easeInOut
+ });
+ }
+ // Add up/down bobbing for walk animation (like enemy)
+ var walkCycle = Math.sin(LK.ticks * 0.15) * 6;
+ self.graphics.y = walkCycle;
+ };
+ self.lastAttack = 0;
+ self.targetEnemy = null;
+ self.followDistance = 80; // Distance to keep from sniper
+ self.active = true;
+ // Health bar background
+ self.healthBarBg = LK.getAsset('bullet', {
+ anchorX: 0.5,
+ anchorY: 0.5,
+ tint: 0x000000,
+ scaleX: 1.6,
+ scaleY: 0.22
+ });
+ self.healthBarBg.y = 60;
+ self.addChild(self.healthBarBg);
+ // Health bar fill
+ self.healthBar = LK.getAsset('bullet', {
+ anchorX: 0,
+ anchorY: 0.5,
+ tint: 0x00FF00,
+ scaleX: 1.5,
+ scaleY: 0.18
+ });
+ self.healthBar.y = 60;
+ self.healthBar.x = -self.healthBarBg.width / 2;
+ self.addChild(self.healthBar);
+ // Update health bar
+ self.updateHealthBar = function () {
+ var percent = Math.max(0, self.health / self.maxHealth);
+ self.healthBar.scale.x = 1.5 * percent;
+ if (percent < 0.3) {
+ self.healthBar.tint = 0xFF0000;
+ } else if (percent < 0.6) {
+ self.healthBar.tint = 0xFFFF00;
+ } else {
+ self.healthBar.tint = 0x00FF00;
+ }
+ self.healthBar.visible = percent < 1;
+ self.healthBarBg.visible = percent < 1;
+ };
+ // Take damage
+ self.takeDamage = function (amount) {
+ self.health -= amount;
+ self.updateHealthBar();
+ LK.effects.flashObject(self, 0xff0000, 200);
+ if (self.health <= 0) {
+ self.active = false;
+ tween(self, {
+ alpha: 0
+ }, {
+ duration: 400
+ });
+ self.visible = false;
+ }
+ };
+ // Ally update: follow sniper or attack nearest enemy
self.update = function () {
- // Move forward and attack nearest enemy
- var closest = null,
- minDist = 99999;
+ if (!self.active) return;
+ self.updateHealthBar();
+ self.updateWalkAnim();
+ // Find nearest enemy in range
+ var nearest = null;
+ var minDist = 99999;
for (var i = 0; i < enemies.length; i++) {
- if (enemies[i].active) {
- var d = Math.abs(enemies[i].x - self.x) + Math.abs(enemies[i].y - self.y);
- if (d < minDist) {
- minDist = d;
- closest = enemies[i];
- }
+ var e = enemies[i];
+ if (!e.active) continue;
+ var dx = e.x - self.x;
+ var dy = e.y - self.y;
+ var dist = Math.sqrt(dx * dx + dy * dy);
+ if (dist < minDist) {
+ minDist = dist;
+ nearest = e;
}
}
- if (closest && minDist < 120) {
- closest.takeDamage(1);
- } else if (closest) {
- // Move towards enemy
- var dx = closest.x - self.x;
- var dy = closest.y - self.y;
+ var now = Date.now();
+ if (nearest) {
+ // Always move toward nearest enemy if any exist
+ self.targetEnemy = nearest;
+ var angle = Math.atan2(nearest.y - self.y, nearest.x - self.x);
+ // If not in attack range, move toward enemy
+ if (minDist > self.attackRange * 0.7) {
+ self.x += Math.cos(angle) * self.speed;
+ self.y += Math.sin(angle) * self.speed;
+ }
+ // Attack if in range and cooldown ready
+ if (minDist <= self.attackRange && now - self.lastAttack > self.attackCooldown) {
+ self.lastAttack = now;
+ nearest.takeDamage(self.damage);
+ LK.effects.flashObject(nearest, 0x00FFFF, 120);
+ }
+ } else {
+ // No enemy present, follow sniper
+ self.targetEnemy = null;
+ var dx = sniper.x - self.x;
+ var dy = sniper.y - self.y;
var dist = Math.sqrt(dx * dx + dy * dy);
- if (dist > 0) {
- self.x += dx / dist * 8;
- self.y += dy / dist * 8;
+ if (dist > self.followDistance) {
+ var angle = Math.atan2(dy, dx);
+ self.x += Math.cos(angle) * self.speed * 0.7;
+ self.y += Math.sin(angle) * self.speed * 0.7;
}
}
- // Remove if dead
- if (self.health <= 0) self.active = false;
};
return self;
});
-// BuildingShop class (stub)
var BuildingShop = Container.expand(function () {
var self = Container.call(this);
+ self.isOpen = false;
+ self.buildings = [{
+ name: "Basic Wall",
+ price: 10,
+ health: 100,
+ owned: false
+ }, {
+ name: "Reinforced Wall",
+ price: 20,
+ health: 200,
+ owned: false
+ }, {
+ name: "Sniper Tower",
+ price: 30,
+ health: 300,
+ damage: 2,
+ fireRate: 2000,
+ owned: false
+ }];
+ // Shop button
+ self.shopButton = new Text2("BUILDINGS", {
+ size: 80,
+ fill: 0xFFFF00
+ });
+ self.shopButton.anchor.set(0.5, 0);
+ // Add background to make button more visible
+ var buttonBg = LK.getAsset('bullet', {
+ width: 300,
+ height: 100,
+ anchorX: 0.5,
+ anchorY: 0.5,
+ tint: 0x333333
+ });
+ buttonBg.alpha = 0.8;
+ buttonBg.y = self.shopButton.height / 2;
+ self.addChild(buttonBg);
+ self.addChild(self.shopButton);
+ // Shop panel (hidden by default)
+ self.panel = new Container();
+ self.panel.visible = false;
+ self.addChild(self.panel);
+ // Create building options
+ self.buildingItems = [];
+ for (var i = 0; i < self.buildings.length; i++) {
+ var building = self.buildings[i];
+ var item = new Container();
+ var bg = LK.getAsset('bullet', {
+ width: 400,
+ height: 150,
+ anchorX: 0.5,
+ anchorY: 0.5,
+ tint: 0x333333
+ });
+ bg.alpha = 0.8;
+ item.addChild(bg);
+ var title = new Text2(building.name, {
+ size: 40,
+ fill: 0xFFFFFF
+ });
+ title.anchor.set(0.5, 0);
+ title.y = -50;
+ item.addChild(title);
+ var info;
+ if (building.name === "Sniper Tower") {
+ info = new Text2("Health: " + building.health + " | Damage: " + building.damage, {
+ size: 30,
+ fill: 0xFFFFFF
+ });
+ } else {
+ info = new Text2("Health: " + building.health, {
+ size: 30,
+ fill: 0xFFFFFF
+ });
+ }
+ info.anchor.set(0.5, 0);
+ info.y = 0;
+ item.addChild(info);
+ var priceText = new Text2(building.owned ? "OWNED" : "$" + building.price, {
+ size: 35,
+ fill: building.owned ? 0x00FF00 : 0xFFFF00
+ });
+ priceText.anchor.set(0.5, 0);
+ priceText.y = 40;
+ item.addChild(priceText);
+ item.y = i * 200;
+ item.buildingIndex = i;
+ self.buildingItems.push(item);
+ self.panel.addChild(item);
+ }
+ // Position panel
+ self.panel.y = 100;
+ // Handle shop button press
+ self.shopButton.down = function (x, y, obj) {
+ self.toggleShop();
+ };
+ // Toggle shop visibility
+ self.toggleShop = function () {
+ self.isOpen = !self.isOpen;
+ self.panel.visible = self.isOpen;
+ };
+ // Handle building selection/purchase
+ self.selectBuilding = function (index) {
+ var building = self.buildings[index];
+ if (currency >= building.price) {
+ // Always allow purchase, deduct currency every time
+ currency -= building.price;
+ updateUI();
+ // Update UI to show price (never "OWNED")
+ var priceText = self.buildingItems[index].children[3];
+ priceText.setText("$" + building.price, {
+ fill: 0xFFFF00
+ });
+ LK.getSound('upgrade').play();
+ // After purchase, immediately select it for placement
+ var buildingType;
+ var buildingHealth = building.health;
+ if (index === 0) {
+ buildingType = 'basic';
+ } else if (index === 1) {
+ buildingType = 'reinforced';
+ } else if (index === 2) {
+ buildingType = 'sniper';
+ }
+ // Create and add placeable wall
+ game.placeableWall = new PlaceableWall(buildingType, buildingHealth);
+ game.addChild(game.placeableWall);
+ game.isPlacing = true;
+ // Close shop
+ self.toggleShop();
+ return true;
+ } else if (building.owned) {
+ // (Legacy: If owned, allow placement without payment, but this should never be true now)
+ var buildingType;
+ var buildingHealth = building.health;
+ if (index === 0) {
+ buildingType = 'basic';
+ } else if (index === 1) {
+ buildingType = 'reinforced';
+ } else if (index === 2) {
+ buildingType = 'sniper';
+ }
+ game.placeableWall = new PlaceableWall(buildingType, buildingHealth);
+ game.addChild(game.placeableWall);
+ game.isPlacing = true;
+ self.toggleShop();
+ return true;
+ } else {
+ // Not enough currency
+ LK.effects.flashScreen(0xFF0000, 300);
+ return false;
+ }
+ };
+ // Check if an item was clicked
self.checkItemClick = function (x, y) {
+ if (!self.isOpen) return false;
+ var pos = self.toLocal({
+ x: x,
+ y: y
+ });
+ for (var i = 0; i < self.buildingItems.length; i++) {
+ var item = self.buildingItems[i];
+ // Use item's actual bounds for hit detection
+ var bounds = item.children[0].getBounds(); // children[0] is the background
+ // Convert bounds to local coordinates relative to the panel
+ var itemLeft = item.x + bounds.x - bounds.width * item.children[0].anchorX;
+ var itemRight = itemLeft + bounds.width;
+ var itemTop = item.y + bounds.y - bounds.height * item.children[0].anchorY;
+ var itemBottom = itemTop + bounds.height;
+ if (pos.x >= itemLeft && pos.x <= itemRight && pos.y >= itemTop && pos.y <= itemBottom) {
+ return self.selectBuilding(item.buildingIndex);
+ }
+ }
return false;
};
+ // Add down event handlers to each building item
+ for (var i = 0; i < self.buildingItems.length; i++) {
+ var item = self.buildingItems[i];
+ item.interactive = true;
+ item.index = i; // Store the index
+ // Using custom event handler for each item
+ (function (index) {
+ item.down = function (x, y, obj) {
+ self.selectBuilding(index);
+ };
+ })(i);
+ }
return self;
});
-// Bullet class (simple bullet)
-var Bullet = Container.expand(function (damage) {
+var Bullet = Container.expand(function () {
var self = Container.call(this);
- self.active = true;
- self.damage = damage || 1;
self.graphics = self.attachAsset('bullet', {
anchorX: 0.5,
anchorY: 0.5
});
- self.vx = 0;
- self.vy = -20;
- self.setDirection = function (angle) {
- self.vx = Math.cos(angle) * 20;
- self.vy = Math.sin(angle) * 20;
- };
+ self.speed = 15; // Default speed, will be overridden
+ self.damage = 1; // Default damage, will be overridden
+ self.active = true;
self.update = function () {
- self.x += self.vx;
- self.y += self.vy;
- if (self.x < -50 || self.x > 2100 || self.y < -50 || self.y > 2800) self.active = false;
+ if (!self.active) return;
+ // Use directional movement if speedX and speedY are defined
+ if (self.speedX !== undefined && self.speedY !== undefined) {
+ self.x += self.speedX;
+ self.y += self.speedY;
+ } else {
+ // Fallback to original vertical-only movement
+ self.y -= self.speed;
+ }
+ // Bullet goes off screen (check all edges)
+ if (self.y < -50 || self.y > 2732 + 50 || self.x < -50 || self.x > 2048 + 50) {
+ self.active = false;
+ }
};
self.hit = function () {
self.active = false;
+ LK.getSound('enemyHit').play();
+ LK.effects.flashObject(self, 0xffffff, 200);
};
return self;
});
-// Bunker class definition
var Bunker = Container.expand(function () {
var self = Container.call(this);
- // Attach bunker image asset
- var bunkerImg = self.attachAsset('bunker', {
+ self.graphics = self.attachAsset('bunker', {
anchorX: 0.5,
anchorY: 0.5
});
- // Add a damage indicator overlay (red box, invisible by default)
- var damageIndicator = self.attachAsset('damageIndicator', {
+ self.damageIndicator = self.attachAsset('damageIndicator', {
anchorX: 0.5,
- anchorY: 0.5,
- alpha: 0
+ anchorY: 0.5
});
- damageIndicator.originalX = 0;
- damageIndicator.originalY = 0;
- self.damageIndicator = damageIndicator;
- // Show damage effect (e.g. shake or flash)
- self.showDamage = function (healthRatio) {
- // Health ratio: 1 = full, 0 = dead
- // Fade in damage indicator if health is low
- if (healthRatio < 0.5) {
- damageIndicator.alpha = 0.5 + (0.5 - healthRatio);
- } else {
- damageIndicator.alpha = 0;
- }
- // Optionally, shake or animate
- damageIndicator.x = damageIndicator.originalX + (Math.random() * 10 - 5);
- damageIndicator.y = damageIndicator.originalY + (Math.random() * 10 - 5);
+ self.damageIndicator.alpha = 0;
+ self.damageIndicator.originalX = 0;
+ self.damageIndicator.originalY = 0;
+ self.showDamage = function (percentage) {
+ // Make indicator more visible as health decreases
+ self.damageIndicator.alpha = 1 - percentage;
+ // Move the indicator based on health percentage
+ // Lower health = more movement
+ var moveFactor = (1 - percentage) * 10;
+ self.damageIndicator.x = self.damageIndicator.originalX + (Math.random() * 2 - 1) * moveFactor;
+ self.damageIndicator.y = self.damageIndicator.originalY + (Math.random() * 2 - 1) * moveFactor;
+ // Change tint to become redder as health decreases
+ var healthColor = Math.floor(percentage * 255);
+ self.damageIndicator.tint = 255 << 16 | healthColor << 8 | healthColor;
};
- // Reset damage indicator position
- self.resetDamageIndicator = function () {
- damageIndicator.x = damageIndicator.originalX;
- damageIndicator.y = damageIndicator.originalY;
- damageIndicator.alpha = 0;
+ // Store original position
+ self.onAddedToStage = function () {
+ self.damageIndicator.originalX = self.damageIndicator.x;
+ self.damageIndicator.originalY = self.damageIndicator.y;
};
- // Call reset on init
- self.resetDamageIndicator();
return self;
});
-// Bush class (decorative)
var Bush = Container.expand(function () {
var self = Container.call(this);
- self.graphics = self.attachAsset('shadow', {
+ // Random bush appearance with slight variation
+ var type = Math.floor(Math.random() * 3);
+ var size = Math.random() * 0.5 + 0.8; // Size between 0.8 and 1.3
+ var rotation = Math.random() * 0.3 - 0.15; // Slight rotation
+ // Create the bush using bullet shape with green tint
+ self.graphics = self.attachAsset('bullet', {
anchorX: 0.5,
anchorY: 0.5,
- tint: 0x228B22
+ tint: 0x2E8B57,
+ // Sea green color
+ scaleX: size,
+ scaleY: size
});
+ // Apply random rotation
+ self.graphics.rotation = rotation;
+ // Add some details to make bushes look different from each other
+ if (type === 0) {
+ // First type - taller bush
+ self.graphics.scale.y *= 1.3;
+ self.graphics.tint = 0x228B22; // Forest green
+ } else if (type === 1) {
+ // Second type - wider bush
+ self.graphics.scale.x *= 1.2;
+ self.graphics.tint = 0x006400; // Dark green
+ } else {
+ // Third type - round bush
+ self.graphics.tint = 0x3CB371; // Medium sea green
+ }
return self;
});
-// Enemy class (basic structure)
+// DesertBandit: Ranged enemy that keeps distance from allies and attacks from range
+var DesertBandit = Container.expand(function () {
+ var self = Container.call(this);
+ // Use a new image asset for Desert Bandit (add to Assets if not present)
+ self.graphics = self.attachAsset('desertBandit', {
+ anchorX: 0.5,
+ anchorY: 0.5,
+ scaleX: 2.5,
+ scaleY: 2.5
+ });
+ self.shadow = new Shadow();
+ self.shadow.y = 30;
+ var baseWidth = self.graphics.width * self.graphics.scale.x;
+ self.shadow.updateSize(baseWidth);
+ self.addChildAt(self.shadow, 0);
+ self.shadow.x = 0;
+ self.shadow.graphics.scale.x = baseWidth * 0.8 / 100;
+ self.shadow.graphics.alpha = 0.5;
+ self.type = 'desertBandit';
+ self.speed = 2.2;
+ self.hp = 3;
+ self.damage = 12;
+ self.points = 20;
+ self.currency = 4;
+ self.active = true;
+ self.attackRange = 420; // Bandit shooting range
+ self.minDistance = 220; // Minimum distance to keep from target
+ self.attackCooldown = 1200; // ms between shots
+ self.lastAttack = 0;
+ self.target = null;
+ self.isAttacking = false;
+ self.moveDirection = Math.random() > 0.5 ? 1 : -1;
+ self.moveCounter = 0;
+ self.maxMoveDistance = Math.random() * 30 + 20;
+ // Walk animation: oscillate left/right
+ self.updateWalkAnim = function () {
+ var walkOsc = Math.sin(LK.ticks * 0.18) * 0.28;
+ tween(self.graphics, {
+ rotation: walkOsc
+ }, {
+ duration: 180,
+ easing: tween.easeInOut
+ });
+ var walkCycle = Math.sin(LK.ticks * 0.15) * 6;
+ self.graphics.y = walkCycle;
+ };
+ // Bandit fires a bullet at the target
+ self.shootAt = function (target) {
+ var now = Date.now();
+ if (now - self.lastAttack < self.attackCooldown) return;
+ self.lastAttack = now;
+ if (!target || !target.active) return;
+ var bullet = new Bullet();
+ bullet.x = self.x;
+ bullet.y = self.y;
+ var dx = target.x - self.x;
+ var dy = target.y - self.y;
+ var dist = Math.sqrt(dx * dx + dy * dy);
+ if (dist === 0) dist = 0.0001;
+ bullet.speedX = dx / dist * 13;
+ bullet.speedY = dy / dist * 13;
+ bullet.damage = self.damage;
+ bullet.graphics.tint = 0xC2B280; // Sand color
+ bullet.graphics.scale.set(0.7, 1.2);
+ bullets.push(bullet);
+ if (game && typeof game.addChild === "function") game.addChild(bullet);
+ // Visual feedback
+ LK.effects.flashObject(self, 0xFFD700, 120);
+ };
+ self.update = function () {
+ if (!self.active) return;
+ self.updateWalkAnim();
+ // Find nearest target: prioritize wall, sniper tower, or main player, then ally
+ var bestTarget = null;
+ var minDist = 99999;
+ // 1. Walls (sniper towers and regular)
+ if (game && game.walls && game.walls.length > 0) {
+ for (var i = 0; i < game.walls.length; i++) {
+ var wall = game.walls[i];
+ if (wall.health > 0) {
+ var dx = wall.x - self.x;
+ var dy = wall.y - self.y;
+ var dist = Math.sqrt(dx * dx + dy * dy);
+ if (dist < minDist) {
+ minDist = dist;
+ bestTarget = wall;
+ }
+ }
+ }
+ }
+ // 2. Sniper (main player)
+ if (sniper && sniper.active) {
+ var dxS = sniper.x - self.x;
+ var dyS = sniper.y - self.y;
+ var distS = Math.sqrt(dxS * dxS + dyS * dyS);
+ if (distS < minDist) {
+ minDist = distS;
+ bestTarget = sniper;
+ }
+ }
+ // 3. Ally
+ if (ally && ally.active) {
+ var dxA = ally.x - self.x;
+ var dyA = ally.y - self.y;
+ var distA = Math.sqrt(dxA * dxA + dyA * dyA);
+ if (distA < minDist) {
+ minDist = distA;
+ bestTarget = ally;
+ }
+ }
+ self.target = bestTarget;
+ // If ally is approaching, keep distance and shoot
+ if (self.target && self.target === ally && minDist < self.attackRange) {
+ // Move away from ally if too close
+ if (minDist < self.minDistance) {
+ var angleAway = Math.atan2(self.y - ally.y, self.x - ally.x);
+ self.x += Math.cos(angleAway) * self.speed * 1.2;
+ self.y += Math.sin(angleAway) * self.speed * 1.2;
+ } else if (minDist > self.attackRange * 0.7) {
+ // Move closer if too far
+ var angleTo = Math.atan2(ally.y - self.y, ally.x - self.x);
+ self.x += Math.cos(angleTo) * self.speed;
+ self.y += Math.sin(angleTo) * self.speed;
+ }
+ // Shoot at ally if in range
+ if (minDist <= self.attackRange) {
+ self.shootAt(ally);
+ }
+ return;
+ }
+ // If target is wall, sniper tower, or player, attack from range
+ if (self.target && minDist < self.attackRange) {
+ // Keep distance: move away if too close, else shoot
+ if (minDist < self.minDistance) {
+ var angleAway = Math.atan2(self.y - self.target.y, self.x - self.target.x);
+ self.x += Math.cos(angleAway) * self.speed * 1.1;
+ self.y += Math.sin(angleAway) * self.speed * 1.1;
+ } else if (minDist > self.attackRange * 0.7) {
+ // Move closer if too far
+ var angleTo = Math.atan2(self.target.y - self.y, self.target.x - self.x);
+ self.x += Math.cos(angleTo) * self.speed;
+ self.y += Math.sin(angleTo) * self.speed;
+ }
+ // Shoot at target if in range
+ if (minDist <= self.attackRange) {
+ self.shootAt(self.target);
+ }
+ return;
+ }
+ // Default movement: move diagonally down
+ self.y += self.speed;
+ self.x += self.moveDirection * (self.speed * 0.5);
+ self.moveCounter += self.speed;
+ if (self.moveCounter >= self.maxMoveDistance) {
+ self.moveDirection *= -1;
+ self.moveCounter = 0;
+ self.maxMoveDistance = Math.random() * 30 + 20;
+ }
+ // Stay within screen bounds
+ if (self.x < 50) {
+ self.x = 50;
+ self.moveDirection = 1;
+ } else if (self.x > 2048 - 50) {
+ self.x = 2048 - 50;
+ self.moveDirection = -1;
+ }
+ // If reached bunker, attack it
+ if (self.y > bunkerY - 50) {
+ self.attackBunker();
+ }
+ };
+ self.takeDamage = function (amount) {
+ self.hp -= amount;
+ if (self.hp <= 0) {
+ self.die();
+ } else {
+ LK.effects.flashObject(self, 0xffffff, 200);
+ }
+ };
+ self.die = function () {
+ tween(self.shadow.graphics, {
+ alpha: 0
+ }, {
+ duration: 300
+ });
+ tween(self.graphics, {
+ rotation: Math.PI / 2,
+ alpha: 0.2
+ }, {
+ duration: 500,
+ easing: tween.easeOut
+ });
+ LK.setTimeout(function () {
+ if (self.graphics) {
+ tween(self.graphics, {
+ alpha: 0
+ }, {
+ duration: 800,
+ easing: tween.easeIn
+ });
+ }
+ }, 400);
+ self.active = false;
+ var currentScore = LK.getScore();
+ var scoreMultiplier = 1;
+ if (currentScore > 300) {
+ scoreMultiplier = 2.5;
+ } else if (currentScore > 200) {
+ scoreMultiplier = 2;
+ } else if (currentScore > 100) {
+ scoreMultiplier = 1.5;
+ }
+ LK.setScore(currentScore + self.points);
+ currency += Math.ceil(self.currency * scoreMultiplier);
+ updateUI();
+ };
+ self.attackBunker = function () {
+ bunkerHealth -= self.damage;
+ updateBunkerHealth();
+ LK.getSound('bunkerHit').play();
+ LK.effects.flashObject(bunker, 0xff0000, 500);
+ bunker.damageIndicator.alpha = 1;
+ self.active = false;
+ if (bunkerHealth <= 0) {
+ gameOver();
+ }
+ };
+ return self;
+});
var Enemy = Container.expand(function (type) {
var self = Container.call(this);
self.type = type || 'regular';
+ // Set properties based on enemy type
+ switch (self.type) {
+ case 'fast':
+ self.graphics = self.attachAsset('fastEnemy', {
+ anchorX: 0.5,
+ anchorY: 0.5,
+ scaleX: 2.5,
+ scaleY: 2.5
+ });
+ self.speed = 3;
+ self.hp = 1;
+ self.damage = 10;
+ self.points = 15;
+ self.currency = 2;
+ break;
+ case 'tank':
+ self.graphics = self.attachAsset('tankEnemy', {
+ anchorX: 0.5,
+ anchorY: 0.5,
+ scaleX: 2.5,
+ scaleY: 2.5
+ });
+ self.speed = 1;
+ self.hp = 5;
+ self.damage = 25;
+ self.points = 30;
+ self.currency = 5;
+ break;
+ default:
+ // regular
+ self.graphics = self.attachAsset('regularEnemy', {
+ anchorX: 0.5,
+ anchorY: 0.5,
+ scaleX: 2.5,
+ scaleY: 2.5
+ });
+ self.speed = 2;
+ self.hp = 2;
+ self.damage = 15;
+ self.points = 10;
+ self.currency = 1;
+ }
+ // Create and add shadow beneath enemy
+ self.shadow = new Shadow();
+ self.shadow.y = 30; // Position shadow slightly below enemy
+ // Update shadow size based on enemy type
+ var baseWidth = self.graphics.width * self.graphics.scale.x;
+ self.shadow.updateSize(baseWidth);
+ self.addChildAt(self.shadow, 0); // Add shadow behind the enemy
+ // Ensure the shadow is visible and properly positioned
+ self.shadow.x = 0;
+ self.shadow.graphics.scale.x = baseWidth * 0.8 / 100;
+ self.shadow.graphics.alpha = 0.5;
self.active = true;
- self.health = 1;
- self.isAttackingWall = false;
- self.attackTarget = null;
- self.attackAnimationTicks = 0;
- // Use different visuals for different types
- var assetId = 'regularEnemy';
- if (self.type === 'tank') assetId = 'tankEnemy';
- if (self.type === 'fast') assetId = 'fastEnemy';
- if (self.type === 'ranged') assetId = 'newEnemy';
- self.graphics = self.attachAsset(assetId, {
- anchorX: 0.5,
- anchorY: 0.5
- });
- self.x = 0;
- self.y = 0;
self.update = function () {
- // Basic movement towards bunker
- if (!self.isAttackingWall && self.active) {
- var dx = 0;
- var dy = 1;
- self.y += self.type === 'fast' ? 7 : self.type === 'tank' ? 2 : 4;
+ if (!self.active) return;
+ // Basic diagonal movement pattern
+ if (!self.moveDirection) {
+ self.moveDirection = Math.random() > 0.5 ? 1 : -1; // Random initial direction
+ self.moveCounter = 0;
+ self.maxMoveDistance = Math.random() * 30 + 20; // Random move distance
+ self.isAttackingWall = false;
+ self.attackTarget = null;
+ self.attackAnimationTicks = 0;
}
- // Attack wall if needed
- if (self.isAttackingWall && self.attackTarget && self.attackTarget.health > 0) {
+ // If enemy is attacking a wall or ally, perform attack animation
+ if (self.isAttackingWall && self.attackTarget) {
+ // Increment animation counter
self.attackAnimationTicks++;
- if (self.attackAnimationTicks % 30 === 0) {
- self.attackTarget.health -= self.type === 'tank' ? 2 : 1;
+ // Every 45 frames complete a full attack cycle
+ if (self.attackAnimationTicks < 20) {
+ // Pull back for attack - move head backwards
+ if (self.attackAnimationTicks === 1) {
+ tween(self.graphics, {
+ rotation: -0.3,
+ y: -10
+ }, {
+ duration: 300,
+ easing: tween.easeOut
+ });
+ }
+ } else if (self.attackAnimationTicks < 25) {
+ // Forward strike - move head forward quickly
+ if (self.attackAnimationTicks === 20) {
+ tween(self.graphics, {
+ rotation: 0.2,
+ y: 10
+ }, {
+ duration: 150,
+ easing: tween.easeIn
+ });
+ }
+ } else {
+ // Reset animation counter and damage the wall or ally
+ self.attackAnimationTicks = 0;
+ // Apply damage to wall or ally
+ if (self.attackTarget) {
+ if (self.attackTarget.takeDamage) {
+ self.attackTarget.takeDamage(self.damage / 5);
+ LK.effects.flashObject(self.attackTarget, 0xFF0000, 200);
+ }
+ }
}
+ // When wall or ally is destroyed, resume movement
+ if (!self.attackTarget || self.attackTarget.health !== undefined && self.attackTarget.health <= 0 || self.attackTarget.active !== undefined && self.attackTarget.active === false) {
+ self.isAttackingWall = false;
+ self.attackTarget = null;
+ // Reset position and rotation
+ tween(self.graphics, {
+ rotation: 0,
+ y: 0
+ }, {
+ duration: 200,
+ easing: tween.easeOut
+ });
+ }
+ return; // Don't move while attacking
}
- // Remove if off screen
- if (self.y > 2732 + 100) self.active = false;
+ // --- Begin: Sniper Tower targeting logic ---
+ // Priority: Attack sniper tower if present and in range, else ally, else bunker
+ var sniperTowerWall = null;
+ var minTowerDist = 99999;
+ if (typeof game !== "undefined" && game.walls && game.walls.length > 0) {
+ for (var i = 0; i < game.walls.length; i++) {
+ var wall = game.walls[i];
+ if (wall.type === "sniper" && wall.health > 0) {
+ var dxTower = wall.x - self.x;
+ var dyTower = wall.y - self.y;
+ var distTower = Math.sqrt(dxTower * dxTower + dyTower * dyTower);
+ if (distTower < minTowerDist) {
+ minTowerDist = distTower;
+ sniperTowerWall = wall;
+ }
+ }
+ }
+ }
+ if (sniperTowerWall) {
+ // Move toward sniper tower and attack if in range
+ var dxTower = sniperTowerWall.x - self.x;
+ var dyTower = sniperTowerWall.y - self.y;
+ var distTower = Math.sqrt(dxTower * dxTower + dyTower * dyTower);
+ if (distTower < 60) {
+ if (!self.isAttackingWall || self.attackTarget !== sniperTowerWall) {
+ self.isAttackingWall = true;
+ self.attackTarget = sniperTowerWall;
+ self.attackAnimationTicks = 0;
+ }
+ // Do not move if attacking tower
+ return;
+ } else {
+ // Move toward sniper tower if not in attack range
+ var angleToTower = Math.atan2(dyTower, dxTower);
+ self.x += Math.cos(angleToTower) * self.speed;
+ self.y += Math.sin(angleToTower) * self.speed;
+ // Add walking animation for following tower
+ if (LK.ticks % 10 === 0 && !self.isAttackingWall) {
+ tween(self.graphics, {
+ rotation: (dxTower > 0 ? 1 : -1) * 0.1
+ }, {
+ duration: 250,
+ easing: tween.easeInOut
+ });
+ }
+ return;
+ }
+ }
+ // --- End: Sniper Tower targeting logic ---
+ // Check for ally in range and attack if present and active
+ if (typeof ally !== "undefined" && ally && ally.active) {
+ var dxAlly = ally.x - self.x;
+ var dyAlly = ally.y - self.y;
+ var distAlly = Math.sqrt(dxAlly * dxAlly + dyAlly * dyAlly);
+ // If ally is present, always follow and attack ally
+ if (distAlly < 60) {
+ if (!self.isAttackingWall || self.attackTarget !== ally) {
+ self.isAttackingWall = true;
+ self.attackTarget = ally;
+ self.attackAnimationTicks = 0;
+ }
+ // Do not move if attacking ally
+ return;
+ } else {
+ // Move toward ally if not in attack range
+ var angleToAlly = Math.atan2(dyAlly, dxAlly);
+ self.x += Math.cos(angleToAlly) * self.speed;
+ self.y += Math.sin(angleToAlly) * self.speed;
+ // Add walking animation for following ally
+ if (LK.ticks % 10 === 0 && !self.isAttackingWall) {
+ tween(self.graphics, {
+ rotation: (dxAlly > 0 ? 1 : -1) * 0.1
+ }, {
+ duration: 250,
+ easing: tween.easeInOut
+ });
+ }
+ return;
+ }
+ }
+ // Move diagonally
+ self.y += self.speed;
+ self.x += self.moveDirection * (self.speed * 0.5);
+ // Switch direction after a certain distance
+ self.moveCounter += self.speed;
+ if (self.moveCounter >= self.maxMoveDistance) {
+ self.moveDirection *= -1; // Reverse direction
+ self.moveCounter = 0;
+ self.maxMoveDistance = Math.random() * 30 + 20; // New random distance
+ }
+ // Make the shadow grow or shrink slightly with height simulation
+ // Calculate height simulation based on movement cycle
+ var heightSimulation = Math.sin(LK.ticks * 0.05) * 0.1;
+ // Update shadow properties to create floating effect
+ self.shadow.graphics.alpha = 0.5 - heightSimulation * 0.1; // Vary opacity with more baseline visibility
+ self.shadow.graphics.scale.x = self.graphics.width * self.graphics.scale.x * 0.8 / 100 * (1 - heightSimulation); // Vary size
+ // Ensure shadow follows enemy even with movement
+ self.shadow.x = 0;
+ // Add walking animation
+ if (LK.ticks % 10 === 0 && !self.isAttackingWall) {
+ // Tilt the enemy slightly in the direction of movement
+ tween(self.graphics, {
+ rotation: self.moveDirection * 0.1
+ }, {
+ duration: 250,
+ easing: tween.easeInOut
+ });
+ }
+ // Ensure enemy stays within screen bounds
+ if (self.x < 50) {
+ self.x = 50;
+ self.moveDirection = 1;
+ } else if (self.x > 2048 - 50) {
+ self.x = 2048 - 50;
+ self.moveDirection = -1;
+ }
+ // Check if enemy reached the bunker
+ if (self.y > bunkerY - 50) {
+ self.attackBunker();
+ }
};
self.takeDamage = function (amount) {
- self.health -= amount || 1;
- if (self.health <= 0) {
- self.active = false;
- LK.setScore(LK.getScore() + 1);
+ self.hp -= amount;
+ if (self.hp <= 0) {
+ self.die();
+ } else {
+ LK.effects.flashObject(self, 0xffffff, 200);
}
};
+ self.die = function () {
+ // Fade out shadow when enemy dies
+ tween(self.shadow.graphics, {
+ alpha: 0
+ }, {
+ duration: 300
+ });
+ // Rotate enemy to make it lie on its side (90 degrees in radians)
+ tween(self.graphics, {
+ rotation: Math.PI / 2,
+ alpha: 0.2
+ }, {
+ duration: 500,
+ easing: tween.easeOut
+ });
+ // After rotation, fade out completely
+ LK.setTimeout(function () {
+ if (self.graphics) {
+ tween(self.graphics, {
+ alpha: 0
+ }, {
+ duration: 800,
+ easing: tween.easeIn
+ });
+ }
+ }, 400);
+ self.active = false;
+ var currentScore = LK.getScore();
+ var scoreMultiplier = 1;
+ // Increase rewards based on score progression
+ if (currentScore > 300) {
+ scoreMultiplier = 2.5;
+ } else if (currentScore > 200) {
+ scoreMultiplier = 2;
+ } else if (currentScore > 100) {
+ scoreMultiplier = 1.5;
+ }
+ LK.setScore(currentScore + self.points);
+ // Set different currency values based on enemy type with score multiplier
+ if (self.type === 'regular') {
+ currency += Math.ceil(3 * scoreMultiplier);
+ } else if (self.type === 'fast') {
+ currency += Math.ceil(4 * scoreMultiplier);
+ } else if (self.type === 'tank') {
+ currency += Math.ceil(6 * scoreMultiplier);
+ } else {
+ currency += Math.ceil(self.currency * scoreMultiplier); // Fallback
+ }
+ updateUI();
+ };
+ self.startAttackingWall = function (wall) {
+ self.isAttackingWall = true;
+ self.attackTarget = wall;
+ self.attackAnimationTicks = 0;
+ };
+ self.attackBunker = function () {
+ bunkerHealth -= self.damage;
+ updateBunkerHealth();
+ LK.getSound('bunkerHit').play();
+ LK.effects.flashObject(bunker, 0xff0000, 500);
+ // Make damage indicator visible when taking damage
+ bunker.damageIndicator.alpha = 1;
+ self.active = false;
+ // Check if bunker is destroyed
+ if (bunkerHealth <= 0) {
+ gameOver();
+ }
+ };
return self;
});
-// EnemyProjectile class
-var EnemyProjectile = Container.expand(function (startX, startY, targetX, targetY) {
+var MuzzleFlash = Container.expand(function () {
var self = Container.call(this);
- self.active = true;
- self.x = startX;
- self.y = startY;
- self.speed = 10;
- var dx = targetX - startX;
- var dy = targetY - startY;
- var dist = Math.sqrt(dx * dx + dy * dy);
- self.vx = dx / dist * self.speed;
- self.vy = dy / dist * self.speed;
+ // Create the flash using bullet shape with yellow-white tint
self.graphics = self.attachAsset('bullet', {
anchorX: 0.5,
anchorY: 0.5,
- tint: 0xff3333
+ tint: 0xFFFF99 // Bright yellow-white color for flash
});
+ // Initially scale small
+ self.graphics.scale.set(0.3, 0.3);
+ self.graphics.alpha = 0.9;
+ // Animation lifetime
+ self.duration = 100; // ms
+ self.startTime = 0;
+ self.active = false;
+ // Start the flash effect
+ self.flash = function () {
+ self.active = true;
+ self.startTime = Date.now();
+ self.visible = true;
+ // Reset and start animation
+ self.graphics.scale.set(0.8, 0.8);
+ self.graphics.alpha = 1;
+ };
+ // Update the flash animation
self.update = function () {
- self.x += self.vx;
- self.y += self.vy;
- // Check collision with bunker
- if (Math.abs(self.x - 2048 / 2) < 80 && Math.abs(self.y - (2732 - 100)) < 80) {
- bunkerHealth -= 5;
- updateBunkerHealth();
+ if (!self.active) return;
+ var elapsed = Date.now() - self.startTime;
+ var progress = elapsed / self.duration;
+ if (progress >= 1) {
+ // Animation complete
self.active = false;
+ self.visible = false;
+ return;
}
- // Remove if off screen
- if (self.x < -50 || self.x > 2100 || self.y < -50 || self.y > 2800) {
- self.active = false;
- }
+ // Animate scale and alpha
+ self.graphics.scale.set(0.8 * (1 - progress), 0.8 * (1 - progress));
+ self.graphics.alpha = 1 - progress;
};
+ // Hide initially
+ self.visible = false;
return self;
});
-// RangedEnemy class
-var RangedEnemy = Container.expand(function () {
- var self = Container.call(this, 'ranged');
- self.shootCooldown = 0;
- self.health = 2;
+var PlaceableWall = Container.expand(function (buildingType, buildingHealth) {
+ var self = Container.call(this);
+ // Create semi-transparent wall based on type
+ if (buildingType === 'reinforced') {
+ self.graphics = self.attachAsset('reinforcedWall', {
+ anchorX: 0.5,
+ anchorY: 0.5,
+ scaleX: 1.3,
+ scaleY: 1.3
+ });
+ } else if (buildingType === 'sniper') {
+ // Create a tower
+ self.graphics = self.attachAsset('sniperTower', {
+ anchorX: 0.5,
+ anchorY: 0.5,
+ scaleX: 1.5,
+ scaleY: 1.5
+ });
+ // No need to add extra sniper on top since it's included in the image
+ } else {
+ // Basic wall
+ self.graphics = self.attachAsset('basicWall', {
+ anchorX: 0.5,
+ anchorY: 0.5,
+ scaleX: 1.3,
+ scaleY: 1.3
+ });
+ }
+ // Make it semi-transparent
+ self.alpha = 0.7;
+ // Store building properties for when it's placed
+ self.buildingType = buildingType;
+ self.buildingHealth = buildingHealth;
+ return self;
+});
+// Security: Melee security guard with walk animation (like Ally/Enemy)
+var Security = Container.expand(function () {
+ var self = Container.call(this);
+ // Use ally soldier image for security for now (replace with unique asset if available)
+ self.graphics = self.attachAsset('allySoldier', {
+ anchorX: 0.5,
+ anchorY: 0.5,
+ scaleX: 1.2,
+ scaleY: 1.2
+ });
+ self.graphics.alpha = 1;
+ self.maxHealth = 120;
+ self.health = self.maxHealth;
+ self.damage = 2;
+ self.speed = 2.8;
+ self.attackRange = 90;
+ self.attackCooldown = 700;
+ self.walkAnimTick = 0;
+ self.lastMoveDirection = 1;
+ self.updateWalkAnim = function () {
+ // Simulate walk animation (same as Ally/Enemy)
+ if (LK.ticks % 10 === 0 && self.active) {
+ var dir = 1;
+ if (self.targetEnemy) {
+ dir = self.targetEnemy.x > self.x ? 1 : -1;
+ }
+ self.lastMoveDirection = dir;
+ tween(self.graphics, {
+ rotation: dir * 0.1
+ }, {
+ duration: 250,
+ easing: tween.easeInOut
+ });
+ }
+ var walkCycle = Math.sin(LK.ticks * 0.15) * 6;
+ self.graphics.y = walkCycle;
+ };
+ self.lastAttack = 0;
+ self.targetEnemy = null;
+ self.followDistance = 100;
+ self.active = true;
+ // Health bar background
+ self.healthBarBg = LK.getAsset('bullet', {
+ anchorX: 0.5,
+ anchorY: 0.5,
+ tint: 0x000000,
+ scaleX: 1.6,
+ scaleY: 0.22
+ });
+ self.healthBarBg.y = 60;
+ self.addChild(self.healthBarBg);
+ // Health bar fill
+ self.healthBar = LK.getAsset('bullet', {
+ anchorX: 0,
+ anchorY: 0.5,
+ tint: 0x00FF00,
+ scaleX: 1.5,
+ scaleY: 0.18
+ });
+ self.healthBar.y = 60;
+ self.healthBar.x = -self.healthBarBg.width / 2;
+ self.addChild(self.healthBar);
+ // Update health bar
+ self.updateHealthBar = function () {
+ var percent = Math.max(0, self.health / self.maxHealth);
+ self.healthBar.scale.x = 1.5 * percent;
+ if (percent < 0.3) {
+ self.healthBar.tint = 0xFF0000;
+ } else if (percent < 0.6) {
+ self.healthBar.tint = 0xFFFF00;
+ } else {
+ self.healthBar.tint = 0x00FF00;
+ }
+ self.healthBar.visible = percent < 1;
+ self.healthBarBg.visible = percent < 1;
+ };
+ // Take damage
+ self.takeDamage = function (amount) {
+ self.health -= amount;
+ self.updateHealthBar();
+ LK.effects.flashObject(self, 0xff0000, 200);
+ if (self.health <= 0) {
+ self.active = false;
+ tween(self, {
+ alpha: 0
+ }, {
+ duration: 400
+ });
+ self.visible = false;
+ }
+ };
+ // Security update: follow sniper or attack nearest enemy
self.update = function () {
- // Move forward until in range, then shoot
- var range = 600;
- var bunkerDist = Math.abs(self.y - (2732 - 100));
- if (bunkerDist > range) {
- self.y += 3;
+ if (!self.active) return;
+ self.updateHealthBar();
+ self.updateWalkAnim();
+ // Find nearest enemy in range
+ var nearest = null;
+ var minDist = 99999;
+ for (var i = 0; i < enemies.length; i++) {
+ var e = enemies[i];
+ if (!e.active) continue;
+ var dx = e.x - self.x;
+ var dy = e.y - self.y;
+ var dist = Math.sqrt(dx * dx + dy * dy);
+ if (dist < minDist) {
+ minDist = dist;
+ nearest = e;
+ }
+ }
+ var now = Date.now();
+ if (nearest) {
+ self.targetEnemy = nearest;
+ var angle = Math.atan2(nearest.y - self.y, nearest.x - self.x);
+ if (minDist > self.attackRange * 0.7) {
+ self.x += Math.cos(angle) * self.speed;
+ self.y += Math.sin(angle) * self.speed;
+ }
+ if (minDist <= self.attackRange && now - self.lastAttack > self.attackCooldown) {
+ self.lastAttack = now;
+ nearest.takeDamage(self.damage);
+ LK.effects.flashObject(nearest, 0x00FFFF, 120);
+ }
} else {
- // Shoot at bunker if cooldown allows
- if (self.shootCooldown <= 0) {
- var proj = new EnemyProjectile(self.x, self.y, 2048 / 2, 2732 - 100);
- enemyProjectiles.push(proj);
- game.addChild(proj);
- self.shootCooldown = 90 + Math.floor(Math.random() * 60);
- } else {
- self.shootCooldown--;
+ self.targetEnemy = null;
+ var dx = sniper.x - self.x;
+ var dy = sniper.y - self.y;
+ var dist = Math.sqrt(dx * dx + dy * dy);
+ if (dist > self.followDistance) {
+ var angle = Math.atan2(dy, dx);
+ self.x += Math.cos(angle) * self.speed * 0.7;
+ self.y += Math.sin(angle) * self.speed * 0.7;
}
}
- // Remove if off screen
- if (self.y > 2732 + 100) self.active = false;
};
return self;
});
-// Sniper class definition
+var Shadow = Container.expand(function () {
+ var self = Container.call(this);
+ // Create shadow using bullet shape
+ self.graphics = self.attachAsset('bullet', {
+ anchorX: 0.5,
+ anchorY: 0.5,
+ tint: 0x000000
+ });
+ // Set shadow properties
+ self.graphics.alpha = 0.5; // More visible shadow
+ // Squish the shadow to appear flat on the ground
+ self.graphics.scale.y = 0.3;
+ // Ensure the shadow is visible with default size
+ self.graphics.scale.x = 0.8;
+ // Method to update shadow size based on parent
+ self.updateSize = function (parentWidth) {
+ // Make shadow slightly smaller than parent
+ self.graphics.scale.x = parentWidth * 0.8 / 100;
+ };
+ return self;
+});
var Sniper = Container.expand(function () {
var self = Container.call(this);
- // Attach sniper image asset
- var sniperImg = self.attachAsset('sniper', {
+ self.graphics = self.attachAsset('sniper', {
anchorX: 0.5,
- anchorY: 0.5
+ anchorY: 0.5,
+ scaleX: 0.8,
+ scaleY: 0.8
});
- // Weapon state
- self.currentWeapon = "sniper";
- self.fireRate = 1800;
- self.bulletDamage = 1;
+ // Add rifle that will rotate toward cursor
+ self.rifle = self.attachAsset('rifle', {
+ anchorX: 0,
+ anchorY: 0.5,
+ scaleX: 1.7,
+ scaleY: 1.7
+ });
+ // Create references to all weapon graphics but only show the active one
+ self.weapons = {
+ basic: self.rifle,
+ sniper: LK.getAsset('sniper_rifle', {
+ anchorX: 0,
+ anchorY: 0.5,
+ scaleX: 1.2,
+ scaleY: 1.2
+ }),
+ "super": LK.getAsset('sniper_rifle', {
+ anchorX: 0,
+ anchorY: 0.5,
+ scaleX: 1.5,
+ scaleY: 1.5,
+ tint: 0xFF00FF // Magenta tint for Super Sniper
+ })
+ };
+ // Add other weapons but hide them initially
+ self.weapons.sniper.visible = false;
+ self.weapons.sniper.x = 10;
+ self.addChild(self.weapons.sniper);
+ self.weapons["super"].visible = false;
+ self.weapons["super"].x = 10;
+ self.addChild(self.weapons["super"]);
+ // Position rifle to appear like it's being held by the sniper
+ self.rifle.x = 10;
+ // Initialize weapon positions
+ Object.keys(self.weapons).forEach(function (key) {
+ // Standard positioning
+ self.weapons[key].x = 10;
+ self.weapons[key].y = 0;
+ if (key === 'basic') {
+ self.weapons[key].scale.x = 1.7;
+ self.weapons[key].scale.y = 1.7;
+ } else if (key === 'sniper') {
+ self.weapons[key].scale.x = 1.2;
+ self.weapons[key].scale.y = 1.2;
+ } else {
+ self.weapons[key].scale.x = 1.7;
+ self.weapons[key].scale.y = 1.7;
+ }
+ });
+ // Create muzzle flash effects for each weapon
self.muzzleFlashes = {};
- // Update aim (returns angle in radians)
- self.updateAim = function (x, y) {
- var dx = x - self.x;
- var dy = y - self.y;
- var angle = Math.atan2(dy, dx);
- sniperImg.rotation = angle;
+ Object.keys(self.weapons).forEach(function (key) {
+ var flash = new MuzzleFlash();
+ self.weapons[key].addChild(flash);
+ // Position at the end of each weapon
+ flash.x = self.weapons[key].width - 10;
+ flash.y = 0;
+ if (key === 'super') {
+ flash.graphics.tint = 0xFF00FF; // Magenta flash for super sniper
+ }
+ self.muzzleFlashes[key] = flash;
+ });
+ self.fireRate = 1000; // ms between shots
+ self.lastShot = 0;
+ self.bulletDamage = 1;
+ self.bulletSpeed = 15; // Default bullet speed
+ self.currentWeapon = 'basic'; // Track current weapon type
+ // Method to update rifle rotation based on cursor position
+ self.updateAim = function (targetX, targetY) {
+ // Calculate angle to target
+ var angle = Math.atan2(targetY - self.y, targetX - self.x);
+ // Determine if target is on left or right side of the screen
+ var isTargetOnLeftSide = targetX < 2048 / 2;
+ // Update sniper graphics based on which side target is on
+ if (isTargetOnLeftSide) {
+ // Target is on left side - character faces left
+ self.graphics.scale.x = -1; // Flip character horizontally
+ // Adjust weapon positions for left-facing stance
+ Object.keys(self.weapons).forEach(function (key) {
+ self.weapons[key].x = -10; // Offset for left side
+ self.weapons[key].scale.x = -1; // Flip weapon horizontally
+ });
+ } else {
+ // Target is on right side - character faces right
+ self.graphics.scale.x = 1; // Normal orientation
+ // Adjust weapon positions for right-facing stance
+ Object.keys(self.weapons).forEach(function (key) {
+ self.weapons[key].x = 10; // Normal position on right side
+ self.weapons[key].scale.x = 1; // Normal weapon orientation
+ });
+ }
+ // Set all weapons rotation to aim at target, properly adjusted for direction
+ if (isTargetOnLeftSide) {
+ // When facing left, we need to adjust the angle
+ self.weapons.basic.rotation = angle + Math.PI;
+ self.weapons.sniper.rotation = angle + Math.PI;
+ self.weapons["super"].rotation = angle + Math.PI;
+ } else {
+ self.weapons.basic.rotation = angle;
+ self.weapons.sniper.rotation = angle;
+ self.weapons["super"].rotation = angle;
+ }
return angle;
};
- // Shoot method (returns bullet or null)
- self.shoot = function (x, y) {
+ self.canShoot = function () {
+ var now = Date.now();
+ if (now - self.lastShot >= self.fireRate) {
+ self.lastShot = now;
+ return true;
+ }
+ return false;
+ };
+ self.switchWeapon = function (weaponIndex) {
+ // Hide all weapons first
+ self.weapons.basic.visible = false;
+ self.weapons.sniper.visible = false;
+ self.weapons["super"].visible = false;
+ // Show selected weapon based on index
+ switch (weaponIndex) {
+ case 0:
+ self.weapons.basic.visible = true;
+ self.currentWeapon = 'basic';
+ break;
+ case 1:
+ self.weapons.sniper.visible = true;
+ self.currentWeapon = 'sniper';
+ break;
+ case 2:
+ self.weapons["super"].visible = true;
+ self.currentWeapon = 'super';
+ break;
+ default:
+ self.weapons.basic.visible = true;
+ self.currentWeapon = 'basic';
+ }
+ // Reset magazine if switching weapon
var mag = magazines[self.currentWeapon];
- if (!mag || mag.reloading || mag.current <= 0) {
+ if (mag && mag.current > mag.max) mag.current = mag.max;
+ updateAmmoUI();
+ };
+ self.shoot = function (targetX, targetY) {
+ // Magazine and reload logic
+ var weapon = self.currentWeapon;
+ var mag = magazines[weapon];
+ if (mag.reloading) return null;
+ if (mag.current <= 0) {
+ // Start reload
+ mag.reloading = true;
+ updateAmmoUI();
+ // Animate reload: move weapon down and up, and show "reloading..." in UI
+ tween(self.weapons[weapon], {
+ y: 60,
+ alpha: 0.5
+ }, {
+ duration: Math.floor(mag.reloadTime * 0.4),
+ easing: tween.easeIn,
+ onFinish: function onFinish() {
+ tween(self.weapons[weapon], {
+ y: 0,
+ alpha: 1
+ }, {
+ duration: Math.floor(mag.reloadTime * 0.6),
+ easing: tween.easeOut
+ });
+ }
+ });
+ LK.setTimeout(function () {
+ mag.current = mag.max;
+ mag.reloading = false;
+ updateAmmoUI();
+ }, mag.reloadTime);
return null;
}
+ if (!self.canShoot()) return null;
mag.current--;
updateAmmoUI();
- // Create bullet
- var bullet = new Bullet(self.bulletDamage);
- bullet.x = self.x;
- bullet.y = self.y;
- var angle = Math.atan2(y - self.y, x - self.x);
- bullet.setDirection(angle);
- // Muzzle flash effect (optional)
- // (not implemented here, but structure is ready)
+ // Update rifle aim
+ var angle = self.updateAim(targetX, targetY);
+ var bullet = new Bullet();
+ // Determine if target is on left or right side of the screen
+ var isTargetOnLeftSide = targetX < 2048 / 2;
+ var activeWeapon = self.weapons[self.currentWeapon];
+ var rifleLength = activeWeapon.width;
+ // Calculate bullet spawn position at the muzzle, always at the tip of the rifle in world space
+ // Get the local muzzle position (rifle tip) in weapon's local space
+ var muzzleLocalX = rifleLength;
+ var muzzleLocalY = 0;
+ // Transform muzzle position to world space
+ // 1. Get weapon's rotation and scale.x (for left/right flip)
+ var weaponRotation = activeWeapon.rotation;
+ var weaponScaleX = activeWeapon.scale.x;
+ // 2. Calculate rotated and flipped muzzle position
+ var cosR = Math.cos(weaponRotation);
+ var sinR = Math.sin(weaponRotation);
+ var muzzleWorldX = self.x + (activeWeapon.x + muzzleLocalX * weaponScaleX) * cosR - muzzleLocalY * sinR;
+ var muzzleWorldY = self.y + (activeWeapon.y + muzzleLocalX * weaponScaleX) * sinR + muzzleLocalY * cosR;
+ // Place bullet at the muzzle tip
+ bullet.x = muzzleWorldX;
+ bullet.y = muzzleWorldY;
+ // Set bullet direction - always use the original angle for bullet direction
+ bullet.speedX = Math.cos(angle) * (self.currentWeapon === "super" ? self.bulletSpeed : bullet.speed);
+ bullet.speedY = Math.sin(angle) * (self.currentWeapon === "super" ? self.bulletSpeed : bullet.speed);
+ bullet.damage = self.bulletDamage;
+ bullet.speed = self.bulletSpeed; // Set bullet speed from sniper
+ // Customize bullet appearance based on weapon type
+ if (self.currentWeapon === 'sniper') {
+ bullet.graphics.tint = 0x33CCFF; // Blue tint for sniper bullets
+ bullet.graphics.scale.set(0.8, 1.5); // Thinner, longer bullets
+ } else if (self.currentWeapon === 'super') {
+ bullet.graphics.tint = 0xFF00FF; // Magenta tint for super sniper bullets
+ bullet.graphics.scale.set(1.2, 2.2); // Even longer, more powerful look
+ bullet.damage = self.bulletDamage; // Ensure correct damage
+ bullet.speed = self.bulletSpeed; // Ensure correct speed
+ }
+ // Trigger muzzle flash for current weapon
+ var muzzleFlash = self.muzzleFlashes[self.currentWeapon];
+ if (muzzleFlash) {
+ muzzleFlash.flash();
+ }
+ // Add weapon recoil effect based on weapon type
+ var recoilDistance = 10; // Default recoil for rifle
+ var recoilDuration = 100; // Default recoil recovery time
+ var originX = activeWeapon.x;
+ // Set different recoil parameters based on weapon type
+ if (self.currentWeapon === 'sniper') {
+ recoilDistance = 20; // Stronger recoil for sniper
+ recoilDuration = 150;
+ } else if (self.currentWeapon === 'super') {
+ recoilDistance = 35; // Even stronger recoil for super sniper
+ recoilDuration = 200;
+ }
+ // Calculate recoil direction (opposite to shot direction)
+ // For recoil, we need to consider which direction the weapon is facing
+ var recoilDirection = isTargetOnLeftSide ? 1 : -1; // Reverse for left-facing
+ var bulletAngle = angle; // Use the same angle as bullet direction
+ var recoilX = recoilDirection * Math.cos(bulletAngle) * recoilDistance;
+ var recoilY = -Math.sin(bulletAngle) * recoilDistance;
+ // Apply recoil to the active weapon
+ tween(activeWeapon, {
+ x: originX + recoilX,
+ y: recoilY
+ }, {
+ duration: 50,
+ // Quick recoil
+ easing: tween.easeOut,
+ onFinish: function onFinish() {
+ // Recover from recoil
+ tween(activeWeapon, {
+ x: originX,
+ y: 0
+ }, {
+ duration: recoilDuration,
+ easing: tween.easeInOut
+ });
+ }
+ });
+ LK.getSound('shoot').play();
return bullet;
};
return self;
});
-// Tree class (decorative)
var Tree = Container.expand(function () {
var self = Container.call(this);
- self.graphics = self.attachAsset('shadow', {
+ // Random tree appearance with variation
+ var type = Math.floor(Math.random() * 3);
+ var size = Math.random() * 0.7 + 1.2; // Size between 1.2 and 1.9
+ var rotation = Math.random() * 0.2 - 0.1; // Slight rotation
+ // Create the tree trunk using bullet shape with brown tint
+ var treeTrunk = self.attachAsset('bullet', {
anchorX: 0.5,
anchorY: 0.5,
- tint: 0x006400
+ tint: 0x8B4513,
+ // Saddle brown color
+ scaleX: size * 0.3,
+ scaleY: size * 0.8
});
+ // Create the tree crown using bullet shape with green tint
+ var treeCrown = self.attachAsset('bullet', {
+ anchorX: 0.5,
+ anchorY: 0.5,
+ tint: 0x228B22,
+ // Forest green color
+ scaleX: size * 1.2,
+ scaleY: size * 1.0
+ });
+ // Position crown on top of trunk
+ treeCrown.y = -treeTrunk.height * 0.5;
+ // Apply random rotation
+ self.rotation = rotation;
+ // Add some details to make trees look different from each other
+ if (type === 0) {
+ // First type - taller tree with darker green
+ treeCrown.scale.y *= 1.4;
+ treeCrown.tint = 0x006400; // Dark green
+ } else if (type === 1) {
+ // Second type - wider tree with lighter green
+ treeCrown.scale.x *= 1.3;
+ treeCrown.tint = 0x32CD32; // Lime green
+ } else {
+ // Third type - autumn tree with different color
+ treeCrown.tint = 0xFF8C00; // Dark orange
+ }
return self;
});
-// Wall class (simple wall)
var Wall = Container.expand(function (type, health) {
var self = Container.call(this);
+ // Set properties based on wall type
self.type = type || 'basic';
- self.health = health || 10;
- self.graphics = self.attachAsset(self.type === 'reinforced' ? 'reinforcedWall' : 'basicWall', {
+ self.maxHealth = health || 100;
+ self.health = self.maxHealth;
+ // Create wall graphic based on type
+ if (self.type === 'reinforced') {
+ self.graphics = self.attachAsset('reinforcedWall', {
+ anchorX: 0.5,
+ anchorY: 0.5,
+ scaleX: 1.3,
+ scaleY: 1.3
+ });
+ } else if (self.type === 'sniper') {
+ // Create a tower
+ self.graphics = self.attachAsset('sniperTower', {
+ anchorX: 0.5,
+ anchorY: 0.5,
+ scaleX: 1.5,
+ scaleY: 1.5
+ });
+ // Sniper tower stats
+ self.maxHealth = 300;
+ self.health = self.maxHealth;
+ self.towerDamage = 2;
+ self.towerRange = 700;
+ self.towerFireRate = 1200; // ms between shots
+ self.towerLastShot = 0;
+ self.towerTarget = null;
+ self.towerBulletSpeed = 18;
+ self.towerBullet = function (target) {
+ var bullet = new Bullet();
+ bullet.x = self.x;
+ bullet.y = self.y - self.graphics.height / 2;
+ var dx = target.x - bullet.x;
+ var dy = target.y - bullet.y;
+ var dist = Math.sqrt(dx * dx + dy * dy);
+ // Prevent division by zero and ensure bullet always moves
+ if (dist === 0) dist = 0.0001;
+ bullet.speedX = dx / dist * self.towerBulletSpeed;
+ bullet.speedY = dy / dist * self.towerBulletSpeed;
+ bullet.damage = self.towerDamage;
+ bullet.graphics.tint = 0x33CCFF;
+ bullet.graphics.scale.set(0.7, 1.3);
+ return bullet;
+ };
+ // No need to add extra sniper on top since it's included in the image
+ } else {
+ // Basic wall
+ self.graphics = self.attachAsset('basicWall', {
+ anchorX: 0.5,
+ anchorY: 0.5,
+ scaleX: 1.3,
+ scaleY: 1.3
+ });
+ }
+ // Add health bar
+ self.healthBar = new Container();
+ self.addChild(self.healthBar);
+ // Health bar background
+ self.healthBarBg = LK.getAsset('bullet', {
anchorX: 0.5,
- anchorY: 0.5
+ anchorY: 0.5,
+ tint: 0x000000,
+ scaleX: 3,
+ scaleY: 0.3
});
+ self.healthBarBg.y = -40;
+ self.healthBar.addChild(self.healthBarBg);
+ // Health bar fill
+ self.healthBarFill = LK.getAsset('bullet', {
+ anchorX: 0,
+ anchorY: 0.5,
+ tint: 0x00FF00,
+ scaleX: 3,
+ scaleY: 0.2
+ });
+ self.healthBarFill.y = -40;
+ self.healthBarFill.x = -self.healthBarBg.width / 2;
+ self.healthBar.addChild(self.healthBarFill);
+ // Hide health bar initially
+ self.healthBar.visible = false;
self.update = function () {
- // Visual health bar (not implemented)
- if (self.health <= 0) self.active = false;
+ // Show health bar if damaged, and always show for sniper tower if not full health
+ if (self.health < self.maxHealth || self.type === 'sniper' && self.health < self.maxHealth) {
+ self.healthBar.visible = true;
+ // Update health bar fill
+ var healthPercent = self.health / self.maxHealth;
+ self.healthBarFill.scale.x = 3 * healthPercent;
+ // Change color based on health
+ if (healthPercent < 0.3) {
+ self.healthBarFill.tint = 0xFF0000; // Red
+ } else if (healthPercent < 0.6) {
+ self.healthBarFill.tint = 0xFFFF00; // Yellow
+ } else {
+ self.healthBarFill.tint = 0x00FF00; // Green
+ }
+ } else {
+ self.healthBar.visible = false;
+ }
+ // Sniper tower AI: shoot at nearest enemy in range
+ if (self.type === 'sniper' && self.health > 0) {
+ var now = Date.now();
+ // Find nearest enemy in range
+ var nearest = null;
+ var minDist = 99999;
+ for (var i = 0; i < enemies.length; i++) {
+ var e = enemies[i];
+ if (!e.active) continue;
+ var dx = e.x - self.x;
+ var dy = e.y - self.y;
+ var dist = Math.sqrt(dx * dx + dy * dy);
+ if (dist < minDist && dist < self.towerRange) {
+ minDist = dist;
+ nearest = e;
+ }
+ }
+ if (nearest && now - self.towerLastShot > self.towerFireRate) {
+ self.towerLastShot = now;
+ var bullet = self.towerBullet(nearest);
+ if (bullet) {
+ bullets.push(bullet);
+ if (game && typeof game.addChild === "function") game.addChild(bullet);
+ }
+ }
+ }
};
+ self.takeDamage = function (amount) {
+ self.health -= amount;
+ if (self.health <= 0) {
+ self.destroy();
+ return true; // Wall destroyed
+ }
+ return false; // Wall still standing
+ };
return self;
});
-// WaveCountdown class (stub)
var WaveCountdown = Container.expand(function () {
var self = Container.call(this);
- self.visible = false;
- self.startCountdown = function (cb) {
- LK.setTimeout(cb, 1000);
+ self.countdownTime = 60; // 60 seconds default
+ self.active = false;
+ // Create countdown text
+ self.countdownText = new Text2(self.countdownTime, {
+ size: 150,
+ fill: 0xFFFFFF
+ });
+ self.countdownText.anchor.set(0.5, 0.5);
+ self.addChild(self.countdownText);
+ // Create skip button
+ self.skipButton = new Text2("SKIP", {
+ size: 80,
+ fill: 0xFFFF00
+ });
+ self.skipButton.anchor.set(0.5, 0.5);
+ self.skipButton.y = 100;
+ self.addChild(self.skipButton);
+ // Skip button background for better visibility
+ var skipButtonBg = LK.getAsset('bullet', {
+ width: 200,
+ height: 100,
+ anchorX: 0.5,
+ anchorY: 0.5,
+ tint: 0x333333
+ });
+ skipButtonBg.alpha = 0.8;
+ skipButtonBg.y = 100;
+ self.addChild(skipButtonBg);
+ // Custom swap implementation since swapChildren is not available
+ var parent = self.skipButton.parent;
+ var skipButtonIndex = parent.children.indexOf(skipButtonBg);
+ var buttonIndex = parent.children.indexOf(self.skipButton);
+ // Manual reordering of children to create swap effect
+ if (skipButtonIndex !== -1 && buttonIndex !== -1) {
+ // Remove both from parent
+ parent.removeChild(skipButtonBg);
+ parent.removeChild(self.skipButton);
+ // Add them back in reverse order
+ parent.addChild(self.skipButton);
+ parent.addChild(skipButtonBg);
+ }
+ // Timer for countdown
+ self.timer = null;
+ // Start countdown
+ self.startCountdown = function (onComplete) {
+ self.active = true;
+ self.visible = true;
+ self.countdownTime = 60;
+ self.countdownText.setText(self.countdownTime);
+ self.onComplete = onComplete;
+ // Update countdown every second
+ self.timer = LK.setInterval(function () {
+ self.countdownTime--;
+ self.countdownText.setText(self.countdownTime);
+ if (self.countdownTime <= 0) {
+ self.stopCountdown();
+ if (self.onComplete) self.onComplete();
+ }
+ }, 1000);
};
+ // Stop countdown
+ self.stopCountdown = function () {
+ if (self.timer) {
+ LK.clearInterval(self.timer);
+ self.timer = null;
+ }
+ self.active = false;
+ self.visible = false;
+ };
+ // Skip button event handler
+ self.skipButton.down = function (x, y, obj) {
+ if (self.active) {
+ self.stopCountdown();
+ if (self.onComplete) self.onComplete();
+ }
+ };
return self;
});
-// WeaponShop class (stub)
var WeaponShop = Container.expand(function () {
var self = Container.call(this);
- self.selectWeapon = function (idx) {};
+ self.isOpen = false;
+ self.weapons = [{
+ name: "Basic Rifle",
+ price: 0,
+ damage: 1,
+ speed: 15,
+ owned: true
+ }, {
+ name: "Sniper Rifle",
+ price: 50,
+ damage: 3,
+ speed: 30,
+ owned: false
+ }, {
+ name: "Super Sniper",
+ price: 150,
+ damage: 7,
+ speed: 40,
+ owned: false
+ }];
+ // Shop button
+ self.shopButton = new Text2("WEAPONS", {
+ size: 80,
+ fill: 0xFFFF00
+ });
+ self.shopButton.anchor.set(0.5, 0);
+ // Add background to make button more visible
+ var buttonBg = LK.getAsset('bullet', {
+ width: 300,
+ height: 100,
+ anchorX: 0.5,
+ anchorY: 0.5,
+ tint: 0x333333
+ });
+ buttonBg.alpha = 0.8;
+ buttonBg.y = self.shopButton.height / 2;
+ self.addChild(buttonBg);
+ self.addChild(self.shopButton);
+ // Shop panel (hidden by default)
+ self.panel = new Container();
+ self.panel.visible = false;
+ self.addChild(self.panel);
+ // Create weapon options
+ self.weaponItems = [];
+ for (var i = 0; i < self.weapons.length; i++) {
+ var weapon = self.weapons[i];
+ var item = new Container();
+ var bg = LK.getAsset('bullet', {
+ width: 400,
+ height: 150,
+ anchorX: 0.5,
+ anchorY: 0.5,
+ tint: 0x333333
+ });
+ bg.alpha = 0.8;
+ item.addChild(bg);
+ var title = new Text2(weapon.name, {
+ size: 40,
+ fill: 0xFFFFFF
+ });
+ title.anchor.set(0.5, 0);
+ title.y = -50;
+ item.addChild(title);
+ var info = new Text2("Damage: " + weapon.damage + " | Speed: " + weapon.speed, {
+ size: 30,
+ fill: 0xFFFFFF
+ });
+ info.anchor.set(0.5, 0);
+ info.y = 0;
+ item.addChild(info);
+ var priceText = new Text2(weapon.owned ? "OWNED" : "$" + weapon.price, {
+ size: 35,
+ fill: weapon.owned ? 0x00FF00 : 0xFFFF00
+ });
+ priceText.anchor.set(0.5, 0);
+ priceText.y = 40;
+ item.addChild(priceText);
+ item.y = i * 200;
+ item.weaponIndex = i;
+ self.weaponItems.push(item);
+ self.panel.addChild(item);
+ }
+ // Position panel
+ self.panel.y = 100;
+ // Handle shop button press
+ self.shopButton.down = function (x, y, obj) {
+ self.toggleShop();
+ };
+ // Toggle shop visibility
+ self.toggleShop = function () {
+ self.isOpen = !self.isOpen;
+ self.panel.visible = self.isOpen;
+ };
+ // Handle weapon selection
+ self.selectWeapon = function (index) {
+ var weapon = self.weapons[index];
+ if (weapon.owned) {
+ // Equip weapon
+ sniper.bulletDamage = weapon.damage;
+ sniper.bulletSpeed = weapon.speed;
+ sniper.switchWeapon(index); // Switch to selected weapon appearance
+ // Visual feedback for equipped weapon
+ for (var i = 0; i < self.weaponItems.length; i++) {
+ var priceText = self.weaponItems[i].children[3];
+ if (i === index) {
+ priceText.setText("EQUIPPED", {
+ fill: i === 2 ? 0xFF00FF : 0x00FFFF // Magenta for super sniper, cyan for others
+ });
+ } else if (self.weapons[i].owned) {
+ priceText.setText("OWNED", {
+ fill: i === 2 ? 0xFF00FF : 0x00FF00 // Magenta for super sniper, green for others
+ });
+ }
+ }
+ LK.getSound('upgrade').play();
+ return true;
+ } else if (currency >= weapon.price) {
+ // Purchase weapon
+ currency -= weapon.price;
+ weapon.owned = true;
+ // Update UI
+ var priceText = self.weaponItems[index].children[3];
+ priceText.setText("EQUIPPED", {
+ fill: index === 2 ? 0xFF00FF : 0x00FFFF // Magenta for super sniper, cyan for others
+ });
+ // Reset other weapon texts to "OWNED"
+ for (var i = 0; i < self.weaponItems.length; i++) {
+ if (i !== index && self.weapons[i].owned) {
+ self.weaponItems[i].children[3].setText("OWNED", {
+ fill: i === 2 ? 0xFF00FF : 0x00FF00 // Magenta for super sniper, green for others
+ });
+ }
+ }
+ // Equip weapon
+ sniper.bulletDamage = weapon.damage;
+ sniper.bulletSpeed = weapon.speed;
+ sniper.switchWeapon(index); // Switch to selected weapon appearance
+ LK.getSound('upgrade').play();
+ updateUI();
+ return true;
+ } else {
+ // Not enough currency
+ LK.effects.flashScreen(0xFF0000, 300);
+ return false;
+ }
+ };
+ // Check if an item was clicked
self.checkItemClick = function (x, y) {
+ if (!self.isOpen) return false;
+ var pos = self.toLocal({
+ x: x,
+ y: y
+ });
+ for (var i = 0; i < self.weaponItems.length; i++) {
+ var item = self.weaponItems[i];
+ // Use item's actual bounds for hit detection
+ var bounds = item.children[0].getBounds(); // children[0] is the background
+ // Convert bounds to local coordinates relative to the panel
+ var itemLeft = item.x + bounds.x - bounds.width * item.children[0].anchorX;
+ var itemRight = itemLeft + bounds.width;
+ var itemTop = item.y + bounds.y - bounds.height * item.children[0].anchorY;
+ var itemBottom = itemTop + bounds.height;
+ if (pos.x >= itemLeft && pos.x <= itemRight && pos.y >= itemTop && pos.y <= itemBottom) {
+ return self.selectWeapon(item.weaponIndex);
+ }
+ }
return false;
};
+ // Add down event handlers to each weapon item
+ for (var i = 0; i < self.weaponItems.length; i++) {
+ var item = self.weaponItems[i];
+ item.interactive = true;
+ item.index = i; // Store the index
+ // Using custom event handler for each item
+ (function (index) {
+ item.down = function (x, y, obj) {
+ self.selectWeapon(index);
+ };
+ })(i);
+ }
return self;
});
/****
@@ -338,9 +1956,8 @@
/****
* Game Code
****/
// Create background
-// New enemy visual asset
var background = game.attachAsset('background', {
width: 2048,
height: 2732,
anchorX: 0,
@@ -358,9 +1975,9 @@
var lastEnemySpawn = 0;
var gameActive = true;
var waveInProgress = false;
var enemyIncreasePerWave = 0.25; // 25% more enemies per wave
-var enemiesPerWave = 10; // Starting with 10 enemies
+var enemiesPerWave = 35; // Starting with 35 enemies
var enemiesSpawned = 0; // Track enemies spawned in current wave
var enemiesRequired = 10; // Initial enemies required for first wave
// Magazine system for each weapon type
var magazines = {
@@ -421,9 +2038,8 @@
// Arrays for tracking game objects
var bullets = [];
var enemies = [];
var walls = [];
-var enemyProjectiles = []; // Track projectiles from ranged enemies
// Ally instance (semi-transparent melee ally)
var ally = null;
// Create bunker
var bunker = new Bunker();
@@ -603,26 +2219,22 @@
// Determine enemy type based on score and wave progression
var enemyType = 'regular';
var random = Math.random();
// More advanced enemies appear based on score thresholds
- if (currentScore >= 350 && random < 0.18) {
- enemyType = 'ranged';
+ if (currentScore >= 350 && random < 0.25) {
+ enemyType = 'desertBandit';
} else if (currentScore >= 300 && random < 0.3) {
enemyType = 'tank';
- } else if (currentScore >= 200 && random < 0.18) {
- enemyType = 'ranged';
} else if (currentScore >= 150 && random < 0.25) {
enemyType = 'tank';
- } else if (currentScore >= 120 && random < 0.22) {
- enemyType = 'ranged';
} else if (currentScore >= 100 && random < 0.35) {
enemyType = 'fast';
} else if (currentScore >= 50 && random < 0.25) {
enemyType = 'fast';
}
var enemy;
- if (enemyType === 'ranged') {
- enemy = new RangedEnemy();
+ if (enemyType === 'desertBandit') {
+ enemy = new DesertBandit();
} else {
enemy = new Enemy(enemyType);
}
// Distribute enemies across the width of the screen
@@ -693,11 +2305,10 @@
// Check if all enemies for this wave are spawned and eliminated
if (waveInProgress && enemiesSpawned >= enemiesRequired && enemies.length === 0) {
// All enemies in current wave defeated, prepare for next wave
wave++;
- // Calculate new enemies required for next wave (increase by 25%), but cap at 40
+ // Calculate new enemies required for next wave (increase by 25%)
enemiesRequired = Math.ceil(enemiesPerWave * Math.pow(1 + 0.25, wave - 1));
- if (enemiesRequired > 40) enemiesRequired = 40;
// Reset enemies spawned counter
enemiesSpawned = 0;
// Decrease spawn rate with each wave (faster spawns) based on score and wave
var currentScore = LK.getScore();
@@ -841,20 +2452,8 @@
if (bullets[i].active) {
bullets[i].update();
}
}
- // Update enemy projectiles
- for (var i = enemyProjectiles.length - 1; i >= 0; i--) {
- var proj = enemyProjectiles[i];
- if (proj.active) {
- proj.update();
- } else {
- if (typeof game !== "undefined" && typeof game.removeChild === "function") {
- game.removeChild(proj);
- }
- enemyProjectiles.splice(i, 1);
- }
- }
// Update walls and check for wall-enemy collisions
if (game.walls && game.walls.length > 0) {
for (var i = game.walls.length - 1; i >= 0; i--) {
var wall = game.walls[i];
kaya. No background. Transparent background. Blank background. No shadows. 2d. In-Game asset. flat
sopalı düşman adam . No background. Transparent background. Blank background. No shadows. 2d. In-Game asset. flat
koşan ninja. No background. Transparent background. Blank background. No shadows. 2d. In-Game asset. flat
tek gözlü dev. In-Game asset. 2d. High contrast. No shadows
barbed wire wall. In-Game asset. 2d. High contrast. No shadows
gözcü kulesi. No background. Transparent background. Blank background. No shadows. 2d. In-Game asset. flat
yazıyı sil
Çöl haydut sniper. In-Game asset. 2d. High contrast. No shadows
önden zırhlı araç . No background. Transparent background. Blank background. No shadows. 2d. In-Game asset. flat
zırhlı aracaın arkası. In-Game asset. 2d. High contrast. No shadows
sağa giden askeri yeşil renkte zırhlı araç. In-Game asset. 2d. High contrast. No shadows