User prompt
para 0 a düşsün
User prompt
gölgeleri biraz daha büyüt ve yuvarlak hale getir ve düşmanın önüne gelsin altında kalsın
User prompt
bazı karkaterler kendi kendine ölüyor bunu düzelt
User prompt
işe yaradı gibi ama sıkılan mermilerde gölge olmasın ve düşman karakterlerinin gölgeleri onun gibi yürüme animasyonu var çok saçma sadece onu takip etsin yürüsün demedim ayrıca biraz daha altına yani önüne gelsin
User prompt
tamam o zaman düşman karakterlerin bizim müttefik karakterlerimizin altında gölge aseti takip etsin ve asetide oluştur
User prompt
neden gölgeler çalışmadı bunu düzeltilir misinb şuan hiç bir karakterin gölgeleri yok
User prompt
bütün asetlerin altına gölge oluştur ve maplerde çalılar ve ağaçlar yap
User prompt
1000 para ile başlat, ayrıca super müttefik saldırısı yaparken araçların arkasında duman çıksın
User prompt
0 dolardan başlasınn
User prompt
haritanın arkasında önüne doğru haraket edecekler soldan sağa doğru değil arkadan öne doğru
User prompt
müttefik zırhlı araçlar çok hızlılar yavşla ayrıca ana oyuncu sniper arkasından doğacaklar müttefik zırhlı araçlar
User prompt
çok hızlılar yavşla ayrıca ana oyuncu sniper arkasından doğacaklar
User prompt
bombanın üzerine 1000 dolarlık bir düğme ekle ona bastığımızda 10 tane müttefik zırhlı aracın aynanda arkadan haritanın diğer ucuna giderek yok olacak tabi o sırada önündeki tüm düşmanları öldürücek, birde oyunu atık 1000 dolardan başlat
User prompt
500 dolar ile başlat oyunu
User prompt
oyuna bomba ekle bomba düğmesi için solda müttefik düğmesinin üstüne bir düme ekle ücreti 50 dolar tıkladığımıza düğme saydamlaşıcak tekrar tıkladığımızda haritanın herhangibi bir yerine oraya bomba düşecek ama havadan düştüğü için yere çarptığında ilk başta sekecek sonra patlyıacak
User prompt
Slow down enemy spawn rate progression and make enemy group size increase more gradual
User prompt
oyun çok hızlı zorlaşıyor bunu düzellt daha yavaş düşmanlar çoğalsın ve güçlensin çok zor böyle
User prompt
düşman zırhlı araca vurduğumda bazen aseti gidip geliyor bunu düzelt
User prompt
bütün düşmanlar 2 dolar daha fazla para versin
User prompt
zırhlı araç hasar aldığında bir anlığına yok olup geri geliyor bunu düzelt ayrıca ikili doğma 200 scoredan 3 lu doğma 500 scorda 4 doğma ise 900 scordan oluşsun
User prompt
zırhlı araç 800 score sonra gelsin ayrıca düşmanların güçlenmesi biraz daha yavaş olsun
User prompt
90 dolarlık olan Tank Gun silahı için aset oluştur
User prompt
tank gun silahı için aset oluştur
User prompt
500 dolar dan başlamıcaz 0 dolara geç
User prompt
tamam şimdik 500 score dan başlamıcaz 0 geri al
/**** * 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 // Add shadow under ally (after graphics is created) self.shadow = new Shadow(); self.shadow.y = 48; var baseWidth = self.graphics.width * (self.graphics.scale ? self.graphics.scale.x : 1); if (self.shadow.updateSize) self.shadow.updateSize(baseWidth); self.shadow.x = 0; self.shadow.graphics.alpha = 0.5; self.addChildAt(self.shadow, 0); // In update, keep shadow under the character var _superUpdate = self.update; self.update = function () { // Shadow follows character if (self.shadow) { self.shadow.x = 0; self.shadow.y = 40; var baseWidth = self.graphics.width * (self.graphics.scale ? self.graphics.scale.x : 1); if (self.shadow.updateSize) self.shadow.updateSize(baseWidth); } if (_superUpdate) _superUpdate.apply(self, arguments); }; 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); // Show floating damage number if (typeof showDamageNumber === "function") { showDamageNumber(self.x, self.y - 40, amount, 0x00FFFF); } 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; // Prevent ally from dying if it moves off the top or sides of the screen if (self.x < 50) self.x = 50; if (self.x > 2048 - 50) self.x = 2048 - 50; if (self.y < -100) self.y = -100; self.updateHealthBar(); self.updateWalkAnim(); // Find nearest enemy anywhere on the map (no range limit) 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; // 10% chance to stun, 10% chance to slow, 5% chance to confuse, 10% crit var isCrit = Math.random() < 0.10; var effectRoll = Math.random(); if (isCrit) { nearest.takeDamage(self.damage * 2, 'basic', true); } else if (effectRoll < 0.10) { nearest.takeDamage(self.damage, 'stun'); } else if (effectRoll < 0.20) { nearest.takeDamage(self.damage, 'slow'); } else if (effectRoll < 0.25) { nearest.takeDamage(self.damage, 'confuse'); } else { nearest.takeDamage(self.damage, 'basic'); } 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; }); // Bomb: Air-dropped bomb that bounces and explodes var Bomb = Container.expand(function () { var self = Container.call(this); // Bomb body (yellow ellipse) self.bombBody = self.attachAsset('bullet', { anchorX: 0.5, anchorY: 0.5, scaleX: 2.2, scaleY: 2.2, tint: 0xFFD700 // Gold/yellow }); // Shadow self.shadow = LK.getAsset('bullet', { anchorX: 0.5, anchorY: 0.5, scaleX: 2.2, scaleY: 0.5, tint: 0x000000 }); self.shadow.alpha = 0.35; self.shadow.y = self.bombBody.height * 1.1; self.addChild(self.shadow); self.vy = 0; // vertical speed self.gravity = 2.2; // gravity self.bounceCount = 0; self.maxBounces = 1; // Only bounce once self.bounced = false; self.exploded = false; self.groundY = null; // set on spawn // Explosion effect self.explode = function () { if (self.exploded) return; self.exploded = true; // Flash and scale up bomb tween(self.bombBody, { scaleX: 5, scaleY: 5, alpha: 0 }, { duration: 220, easing: tween.easeOut, onFinish: function onFinish() { self.visible = false; self.active = false; } }); // Flash screen LK.effects.flashScreen(0xFFFF00, 200); // Damage all enemies in radius var radius = 220; 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 < radius) { e.takeDamage(20, 'basic', true); } } // Show explosion text showDamageNumber(self.x, self.y - 60, "BOOM", 0xFFD700, "CRIT"); }; self.update = function () { if (self.exploded) return; if (self.groundY === null) { // Set groundY to the first y where bomb lands (simulate ground) self.groundY = self.y + 320; } // Move bomb down self.y += self.vy; self.vy += self.gravity; // Shadow follows x, stays at groundY self.shadow.x = 0; self.shadow.y = self.bombBody.height * 1.1 + (self.groundY - self.y); // Bounce logic if (!self.bounced && self.y >= self.groundY) { self.bounced = true; self.bounceCount++; // Bounce up with less speed self.y = self.groundY; self.vy = -22; // Squash bomb for bounce tween(self.bombBody, { scaleY: 1.2, scaleX: 2.8 }, { duration: 80, yoyo: true, repeat: 1 }); // Play bounce sound/effect (optional) } else if (self.bounced && self.y >= self.groundY) { // Landed after bounce, explode self.y = self.groundY; self.vy = 0; self.explode(); } }; self.active = true; 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: 100, 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 = 250; // 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); // No shadow for bullets 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); // Add shadow under bunker self.shadow = new Shadow(); self.shadow.y = 60; self.addChildAt(self.shadow, 0); if (self.graphics) { var baseWidth = self.graphics.width * (self.graphics.scale ? self.graphics.scale.x : 1); if (self.shadow.updateSize) self.shadow.updateSize(baseWidth); self.shadow.x = 0; self.shadow.graphics.scale.x = baseWidth * 0.8 / 100; self.shadow.graphics.alpha = 0.5; } 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; }); 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 (after graphics is created) self.shadow = new Shadow(); // Position shadow further in front of enemy, and a bit more forward self.shadow.y = self.graphics.height * 0.62; // More in front var baseWidth = self.graphics.width * self.graphics.scale.x; if (self.shadow.updateSize) self.shadow.updateSize(baseWidth); self.shadow.x = 0; self.shadow.graphics.alpha = 0.5; self.addChildAt(self.shadow, 0); // Add shadow behind the enemy // In update, keep shadow under the character var _superUpdate = self.update; self.update = function () { // Shadow always follows enemy, not walk animation if (self.shadow) { self.shadow.x = 0; self.shadow.y = self.graphics.height * 0.62; // More in front var baseWidth = self.graphics.width * self.graphics.scale.x; if (self.shadow.updateSize) self.shadow.updateSize(baseWidth); } if (_superUpdate) _superUpdate.apply(self, arguments); }; self.active = true; self.update = function () { if (!self.active) return; // --- Begin: Status effect processing --- // Burning: take 1 damage every 400ms while burning if (self.burningUntil && Date.now() < self.burningUntil) { if (!self.burnTick) self.burnTick = Date.now(); if (Date.now() - self.burnTick > 400) { self.burnTick = Date.now(); self.hp -= 1; if (typeof showDamageNumber === "function") { showDamageNumber(self.x, self.y - 40, 1, 0xFF6600); } LK.effects.flashObject(self, 0xFF6600, 100); if (self.hp <= 0) { self.die(); return; } } } else if (self.burningUntil && Date.now() >= self.burningUntil) { self.burningUntil = null; self.burnTick = null; } // Poison: take 1 damage every 600ms while poisoned if (self.poisonedUntil && Date.now() < self.poisonedUntil) { if (!self.poisonTick) self.poisonTick = Date.now(); if (Date.now() - self.poisonTick > 600) { self.poisonTick = Date.now(); self.hp -= 1; if (typeof showDamageNumber === "function") { showDamageNumber(self.x, self.y - 40, 1, 0x00FF66); } LK.effects.flashObject(self, 0x00FF66, 100); if (self.hp <= 0) { self.die(); return; } } } else if (self.poisonedUntil && Date.now() >= self.poisonedUntil) { self.poisonedUntil = null; self.poisonTick = null; } // Shield: show visual effect if shielded if (self.hasShield && self.graphics) { if (!self.shieldGlow) { self.shieldGlow = LK.getAsset('bullet', { anchorX: 0.5, anchorY: 0.5, scaleX: 2.8, scaleY: 2.8, tint: 0xFFFF00 }); self.shieldGlow.alpha = 0.25; self.addChildAt(self.shieldGlow, 1); } } else if (self.shieldGlow) { self.removeChild(self.shieldGlow); self.shieldGlow = null; } // --- End: Status effect processing --- // --- Status effect handling: stun, slow, confuse --- var now = Date.now(); if (self.stunnedUntil && now < self.stunnedUntil) { // Stunned: skip all movement and attack, but allow animation if (self.graphics) { tween(self.graphics, { rotation: 0 }, { duration: 120 }); } // Still allow walk animation for visual feedback var walkCycle = Math.sin(LK.ticks * 0.15) * 6; self.graphics.y = walkCycle; return; } else if (self.stunnedUntil && now >= self.stunnedUntil) { self.stunnedUntil = null; } if (self.slowedUntil && now < self.slowedUntil) { self.speedModifier = self.slowFactor || 0.5; } else if (self.slowedUntil && now >= self.slowedUntil) { self.slowedUntil = null; self.speedModifier = 1; } if (self.confusedUntil && now < self.confusedUntil) { // Randomize move direction if (Math.random() < 0.1) { self.moveDirection = Math.random() > 0.5 ? 1 : -1; } } else if (self.confusedUntil && now >= self.confusedUntil) { self.confusedUntil = null; } // Default speed modifier if (typeof self.speedModifier !== "number") self.speedModifier = 1; // Armored vehicle animation: switch asset for all 8 directions (including diagonals) if (self.type === "armoredVehicle" && self.graphics) { // Prevent asset switch if _preventAssetSwitch is set (e.g. during damage flash) if (self._preventAssetSwitch) { // Still update lastX/lastY for correct direction after flash self.lastX = self.x; self.lastY = self.y; } else { // Calculate movement direction var dx = 0, dy = 0; if (typeof self.lastX === "number" && typeof self.lastY === "number") { dx = self.x - self.lastX; dy = self.y - self.lastY; } // Only update if moved enough to change direction // Only switch asset if direction meaningfully changed (not for tiny dx/dy) var minMoveDist = 2.5; // Only consider direction change if moved at least this much if (Math.abs(dx) > minMoveDist || Math.abs(dy) > minMoveDist) { var dirAsset = "armoredVehicle_front"; // Calculate angle in degrees for direction var angle = Math.atan2(dy, dx) * 180 / Math.PI; // Normalize angle to [0, 360) if (angle < 0) angle += 360; // 8-directional asset selection if (angle >= 337.5 || angle < 22.5) { dirAsset = "armoredVehicle_right"; } else if (angle >= 22.5 && angle < 67.5) { dirAsset = "armoredVehicle_frontRight"; } else if (angle >= 67.5 && angle < 112.5) { dirAsset = "armoredVehicle_front"; } else if (angle >= 112.5 && angle < 157.5) { dirAsset = "armoredVehicle_frontLeft"; } else if (angle >= 157.5 && angle < 202.5) { dirAsset = "armoredVehicle_left"; } else if (angle >= 202.5 && angle < 247.5) { dirAsset = "armoredVehicle_backLeft"; } else if (angle >= 247.5 && angle < 292.5) { dirAsset = "armoredVehicle_back"; } else if (angle >= 292.5 && angle < 337.5) { dirAsset = "armoredVehicle_backRight"; } // Always use 'front' asset if moving almost straight down (dy > 0, dx is small) if (Math.abs(dx) < minMoveDist && dy > minMoveDist) { dirAsset = "armoredVehicle_front"; } // Only change asset if needed (and only if direction changed) if (!self.graphics.assetId || self.graphics.assetId !== dirAsset) { // Remove old graphics self.removeChild(self.graphics); self.graphics = self.attachAsset(dirAsset, { anchorX: 0.5, anchorY: 0.5, scaleX: 1.7, scaleY: 1.7 }); // Always set assetId to prevent asset loss self.graphics.assetId = dirAsset; // Ensure shadow stays behind if (self.shadow) { self.setChildIndex(self.shadow, 0); } } // Save last direction asset for next frame self._lastArmoredVehicleAsset = dirAsset; } // Save last position for next frame self.lastX = self.x; self.lastY = self.y; } } // 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) { // --- Always run walk animation, even while attacking --- // Diagonal walk: oscillate rotation left and right as walking (like Ally) var walkOsc = Math.sin(LK.ticks * 0.18) * 0.28; // Always reset scale to default before tween to prevent cumulative shrinking self.graphics.scale.x = self.type === 'fast' || self.type === 'tank' || self.type === 'regular' ? 2.5 : self.graphics.scale.x; self.graphics.scale.y = self.type === 'fast' || self.type === 'tank' || self.type === 'regular' ? 2.5 : self.graphics.scale.y; tween(self.graphics, { rotation: walkOsc }, { duration: 180, easing: tween.easeInOut }); // Add up/down bobbing for walk animation var walkCycle = Math.sin(LK.ticks * 0.15) * 6; self.graphics.y = walkCycle; // --- Ava fix: If being tweened (knockback), do not freeze in place --- // If a knockback tween is active, allow movement to resume after knockback ends if (self._knockbackTweenActive) { // Wait for knockback tween to finish before resuming attack animation return; } // 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); // --- FIX: Armored vehicle must attack only from close range, not from distance --- var attackRange = 60; if (self.type === "armoredVehicle") { attackRange = 60; // Same as other enemies, no ranged attack } if (distTower < attackRange) { 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 (always run, not just every 10 ticks, to prevent animation freeze) if (!self.isAttackingWall) { // Only apply walk animation to non-armoredVehicle types if (self.type === 'fast' || self.type === 'tank' || self.type === 'regular') { // Diagonal walk: oscillate rotation left and right as walking (like Ally) var walkOsc = Math.sin(LK.ticks * 0.18) * 0.28; // Always reset scale to default before tween to prevent cumulative shrinking self.graphics.scale.x = 2.5; self.graphics.scale.y = 2.5; tween(self.graphics, { rotation: walkOsc }, { duration: 180, easing: tween.easeInOut }); // Add up/down bobbing for walk animation var walkCycle = Math.sin(LK.ticks * 0.15) * 6; self.graphics.y = walkCycle; } // For armoredVehicle, do not animate walk at all (no rotation, no bobbing) } // Ensure enemy stays within screen bounds (but do NOT kill enemy if off screen, just clamp position) if (self.x < 50) { self.x = 50; self.moveDirection = 1; } else if (self.x > 2048 - 50) { self.x = 2048 - 50; self.moveDirection = -1; } // Prevent enemy from dying if it moves off the top or sides of the screen if (self.y < -100) { self.y = -100; } // Check if enemy reached the bunker if (self.y > bunkerY - 50) { self.attackBunker(); } }; self.takeDamage = function (amount, weaponType, isCritical) { // --- Begin: Advanced status effects and reactions --- weaponType = weaponType || 'basic'; isCritical = !!isCritical; // Burning: continuous damage over time, red flash if (weaponType === 'burn' && !self.burningUntil) { self.burningUntil = Date.now() + 2500; self.burnTick = Date.now(); LK.effects.flashObject(self, 0xFF6600, 200); } // Freezing: slow and blue flash if (weaponType === 'freeze') { self.slowedUntil = Date.now() + 2500; self.slowFactor = 0.3; LK.effects.flashObject(self, 0x66CCFF, 200); } // Poison: green flash, damage over time if (weaponType === 'poison' && !self.poisonedUntil) { self.poisonedUntil = Date.now() + 4000; self.poisonTick = Date.now(); LK.effects.flashObject(self, 0x00FF66, 200); } // Shield: absorb one hit, yellow flash if (self.hasShield) { self.hasShield = false; LK.effects.flashObject(self, 0xFFFF00, 400); // Show floating shield break if (typeof showDamageNumber === "function") { showDamageNumber(self.x, self.y - 60, "SHIELD", 0xFFFF00); } return; } // --- End: Advanced status effects and reactions --- // Default weaponType to 'basic' if not provided weaponType = weaponType || 'basic'; isCritical = !!isCritical; // Critical hit: 2x damage, special effect if (isCritical) { amount = Math.floor(amount * 2); if (typeof showDamageNumber === "function") { showDamageNumber(self.x, self.y - 60, amount, 0xFFD700); // Gold color for crit } LK.effects.flashObject(self, 0xFFD700, 350); // Briefly scale up enemy for crit tween(self.graphics, { scaleX: self.type === 'armoredVehicle' ? 1.7 : self.graphics.scale.x * 1.3, scaleY: self.type === 'armoredVehicle' ? 1.7 : self.graphics.scale.y * 1.3 }, { duration: 80, yoyo: true, repeat: 1, onFinish: function onFinish() { if (self.type === 'fast' || self.type === 'tank' || self.type === 'regular') { self.graphics.scale.x = 2.5; self.graphics.scale.y = 2.5; } else if (self.type === 'armoredVehicle') { self.graphics.scale.x = 1.7; self.graphics.scale.y = 1.7; } } }); } self.hp -= amount; // Show floating damage number if (!isCritical && typeof showDamageNumber === "function") { showDamageNumber(self.x, self.y - 40, amount, 0xFF2222); } // --- Weapon-specific hit reactions --- if (self.hp > 0) { // Rifle: 10% chance to briefly stun if (weaponType === 'basic' && Math.random() < 0.10) { self.stunnedUntil = Date.now() + 350; LK.effects.flashObject(self, 0x00FFFF, 120); } // Sniper: always knockback if (weaponType === 'sniper') { var angle = Math.atan2(self.y - sniper.y, self.x - sniper.x); var knockbackDist = 60 + Math.random() * 30; var targetX = self.x + Math.cos(angle) * knockbackDist; var targetY = self.y + Math.sin(angle) * knockbackDist; // Clamp to screen targetX = Math.max(50, Math.min(2048 - 50, targetX)); targetY = Math.max(0, Math.min(2732 - 50, targetY)); // --- Ava fix: Set knockback tween flag so update() can allow movement after knockback --- // Only apply knockback tween if not armoredVehicle, armoredVehicle just flashes if (self.type === "armoredVehicle") { // Only flash, do not hide or move, and do NOT remove/re-add graphics to prevent flicker if (self.graphics) { // Temporarily set a flag to prevent asset switching in update during flash self._preventAssetSwitch = true; LK.effects.flashObject(self, 0x33CCFF, 180); // Remove the flag after flash duration LK.setTimeout(function () { self._preventAssetSwitch = false; }, 180); } } else { self._knockbackTweenActive = true; tween(self, { x: targetX, y: targetY }, { duration: 180, easing: tween.easeOut, onFinish: function onFinish() { self._knockbackTweenActive = false; } }); // Briefly flash blue LK.effects.flashObject(self, 0x33CCFF, 180); } } // Super Sniper: stun and strong knockback if (weaponType === 'super') { var angle = Math.atan2(self.y - sniper.y, self.x - sniper.x); var knockbackDist = 120 + Math.random() * 40; var targetX = self.x + Math.cos(angle) * knockbackDist; var targetY = self.y + Math.sin(angle) * knockbackDist; targetX = Math.max(50, Math.min(2048 - 50, targetX)); targetY = Math.max(0, Math.min(2732 - 50, targetY)); self._knockbackTweenActive = true; tween(self, { x: targetX, y: targetY }, { duration: 220, easing: tween.easeOut, onFinish: function onFinish() { self._knockbackTweenActive = false; } }); self.stunnedUntil = Date.now() + 600; LK.effects.flashObject(self, 0xFF00FF, 220); } // Tank: 20% chance to slow enemy for 2 seconds if (weaponType === 'tank' && Math.random() < 0.20) { self.slowedUntil = Date.now() + 2000; self.slowFactor = 0.5; LK.effects.flashObject(self, 0x00FF00, 200); } // Fast: 10% chance to confuse (random direction) for 1s if (weaponType === 'fast' && Math.random() < 0.10) { self.confusedUntil = Date.now() + 1000; LK.effects.flashObject(self, 0xFF00FF, 120); } // Burn: apply burning status if (weaponType === 'burn') { self.burningUntil = Date.now() + 2500; self.burnTick = Date.now(); if (typeof showDamageNumber === "function") { showDamageNumber(self.x, self.y - 60, "BURN", 0xFF6600); } LK.effects.flashObject(self, 0xFF6600, 200); } // Freeze: apply slow if (weaponType === 'freeze') { self.slowedUntil = Date.now() + 2500; self.slowFactor = 0.3; if (typeof showDamageNumber === "function") { showDamageNumber(self.x, self.y - 60, "FREEZE", 0x66CCFF); } LK.effects.flashObject(self, 0x66CCFF, 200); } // Poison: apply poison status if (weaponType === 'poison') { self.poisonedUntil = Date.now() + 4000; self.poisonTick = Date.now(); if (typeof showDamageNumber === "function") { showDamageNumber(self.x, self.y - 60, "POISON", 0x00FF66); } LK.effects.flashObject(self, 0x00FF66, 200); } // ShieldPierce: remove shield if present if (weaponType === 'shieldPierce' && self.hasShield) { self.hasShield = false; if (typeof showDamageNumber === "function") { showDamageNumber(self.x, self.y - 60, "SHIELD BREAK", 0xFFFF00); } LK.effects.flashObject(self, 0xFFFF00, 400); } // Default flash if (!isCritical && weaponType === 'basic') { LK.effects.flashObject(self, 0xffffff, 200); } } // --- End weapon-specific hit reactions --- if (self.hp <= 0) { self.die(); } }; 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 + 2) * scoreMultiplier); } else if (self.type === 'fast') { currency += Math.ceil((4 + 2) * scoreMultiplier); } else if (self.type === 'tank') { currency += Math.ceil((6 + 2) * scoreMultiplier); } else { currency += Math.ceil((self.currency + 2) * 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); // Show floating damage number on bunker if (typeof showDamageNumber === "function") { showDamageNumber(bunker.x, bunker.y - 80, self.damage, 0xFF8800); } // 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); // Add shadow under muzzle flash self.shadow = new Shadow(); self.shadow.y = 8; self.addChildAt(self.shadow, 0); if (self.graphics) { var baseWidth = self.graphics.width * (self.graphics.scale ? self.graphics.scale.x : 1); if (self.shadow.updateSize) self.shadow.updateSize(baseWidth); self.shadow.x = 0; self.shadow.graphics.scale.x = baseWidth * 0.8 / 100; self.shadow.graphics.alpha = 0.5; } // 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); // Add shadow under placeable wall self.shadow = new Shadow(); self.shadow.y = 60; self.addChildAt(self.shadow, 0); if (self.graphics) { var baseWidth = self.graphics.width * (self.graphics.scale ? self.graphics.scale.x : 1); if (self.shadow.updateSize) self.shadow.updateSize(baseWidth); self.shadow.x = 0; self.shadow.graphics.scale.x = baseWidth * 0.8 / 100; self.shadow.graphics.alpha = 0.5; } // 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); // Add shadow under security self.shadow = new Shadow(); self.shadow.y = 48; self.addChildAt(self.shadow, 0); if (self.graphics) { var baseWidth = self.graphics.width * (self.graphics.scale ? self.graphics.scale.x : 1); if (self.shadow.updateSize) self.shadow.updateSize(baseWidth); self.shadow.x = 0; self.shadow.graphics.alpha = 0.5; } // 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); // Show floating damage number if (typeof showDamageNumber === "function") { showDamageNumber(self.x, self.y - 40, amount, 0x00FFFF); } 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; // Prevent security from dying if it moves off the top or sides of the screen if (self.x < 50) self.x = 50; if (self.x > 2048 - 50) self.x = 2048 - 50; if (self.y < -100) self.y = -100; 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; // 10% chance to stun, 10% chance to slow, 5% chance to confuse, 10% crit var isCrit = Math.random() < 0.10; var effectRoll = Math.random(); if (isCrit) { nearest.takeDamage(self.damage * 2, 'basic', true); } else if (effectRoll < 0.10) { nearest.takeDamage(self.damage, 'stun'); } else if (effectRoll < 0.20) { nearest.takeDamage(self.damage, 'slow'); } else if (effectRoll < 0.25) { nearest.takeDamage(self.damage, 'confuse'); } else { nearest.takeDamage(self.damage, 'basic'); } 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 dedicated shadow asset self.graphics = self.attachAsset('shadow', { anchorX: 0.5, anchorY: 0.5 }); // Set shadow properties self.graphics.alpha = 0.5; // More visible shadow // Method to update shadow size based on parent self.updateSize = function (parentWidth) { // Make shadow larger and rounder self.graphics.scale.x = parentWidth * 1.1 / 100; self.graphics.scale.y = 0.7; // More circular/ellipse }; // Default scale for initial appearance self.graphics.scale.x = 1.1; self.graphics.scale.y = 0.7; return self; }); var Sniper = Container.expand(function () { var self = Container.call(this); // Add shadow under sniper self.shadow = new Shadow(); self.shadow.y = 40; self.addChildAt(self.shadow, 0); if (self.graphics) { var baseWidth = self.graphics.width * (self.graphics.scale ? self.graphics.scale.x : 1); if (self.shadow.updateSize) self.shadow.updateSize(baseWidth); self.shadow.x = 0; self.shadow.graphics.scale.x = baseWidth * 0.8 / 100; self.shadow.graphics.alpha = 0.5; } 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 bullet.weaponType = 'sniper'; } 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 bullet.weaponType = 'super'; } else if (self.currentWeapon === 'tank') { bullet.graphics.tint = 0x00FF00; // Green for tank bullet.graphics.scale.set(1.5, 1.5); bullet.damage = self.bulletDamage; bullet.speed = self.bulletSpeed; bullet.weaponType = 'tank'; } else if (self.currentWeapon === 'fast') { bullet.graphics.tint = 0xFF00FF; // Magenta for fast bullet.graphics.scale.set(0.5, 0.7); bullet.damage = self.bulletDamage; bullet.speed = self.bulletSpeed; bullet.weaponType = 'fast'; } else if (self.currentWeapon === 'burn') { bullet.graphics.tint = 0xFF6600; bullet.graphics.scale.set(1.1, 1.1); bullet.weaponType = 'burn'; } else if (self.currentWeapon === 'freeze') { bullet.graphics.tint = 0x66CCFF; bullet.graphics.scale.set(1.1, 1.1); bullet.weaponType = 'freeze'; } else if (self.currentWeapon === 'poison') { bullet.graphics.tint = 0x00FF66; bullet.graphics.scale.set(1.1, 1.1); bullet.weaponType = 'poison'; } else if (self.currentWeapon === 'shieldPierce') { bullet.graphics.tint = 0xFFFF00; bullet.graphics.scale.set(1.1, 1.1); bullet.weaponType = 'shieldPierce'; } // 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); // Add shadow under wall self.shadow = new Shadow(); self.shadow.y = 60; self.addChildAt(self.shadow, 0); if (self.graphics) { var baseWidth = self.graphics.width * (self.graphics.scale ? self.graphics.scale.x : 1); if (self.shadow.updateSize) self.shadow.updateSize(baseWidth); self.shadow.x = 0; self.shadow.graphics.scale.x = baseWidth * 0.8 / 100; self.shadow.graphics.alpha = 0.5; } // 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; // Show floating damage number on wall if (typeof showDamageNumber === "function") { showDamageNumber(self.x, self.y - 40, amount, 0xFFAA00); } 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); // Update next wave timer UI if available if (typeof nextWaveTimerTxt !== "undefined") { nextWaveTimerTxt.setText('Next Wave: ' + self.countdownTime + 's'); } 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, weaponType: "basic" }, { name: "Sniper Rifle", price: 50, damage: 3, speed: 30, owned: false, weaponType: "sniper" }, { name: "Super Sniper", price: 150, damage: 7, speed: 40, owned: false, weaponType: "super" }, { name: "Tank Gun", price: 90, damage: 5, speed: 18, owned: false, weaponType: "tank" }, { name: "Fast Blaster", price: 70, damage: 2, speed: 35, owned: false, weaponType: "fast" }, { name: "Burn Gun", price: 120, damage: 2, speed: 20, owned: false, weaponType: "burn" }, { name: "Freeze Gun", price: 120, damage: 2, speed: 20, owned: false, weaponType: "freeze" }, { name: "Poison Gun", price: 120, damage: 2, speed: 20, owned: false, weaponType: "poison" }, { name: "Shield Piercer", price: 200, damage: 3, speed: 25, owned: false, weaponType: "shieldPierce" }]; // 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 = 250; // 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.currentWeapon = weapon.weaponType || "basic"; 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 ****/ // Dedicated shadow asset for under characters // Armored Vehicle (Zırhlı Araç) enemy assets for all directions // 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 = 0; 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 = 25; // Starting with 25 enemies var enemiesSpawned = 0; // Track enemies spawned in current wave var enemiesRequired = 10; // Initial enemies required for first wave // Start score at 0 LK.setScore(0); // 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); } } // --- Floating damage numbers --- // Show floating text at (x, y) with value and color, and special effect for status function showDamageNumber(x, y, value, color, status) { var textValue = value; var textColor = color || 0xFF2222; var textSize = 48; var extraTween = null; var statusEffect = status || ""; // Status effect: show text and color for effect if (statusEffect === "CRIT") { textValue = "CRIT " + value; textColor = 0xFFD700; textSize = 60; } else if (statusEffect === "SLOW") { textValue = "SLOW"; textColor = 0x00FFFF; textSize = 44; } else if (statusEffect === "BURN") { textValue = "BURN"; textColor = 0xFF6600; textSize = 44; } else if (statusEffect === "FREEZE") { textValue = "FREEZE"; textColor = 0x66CCFF; textSize = 44; } else if (statusEffect === "POISON") { textValue = "POISON"; textColor = 0x00FF66; textSize = 44; } else if (statusEffect === "SHIELD") { textValue = "SHIELD"; textColor = 0xFFFF00; textSize = 44; } else if (statusEffect === "SHIELD BREAK") { textValue = "SHIELD BREAK"; textColor = 0xFFFF00; textSize = 44; } var dmgTxt = new Text2('-' + textValue, { size: textSize, fill: textColor }); dmgTxt.anchor.set(0.5, 0.5); dmgTxt.x = x; dmgTxt.y = y; dmgTxt.alpha = 1; game.addChild(dmgTxt); // Animate up and fade out, with a little scale pop for crit/status var scaleFrom = statusEffect === "CRIT" ? 1.5 : 1.1; dmgTxt.scale.set(scaleFrom, scaleFrom); tween(dmgTxt.scale, { x: 1, y: 1 }, { duration: 200, easing: tween.easeOut }); tween(dmgTxt, { y: y - 60, alpha: 0 }, { duration: 700, easing: tween.easeOut, onFinish: function onFinish() { game.removeChild(dmgTxt); } }); } // 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); // --- Mini-map UI --- // Mini-map config var minimapWidth = 320; var minimapHeight = 420; var minimapScaleX = minimapWidth / 2048; var minimapScaleY = minimapHeight / 2732; var minimapMargin = 40; // Mini-map container var minimapContainer = new Container(); minimapContainer.x = 2048 - minimapWidth - minimapMargin; minimapContainer.y = 2732 - minimapHeight - minimapMargin; minimapContainer.alpha = 0.85; // Mini-map background var minimapBg = LK.getAsset('bullet', { width: minimapWidth, height: minimapHeight, anchorX: 0, anchorY: 0, tint: 0x222222 }); minimapBg.alpha = 0.7; minimapContainer.addChild(minimapBg); // Mini-map bunker marker var minimapBunker = LK.getAsset('bullet', { width: 32, height: 32, anchorX: 0.5, anchorY: 0.5, tint: 0x00FF00 }); minimapBunker.alpha = 0.9; minimapContainer.addChild(minimapBunker); // Mini-map enemy markers (will be updated every frame) var minimapEnemyMarkers = []; // Add to game (not GUI, so it scales with world) game.addChild(minimapContainer); // Mini-map update function function updateMinimap() { // Update bunker marker minimapBunker.x = bunker.x * minimapScaleX; minimapBunker.y = bunker.y * minimapScaleY; // Remove old enemy markers for (var i = 0; i < minimapEnemyMarkers.length; i++) { minimapContainer.removeChild(minimapEnemyMarkers[i]); } minimapEnemyMarkers = []; // Add enemy markers for (var i = 0; i < enemies.length; i++) { var e = enemies[i]; if (!e.active) continue; var marker = LK.getAsset('bullet', { width: 18, height: 18, anchorX: 0.5, anchorY: 0.5, tint: e.type === "armoredVehicle" ? 0xFF8800 : e.type === "tank" ? 0xFF0000 : 0xFFFF00 }); marker.x = e.x * minimapScaleX; marker.y = e.y * minimapScaleY; marker.alpha = 0.95; minimapContainer.addChild(marker); minimapEnemyMarkers.push(marker); } } // Super Ally button UI (above bomb button) var superAllyBtnBg = LK.getAsset('bullet', { width: 260, height: 110, anchorX: 0.5, anchorY: 0.5, tint: 0x333333 }); superAllyBtnBg.alpha = 0.8; var superAllyBtn = new Text2("SUPER ALLY\n$1000", { size: 48, fill: 0xFF00FF }); superAllyBtn.anchor.set(0.5, 0.5); var superAllyBtnContainer = new Container(); superAllyBtnContainer.addChild(superAllyBtnBg); superAllyBtnContainer.addChild(superAllyBtn); superAllyBtnContainer.x = 140; superAllyBtnContainer.y = 2732 - 410; // Above bomb button superAllyBtnContainer.interactive = true; superAllyBtnContainer.visible = true; game.addChild(superAllyBtnContainer); // Bomb button UI (above ally button) var bombBtnBg = LK.getAsset('bullet', { width: 260, height: 110, anchorX: 0.5, anchorY: 0.5, tint: 0x333333 }); bombBtnBg.alpha = 0.8; var bombBtn = new Text2("BOMB\n$50", { size: 55, fill: 0xFFD700 }); bombBtn.anchor.set(0.5, 0.5); var bombBtnContainer = new Container(); bombBtnContainer.addChild(bombBtnBg); bombBtnContainer.addChild(bombBtn); bombBtnContainer.x = 140; bombBtnContainer.y = 2732 - 280; // Above ally button bombBtnContainer.interactive = true; bombBtnContainer.visible = true; game.addChild(bombBtnContainer); // Super Ally effect state var superAllyActive = false; var superAllyCooldown = false; // Bomb state var bombMode = false; var bombBtnCooldown = false; // Super Ally button event superAllyBtnContainer.down = function (x, y, obj) { if (superAllyCooldown) return; if (currency >= 1000 && !superAllyActive) { currency -= 1000; updateUI(); superAllyActive = true; superAllyBtnBg.alpha = 0.4; superAllyBtn.alpha = 0.5; // Spawn 10 armored vehicles from back (top) edge, move to front (bottom), destroy all enemies in their path var superAllyVehicles = []; var startX = sniper.x - 220; // Spawn behind sniper horizontally var startY = 0 - 120; // Start above the visible area var endY = bunkerY - 120; // End just before the bunker var spacing = (2048 - 400) / 9; // Spread across width, avoid edges for (var i = 0; i < 10; i++) { var av = new Enemy("armoredVehicle"); av.hp = 99999; // Invincible av.damage = 9999; av.speed = 7; // Slowed down from 32 to 7 for more reasonable speed av.points = 0; av.currency = 0; av.type = "armoredVehicle"; // Use 'front' asset (facing down/forward) if (av.graphics) av.removeChild(av.graphics); av.graphics = av.attachAsset('armoredVehicle_front', { anchorX: 0.5, anchorY: 0.5, scaleX: 1.7, scaleY: 1.7 }); // Add shadow if (av.shadow) av.removeChild(av.shadow); av.shadow = new Shadow(); av.shadow.y = 40; var baseWidth = av.graphics.width * av.graphics.scale.x; av.shadow.updateSize(baseWidth); av.addChildAt(av.shadow, 0); av.shadow.x = 0; av.shadow.graphics.scale.x = baseWidth * 0.8 / 100; av.shadow.graphics.alpha = 0.5; // Spread vehicles horizontally, all start at the back (top) av.x = 200 + i * spacing; av.y = startY; av.superAlly = true; // Mark as super ally // --- Smoke effect state for this vehicle --- av.smokeParticles = []; av.smokeTick = 0; // --- Super Ally update with smoke effect --- av.update = function () { // Move forward (down) this.y += this.speed; // --- Smoke effect: spawn smoke every few frames behind the vehicle --- if (typeof this.smokeTick !== "number") this.smokeTick = 0; this.smokeTick++; if (this.smokeTick % 3 === 0) { // Create a smoke particle (small gray ellipse) var smoke = LK.getAsset('bullet', { anchorX: 0.5, anchorY: 0.5, scaleX: 0.7 + Math.random() * 0.5, scaleY: 0.5 + Math.random() * 0.3, tint: 0x888888 }); smoke.x = this.x + (Math.random() - 0.5) * 30; smoke.y = this.y - 60 + (Math.random() - 0.5) * 10; smoke.alpha = 0.7 + Math.random() * 0.2; smoke.life = 0; smoke.maxLife = 24 + Math.floor(Math.random() * 10); // Add to game and to this vehicle's smokeParticles array game.addChildAt(smoke, 0); this.smokeParticles.push(smoke); } // Update and fade out smoke particles for (var s = this.smokeParticles.length - 1; s >= 0; s--) { var sp = this.smokeParticles[s]; sp.life++; sp.y += 1.2 + Math.random() * 0.5; // Drift down sp.x += (Math.random() - 0.5) * 1.2; // Slight horizontal drift sp.alpha *= 0.96; // Fade out sp.scale.x *= 1.01; sp.scale.y *= 1.01; if (sp.life > sp.maxLife || sp.alpha < 0.05) { game.removeChild(sp); this.smokeParticles.splice(s, 1); } } // Destroy all enemies in path for (var j = enemies.length - 1; j >= 0; j--) { var e = enemies[j]; if (e.active && Math.abs(e.x - this.x) < 120 && Math.abs(e.y - this.y) < 80) { e.die(); } } // Remove self if off screen (past bunker) if (this.y > bunkerY + 200) { // Remove all smoke particles for (var s = this.smokeParticles.length - 1; s >= 0; s--) { game.removeChild(this.smokeParticles[s]); } this.smokeParticles = []; this.active = false; if (game && typeof game.removeChild === "function") game.removeChild(this); } }; superAllyVehicles.push(av); game.addChild(av); } // Animate button cooldown superAllyCooldown = true; LK.setTimeout(function () { superAllyActive = false; superAllyBtnBg.alpha = 0.8; superAllyBtn.alpha = 1; superAllyCooldown = false; }, 4000); // Remove all super ally vehicles after 4 seconds LK.setTimeout(function () { for (var i = 0; i < superAllyVehicles.length; i++) { if (superAllyVehicles[i] && game && typeof game.removeChild === "function") { game.removeChild(superAllyVehicles[i]); } } }, 4200); } else if (currency < 1000) { LK.effects.flashObject(superAllyBtnContainer, 0xFF0000, 200); } }; // Bomb button event bombBtnContainer.down = function (x, y, obj) { if (bombBtnCooldown) return; if (currency >= 50 && !bombMode) { bombMode = true; bombBtnBg.alpha = 0.4; bombBtn.alpha = 0.5; } else if (bombMode) { // Already in bomb mode, ignore } else { // Not enough money LK.effects.flashObject(bombBtnContainer, 0xFF0000, 200); } }; // 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(); // Spawn behind the sniper (above, on y axis) ally.x = sniper.x; ally.y = sniper.y + 120; game.addChild(ally); // --- Fix: Reset all enemy walk animation scale to default when ally is created --- for (var i = 0; i < enemies.length; i++) { var e = enemies[i]; if (e && e.graphics && (e.type === 'fast' || e.type === 'tank' || e.type === 'regular')) { e.graphics.scale.x = 2.5; e.graphics.scale.y = 2.5; } } 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); // Enemy counter UI var enemyCounterTxt = new Text2('Enemies: 0', { size: 50, fill: 0xFF4444 }); enemyCounterTxt.anchor.set(1, 0); enemyCounterTxt.x = 2048 - 80; enemyCounterTxt.y = 130; LK.gui.top.addChild(enemyCounterTxt); // Next wave timer UI var nextWaveTimerTxt = new Text2('', { size: 50, fill: 0x00FFFF }); nextWaveTimerTxt.anchor.set(1, 0); nextWaveTimerTxt.x = 2048 - 80; nextWaveTimerTxt.y = 200; LK.gui.top.addChild(nextWaveTimerTxt); // 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() { // Animate score if changed if (typeof updateUI.lastScore === "undefined") updateUI.lastScore = 0; var currentScore = LK.getScore(); if (currentScore !== updateUI.lastScore) { scoreTxt.setText('Score: ' + currentScore); // Animate score text pop scoreTxt.scale.set(1.2, 1.2); tween(scoreTxt.scale, { x: 1, y: 1 }, { duration: 200, easing: tween.easeOut }); updateUI.lastScore = currentScore; } // Animate currency if changed if (typeof updateUI.lastCurrency === "undefined") updateUI.lastCurrency = 0; if (currency !== updateUI.lastCurrency) { currencyTxt.setText('$: ' + currency); currencyTxt.scale.set(1.2, 1.2); tween(currencyTxt.scale, { x: 1, y: 1 }, { duration: 200, easing: tween.easeOut }); updateUI.lastCurrency = currency; } // Animate wave if changed if (typeof updateUI.lastWave === "undefined") updateUI.lastWave = 0; if (wave !== updateUI.lastWave) { waveTxt.setText('Wave: ' + wave); waveTxt.scale.set(1.2, 1.2); tween(waveTxt.scale, { x: 1, y: 1 }, { duration: 200, easing: tween.easeOut }); updateUI.lastWave = wave; } // Update enemy counter var aliveEnemies = 0; for (var i = 0; i < enemies.length; i++) { if (enemies[i] && enemies[i].active) aliveEnemies++; } enemyCounterTxt.setText('Enemies: ' + aliveEnemies + '/' + enemiesRequired); // Animate enemy counter if changed if (typeof updateUI.lastEnemies === "undefined") updateUI.lastEnemies = 0; if (aliveEnemies !== updateUI.lastEnemies) { enemyCounterTxt.scale.set(1.2, 1.2); tween(enemyCounterTxt.scale, { x: 1, y: 1 }, { duration: 200, easing: tween.easeOut }); updateUI.lastEnemies = aliveEnemies; } // Update next wave timer (if countdown active) if (waveCountdown && waveCountdown.active) { nextWaveTimerTxt.setText('Next Wave: ' + waveCountdown.countdownTime + 's'); // Animate timer if changed if (typeof updateUI.lastWaveTimer === "undefined") updateUI.lastWaveTimer = 0; if (waveCountdown.countdownTime !== updateUI.lastWaveTimer) { nextWaveTimerTxt.scale.set(1.2, 1.2); tween(nextWaveTimerTxt.scale, { x: 1, y: 1 }, { duration: 200, easing: tween.easeOut }); updateUI.lastWaveTimer = waveCountdown.countdownTime; } } else { nextWaveTimerTxt.setText(''); } updateUpgradeButtons(); } function updateBunkerHealth() { var healthPercentage = Math.max(0, Math.min(100, Math.round(bunkerHealth / maxBunkerHealth * 100))); healthTxt.setText('Bunker: ' + healthPercentage + '%'); // Animate health text color based on health if (healthPercentage < 30) { healthTxt.setStyle({ fill: 0xFF2222 }); } else if (healthPercentage < 60) { healthTxt.setStyle({ fill: 0xFFFF00 }); } else { healthTxt.setStyle({ fill: 0xFFFFFF }); } // Animate health text pop if health drops if (typeof updateBunkerHealth.lastHealth === "undefined") updateBunkerHealth.lastHealth = 100; if (healthPercentage < updateBunkerHealth.lastHealth) { healthTxt.scale.set(1.2, 1.2); tween(healthTxt.scale, { x: 1, y: 1 }, { duration: 200, easing: tween.easeOut }); } updateBunkerHealth.lastHealth = healthPercentage; bunker.showDamage(bunkerHealth / maxBunkerHealth); // Only make damage indicator visible and shake when actually taking damage if (bunkerHealth < maxBunkerHealth) { bunker.damageIndicator.alpha = 1; 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() { LK.setTimeout(function () { if (gameActive) { tween(bunker.damageIndicator, { alpha: 0 }, { duration: 300 }); } }, 1000); } }); } // Create a visual indicator effect when health is low if (bunkerHealth / maxBunkerHealth < 0.5) { LK.setTimeout(function () { if (gameActive) bunker.showDamage(bunkerHealth / maxBunkerHealth); }, 100); } } function updateUpgradeButtons() { // Upgrade buttons removed as requested } // Game mechanics functions // Track if armored vehicle has spawned this game if (typeof armoredVehicleSpawned === "undefined") { var armoredVehicleSpawned = false; } if (typeof armoredVehicleWave === "undefined") { var armoredVehicleWave = 0; // Track the last wave armored vehicle spawned } 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; // Limit: never spawn more than 10 alive enemies at once var aliveEnemies = 0; for (var i = 0; i < enemies.length; i++) { if (enemies[i] && enemies[i].active) aliveEnemies++; } if (aliveEnemies >= 10) 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 more gradual score thresholds if (currentScore >= 1200) { enemiesToSpawnAtOnce = 5; // 5 at 1200+ } else if (currentScore >= 900) { enemiesToSpawnAtOnce = 4; // 4 at 900+ } else if (currentScore >= 500) { enemiesToSpawnAtOnce = 3; // 3 at 500+ } else if (currentScore >= 200) { enemiesToSpawnAtOnce = 2; // 2 at 200+ } // Make sure we don't spawn more enemies than required for this wave enemiesToSpawnAtOnce = Math.min(enemiesToSpawnAtOnce, enemiesRequired - enemiesSpawned); // Also, do not spawn more than (10 - aliveEnemies) at once enemiesToSpawnAtOnce = Math.min(enemiesToSpawnAtOnce, 10 - aliveEnemies); // If no room to spawn, return if (enemiesToSpawnAtOnce <= 0) return; // Spawn armored vehicle after 800 score, once per wave if (currentScore >= 800 && waveInProgress && wave > 0 && armoredVehicleWave !== wave && aliveEnemies < 10) { var armoredVehicle = new Enemy("armoredVehicle"); armoredVehicle.hp = 100; armoredVehicle.damage = 15; // Zırhlı araç hasarı 15 olarak ayarlandı armoredVehicle.speed = 3.5; armoredVehicle.points = 100; armoredVehicle.currency = 20; // Use a random direction asset for variety var directions = ["armoredVehicle_front", "armoredVehicle_back", "armoredVehicle_left", "armoredVehicle_right", "armoredVehicle_frontLeft", "armoredVehicle_frontRight", "armoredVehicle_backLeft", "armoredVehicle_backRight"]; var dirAsset = directions[Math.floor(Math.random() * directions.length)]; if (armoredVehicle.graphics) armoredVehicle.removeChild(armoredVehicle.graphics); armoredVehicle.graphics = armoredVehicle.attachAsset(dirAsset, { anchorX: 0.5, anchorY: 0.5, scaleX: 1.7, scaleY: 1.7 }); // Add shadow if (armoredVehicle.shadow) armoredVehicle.removeChild(armoredVehicle.shadow); armoredVehicle.shadow = new Shadow(); armoredVehicle.shadow.y = 40; var baseWidth = armoredVehicle.graphics.width * armoredVehicle.graphics.scale.x; armoredVehicle.shadow.updateSize(baseWidth); armoredVehicle.addChildAt(armoredVehicle.shadow, 0); armoredVehicle.shadow.x = 0; armoredVehicle.shadow.graphics.scale.x = baseWidth * 0.8 / 100; armoredVehicle.shadow.graphics.alpha = 0.5; armoredVehicle.type = "armoredVehicle"; armoredVehicle.x = Math.random() * (2048 - 200) + 100; armoredVehicle.y = -150; enemies.push(armoredVehicle); game.addChild(armoredVehicle); enemiesSpawned++; armoredVehicleWave = wave; // Do not spawn other enemies this tick if armored vehicle is spawned return; } // 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 (slower, more gradual progression) if (currentScore >= 700 && random < 0.13) { enemyType = 'tank'; } else if (currentScore >= 500 && random < 0.11) { enemyType = 'tank'; } else if (currentScore >= 300 && random < 0.09) { enemyType = 'tank'; } else if (currentScore >= 200 && random < 0.18) { enemyType = 'fast'; } else if (currentScore >= 100 && random < 0.10) { enemyType = 'fast'; } var enemy; if (enemyType === 'armoredVehicle') { // Should not happen here, armored vehicle is handled above continue; } else { enemy = new Enemy(enemyType); // Randomly assign advanced status effects for variety if (Math.random() < 0.08) { enemy.hasShield = true; } if (Math.random() < 0.05) { enemy.burningUntil = Date.now() + 2000 + Math.random() * 2000; enemy.burnTick = Date.now(); } if (Math.random() < 0.05) { enemy.poisonedUntil = Date.now() + 2000 + Math.random() * 2000; enemy.poisonTick = Date.now(); } if (Math.random() < 0.04) { enemy.slowedUntil = Date.now() + 2000 + Math.random() * 2000; enemy.slowFactor = 0.4; } } // 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 } if (enemyType === 'desertBandit') { enemy.y = -120; // Spawn desert bandit further back } else { 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; } // Allow bullets to damage all enemies, including DesertBandit if (bullet.active && enemy.active && bullet.intersects(enemy)) { // Determine weaponType and crit var weaponType = 'basic'; var isCritical = false; if (sniper && sniper.currentWeapon) { weaponType = sniper.currentWeapon; // 10% crit chance for sniper, 20% for super, 5% for rifle if (weaponType === 'sniper' && Math.random() < 0.10) isCritical = true; if (weaponType === 'super' && Math.random() < 0.20) isCritical = true; if (weaponType === 'basic' && Math.random() < 0.05) isCritical = true; } enemy.takeDamage(bullet.damage, weaponType, isCritical); 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(); // Calculate spawn rate based on score: higher score = faster spawn, but never below 2200ms // Example: every 80 score reduces spawn rate by 120ms, but never below 2200ms var scoreSpawnReduction = Math.floor(currentScore / 80) * 120; enemySpawnRate = Math.max(2200, 3200 - scoreSpawnReduction); // Also apply wave-based reduction, but never below 2200ms, and reduce per wave by only 60ms enemySpawnRate = Math.max(2200, enemySpawnRate - (wave - 1) * 60); // 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; // --- Begin: Overlap and stacking logic for buildings --- // If placing a sniper tower, check for overlap with other walls and adjust stacking order if (wall.type === "sniper" && game.walls && game.walls.length > 0) { // Find all overlapping walls (excluding self) var overlapping = []; for (var i = 0; i < game.walls.length; i++) { var other = game.walls[i]; if (!other || !other.graphics) continue; // Simple bounding box overlap check var dx = Math.abs(wall.x - other.x); var dy = Math.abs(wall.y - other.y); var combinedHalfWidth = (wall.graphics.width * wall.graphics.scale.x + other.graphics.width * other.graphics.scale.x) / 2; var combinedHalfHeight = (wall.graphics.height * wall.graphics.scale.y + other.graphics.height * other.graphics.scale.y) / 2; if (dx < combinedHalfWidth && dy < combinedHalfHeight) { overlapping.push(other); } } // If overlap, ensure sniper tower is above if closer to player (higher y) if (overlapping.length > 0) { // Find the wall with the highest y (closest to player) var topWall = wall; for (var i = 0; i < overlapping.length; i++) { if (overlapping[i].y > topWall.y) { topWall = overlapping[i]; } } // Add new wall to game game.addChild(wall); // If the new wall is the top wall, bring it to the top if (topWall === wall) { game.setChildIndex(wall, game.children.length - 1); } else { // Otherwise, ensure the top wall is above game.setChildIndex(topWall, game.children.length - 1); } } else { game.addChild(wall); } } else if (game.walls && game.walls.length > 0) { // If placing a non-sniper wall, check for overlap with sniper towers and other walls var overlapping = []; for (var i = 0; i < game.walls.length; i++) { var other = game.walls[i]; if (!other || !other.graphics) continue; var dx = Math.abs(wall.x - other.x); var dy = Math.abs(wall.y - other.y); var combinedHalfWidth = (wall.graphics.width * wall.graphics.scale.x + other.graphics.width * other.graphics.scale.x) / 2; var combinedHalfHeight = (wall.graphics.height * wall.graphics.scale.y + other.graphics.height * other.graphics.scale.y) / 2; if (dx < combinedHalfWidth && dy < combinedHalfHeight) { overlapping.push(other); } } if (overlapping.length > 0) { // Find the wall with the highest y (closest to player) var topWall = wall; for (var i = 0; i < overlapping.length; i++) { if (overlapping[i].y > topWall.y) { topWall = overlapping[i]; } } game.addChild(wall); if (topWall === wall) { game.setChildIndex(wall, game.children.length - 1); } else { game.setChildIndex(topWall, game.children.length - 1); } } else { game.addChild(wall); } } else { // No other walls, just add game.addChild(wall); } // --- End: Overlap and stacking logic for buildings --- // 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; } // Bomb placement logic if (typeof bombMode !== "undefined" && bombMode) { // Don't allow bomb in top left 100x100 or on UI if (x < 100 && y < 100) { LK.effects.flashObject(bombBtnContainer, 0xFF0000, 200); return; } // Place bomb at this location var bomb = new Bomb(); bomb.x = x; bomb.y = y - 400; // Drop from above game.addChild(bomb); // Deduct money currency -= 50; updateUI(); // Reset bomb button state bombMode = false; bombBtnBg.alpha = 0.8; bombBtn.alpha = 1; // Cooldown: prevent spamming bombBtnCooldown = true; LK.setTimeout(function () { bombBtnCooldown = false; }, 1200); 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 bombs if (typeof game.children !== "undefined") { for (var i = game.children.length - 1; i >= 0; i--) { var child = game.children[i]; // Super Ally vehicles: update and remove if inactive if (child && child.superAlly && child.active && typeof child.update === "function") { child.update(); if (!child.active) { game.removeChild(child); } } // Bombs if (child && child instanceof Container && child.update && child instanceof Bomb && child.active) { child.update(); // Remove bomb if not visible anymore if (child.exploded && child.visible === false) { game.removeChild(child); } } } } // 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(); } // Update minimap every frame updateMinimap(); }; // 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 } }); // Armored vehicle asset naming legend: // front: ileri (aşağı) // back: geri (yukarı) // left: sola // right: sağa // frontLeft: çapraz aşağı-sola // frontRight: çapraz aşağı-sağa // backLeft: çapraz yukarı-sola // backRight: çapraz yukarı-sağa
===================================================================
--- original.js
+++ change.js
@@ -2438,11 +2438,11 @@
/****
* Game Code
****/
-// Create background
-// Armored Vehicle (Zırhlı Araç) enemy assets for all directions
// Dedicated shadow asset for under characters
+// Armored Vehicle (Zırhlı Araç) enemy assets for all directions
+// Create background
var background = game.attachAsset('background', {
width: 2048,
height: 2732,
anchorX: 0,
@@ -2452,9 +2452,9 @@
// Game variables
var bunkerHealth = 100;
var maxBunkerHealth = 100;
var bunkerY = 2732 - 100; // Position near bottom of screen
-var currency = 1000;
+var currency = 0;
var difficulty = 1;
var wave = 1;
var enemySpawnRate = 3000; // ms between enemy spawns
var lastEnemySpawn = 0;
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