User prompt
Silahın efsununda İngilizce dil seçeneğinde Damage+ kartının açıklamasında hasarı %10 arttırır yazmışsın ama %20 olması gerekmiyor muydu? Ayırca bu kartların (efsun ve buff) açıklamalarındaki cümlelerin bazıları nokta ile bitiyor bazıları bitmiyor, hepsi nokta ile bitmeli. Ayrıca Türkçe oyundaki "Güçlendirmenizi Seçin" yazısında "Seçin" kısmı aşağı satıra iniyor, onun aşağı satıra inmemesi gerekiyor, "Güçlendirmenizi Seçin" tek satırda yazmalı. Ayrıca oyuna başlarken 9 silah seçimi yaparken kartlar yatay mesafe olarak birbirine çok yakın duruyor, o mesafeyi 20 falan açabilir misin hem Türkçe hem İngilizce için? Hatta biraz daha aşağı indirsen de iyi olur çünkü başlangıçta 9 silah arasından seçim yaparken üstte yazı yazmıyor İngilizcede de Türkçede de.
User prompt
Ama İngilizcesindeki halini de düzeltmen lazım. "Select Your Weapon" yazıyor. Oyunun başında 9 silahtan birini seçtiğim kısımda üstte İngilizce ise "Choose Your Starting Weapon", Türkçe ise "Başlangıç Silahınızı Seçin" yazsın. Ama oyun içinde level atlayınca karşıma gelen 4 kart seçiminde İngilizce ise "Choose Your Power-Up", Türkçe ise "Güçlendirmenizi Seçin" yazsın. Ayrıca bazı efsun var buff kartlarımda kartların isimleri karta tam sığmıyor kenardan (yandan) taşıyor, onu da düzenleyebilirsen güzel olur.
User prompt
Şu anda silah efsunlarında saldırı hızı+ efsununun açıklamasında saldırı hızını %10 azaltır diyor fakat arttırır demesi gerekiyordu çünkü o kart ilgili silahın saldırı hızını %10 arttırıyor olmalı azaltıyor değil. Ayrıca bazı buff kartlarında "her seviyede" diye bir ibare var, onun her seviye değil "her yükseltilişte" şeklinde olması gerekirdi. Ayrıca karşıma 4 seçenek geldiği zaman kart seçmem için yukarıda bana silahını seç değil Yükseltmeni Seç yazmalı. Ayrıca aynı efsun veya buff kartını en fazla 5 defa alabilirim, sonrasında kart seçiminde karşıma bir daha o kart gelmemeli yani havuzdan çıkmalı ve kart seçiminde bana her zaman ama her zaman 4 tane seçenek sunmalı. Eğer havuzda 4'ten az kart kaldıysa (son 3, 2 ya da 1 kart) yalnızca o zaman 4 seçenek yerine 3, 2 ya da 1 seçenek sunabilir. Havuzdaki tüm kartlar (efsun, buff, upgrade) alındıysa ve her biri maksimum yükseltmeye getirildiyse (her biri kendisine özel olarak 5 kere alındıysa) yani havuzda kart kalmadıysa artık seviye atlanması halinde azami canın %100 kadarı yenilenir.
User prompt
azami hp ve saldırı gücü artışını 20den 10a çekmeyeceksin, onlar 20 olarak kalmalı!
User prompt
Azami HP artışını niye elledin onu ellememen lazımdı! Saldırı gücünü de ellememen lazımdı! Onları geri al!
User prompt
Şu anda saldırı hızı ve menzil absürt derecede hızlı artıyor. Onları sabit 20 (%)'den 10'a çekelim. Damage 20% olarak kalabilir. Kodu iyice incelemen lazım. Yazılarına kadar düzeltmen lazım hepsini.
User prompt
Please fix the bug: 'Timeout.tick error: TankZombie is not defined' in or related to this line: 'enemy = new TankZombie();' Line Number: 2801
User prompt
Menzilli zombinin saldırısı rangedZombieSaliva, şifacı zombinin saldırısı healerZombiePot ve uzak dövüş modundaki patron zombinin saldırısı bossZombieAttack2 assetleri ilerlerken kendi etraflarında dönerek ilerlesinler.
User prompt
Karakterimin merkezinden 60 menzil yarıçaplı hayali bir daire varsaymıştık hatırladın mı? rangedZombie türü de rangedZombieSaliva ile saldırdığı zaman bana attıkları o saldırı eğer bu hayali daireye temas ederse hasar alayım sadece. Ayrıca boss zombinin her saniye azami canının %5 kadarını yenileme özelliği de olsun ve her saniye azami canının %5 kadarını yenilesin. 10. dalgadan sonraki dalgalardan itibaren (11. dalganın başından itibaren) tüm zombi türleri her dalgada %5 azami can (mesela canı 16 olan bir zombinin canı 11. dalgada 16.8 olur) ve %5 saldırı gücü (mesela saldırı hasarı 8 olan bir zombinin saldırı hasarı 11. dalgada 8.4 olur) artışı kazansın. Ayrıca healerZombie diye yeni bir zombi türü olsun. Can: 24 Hasar: Yok Hareket Hızı: 1.4 Saldırı Hızı: 6 saniyede bir. (360) Menzil: 950 Bu zombi türü saldırı menziline girene kadar karaktere doğru yürür, saldırı menziline girdikten sonra durur ve en kalabalık zombi grubuna (mümkünse boss zombilere öncelik verir) doğru bir iyileştirme potu (healerZombiePot asseti) fırlatır, bu pot hedefe ulaşınca kırılıp patlar ve yarıçapı 300 olan dairesel bir alandaki tüm zombileri o alanda durdukları süre boyunca her 0.5 saniyede bir (potun ilk kırıldığı andaki anında iyileştirme dahil) azami canlarının %8 kadarını iyileştirir. Bu dairesel alan (healerZombiePotArea) 4 saniye boyunca varlığını korur. Bu zombi türü 9. dalgadan itibaren (9. dalga dahil) gelmeye başlasın. 9. dalgada 1 tane gelsin ve her sonraki dalgada sayısı +1 artarak gelmeye devam etsin.
Code edit (1 edits merged)
Please save this source code
User prompt
Ya şu anda ekranın ortasında karakterim var ama joysticik ve knob yok ben anlamadım bu işi. Ekranın ortasını mı bulamıyorsun sorun ne anlamıyorum seni gerçekten... Karakterim ekranın ortasında işte, joystcik ve knob da orada ekranın ortasında neden olmaıyor?
User prompt
Ya sen ekran ortası olarak herhangi bir arka plan çizimini falan mı alıyorsun yoksa kameranın ortasını falan mı alıyorsun nereyi alıyorsun? Şu anda joystcik de knob da gözükmüyor. Nereye tıklarsam tıklayım karakterim hep yukarı çıkıyor ve sola gidiyor. Çok büyük kaydırmışsın!
User prompt
Ya sen ekran ortası olarak herhangi bir arka plan çizimini falan mı alıyorsun yoksa kameranın ortasını falan mı alıyorsun nereyi alıyorsun? Şu anda joystcik de knob da gözükmüyor. Nereye tıklarsam tıklayım karakterim hep yukarı çıkıyor ve sola gidiyor. Çok büyük kaydırmışsın!
User prompt
Joystick ve knobu ekranın tam ortasına hizala ve kontrolleri ortadan başlat
User prompt
Joystick ve knobu ekranın tam ortasına hizala ve kontrolleri ortadan başlat.
User prompt
Peki şu joysticki ve knobu ekranın tam ortasına alalım. Kontrolleri ekranın tam ortasından yapmak istiyorum.
Code edit (2 edits merged)
Please save this source code
User prompt
Ama şimdi Loading... ekranının üstündeki myLogo assetini göremiyorum.
User prompt
Göremiyorum! Yok! Peki kameranın sağ alt tarafına (sağ alt köşe) koy bakalım o halde bakalım o zaman görebilecek miyim joystick'i knob'u. Unutma ki hareket kontrolleri bu knob ile joystick'in alanı içinde yapılacak knob'u oynatarak (360 derece kadar dönebiliyor knob).
User prompt
Ama şu anda joystick ve knob'u göremiyorum. Kameranın sol alt köşesinde joystick ve kamera olmalıydı!
User prompt
Joystick'i ekranın sol altına sabitle ve referans noktası olmadan joystick merkezinden 360 derece sürüklemeye göre hareket ettir
User prompt
2. durumda oyun ekranında kameranın (ekranın) sol alt tarafında olsun joystick ve ben yalnızca bu joystick ve düğmesini sürükleyip (joystick etrafında 360 derece) hareketi sağlayabileyim. Yani referans noktasına falan gerek yok. Söylediğim şekilde olsun kontroller.
User prompt
Şu anda neden Kırmızı Loading... ekranından çıkamıyorum sonsuza kadar o ekranda kalıyor? O ekran sadece 10 saniye olmalı sonra yok olmalı. Kodu düzelt!
Code edit (1 edits merged)
Please save this source code
User prompt
O halde gerekli her şeyi yap ve bunu düzelt!
/**** * Plugins ****/ var tween = LK.import("@upit/tween.v1"); var storage = LK.import("@upit/storage.v1"); /**** * Classes ****/ var Bomb = Container.expand(function () { var self = Container.call(this); self.active = true; var bombGraphics = self.attachAsset('bomb', { anchorX: 0.5, anchorY: 0.5, width: 60, height: 60, tint: 0xff0000 }); self.update = function () { if (!player || !self.active) { return; } var dx = player.x - self.x; var dy = player.y - self.y; var dist = Math.sqrt(dx * dx + dy * dy); if (dist < 80) { self.active = false; // Deal 20 damage to all non-boss enemies on screen for (var i = enemies.length - 1; i >= 0; i--) { var enemy = enemies[i]; var screenDx = Math.abs(enemy.x - player.x); var screenDy = Math.abs(enemy.y - player.y); if (screenDx < 1024 && screenDy < 1366) { // Deal 20 damage, drop exp and remove if dead if (enemy.takeDamage(20)) { dropExpOrb(enemy.x, enemy.y); enemy.destroy(); enemies.splice(i, 1); } } } LK.effects.flashScreen(0xff0000, 300); if (self.parent) { self.destroy(); } return true; } }; return self; }); var Bullet = Container.expand(function () { var self = Container.call(this); self.damage; self.speed; self.dirX = 0; self.dirY = 0; self.piercing = 1; self.hitEnemies = []; var bulletGraphics = self.attachAsset('bullet', { anchorX: 0.5, anchorY: 0.5 }); self.update = function () { // Seviye seçim ekranı açıkken hiçbir şey yapma if (skillSelectionActive) { return; } // Hareket this.x += this.dirX * this.speed; this.y += this.dirY * this.speed; // Kameranın (görünen alanın) dışına çıktıysa sil var camMinX = camera.x; var camMaxX = camera.x + 2048; var camMinY = camera.y; var camMaxY = camera.y + 2732; if (this.x < camMinX || this.x > camMaxX || this.y < camMinY || this.y > camMaxY) { this.destroy(); var bi = bullets.indexOf(this); if (bi >= 0) { bullets.splice(bi, 1); } return; } // “Menzil” filtresi: bu top silahının menzili WEAPONS.ball.range var maxRange = WEAPONS.ball.range * (player.weapons && player.weapons.ball ? player.weapons.ball.rngMul : 1); // (Eğer o envanterde yoksa rngMul=1 varsaydık) // Düşmana çarpma kontrolü & menzil filtresi for (var i = enemies.length - 1; i >= 0; i--) { var e = enemies[i]; if (this.intersects(e) && isWithinRange(e, maxRange)) { damageEnemy(e, this.damage); this.destroy(); var bi2 = bullets.indexOf(this); if (bi2 >= 0) { bullets.splice(bi2, 1); } return; } } }; return self; }); // ChickenLeg pickup class var ChickenLeg = Container.expand(function () { var self = Container.call(this); self.active = true; var chickenLegGraphics = self.attachAsset('chickenLeg', { anchorX: 0.5, anchorY: 0.5, width: 60, height: 60 }); self.update = function () { if (!player || !self.active) { return; } var dx = player.x - self.x; var dy = player.y - self.y; var dist = Math.sqrt(dx * dx + dy * dy); if (dist < 80) { self.active = false; // Heal player for 30% of max HP, but not above max HP var healAmount = Math.floor(player.maxHp * 0.3); var oldHp = player.hp; player.hp = Math.min(player.hp + healAmount, player.maxHp); var actualHealed = player.hp - oldHp; if (actualHealed > 0) { LK.effects.flashObject(player, 0x00ff00, 300); // Show green HP gain text with black border var healText = new Text2('+' + actualHealed.toFixed(1), { size: 48, fill: 0x00ff00, stroke: 0x000000, strokeThickness: 4 }); healText.anchor.set(0.5); healText.x = player.x; healText.y = player.y - (player.height ? player.height / 2 : 60); healText.alpha = 1; gameContainer.addChild(healText); tween(healText, { y: healText.y - 60, alpha: 0 }, { duration: 500, easing: tween.linear, onFinish: function onFinish() { if (healText.parent) { healText.destroy(); } } }); } LK.getSound('pickup').play(); if (self.parent) { self.destroy(); } return true; } }; return self; }); // Enemy base class var Enemy = Container.expand(function () { var self = Container.call(this); self.hp = 9999; self.maxHp = 9999; self.damage = 1; self.speed = 0; self.expValue = 1; self.type = 'normal'; self.critChance = 0.25; // Enemy does not attach a graphic directly; subclasses should do this self.takeDamage = function (amount) { self.hp -= amount; LK.effects.flashObject(self, 0xff0000, 200); if (self.hp <= 0) { return true; // Enemy died } return false; // Enemy still alive }; self.update = function () { // Kart seçim ekranı aktifken düşman hareket etmesin ve saldırmasın if (!player || skillSelectionActive) { return; } // Subclasses should implement facing logic if needed // Debuff: Stun if (self.stunBuff && self.stunBuffTime !== undefined) { var stunDuration = Math.floor(self.stunBuff * 60); // seconds to frames if (gameTime - self.stunBuffTime < stunDuration) { // Stunned: do not move or attack return; } else { self.stunBuff = 0; self.stunBuffTime = undefined; } } // 1) Oyuncuya olan vektörü bul var dx = player.x - self.x; var dy = player.y - self.y; var dist = Math.sqrt(dx * dx + dy * dy); // Debuff: Slow var speedMul = 1; if (self.slowBuff && self.slowBuffTime !== undefined) { var slowDuration = 120; // 2 seconds if (gameTime - self.slowBuffTime < slowDuration) { speedMul = Math.max(0, 1 - self.slowBuff / 100); } else { self.slowBuff = 0; self.slowBuffTime = undefined; } } // 2) Eğer düşman, oyuncuya 150 pikselden yakınsa hareket etme if (dist > 150) { // 3) Aksi halde 50px uzaktan hareket et self.x += dx / dist * self.speed * speedMul; self.y += dy / dist * self.speed * speedMul; } // Bleeding+ if (self.bleedBuff && self.bleedBuffTime !== undefined) { var bleedDuration = 240; // 4 seconds (60fps) if (gameTime - self.bleedBuffTime < bleedDuration) { // Every 30 frames (0.5s), apply bleed damage if (!self.bleedTicks) { self.bleedTicks = 0; } var ticks = Math.floor((gameTime - self.bleedBuffTime) / 30); while (self.bleedTicks < ticks) { var bleedDmg = self.maxHp * (self.bleedBuff / 100); // allow fractional self.hp -= bleedDmg; LK.effects.flashObject(self, 0xaa0000, 100); // Show damage text for bleeding (always, even if <1) var dmgText = new Text2(bleedDmg.toFixed(1), { size: 48, fill: 0x00ff00, // green for regen, but for bleed use red stroke: 0x000000, strokeThickness: 4 }); dmgText.setText('-' + bleedDmg.toFixed(1)); dmgText.fill = 0xff3333; dmgText.anchor.set(0.5); dmgText.x = self.x; dmgText.y = self.y - (self.height ? self.height / 2 : 60); dmgText.alpha = 1; gameContainer.addChild(dmgText); tween(dmgText, { y: dmgText.y - 60, alpha: 0 }, { duration: 500, easing: tween.linear, onFinish: function onFinish() { if (dmgText.parent) { dmgText.destroy(); } } }); if (self.hp <= 0) { if (self.parent) { self.destroy(); } break; } self.bleedTicks++; } } else { self.bleedBuff = 0; self.bleedBuffTime = undefined; self.bleedTicks = 0; } } // Grit Wound+ if (self.gritWound && self.lastGritWoundTime !== undefined) { var gritDuration = 240; // 4 seconds if (gameTime - self.lastGritWoundTime < gritDuration) { // No regen logic here, but you can use self.gritWound for regen reduction elsewhere // Example: If you add enemy regen, show green text here if (self.enemyRegen && self.hp < self.maxHp) { var regenAmount = self.maxHp * (self.enemyRegen / 100); if (self.gritWound > 0) { regenAmount = regenAmount * (1 - self.gritWound / 100); } var oldHp = self.hp; self.hp = Math.min(self.hp + regenAmount, self.maxHp); var actualHealed = self.hp - oldHp; if (actualHealed > 0) { // Show green HP gain text with black border var healText = new Text2('+' + actualHealed.toFixed(1), { size: 48, fill: 0x00ff00, stroke: 0x000000, strokeThickness: 4 }); healText.anchor.set(0.5); healText.x = self.x; healText.y = self.y - (self.height ? self.height / 2 : 60); healText.alpha = 1; gameContainer.addChild(healText); tween(healText, { y: healText.y - 60, alpha: 0 }, { duration: 500, easing: tween.linear, onFinish: function onFinish() { if (healText.parent) { healText.destroy(); } } }); } } } else { self.gritWound = 0; self.lastGritWoundTime = undefined; } } // --- Enemy HP regen after wave 5, scaling with wave --- if (self.enemyRegen && self.enemyRegen > 0 && (!self.gritWound || self.gritWound === 0)) { if (!self.lastEnemyRegenTime) { self.lastEnemyRegenTime = 0; } if (gameTime - self.lastEnemyRegenTime >= 300) { // every 5s var regenAmount = self.maxHp * (self.enemyRegen / 100); var oldHp = self.hp; self.hp = Math.min(self.hp + regenAmount, self.maxHp); var actualHealed = self.hp - oldHp; if (actualHealed > 0) { // Show green HP gain text with black border var healText = new Text2('+' + actualHealed.toFixed(1), { size: 48, fill: 0x00ff00, stroke: 0x000000, strokeThickness: 4 }); healText.anchor.set(0.5); healText.x = self.x; healText.y = self.y - (self.height ? self.height / 2 : 60); healText.alpha = 1; gameContainer.addChild(healText); tween(healText, { y: healText.y - 60, alpha: 0 }, { duration: 500, easing: tween.linear, onFinish: function onFinish() { if (healText.parent) { healText.destroy(); } } }); } self.lastEnemyRegenTime = gameTime; } } // Burada asla destroy() yok; sadece hareketi sınırlıyoruz }; return self; }); // Tank Zombie subclass var TankZombie = Enemy.expand(function () { var self = Enemy.call(this); var tankGraphics = self.attachAsset('tankZombie', { anchorX: 0.5, anchorY: 0.5 }); self.maxHp = 80; self.hp = 80; self.damage = 20; self.speed = 0.8; self.attackRange = 200; self.attackCooldown = 180; // 3 saniye (60fps) self.attackTimer = 0; self.type = 'tankZombie'; self.update = function (origUpdate) { return function () { // Kart seçim ekranı aktifken tank zombi hareket etmesin ve saldırmasın if (skillSelectionActive) { return; } if (self.x < player.x) { tankGraphics.scaleX = 1; } else if (self.x > player.x) { tankGraphics.scaleX = -1; } var dx = player.x - self.x; var dy = player.y - self.y; var dist = Math.sqrt(dx * dx + dy * dy); if (dist > self.attackRange) { var speedMul = 1; if (self.slowBuff && self.slowBuffTime !== undefined) { var slowDuration = 120; if (gameTime - self.slowBuffTime < slowDuration) { speedMul = Math.max(0, 1 - self.slowBuff / 100); } } self.x += dx / dist * self.speed * speedMul; self.y += dy / dist * self.speed * speedMul; self.attackTimer = 0; } else { self.attackTimer++; if (self.attackTimer >= self.attackCooldown) { self.attackTimer = 0; var atk = gameContainer.addChild(new Container()); atk.attachAsset('tankZombieAttack', { anchorX: 0.5, anchorY: 0.5 }); atk.x = self.x + dx / dist * 70; atk.y = self.y + dy / dist * 70; atk.life = 6; // 6 frame = 0.1 saniye atk.hasHit = false; // Hasar verildi mi, onu izlemek için atk.update = function () { this.life--; // 2) Eğer henüz hasar vermediysen, o anda intersection kontrol et: if (!this.hasHit && this.intersects(player)) { player.takeDamage(self.damage); this.hasHit = true; // NOT: Burada destroy() çağırmıyoruz, sadece hasHit flag’ini true yapıyoruz. } // 3) Süre (life) dolduğunda nesneyi sil: if (this.life <= 0) { if (this.parent) { this.destroy(); } } }; } } origUpdate.call(self); }; }(self.update); return self; }); // Ranged Zombie subclass var RangedZombie = Enemy.expand(function () { var self = Enemy.call(this); var rangedGraphics = self.attachAsset('rangedZombie', { anchorX: 0.5, anchorY: 0.5 }); self.maxHp = 12; self.hp = 12; self.damage = 16; self.speed = 1.2; self.attackRange = 900; self.attackCooldown = 300; // 5 saniye (60fps) self.attackTimer = 0; self.type = 'rangedZombie'; self.update = function (origUpdate) { return function () { // Kart seçim ekranı aktifken menzilli zombi hareket etmesin ve saldırmasın if (skillSelectionActive) { return; } // Flip to face player if (self.x < player.x) { rangedGraphics.scaleX = 1; } else if (self.x > player.x) { rangedGraphics.scaleX = -1; } var dx = player.x - self.x; var dy = player.y - self.y; var dist = Math.sqrt(dx * dx + dy * dy); // Hareket ve saldırı state'i: menzile girince hareketi bırakıp saldırıya geç, çıkınca tekrar hareket et if (typeof self.isAttacking === "undefined") { self.isAttacking = false; } if (dist > self.attackRange) { // === MENZİL DIŞI: sadece hareket et ve origUpdate’i çağır === var speedMul = 1; if (self.slowBuff && self.slowBuffTime !== undefined) { var slowDuration = 120; if (gameTime - self.slowBuffTime < slowDuration) { speedMul = Math.max(0, 1 - self.slowBuff / 100); } } self.x += dx / dist * self.speed * speedMul; self.y += dy / dist * self.speed * speedMul; self.attackTimer = 0; self.isAttacking = false; // YALNIZCA BURADA Enemy.update’in (origUpdate’in) hareket+debuff kısımlarını çalıştırıyoruz origUpdate.call(self); } else { // === MENZİL İÇİ: hareket etmeyi tamamen bırak, sadece saldırı yap === self.isAttacking = true; self.attackTimer++; if (self.attackTimer >= self.attackCooldown) { self.attackTimer = 0; // Fire saliva projectile towards player var saliva = gameContainer.addChild(new Container()); saliva.attachAsset('rangedZombieSaliva', { anchorX: 0.5, anchorY: 0.5 }); saliva.x = self.x; saliva.y = self.y; var dirX = dx / dist; var dirY = dy / dist; saliva.speed = 18; saliva.update = function () { this.x += dirX * this.speed; this.y += dirY * this.speed; // If hits player, deal damage if (this.intersects(player)) { player.takeDamage(self.damage); if (this.parent) { this.destroy(); } } // Remove if out of camera var camMinX = camera.x; var camMaxX = camera.x + 2048; var camMinY = camera.y; var camMaxY = camera.y + 2732; if (this.x < camMinX || this.x > camMaxX || this.y < camMinY || this.y > camMaxY) { if (this.parent) { this.destroy(); } } }; } // Burada origUpdate çağrılmıyor → Enemy.update’in “hareket et” kısmı pasif kalacak } }; }(self.update); return self; }); // Normal Zombie subclass (renamed from Zombie) var NormalZombie = Enemy.expand(function () { var self = Enemy.call(this); var zombieGraphics = self.attachAsset('normalZombie', { anchorX: 0.5, anchorY: 0.5 }); self.maxHp = 16; self.hp = 16; self.damage = 8; self.speed = 1.0; self.attackRange = 150; self.attackCooldown = 90; // 1.5 saniye (60fps) self.attackTimer = 0; self.type = 'normalZombie'; self.update = function (origUpdate) { return function () { // Kart seçim ekranı aktifken normal zombi hareket etmesin ve saldırmasın if (skillSelectionActive) { return; } // Flip to face player if (self.x < player.x) { zombieGraphics.scaleX = 1; } else if (self.x > player.x) { zombieGraphics.scaleX = -1; } // Move or attack var dx = player.x - self.x; var dy = player.y - self.y; var dist = Math.sqrt(dx * dx + dy * dy); if (dist > self.attackRange) { // Move towards player var speedMul = 1; if (self.slowBuff && self.slowBuffTime !== undefined) { var slowDuration = 120; if (gameTime - self.slowBuffTime < slowDuration) { speedMul = Math.max(0, 1 - self.slowBuff / 100); } } self.x += dx / dist * self.speed * speedMul; self.y += dy / dist * self.speed * speedMul; self.attackTimer = 0; } else { // Attack every 1.5s self.attackTimer++; if (self.attackTimer >= self.attackCooldown) { self.attackTimer = 0; // Attack: spawn attack asset towards player var atk = gameContainer.addChild(new Container()); atk.attachAsset('normalZombieAttack', { anchorX: 0.5, anchorY: 0.5 }); atk.x = self.x + dx / dist * 60; atk.y = self.y + dy / dist * 60; atk.life = 6; // 6 frame = 0.1 saniye atk.hasHit = false; // Hasar verildi mi, onu izlemek için atk.update = function () { // 1) Önce remaining life’ı azalt: this.life--; // 2) Eğer henüz hasar vermediysen, o anda intersection kontrol et: if (!this.hasHit && this.intersects(player)) { player.takeDamage(self.damage); this.hasHit = true; // NOT: Burada destroy() çağırmıyoruz, sadece hasHit flag’ini true yapıyoruz. } // 3) Süre (life) dolduğunda nesneyi sil: if (this.life <= 0) { if (this.parent) { this.destroy(); } } }; } } origUpdate.call(self); }; }(self.update); return self; }); // Fast Zombie subclass var FastZombie = Enemy.expand(function () { var self = Enemy.call(this); var fastGraphics = self.attachAsset('fastZombie', { anchorX: 0.5, anchorY: 0.5 }); self.maxHp = 8; self.hp = 8; self.damage = 10; self.speed = 2.0; self.attackRange = 100; self.attackCooldown = 30; // 0.5 saniye (60fps) self.attackTimer = 0; self.type = 'fastZombie'; self.update = function (origUpdate) { return function () { // Kart seçim ekranı aktifken hızlı zombi hareket etmesin ve saldırmasın if (skillSelectionActive) { return; } if (self.x < player.x) { fastGraphics.scaleX = 1; } else if (self.x > player.x) { fastGraphics.scaleX = -1; } var dx = player.x - self.x; var dy = player.y - self.y; var dist = Math.sqrt(dx * dx + dy * dy); if (dist > self.attackRange) { var speedMul = 1; if (self.slowBuff && self.slowBuffTime !== undefined) { var slowDuration = 120; if (gameTime - self.slowBuffTime < slowDuration) { speedMul = Math.max(0, 1 - self.slowBuff / 100); } } self.x += dx / dist * self.speed * speedMul; self.y += dy / dist * self.speed * speedMul; self.attackTimer = 0; } else { self.attackTimer++; if (self.attackTimer >= self.attackCooldown) { self.attackTimer = 0; var atk = gameContainer.addChild(new Container()); atk.attachAsset('fastZombieAttack', { anchorX: 0.5, anchorY: 0.5 }); atk.x = self.x + dx / dist * 50; atk.y = self.y + dy / dist * 50; atk.life = 6; // 6 frame = 0.1 saniye atk.hasHit = false; // Hasar verildi mi, onu izlemek için atk.update = function () { this.life--; // 2) Eğer henüz hasar vermediysen, o anda intersection kontrol et: if (!this.hasHit && this.intersects(player)) { player.takeDamage(self.damage); this.hasHit = true; // NOT: Burada destroy() çağırmıyoruz, sadece hasHit flag’ini true yapıyoruz. } // 3) Süre (life) dolduğunda nesneyi sil: if (this.life <= 0) { if (this.parent) { this.destroy(); } } }; } } origUpdate.call(self); }; }(self.update); return self; }); // Boss Zombie subclass var BossZombie = Enemy.expand(function () { var self = Enemy.call(this); var bossGraphics = self.attachAsset('bossZombie', { anchorX: 0.5, anchorY: 0.5 }); self.maxHp = 400; self.hp = 400; self.type = 'bossZombie'; self.mode = 'melee'; // 'melee' or 'ranged' self.modeTimer = 0; self.modeSwitchDelay = 0; self.attackTimer = 0; self.attackCooldownMelee = 180; // 3s self.attackCooldownRanged = 24; // 0.4s self.attackRangeMelee = 250; self.attackRangeRanged = 1000; self.damageMelee = 40; self.damageRanged = 5; self.speed = 0; self.speedMelee = 1.2; self.speedRanged = 1.8; self.modeDurationMelee = 720; // 12s self.modeDurationRanged = 240; // 4s self.modeSwitchPause = 60; // 1s self.lastWarningTime = 0; self.warningCountdown = 0; self.warningActive = false; self.warningText = null; self.update = function (origUpdate) { return function () { // Kart seçim ekranı aktifken boss hareket etmesin ve saldırmasın if (skillSelectionActive) { return; } // Flip to face player if (self.x < player.x) { bossGraphics.scaleX = 1; } else if (self.x > player.x) { bossGraphics.scaleX = -1; } // Mode switching logic if (self.modeSwitchDelay > 0) { self.modeSwitchDelay--; // During pause, boss cannot move, but can attack if already in attack range // Defensive: do not move, but allow attack animation/effects if in range // For melee mode var dx = player.x - self.x; var dy = player.y - self.y; var dist = Math.sqrt(dx * dx + dy * dy); if (self.mode === 'melee' && dist <= self.attackRangeMelee) { self.attackTimer++; self.isAttacking = true; if (self.attackTimer >= self.attackCooldownMelee) { self.attackTimer = 0; var atk = gameContainer.addChild(new Container()); atk.attachAsset('bossZombieAttack1', { anchorX: 0.5, anchorY: 0.5 }); atk.x = self.x + dx / dist * 90; atk.y = self.y + dy / dist * 90; atk.life = 6; atk.hasHit = false; atk.update = function () { this.life--; if (!this.hasHit) { // Yakın dövüş saldırısı: doğrudan player.intersects ile kontrol et if (player && typeof player.x === "number" && typeof player.y === "number") { if (this.intersects(player)) { player.takeDamage(self.damageMelee); this.hasHit = true; } } } if (this.life <= 0) { if (this.parent) { this.destroy(); } } }; } } else if (self.mode === 'ranged' && dist <= self.attackRangeRanged) { self.attackTimer++; self.isAttacking = true; if (self.attackTimer % self.attackCooldownRanged === 0) { var bone = gameContainer.addChild(new Container()); bone.attachAsset('bossZombieAttack2', { anchorX: 0.5, anchorY: 0.5 }); bone.x = self.x; bone.y = self.y; var dirX = dx / dist; var dirY = dy / dist; bone.speed = 28; bone.update = function () { this.x += dirX * bone.speed; this.y += dirY * bone.speed; if (player && typeof player.x === "number" && typeof player.y === "number") { var dx2 = this.x - player.x; var dy2 = this.y - player.y; var dist2 = Math.sqrt(dx2 * dx2 + dy2 * dy2); if (dist2 <= 60) { player.takeDamage(self.damageRanged); if (this.parent) { this.destroy(); } } } var camMinX = camera.x; var camMaxX = camera.x + 2048; var camMinY = camera.y; var camMaxY = camera.y + 2732; if (this.x < camMinX || this.x > camMaxX || this.y < camMinY || this.y > camMaxY) { if (this.parent) { this.destroy(); } } }; } } else { self.isAttacking = false; self.attackTimer = 0; } // Do not move during pause return; } self.modeTimer++; if (self.mode === 'melee' && self.modeTimer >= self.modeDurationMelee) { // --- Only teleport boss away from player if within 800 range --- var dxTeleport = self.x - player.x; var dyTeleport = self.y - player.y; var distTeleport = Math.sqrt(dxTeleport * dxTeleport + dyTeleport * dyTeleport); // Only teleport if boss is within 800 units of player if (distTeleport <= 800 && distTeleport > 0) { // Move boss 800 units away from player, in the direction away from player self.x = player.x + dxTeleport / distTeleport * 800; self.y = player.y + dyTeleport / distTeleport * 800; self.mode = 'ranged'; self.modeTimer = 0; self.modeSwitchDelay = 120; // 2 seconds pause when switching to ranged self.isAttacking = false; // Defensive: reset attack state } else { // If not close enough, keep walking towards player (do not switch to ranged mode yet) // Just reset modeTimer so boss keeps trying until close enough self.modeTimer = self.modeDurationMelee; // keep at threshold, so check again next frame } } else if (self.mode === 'ranged' && self.modeTimer >= self.modeDurationRanged) { self.mode = 'melee'; self.modeTimer = 0; self.modeSwitchDelay = self.modeSwitchPause; self.isAttacking = false; // Defensive: reset attack state } var dx = player.x - self.x; var dy = player.y - self.y; var dist = Math.sqrt(dx * dx + dy * dy); // Boss warning if player is too far var bossDist = Math.sqrt((self.x - player.x) * (self.x - player.x) + (self.y - player.y) * (self.y - player.y)); if (bossDist > 2000) { if (!self.warningActive) { self.warningActive = true; self.warningCountdown = 5; self.lastWarningTime = gameTime; if (!self.warningText) { self.warningText = new Text2(t("bossWarning") + "5", { size: 80, fill: 0xff0000 }); self.warningText.anchor.set(0.5, 0); self.warningText.y = 300; LK.gui.top.addChild(self.warningText); } } else { if (gameTime - self.lastWarningTime >= 60) { self.warningCountdown--; self.lastWarningTime = gameTime; if (self.warningText) { self.warningText.setText(t("bossWarning") + self.warningCountdown); } } if (self.warningCountdown <= 0) { if (self.warningText && self.warningText.parent) { self.warningText.destroy(); } LK.showGameOver(); return; } } } else if (self.warningActive) { self.warningActive = false; if (self.warningText && self.warningText.parent) { self.warningText.destroy(); } self.warningText = null; } // Movement and attack logic if (self.mode === 'melee') { // Melee mode: slow, short range, high damage if (typeof self.isAttacking === "undefined") { self.isAttacking = false; } // Boss zombi yakın dövüşçü modunda saldırı menziline girdiğinde hareket etmeye devam etsin ve saldırı yapabilsin var speedMul = 1; if (self.slowBuff && self.slowBuffTime !== undefined) { var slowDuration = 120; if (gameTime - self.slowBuffTime < slowDuration) { speedMul = Math.max(0, 1 - self.slowBuff / 100); } } // Sadece saldırı menziline kadar hareket etsin, daha fazla yaklaşmasın if (dist > self.attackRangeMelee) { self.x += dx / dist * self.speedMelee * speedMul; self.y += dy / dist * self.speedMelee * speedMul; } // Saldırı menziline girildiyse saldırı yapabilsin if (dist <= self.attackRangeMelee) { self.attackTimer++; self.isAttacking = true; if (self.attackTimer >= self.attackCooldownMelee) { self.attackTimer = 0; var atk = gameContainer.addChild(new Container()); atk.attachAsset('bossZombieAttack1', { anchorX: 0.5, anchorY: 0.5 }); atk.x = self.x + dx / dist * 90; atk.y = self.y + dy / dist * 90; atk.life = 6; // 6 frame = 0.1 saniye atk.hasHit = false; // Hasar verildi mi, onu izlemek için atk.update = function () { this.life--; // Her frame'de intersection kontrolü yap, böylece animasyon karaktere her zaman hasar verebilir if (!this.hasHit && player && typeof player.x === "number" && typeof player.y === "number") { if (this.intersects(player)) { player.takeDamage(self.damageMelee); this.hasHit = true; } } if (this.life <= 0) { if (this.parent) { this.destroy(); } } }; } } else { self.attackTimer = 0; self.isAttacking = false; } } else if (self.mode === 'ranged') { // Ranged mode: normal speed, long range, low damage, fires bones if (typeof self.isAttacking === "undefined") { self.isAttacking = false; } if (dist > self.attackRangeRanged) { var speedMul = 1; if (self.slowBuff && self.slowBuffTime !== undefined) { var slowDuration = 120; if (gameTime - self.slowBuffTime < slowDuration) { speedMul = Math.max(0, 1 - self.slowBuff / 100); } } // Only move if not attacking if (!self.isAttacking) { self.x += dx / dist * self.speedRanged * speedMul; self.y += dy / dist * self.speedRanged * speedMul; } self.attackTimer = 0; self.isAttacking = false; } else { self.attackTimer++; self.isAttacking = true; if (self.attackTimer % self.attackCooldownRanged === 0) { // Fire bone projectile var bone = gameContainer.addChild(new Container()); bone.attachAsset('bossZombieAttack2', { anchorX: 0.5, anchorY: 0.5 }); bone.x = self.x; bone.y = self.y; var dirX = dx / dist; var dirY = dy / dist; bone.speed = 28; bone.update = function () { this.x += dirX * bone.speed; this.y += dirY * bone.speed; // --- Use circular hitbox for player (radius 60) --- if (player && typeof player.x === "number" && typeof player.y === "number") { var dx = this.x - player.x; var dy = this.y - player.y; var dist = Math.sqrt(dx * dx + dy * dy); if (dist <= 60) { player.takeDamage(self.damageRanged); if (this.parent) { this.destroy(); } } } var camMinX = camera.x; var camMaxX = camera.x + 2048; var camMinY = camera.y; var camMaxY = camera.y + 2732; if (this.x < camMinX || this.x > camMaxX || this.y < camMinY || this.y > camMaxY) { if (this.parent) { this.destroy(); } } }; } } } origUpdate.call(self); }; }(self.update); return self; }); var ExpOrb = Container.expand(function () { var self = Container.call(this); // 1) spawnTime kaydet self.spawnTime = gameTime; self.value = 1; // Each orb always gives 1 EXP self.magnetSpeed = 0; var orbGraphics = self.attachAsset('expOrb', { anchorX: 0.5, anchorY: 0.5 }); self.update = function () { // ORB YAŞI (frame cinsinden) var age = gameTime - self.spawnTime; // 2) Eğer 20 saniye geçmişse (1200 frame), orb’u yok et ve listeden çıkar if (age >= 1200) { // yok et if (self.parent) { self.destroy(); } var idx = expOrbs.indexOf(self); if (idx >= 0) { expOrbs.splice(idx, 1); } return; } // 3) Son 5 saniyede (age >= 900), yanıp sönme uygulaması if (age >= 900) { // kalan süre var remaining = 1200 - age; // 300’den 0’a kadar azalır // blink periyodu: remaining / 50’ye bağlı (en hızlı 1 frame’lik periyot) var blinkInterval = Math.ceil(remaining / 50); if (blinkInterval < 1) { blinkInterval = 1; } // yaşa göre çift/tek periyoda bölerek görünürlüğü değiştir var phase = Math.floor(age / blinkInterval); self.visible = phase % 2 === 0; } else { // son 5 saniye değilse görünür olsun self.visible = true; } // 4) Normal manyetik çekim hareketi if (!player || self.magnetSpeed === 0) { return; } var dx = player.x - self.x; var dy = player.y - self.y; var dist = Math.sqrt(dx * dx + dy * dy); if (dist > 0) { self.x += dx / dist * self.magnetSpeed; self.y += dy / dist * self.magnetSpeed; } }; return self; }); var Joystick = Container.expand(function () { var self = Container.call(this); self.active = false; self.dirX = 0; self.dirY = 0; var base = self.attachAsset('joystickBase', { anchorX: 0.5, anchorY: 0.5 }); base.alpha = 0.5; var knob = self.attachAsset('joystickKnob', { anchorX: 0.5, anchorY: 0.5 }); knob.alpha = 0.7; // Manuel knob merkez offset'i (ör: knob assetinin görsel merkezi ile gerçek merkez arasındaki fark) // Bu değerleri elle ayarlayabilirsin. Örneğin, knob görselinin merkezi 10px sağda ve 5px yukarıda ise: self.knobCenterOffsetX = 0; // Buraya elle düzeltmek istediğin değeri gir self.knobCenterOffsetY = 0; // Buraya elle düzeltmek istediğin değeri gir self.setKnobPosition = function (x, y) { // x, y: joystick base'in merkezine göre offset (dokunulan nokta - joystick merkezi) // Knob'un merkezi joystick base'in merkeziyle tam hizalı olmalı // Hareket yönü, knob merkezinden dokunulan pozisyona doğru olmalı // dx, dy: knob'un merkezinden dokunulan noktaya vektör // Manuel offset uygula var dx = x - self.knobCenterOffsetX; var dy = y - self.knobCenterOffsetY; var dist = Math.sqrt(dx * dx + dy * dy); var maxDist = 60; if (dist > maxDist) { dx = dx / dist * maxDist; dy = dy / dist * maxDist; } knob.x = dx; knob.y = dy; // Defensive: avoid division by zero if (maxDist === 0) { self.dirX = 0; self.dirY = 0; } else { // Hareket yönü: knob'un merkezinden dokunulan noktaya doğru normalize edilmiş vektör self.dirX = dx / maxDist; self.dirY = dy / maxDist; } }; self.reset = function () { knob.x = 0; knob.y = 0; self.dirX = 0; self.dirY = 0; self.active = false; }; return self; }); var Magnet = Container.expand(function () { var self = Container.call(this); self.active = true; var magnetGraphics = self.attachAsset('magnet', { anchorX: 0.5, anchorY: 0.5, width: 60, height: 60, tint: 0x0099ff }); self.update = function () { if (!player || !self.active) { return; } var dx = player.x - self.x; var dy = player.y - self.y; var dist = Math.sqrt(dx * dx + dy * dy); if (dist < 80) { self.active = false; // Magnetize all exp orbs on screen for (var i = 0; i < expOrbs.length; i++) { var orb = expOrbs[i]; var orbDx = Math.abs(orb.x - player.x); var orbDy = Math.abs(orb.y - player.y); if (orbDx < 1024 && orbDy < 1366) { orb.magnetSpeed = 20; } } if (self.parent) { self.destroy(); } return true; } }; return self; }); var Player = Container.expand(function () { var self = Container.call(this); self.level = 1; self.exp = 0; self.expToNext = 10; self.hp = 100; self.maxHp = 100; self.damage = 10.0; self.fireRate = 30; self.bulletSpeed = 15; self.moveSpeed = 5; self.pickupRange = 100; self.lastFire = 0; self.critChance = 0.25; // Oyuncu başlangıçta %25 kritik vuruş şansı /* ===== BUFFS ===== */ self.buffs = { damageReduction: 0, // percent, e.g. 10 means 10% moveSpeed: 0, // percent, e.g. 20 means 20% hpRegen: 0, // percent per 5s damageReflection: 0, // percent gritWound: 0, // percent enemySlow: 0, // percent enemyStun: 0, // seconds bleeding: 0, // percent per tick critChance: 0 // Critical Hit Chance+ buff, percent (15 means +15%) }; self.lastRegenTime = 0; /* ===== WEAPON INVENTORY ===== */ self.weapons = {}; // key → instance var playerGraphics = self.attachAsset('player', { anchorX: 0.5, anchorY: 0.5 }); self.takeDamage = function (amount) { // Damage Reduction+ var finalAmount = amount; if (self.buffs.damageReduction > 0) { finalAmount = Math.ceil(amount * (1 - self.buffs.damageReduction / 100)); } self.hp -= finalAmount; LK.effects.flashObject(self, 0xff0000, 300); LK.getSound('damageTaken').play(); // Damage Reflection+ if (self.buffs.damageReflection > 0 && enemies.length > 0) { // Find nearest enemy in contact (or just first in range) for (var i = 0; i < enemies.length; i++) { var e = enemies[i]; var dx = e.x - self.x; var dy = e.y - self.y; var distSq = dx * dx + dy * dy; if (distSq <= 22500) { // same as contact check var reflectDmg = Math.ceil(finalAmount * self.buffs.damageReflection / 100); if (reflectDmg > 0) { damageEnemy(e, reflectDmg); // Show damage text for reflection var dmgText = new Text2(reflectDmg.toFixed(1), { size: 48, fill: 0xffffff, stroke: 0x000000, strokeThickness: 4 }); dmgText.anchor.set(0.5); dmgText.x = e.x; dmgText.y = e.y - (e.height ? e.height / 2 : 60); dmgText.alpha = 1; gameContainer.addChild(dmgText); tween(dmgText, { y: dmgText.y - 60, alpha: 0 }, { duration: 500, easing: tween.linear, onFinish: function onFinish() { if (dmgText.parent) { dmgText.destroy(); } } }); } break; } } } if (self.hp <= 0) { // Show death screen and block game update showDeathScreen(); return; } }; self.gainExp = function (amount) { // Add exp orbs self.exp += amount; // Level up if exp orbs >= current level while (self.exp >= self.level) { self.exp -= self.level; self.levelUp(); } }; self.update = function () { // Kart seçim ekranı aktifken karakter hareket etmesin ve saldırmasın if (skillSelectionActive) { return; } // Flip player graphic based on movement direction if (self.lastX === undefined) { self.lastX = self.x; } if (self.x > self.lastX) { playerGraphics.scaleX = 1; } else if (self.x < self.lastX) { playerGraphics.scaleX = -1; } self.lastX = self.x; // Movement Speed+ if (self.buffs.moveSpeed > 0) { self.moveSpeed = 5 * (1 + self.buffs.moveSpeed / 100); } else { self.moveSpeed = 5; } // HP Regeneration+ if (self.buffs.hpRegen > 0 && gameTime - (self.lastRegenTime || 0) >= 300) { var regenAmount = self.maxHp * (self.buffs.hpRegen / 100); // allow fractional regen if (regenAmount > 0 && self.hp < self.maxHp) { var oldHp = self.hp; self.hp = Math.min(self.hp + regenAmount, self.maxHp); var actualHealed = self.hp - oldHp; if (actualHealed > 0) { LK.effects.flashObject(self, 0x00ff00, 200); // Show green HP gain text with black border var healText = new Text2('+' + actualHealed.toFixed(1), { size: 48, fill: 0x00ff00, stroke: 0x000000, strokeThickness: 4 }); healText.anchor.set(0.5); healText.x = self.x; healText.y = self.y - (self.height ? self.height / 2 : 60); healText.alpha = 1; gameContainer.addChild(healText); tween(healText, { y: healText.y - 60, alpha: 0 }, { duration: 500, easing: tween.linear, onFinish: function onFinish() { if (healText.parent) { healText.destroy(); } } }); } } self.lastRegenTime = gameTime; } for (var k in self.weapons) { var inst = self.weapons[k], meta = WEAPONS[k]; if (--inst.cdCnt <= 0) { fireWeapon(k, inst, meta); // inst.cd muhtemelen kesirsiz olmayabilir; istersen Math.floor ile yuvarla: inst.cdCnt = Math.floor(inst.cd); } } }; self.levelUp = function () { self.level++; // self.exp is now handled in gainExp, do not reset here self.expToNext = self.level * 15; // self.maxHp += 10; // Artık seviye atlayınca max HP artmıyor // Do NOT restore full health on level up // self.hp = self.maxHp; // Increase movement speed by 4% per level up self.moveSpeed *= 1.04; LK.getSound('levelup').play(); showSkillSelection(); }; return self; }); /**** * Initialize Game ****/ var game = new LK.Game({ backgroundColor: 0x111111 }); /**** * Game Code ****/ /* ==== UNIVERSAL HELPERS ==== */ // --- SOUND/MUSIC BLOCKER: Only allow introSpeech while red overlay is visible --- (function () { // Patch LK.getSound and LK.playMusic to block all except introSpeech/mainMenuMusic when window._blockAllSounds is true var origGetSound = LK.getSound; var origPlayMusic = LK.playMusic; // Track if introSpeech is currently playing window._introSpeechPlaying = false; LK.getSound = function (key) { var sound = origGetSound.call(LK, key); if (!sound) return sound; // Patch .play() to block all except introSpeech if _blockAllSounds is set or introSpeech is playing var origPlay = sound.play; sound.play = function () { // Block all sounds except introSpeech if _blockAllSounds or introSpeech is playing if ((window._blockAllSounds || window._introSpeechPlaying) && key !== 'introSpeech') { return; } // If introSpeech is about to play, set flag and set up end event if (key === 'introSpeech') { // Only set if not already playing if (!window._introSpeechPlaying) { window._introSpeechPlaying = true; // Try to listen for 'ended' event to clear the flag if (typeof sound.once === "function") { sound.once('ended', function () { window._introSpeechPlaying = false; }); } else { // Fallback: clear after 10s (length of introSpeech) LK.setTimeout(function () { window._introSpeechPlaying = false; }, 10000); } } } return origPlay.apply(sound, arguments); }; return sound; }; LK.playMusic = function (key, opts) { // Block all music except mainMenuMusic if _blockAllSounds, or block all music if introSpeech is playing // Also block mainMenuMusic if red overlay is visible (mainMenuRedBlocker exists and is visible) if (window._blockAllSounds && key !== 'mainMenuMusic' || window._introSpeechPlaying || key === 'mainMenuMusic' && typeof mainMenuRedBlocker !== "undefined" && mainMenuRedBlocker && mainMenuRedBlocker.visible) { return; } return origPlayMusic.apply(LK, arguments); }; })(); // This is normal for large files or slow connections and cannot be fixed in code. // Note: If music starts late, it may be due to the time required to load the music asset on first play. // --- MENU & LANGUAGE STATE --- var GAME_STATE = { MAIN_MENU: 0, PLAYING: 1, DEATH: 2 }; var gameState = GAME_STATE.MAIN_MENU; var currentLanguage = "en"; // "en" or "tr" var mainMenuContainer = null; var languageMenuContainer = null; var deathScreenContainer = null; // --- TEXTS --- var TEXTS = { en: { start: "Start Game", languages: "Languages", returnMainMenu: "Return to Main Menu", death: "You Died!", lang_tr: "Türkçe", lang_en: "English", hp: "HP", wave: "Wave", level: "Level", exp: "EXP", expShort: "EXP", weaponSelect: "Select Your Weapon", unlock: "Unlock", noOption: "No Option", buffs: { damageReduction: "Damage Reduction+", moveSpeed: "Movement Speed Increase+", hpRegen: "HP Regeneration+", damageReflection: "Damage Reflection+", gritWound: "Grit Wound+", enemySlow: "Enemy Slow+", enemyStun: "Enemy Stun+", bleeding: "Bleeding+", critChance: "Critical Hit Chance+", maxHpPlus: "Maximum HP+", magnetPlus: "Magnet+" }, buffDescs: { damageReduction: "Take 10% less damage per level", moveSpeed: "Move 20% faster per level", hpRegen: "Regenerate 1% max HP every 5s per level", damageReflection: "Reflect 5% damage per level", gritWound: "Reduce enemy regen by 10% per level", enemySlow: "Slow enemies by 10% per level", enemyStun: "Stun enemies for 0.1s per level", bleeding: "Bleed: 4s, 0.5s tick, 5% max HP per tick per level", critChance: "Increase critical hit chance by 15% per level", maxHpPlus: "Increase max HP by 25 per level", magnetPlus: "Nearby EXP orbs (150 radius, +150 per level) are quickly pulled to you." }, upgrade: "Upgrade", desc_damage: "Increases weapon damage by 20%", desc_atkspd: "Increases weapon attack speed by 20%", desc_range: "Increases weapon range and size by 20%", bossWarning: "You are too far from the Boss! ", expGain: "+", wavePrefix: "Wave", levelPrefix: "Level", expPrefix: "EXP", hpPrefix: "HP" }, tr: { start: "Oyuna Başla", languages: "Diller", returnMainMenu: "Ana Menüye Dön", death: "Öldün!", lang_tr: "Türkçe", lang_en: "English", hp: "Can", wave: "Dalga", level: "Seviye", exp: "EXP", expShort: "EXP", weaponSelect: "Silahını Seç", unlock: "Aç", noOption: "Seçenek Yok", buffs: { damageReduction: "Hasar Azaltma+", moveSpeed: "Hareket Hızı Artışı+", hpRegen: "Can Yenileme+", damageReflection: "Hasar Yansıtma+", gritWound: "Derin Yara+", enemySlow: "Düşman Yavaşlatma+", enemyStun: "Düşman Sersemletme+", bleeding: "Kanama+", critChance: "Kritik Vuruş Şansı+", maxHpPlus: "Maksimum Can+", magnetPlus: "Mıknatıs+" }, buffDescs: { damageReduction: "Her seviyede %10 daha az hasar alırsın", moveSpeed: "Her seviyede %20 daha hızlı hareket edersin", hpRegen: "Her seviyede 5 saniyede bir maksimum canının %1'i kadar yenilenirsin", damageReflection: "Her seviyede %5 hasarı yansıtırsın", gritWound: "Her seviyede düşman yenilenmesini %10 azaltır", enemySlow: "Her seviyede düşmanları %10 yavaşlatır", enemyStun: "Her seviyede düşmanları 0.1s sersemletir", bleeding: "Her seviyede 4s boyunca, 0.5s arayla, her tikte maksimum canın %5'i kadar kanama", critChance: "Her seviyede kritik vuruş şansını %15 artırır", maxHpPlus: "Her seviyede maksimum canı 25 artırır", magnetPlus: "Yakındaki EXP küreleri (150 yarıçap, seviye başına +150) hızlıca sana çekilir." }, upgrade: "Yükselt", desc_damage: "Silah hasarını %20 artırır", desc_atkspd: "Silah saldırı hızını %20 artırır", desc_range: "Silah menzilini ve boyutunu %20 artırır", bossWarning: "Boss'tan çok uzaktasın! ", expGain: "+", wavePrefix: "Dalga", levelPrefix: "Seviye", expPrefix: "EXP", hpPrefix: "Can" } }; // --- Language helper --- function t(key) { if (TEXTS[currentLanguage] && TEXTS[currentLanguage][key]) { return TEXTS[currentLanguage][key]; } // fallback return TEXTS["en"][key] || key; } // --- Menu helpers --- function showMainMenu() { gameState = GAME_STATE.MAIN_MENU; // Hide all game UI and stop all game sounds/music setGameUIVisible(false); LK.stopMusic(); window._mainMenuMusicPlaying = false; if (typeof LK.stopAllSounds === "function") { LK.stopAllSounds(); } // Remove other overlays if any if (mainMenuContainer) { mainMenuContainer.destroy(); } if (languageMenuContainer) { languageMenuContainer.destroy(); } if (deathScreenContainer) { deathScreenContainer.destroy(); } // Music is now handled in game.update based on gameState if (typeof currentMusic === "undefined") { currentMusic = null; } // Always play main menu music immediately when showing main menu if (currentMusic !== "mainMenuMusic") { LK.stopMusic(); // Defensive: ensure music is loaded and play only once if (typeof LK.playMusic === "function") { if (!window._mainMenuMusicPlaying) { window._mainMenuMusicPlaying = true; var musicObj = LK.playMusic('mainMenuMusic', { loop: true }); // Listen for 'ended' event to clear flag (should not happen for loop, but defensive) if (musicObj && typeof musicObj.once === "function") { musicObj.once('ended', function () { window._mainMenuMusicPlaying = false; }); } } } currentMusic = "mainMenuMusic"; } else { // Defensive: if music is already playing, do not start again } // === RED OVERLAY BLOCKER === // Show the red overlay only on first app launch, not when returning to main menu after death if (typeof window._hasShownRedOverlay === "undefined") { window._hasShownRedOverlay = false; } if (!window._hasShownRedOverlay) { window._hasShownRedOverlay = true; if (typeof mainMenuRedBlocker !== "undefined" && mainMenuRedBlocker && mainMenuRedBlocker.destroy) { mainMenuRedBlocker.destroy(); } // Play intro speech sound when red loading screen appears if (typeof LK.getSound === "function" && LK.getSound('introSpeech')) { // Stop all music and all sounds before playing introSpeech if (typeof LK.stopMusic === "function") { LK.stopMusic(); } if (typeof LK.stopAllSounds === "function") { LK.stopAllSounds(); } // Play introSpeech only once LK.getSound('introSpeech').play(); // Block all other sounds/music while red overlay is visible window._blockAllSounds = true; } mainMenuRedBlocker = game.addChild(new Container()); // Create a solid red rectangle using a shape asset var redRect = mainMenuRedBlocker.attachAsset('hpBarRect', { anchorX: 0, anchorY: 0, x: 0, y: 0, width: 2048, height: 2732, color: 0xff0000, tint: 0xff0000, alpha: 1 }); mainMenuRedBlocker.x = 0; mainMenuRedBlocker.y = 0; mainMenuRedBlocker.visible = true; mainMenuRedBlocker.interactive = true; mainMenuRedBlocker.down = function () {/* block all input */}; mainMenuRedBlocker.move = function () {/* block all input */}; mainMenuRedBlocker.up = function () {/* block all input */}; // --- Add "Loading..." text and spinner to red overlay --- // Add a logo image asset above Loading... text var logoArea = new Container(); logoArea.x = 1024; logoArea.y = 1000; // Replace this with your actual logo asset key and dimensions var logoImg = logoArea.attachAsset('myLogo', { anchorX: 0.5, anchorY: 0.5, width: 600, height: 220 }); mainMenuRedBlocker.addChild(logoArea); var redLoadingText = new Text2("Loading...", { size: 100, fill: 0xffffff }); redLoadingText.anchor.set(0.5, 0.5); redLoadingText.x = 1024; redLoadingText.y = 1200; mainMenuRedBlocker.addChild(redLoadingText); var redSpinner = new Container(); var redSpinnerAsset = redSpinner.attachAsset('chickenLeg', { anchorX: 0.5, anchorY: 0.5, width: 120, height: 120 }); redSpinner.x = 1024; redSpinner.y = 1350; mainMenuRedBlocker.addChild(redSpinner); redSpinner.update = function () { if (redSpinnerAsset) { redSpinnerAsset.rotation += 0.2; } }; mainMenuRedBlocker.update = function () { if (redSpinner && redSpinner.update) { redSpinner.update(); } }; // Preload main menu container and its children, but keep them hidden until red overlay is removed mainMenuContainer = game.addChild(new Container()); // Background var bg = mainMenuContainer.attachAsset('mainMenuBackground', { anchorX: 0.5, anchorY: 0.5, x: 1024, y: 1366 }); // Start Button var startBtn = mainMenuContainer.addChild(new Container()); startBtn.attachAsset('startButton', { anchorX: 0.5, anchorY: 0.5 }); startBtn.x = 1024; startBtn.y = 1200; var startTxt = new Text2(t("start"), { size: 72, fill: 0xffffff }); startTxt.anchor.set(0.5); startBtn.addChild(startTxt); startBtn.visible = true; startBtn.interactive = true; startBtn.down = function () { mainMenuContainer.destroy(); mainMenuContainer = null; startGame(); }; // Languages Button var langBtn = mainMenuContainer.addChild(new Container()); langBtn.attachAsset('languagesButton', { anchorX: 0.5, anchorY: 0.5 }); langBtn.x = 1024; langBtn.y = 1600; var langTxt = new Text2(t("languages"), { size: 60, fill: 0xffffff }); langTxt.anchor.set(0.5); langBtn.addChild(langTxt); langBtn.visible = true; langBtn.interactive = true; langBtn.down = function () { // Hide start and lang buttons and make them untouchable startBtn.visible = false; startBtn.interactive = false; langBtn.visible = false; langBtn.interactive = false; showLanguageMenu(function () { // After language selection, show buttons again and update texts startBtn.visible = true; startBtn.interactive = true; langBtn.visible = true; langBtn.interactive = true; // Update button texts to new language if (startTxt && startTxt.setText) { startTxt.setText(t("start")); } if (langTxt && langTxt.setText) { langTxt.setText(t("languages")); } }); }; // Hide main menu container and its children while red overlay is visible if (mainMenuContainer && mainMenuContainer.visible !== undefined) { mainMenuContainer.visible = false; for (var i = 0; i < mainMenuContainer.children.length; i++) { if (mainMenuContainer.children[i] && mainMenuContainer.children[i].visible !== undefined) { mainMenuContainer.children[i].visible = false; } } } // Remove after 10 seconds and show main menu container LK.setTimeout(function () { // Remove loading text and spinner from red overlay if (mainMenuRedBlocker) { // Defensive: remove children if not already destroyed if (typeof redLoadingText !== "undefined" && redLoadingText && redLoadingText.parent) { redLoadingText.destroy(); } if (typeof redSpinner !== "undefined" && redSpinner && redSpinner.parent) { redSpinner.destroy(); } } if (mainMenuRedBlocker && mainMenuRedBlocker.parent) { mainMenuRedBlocker.destroy(); mainMenuRedBlocker = null; } window._mainMenuMusicPlaying = false; // Unblock all sounds/music after red overlay is gone window._blockAllSounds = false; // Show main menu container and its children after red overlay is gone if (mainMenuContainer && mainMenuContainer.visible !== undefined) { mainMenuContainer.visible = true; for (var i = 0; i < mainMenuContainer.children.length; i++) { if (mainMenuContainer.children[i] && mainMenuContainer.children[i].visible !== undefined) { mainMenuContainer.children[i].visible = true; } } // Also ensure background is visible if (bg && bg.visible !== undefined) { bg.visible = true; } } // Play main menu music after red overlay is gone, but only if introSpeech is NOT playing if (typeof LK.playMusic === "function") { if (!window._introSpeechPlaying) { if (!window._mainMenuMusicPlaying) { window._mainMenuMusicPlaying = true; var musicObj = LK.playMusic('mainMenuMusic', { loop: true }); if (musicObj && typeof musicObj.once === "function") { musicObj.once('ended', function () { window._mainMenuMusicPlaying = false; }); } } currentMusic = "mainMenuMusic"; } else { // If introSpeech is still playing, wait until it ends before playing mainMenuMusic if (typeof LK.getSound === "function" && LK.getSound('introSpeech') && typeof LK.getSound('introSpeech').once === "function") { LK.getSound('introSpeech').once('ended', function () { if (typeof LK.playMusic === "function") { if (!window._mainMenuMusicPlaying) { window._mainMenuMusicPlaying = true; var musicObj = LK.playMusic('mainMenuMusic', { loop: true }); if (musicObj && typeof musicObj.once === "function") { musicObj.once('ended', function () { window._mainMenuMusicPlaying = false; }); } } currentMusic = "mainMenuMusic"; } }); } else { // Fallback: set a timeout to check again after 500ms (function waitForIntroSpeech() { if (!window._introSpeechPlaying) { if (typeof LK.playMusic === "function") { if (!window._mainMenuMusicPlaying) { window._mainMenuMusicPlaying = true; var musicObj = LK.playMusic('mainMenuMusic', { loop: true }); if (musicObj && typeof musicObj.once === "function") { musicObj.once('ended', function () { window._mainMenuMusicPlaying = false; }); } } currentMusic = "mainMenuMusic"; } } else { LK.setTimeout(waitForIntroSpeech, 500); } })(); } } } }, 10000); // === END RED OVERLAY BLOCKER === return; } // If not first launch, show main menu instantly (no red overlay) mainMenuContainer = game.addChild(new Container()); // Background var bg = mainMenuContainer.attachAsset('mainMenuBackground', { anchorX: 0.5, anchorY: 0.5, x: 1024, y: 1366 }); // Start Button var startBtn = mainMenuContainer.addChild(new Container()); startBtn.attachAsset('startButton', { anchorX: 0.5, anchorY: 0.5 }); startBtn.x = 1024; startBtn.y = 1200; var startTxt = new Text2(t("start"), { size: 72, fill: 0xffffff }); startTxt.anchor.set(0.5); startBtn.addChild(startTxt); startBtn.visible = true; startBtn.interactive = true; startBtn.down = function () { mainMenuContainer.destroy(); mainMenuContainer = null; startGame(); }; // Languages Button var langBtn = mainMenuContainer.addChild(new Container()); langBtn.attachAsset('languagesButton', { anchorX: 0.5, anchorY: 0.5 }); langBtn.x = 1024; langBtn.y = 1600; var langTxt = new Text2(t("languages"), { size: 60, fill: 0xffffff }); langTxt.anchor.set(0.5); langBtn.addChild(langTxt); langBtn.visible = true; langBtn.interactive = true; langBtn.down = function () { // Hide start and lang buttons and make them untouchable startBtn.visible = false; startBtn.interactive = false; langBtn.visible = false; langBtn.interactive = false; showLanguageMenu(function () { // After language selection, show buttons again and update texts startBtn.visible = true; startBtn.interactive = true; langBtn.visible = true; langBtn.interactive = true; // Update button texts to new language if (startTxt && startTxt.setText) { startTxt.setText(t("start")); } if (langTxt && langTxt.setText) { langTxt.setText(t("languages")); } }); }; if (mainMenuContainer && mainMenuContainer.visible !== undefined) { mainMenuContainer.visible = true; for (var i = 0; i < mainMenuContainer.children.length; i++) { if (mainMenuContainer.children[i] && mainMenuContainer.children[i].visible !== undefined) { mainMenuContainer.children[i].visible = true; } } } if (bg && bg.visible !== undefined) { bg.visible = true; } } function showLanguageMenu(onClose) { if (languageMenuContainer) { languageMenuContainer.destroy(); } languageMenuContainer = game.addChild(new Container()); languageMenuContainer.x = 1024; languageMenuContainer.y = 1366; // Türkçe button var trBtn = languageMenuContainer.addChild(new Container()); trBtn.attachAsset('langButton', { anchorX: 0.5, anchorY: 0.5 }); trBtn.x = 0; trBtn.y = -120; var trTxt = new Text2(t("lang_tr"), { size: 56, fill: 0x222222 }); trTxt.anchor.set(0.5); trBtn.addChild(trTxt); trBtn.down = function () { currentLanguage = "tr"; languageMenuContainer.destroy(); languageMenuContainer = null; window._mainMenuMusicPlaying = false; // Defensive: always reset flag on language menu // Do not stop or restart music here; showMainMenu will handle music logic // Do not stop or restart music here; let showMainMenu handle music logic // Instead of showMainMenu, just refresh mainMenuContainer text and language if (mainMenuContainer) { // Update start and language button texts for (var i = 0; i < mainMenuContainer.children.length; i++) { var ch = mainMenuContainer.children[i]; if (ch && ch.children && ch.children.length > 0) { var txt = ch.children[0]; if (txt && txt.setText) { if (ch === mainMenuContainer.children[0]) { txt.setText(t("start")); } else if (ch === mainMenuContainer.children[1]) { txt.setText(t("languages")); } } } } } if (typeof onClose === "function") { onClose(); } }; // English button var enBtn = languageMenuContainer.addChild(new Container()); enBtn.attachAsset('langButton', { anchorX: 0.5, anchorY: 0.5 }); enBtn.x = 0; enBtn.y = 120; var enTxt = new Text2(t("lang_en"), { size: 56, fill: 0x222222 }); enTxt.anchor.set(0.5); enBtn.addChild(enTxt); enBtn.down = function () { currentLanguage = "en"; languageMenuContainer.destroy(); languageMenuContainer = null; window._mainMenuMusicPlaying = false; // Defensive: always reset flag on language menu // Do not stop or restart music here; let showMainMenu handle music logic // Instead of showMainMenu, just refresh mainMenuContainer text and language if (mainMenuContainer) { // Update start and language button texts for (var i = 0; i < mainMenuContainer.children.length; i++) { var ch = mainMenuContainer.children[i]; if (ch && ch.children && ch.children.length > 0) { var txt = ch.children[0]; if (txt && txt.setText) { if (ch === mainMenuContainer.children[0]) { txt.setText(t("start")); } else if (ch === mainMenuContainer.children[1]) { txt.setText(t("languages")); } } } } } if (typeof onClose === "function") { onClose(); } }; } function showDeathScreen() { gameState = GAME_STATE.DEATH; // Hide all game UI and stop all game sounds/music setGameUIVisible(false); LK.stopMusic(); window._mainMenuMusicPlaying = false; if (typeof LK.stopAllSounds === "function") { LK.stopAllSounds(); } // Play death sound when death screen is shown if (LK.getSound && LK.getSound('death')) { LK.getSound('death').play(); } // Remove overlays if any if (mainMenuContainer) { mainMenuContainer.destroy(); } if (languageMenuContainer) { languageMenuContainer.destroy(); } if (deathScreenContainer) { deathScreenContainer.destroy(); } // Destroy all enemies and clear enemy array if (typeof enemies !== "undefined" && enemies.length > 0) { for (var i = enemies.length - 1; i >= 0; i--) { if (enemies[i] && typeof enemies[i].destroy === "function") { enemies[i].destroy(); } enemies.splice(i, 1); } } // Music is now handled in game.update based on gameState if (typeof currentMusic === "undefined") { currentMusic = null; } deathScreenContainer = game.addChild(new Container()); // Background var bg = deathScreenContainer.attachAsset('deathBackground', { anchorX: 0.5, anchorY: 0.5, x: 1024, y: 1366 }); // Death text var deathTxt = new Text2(t("death"), { size: 120, fill: 0xffffff }); deathTxt.anchor.set(0.5); deathTxt.x = 1024; deathTxt.y = 1100; deathScreenContainer.addChild(deathTxt); // Return Main Menu Button var retBtn = deathScreenContainer.addChild(new Container()); retBtn.attachAsset('returnMainMenu', { anchorX: 0.5, anchorY: 0.5 }); retBtn.x = 1024; retBtn.y = 1450; var retTxt = new Text2(t("returnMainMenu"), { size: 60, fill: 0xffffff }); retTxt.anchor.set(0.5); retBtn.addChild(retTxt); retBtn.down = function () { // Reset everything and go to main menu deathScreenContainer.destroy(); deathScreenContainer = null; resetGameToMainMenu(); }; } // --- Game start/reset helpers --- function startGame() { gameState = GAME_STATE.PLAYING; // Show all game UI and resume game sounds/music setGameUIVisible(true); LK.stopMusic(); window._mainMenuMusicPlaying = false; if (typeof LK.stopAllSounds === "function") { LK.stopAllSounds(); } if (typeof currentMusic === "undefined") { currentMusic = null; } // Music is now handled in game.update based on gameState // Reset all game variables and containers resetGameWorld(); } function resetGameToMainMenu() { // Destroy all overlays and containers setGameUIVisible(false); LK.stopMusic(); window._mainMenuMusicPlaying = false; if (typeof LK.stopAllSounds === "function") { LK.stopAllSounds(); } // Music is now handled in game.update based on gameState // Destroy all overlays if exist if (mainMenuContainer && mainMenuContainer.destroy) { mainMenuContainer.destroy(); } if (languageMenuContainer && languageMenuContainer.destroy) { languageMenuContainer.destroy(); } if (deathScreenContainer && deathScreenContainer.destroy) { deathScreenContainer.destroy(); } mainMenuContainer = null; languageMenuContainer = null; deathScreenContainer = null; // Destroy all game world objects and arrays if (typeof enemies !== "undefined" && enemies.length > 0) { for (var i = enemies.length - 1; i >= 0; i--) { if (enemies[i] && typeof enemies[i].destroy === "function") { enemies[i].destroy(); } enemies.splice(i, 1); } } if (typeof bullets !== "undefined" && bullets.length > 0) { for (var i = bullets.length - 1; i >= 0; i--) { if (bullets[i] && typeof bullets[i].destroy === "function") { bullets[i].destroy(); } bullets.splice(i, 1); } } if (typeof expOrbs !== "undefined" && expOrbs.length > 0) { for (var i = expOrbs.length - 1; i >= 0; i--) { if (expOrbs[i] && typeof expOrbs[i].destroy === "function") { expOrbs[i].destroy(); } expOrbs.splice(i, 1); } } if (typeof pickups !== "undefined" && pickups.length > 0) { for (var i = pickups.length - 1; i >= 0; i--) { if (pickups[i] && typeof pickups[i].destroy === "function") { pickups[i].destroy(); } pickups.splice(i, 1); } } if (typeof shurikenObjs !== "undefined" && shurikenObjs.length > 0) { for (var i = shurikenObjs.length - 1; i >= 0; i--) { if (shurikenObjs[i] && typeof shurikenObjs[i].destroy === "function") { shurikenObjs[i].destroy(); } shurikenObjs.splice(i, 1); } } if (typeof backgroundTiles !== "undefined" && backgroundTiles.length > 0) { for (var i = backgroundTiles.length - 1; i >= 0; i--) { if (backgroundTiles[i] && typeof backgroundTiles[i].destroy === "function") { backgroundTiles[i].destroy(); } backgroundTiles.splice(i, 1); } } // Remove all children from gameContainer if (typeof gameContainer !== "undefined" && gameContainer && gameContainer.children) { for (var i = gameContainer.children.length - 1; i >= 0; i--) { var ch = gameContainer.children[i]; if (ch && ch.destroy) { ch.destroy(); } } gameContainer.children = []; } // Remove overlays if (typeof weaponSelectionOverlay !== "undefined" && weaponSelectionOverlay && weaponSelectionOverlay.destroy) { weaponSelectionOverlay.destroy(); weaponSelectionOverlay = null; } if (typeof ov !== "undefined" && ov && ov.destroy) { ov.destroy(); } window._mainMenuMusicPlaying = false; // Defensive: always reset flag on overlays // Remove joystick from GUI if (typeof joystick !== "undefined" && joystick && joystick.parent && joystick.parent.removeChild) { joystick.parent.removeChild(joystick); } joystick = null; // Reset all global variables to initial state enemies = []; bullets = []; expOrbs = []; pickups = []; shurikenObjs = []; backgroundTiles = []; player = undefined; gameTime = 0; waveNumber = 1; nextWaveTimeout = null; skillSelectionActive = false; weaponSelectionActive = false; weaponSelectionOverlay = null; auraField = null; lastPickupSpawn = 0; camera = { x: 0, y: 0 }; selectedSkills = []; // UI elements timerText = null; waveText = null; levelText = null; hpText = null; // Remove any global buff counters if (typeof window.magnetPlusBuffCount !== "undefined") { window.magnetPlusBuffCount = undefined; } // Reset game state to main menu gameState = GAME_STATE.MAIN_MENU; currentMusic = null; // Show main menu showMainMenu(); } // --- Reset game world (player, enemies, etc) --- function resetGameWorld() { // Remove all children from gameContainer (or create if not exists) if (!gameContainer) { gameContainer = game.addChild(new Container()); } else if (gameContainer.children) { for (var i = gameContainer.children.length - 1; i >= 0; i--) { var ch = gameContainer.children[i]; if (ch && ch.destroy) { ch.destroy(); } } gameContainer.children = []; } // Reset arrays and variables enemies = []; bullets = []; expOrbs = []; pickups = []; shurikenObjs = []; auraField = null; waveNumber = 1; gameTime = 0; nextWaveTimeout = null; skillSelectionActive = false; weaponSelectionActive = true; // Remove overlays if (weaponSelectionOverlay) { weaponSelectionOverlay.destroy(); weaponSelectionOverlay = null; } window._mainMenuMusicPlaying = false; // Defensive: always reset flag on world reset // Remove background tiles if (backgroundTiles && backgroundTiles.length) { for (var i = 0; i < backgroundTiles.length; i++) { if (backgroundTiles[i] && backgroundTiles[i].destroy) { backgroundTiles[i].destroy(); } } backgroundTiles = []; } // Recreate background tiles for (var i = 0; i < bgTilesX; i++) { for (var j = 0; j < bgTilesY; j++) { var tile = LK.getAsset('seamlessBackGround', { anchorX: 0, anchorY: 0 }); tile.x = i * bgTileWidth; tile.y = j * bgTileHeight; gameContainer.addChild(tile); backgroundTiles.push(tile); } } // Create player player = gameContainer.addChild(new Player()); player.x = 1024; player.y = 1366; // Health bar for player player.hpBar = new Container(); player.hpBarBg = LK.getAsset('hpBarRect', { anchorX: 0, anchorY: 0.5 }); player.hpBarBg.alpha = 0.3; player.hpBarFg = LK.getAsset('hpBarRectFg', { anchorX: -0.02, anchorY: 0.5 }); player.hpBar.addChild(player.hpBarBg); player.hpBar.addChild(player.hpBarFg); player.hpBar.y = -140; player.hpBar.x = -110; player.addChild(player.hpBar); // Create joystick if not exists if (!joystick) { joystick = new Joystick(); joystick.x = 0; joystick.y = -200; LK.gui.bottom.addChild(joystick); } else { joystick.reset(); } // UI: create if not exists, else reset text if (!timerText) { timerText = new Text2('00:00', { size: 80, fill: 0xFFFFFF }); timerText.anchor.set(0.5, 0); LK.gui.top.addChild(timerText); } timerText.setText('00:00'); if (!waveText) { waveText = new Text2(t("wavePrefix") + " 1", { size: 80, fill: 0x00FFFF }); waveText.anchor.set(0.5, 0); waveText.y = 100; LK.gui.top.addChild(waveText); } waveText.setText(t("wavePrefix") + " 1"); if (!levelText) { levelText = new Text2(t("levelPrefix") + " 1", { size: 60, fill: 0xFFFF00 }); levelText.anchor.set(0, 0); levelText.y = 10; levelText.x = 200; LK.gui.top.addChild(levelText); } levelText.setText(t("levelPrefix") + " 1"); if (!hpText) { hpText = new Text2(t("hpPrefix") + ": 100/100", { size: 60, fill: 0xFF0000 }); hpText.anchor.set(1, 0); hpText.y = 10; hpText.x = -200; LK.gui.top.addChild(hpText); } hpText.setText(t("hpPrefix") + ": 100/100"); // Remove skill overlays if (typeof ov !== "undefined" && ov && ov.destroy) { ov.destroy(); } window._mainMenuMusicPlaying = false; // Defensive: always reset flag on overlays (world reset) // Weapon selection overlay weaponSelectionActive = true; weaponSelectionOverlay = game.addChild(new Container()); weaponSelectionOverlay.x = 1024; weaponSelectionOverlay.y = 1366; var allWeaponKeys = Object.keys(WEAPONS); for (var i = 0; i < allWeaponKeys.length; i++) { (function (idx) { var key = allWeaponKeys[idx]; var card = weaponSelectionOverlay.addChild(new Container()); card.attachAsset('skillCard', { anchorX: 0.5, anchorY: 0.5 }); var col = idx % 3; var row = Math.floor(idx / 3); card.x = (col - 1) * 420; card.y = (row - 1) * 700; // Localize weapon name for selection overlay var trWeaponNames = { sword: "Kılıç", boomerang: "Bumerang", ball: "Top", rocket: "Roket", brick: "Tuğla", lightning: "Yıldırım", aura: "Aura", molotov: "Molotof", shuriken: "Ninja Yıldızı" }; var weaponLabel = currentLanguage === "tr" && trWeaponNames[key] ? trWeaponNames[key] : key.charAt(0).toUpperCase() + key.slice(1); var txt = new Text2(weaponLabel, { size: 48, fill: 0xffffff }); txt.anchor.set(0.5); card.addChild(txt); card.down = function () { player.weapons = {}; addWeapon(key); weaponSelectionActive = false; weaponSelectionOverlay.destroy(); skillSelectionActive = false; spawnWave(); }; })(i); } skillSelectionActive = true; } // --- New zombie attack/projectile assets --- function _typeof(o) { "@babel/helpers - typeof"; return _typeof = "function" == typeof Symbol && "symbol" == typeof Symbol.iterator ? function (o) { return typeof o; } : function (o) { return o && "function" == typeof Symbol && o.constructor === Symbol && o !== Symbol.prototype ? "symbol" : typeof o; }, _typeof(o); } function isWithinRange(e, maxRange) { var dx = e.x - player.x; var dy = e.y - player.y; return dx * dx + dy * dy <= maxRange * maxRange; } function addWeapon(key) { // <-- artık global if (player && Object.keys(player.weapons).length >= 6) { return; // Zaten 6 silah varsa yeni eklemeye izin verme } var m = WEAPONS[key]; var target = player ? player : { weapons: {} }; // sınıf kurulumunda da çalışsın target.weapons[key] = { cd: m.cd, cdCnt: m.cd, dmgMul: 1, rngMul: 1, amount: 1, upgrade: 0, enchTaken: [] }; } function rand(min, max) { return min + Math.random() * (max - min); } function distSq(a, b) { var dx = a.x - b.x, dy = a.y - b.y; return dx * dx + dy * dy; } function unitDir(from, to) { var dx = to.x - from.x, dy = to.y - from.y, d = Math.sqrt(dx * dx + dy * dy); return { dx: dx / d, dy: dy / d, len: d }; } /* ==== WEAPON META TABLE ==== */ var WEAPONS = { sword: { cd: 120, dmg: 10, range: 200, type: 'melee', ench: ["Sword Damage+", "Sword Attack Speed+", "Sword Range+"], up: ["Sword +1", "Sword +2", "Sword +3"] }, boomerang: { cd: 240, dmg: 6, range: 650, type: 'boomerang', ench: ["Boomerang Damage+", "Boomerang Attack Speed+", "Boomerang Range+"], up: ["Boomerang +1", "Boomerang +2", "Boomerang +3"] }, ball: { cd: 240, dmg: 6, range: 650, type: 'ball', ench: ["Ball Damage+", "Ball Attack Speed+", "Ball Range+"], up: ["Ball +1", "Ball +2", "Ball +3"] }, rocket: { cd: 480, dmg: 16, range: 800, type: 'rocket', ench: ["Rocket Damage+", "Rocket Attack Speed+", "Rocket Range+"], up: ["Rocket +1", "Rocket +2", "Rocket +3"] }, brick: { cd: 240, dmg: 6, range: 450, type: 'brick', ench: ["Brick Damage+", "Brick Attack Speed+", "Brick Range+"], up: ["Back 75°", "Front 45°", "Back 45°"] }, lightning: { cd: 180, dmg: 10, range: 900, type: 'lightning', ench: ["Lightning Damage+", "Lightning Attack Speed+", "Lightning Range+"], up: ["Lightning +1", "Lightning +2", "Lightning +3"] }, aura: { cd: 60, dmg: 3, range: 250, type: 'aura', ench: ["Aura Damage+", "Aura Attack Speed+", "Aura Range+"], up: ["Slow 25%", "Slow 50%", "Slow 75%"] }, molotov: { cd: 180, dmg: 9, range: 550, type: 'molotov', ench: ["Molotov Damage+", "Molotov Attack Speed+", "Molotov Range+"], up: ["Molotov +1", "Molotov +2", "Molotov +3"] }, shuriken: { cd: 180, dmg: 6, range: 300, type: 'shuriken', ench: ["Shuriken Damage+", "Shuriken Attack Speed+", "Shuriken Range+"], up: ["Shuriken +1", "Shuriken +2", "Shuriken +3"] } }; var player; var enemies = []; var bullets = []; var expOrbs = []; var joystick; var gameTime = 0; var waveNumber = 1; // bossSpawned variable removed; boss is now spawned every 10th wave var selectedSkills = []; var camera = { x: 0, y: 0 }; var gameContainer; var skillSelectionActive = false; var pickups = []; var lastPickupSpawn = 0; // --- Game world variables (do not initialize world here, only declare) --- var gameContainer; var bgTileAsset = LK.getAsset('seamlessBackGround', { anchorX: 0, anchorY: 0 }); var bgTileWidth = bgTileAsset.width; var bgTileHeight = bgTileAsset.height; var bgTilesX = Math.ceil(2048 / bgTileWidth) + 2; // +2 for safe margin var bgTilesY = Math.ceil(2732 / bgTileHeight) + 2; var backgroundTiles = []; var player; var weaponSelectionActive = false; var weaponSelectionOverlay = null; var joystick = null; var timerText = null; var waveText = null; var levelText = null; var hpText = null; // Helper to show/hide all game UI elements function setGameUIVisible(visible) { if (timerText) { timerText.visible = visible; } if (waveText) { waveText.visible = visible; } if (levelText) { levelText.visible = visible; } if (hpText) { hpText.visible = visible; } // Hide joystick as well if not playing if (joystick) { joystick.visible = visible; } if (!visible) { window._mainMenuMusicPlaying = false; } } // Spawn functions function spawnEnemy(type, x, y) { var enemy; if (type === 'normal' || type === 'normalZombie') { enemy = new NormalZombie(); } else if (type === 'rangedZombie') { enemy = new RangedZombie(); } else if (type === 'fastZombie') { enemy = new FastZombie(); } else if (type === 'tankZombie') { enemy = new TankZombie(); } else if (type === 'bossZombie') { enemy = new BossZombie(); } else { enemy = new NormalZombie(); } enemy.x = x; enemy.y = y; // --- Düşman can yenileme: 5. dalgadan sonra, her 5 dalgada +1% artar --- if (waveNumber > 5) { var regenPercent = 5 + Math.floor((waveNumber - 6) / 5) + 0; enemy.enemyRegen = regenPercent; } else { enemy.enemyRegen = 0; } enemies.push(enemy); gameContainer.addChild(enemy); // --- Health bar for enemy --- enemy.hpBar = new Container(); enemy.hpBarBg = LK.getAsset('enemyHpBarRect', { anchorX: 0.0, anchorY: 0.5 }); enemy.hpBarBg.alpha = 0.3; enemy.hpBarFg = LK.getAsset('enemyHpBarRectFg', { anchorX: -0.04, anchorY: 0.5 }); enemy.hpBar.addChild(enemy.hpBarBg); enemy.hpBar.addChild(enemy.hpBarFg); enemy.hpBar.y = -110; enemy.hpBar.x = -80; enemy.addChild(enemy.hpBar); } function spawnWave() { var spawnRadius = 1500; var spawnList = []; // Eski dalga sistemi: her dalgada normal, ranged, fast, tank ve boss (her 10. dalgada) zombi var normalCount = 5 + (waveNumber - 1); var rangedCount = waveNumber >= 3 ? waveNumber - 2 : 0; var fastCount = waveNumber >= 6 ? waveNumber - 5 : 0; var tankCount = waveNumber >= 9 ? waveNumber - 8 : 0; var bossCount = waveNumber % 10 === 0 ? Math.floor(waveNumber / 10) : 0; for (var i = 0; i < normalCount; i++) { var angle = Math.random() * Math.PI * 2; var x = player.x + Math.cos(angle) * spawnRadius; var y = player.y + Math.sin(angle) * spawnRadius; spawnList.push({ type: 'normalZombie', x: x, y: y }); } for (var i = 0; i < rangedCount; i++) { var angle = Math.random() * Math.PI * 2; var x = player.x + Math.cos(angle) * spawnRadius; var y = player.y + Math.sin(angle) * spawnRadius; spawnList.push({ type: 'rangedZombie', x: x, y: y }); } for (var i = 0; i < fastCount; i++) { var angle = Math.random() * Math.PI * 2; var x = player.x + Math.cos(angle) * spawnRadius; var y = player.y + Math.sin(angle) * spawnRadius; spawnList.push({ type: 'fastZombie', x: x, y: y }); } for (var i = 0; i < tankCount; i++) { var angle = Math.random() * Math.PI * 2; var x = player.x + Math.cos(angle) * spawnRadius; var y = player.y + Math.sin(angle) * spawnRadius; spawnList.push({ type: 'tankZombie', x: x, y: y }); } for (var i = 0; i < bossCount; i++) { var angle = Math.random() * Math.PI * 2; var x = player.x + Math.cos(angle) * spawnRadius; var y = player.y + Math.sin(angle) * spawnRadius; spawnList.push({ type: 'bossZombie', x: x, y: y }); } // Shuffle spawnList for random order for (var i = spawnList.length - 1; i > 0; i--) { var j = Math.floor(Math.random() * (i + 1)); var temp = spawnList[i]; spawnList[i] = spawnList[j]; spawnList[j] = temp; } // Staggered spawn with 0.1s (6 frames) delay between each var spawnIdx = 0; function spawnNextEnemy() { if (spawnIdx < spawnList.length) { var info = spawnList[spawnIdx]; spawnEnemy(info.type, info.x, info.y); spawnIdx++; if (spawnIdx < spawnList.length) { LK.setTimeout(spawnNextEnemy, 100); // 0.1s = 100ms } } } spawnNextEnemy(); } function dropExpOrb(x, y) { var orb = new ExpOrb(); orb.x = x; orb.y = y; // orb.value is always 1 in ExpOrb class expOrbs.push(orb); gameContainer.addChild(orb); } /* =============================================================== */ /* NINE-WEAPON FIRE / PROJECTILES */ /* =============================================================== */ function nearestEnemy() { var best = null, bestD = 1e9; for (var i = 0; i < enemies.length; i++) { var d = distSq(player, enemies[i]); if (d < bestD) { best = enemies[i]; bestD = d; } } return best; } function damageEnemy(e, dmg, attacker) { // attacker: optional, can be player or enemy // Play hit sound on every hit LK.getSound('hit').play(); // Determine crit chance and crit type var critChance = 0.25; // default var critType = null; // null, "low", "high" var isPlayerAttack = false; if (attacker === undefined) { attacker = player; } if (attacker === player) { isPlayerAttack = true; critChance = player.critChance !== undefined ? player.critChance : 0.25; } else if (attacker && attacker.type === 'normal') { critChance = attacker.critChance !== undefined ? attacker.critChance : 0.25; } var critRoll = Math.random(); var critValue = 1; if (critRoll < critChance) { // Crit! Now decide low or high if (Math.random() < 0.5) { critType = "low"; critValue = 1.2; } else { critType = "high"; critValue = 1.4; } } var finalDmg = dmg * critValue; // Apply debuffs if player has buffs if (player && player.buffs) { // Grit Wound+ if (player.buffs.gritWound > 0) { e.gritWound = player.buffs.gritWound; e.lastGritWoundTime = gameTime; } // Enemy Slow+ if (player.buffs.enemySlow > 0) { e.slowBuff = player.buffs.enemySlow; e.slowBuffTime = gameTime; } // Enemy Stun+ if (player.buffs.enemyStun > 0) { e.stunBuff = player.buffs.enemyStun; e.stunBuffTime = gameTime; } // Bleeding+ if (player.buffs.bleeding > 0) { e.bleedBuff = player.buffs.bleeding; e.bleedBuffTime = gameTime; e.bleedTicks = 0; } } // Hasar yazısı göster if (typeof finalDmg === "number") { var fillColor = 0xffffff; var strokeColor = 0x000000; if (isPlayerAttack) { if (critType === "low") { fillColor = 0xffe066; // sarı } else if (critType === "high") { fillColor = 0xff3333; // kırmızı } else { fillColor = 0xffffff; // beyaz } } else { // Enemy attack, use default fillColor = 0xff3333; } var dmgText = new Text2(finalDmg.toFixed(1), { size: 48, fill: fillColor, stroke: strokeColor, strokeThickness: 4 }); dmgText.anchor.set(0.5); dmgText.x = e.x; dmgText.y = e.y - (e.height ? e.height / 2 : 60); dmgText.alpha = 1; gameContainer.addChild(dmgText); // Yavaşça yukarı çıkıp kaybolsun tween(dmgText, { y: dmgText.y - 60, alpha: 0 }, { duration: 500, easing: tween.linear, onFinish: function onFinish() { if (dmgText.parent) { dmgText.destroy(); } } }); } if (e.takeDamage(finalDmg)) { // Play death sound (if you have a death sound asset, e.g. 'death') if (LK.getSound('death')) { LK.getSound('death').play(); } // Ölünce exp orb düşür: dropExpOrb(e.x, e.y); // Sonra düşmanı kaldır ve yok et: var idx = enemies.indexOf(e); enemies.splice(idx, 1); e.destroy(); } } /* ---- sword (melee slash) ---- */ function swingSword(inst) { // Kılıç ateşlendiğinde sesi çal if (LK.getSound && LK.getSound('swordSoundEffect')) { LK.getSound('swordSoundEffect').play(); } var sorted = enemies.slice().sort(function (a, b) { return distSq(player, a) - distSq(player, b); }); if (sorted.length === 0) { return; } for (var n = 0; n < inst.amount; n++) { var idx = n < sorted.length ? n : 0; var target = sorted[idx]; var dir = unitDir(player, target); var arc = gameContainer.addChild(new Container()); arc.x = player.x; arc.y = player.y; arc.attachAsset('swordHit', { anchorX: 0, anchorY: 0.5, rotation: Math.atan2(dir.dy, dir.dx), scaleX: inst.rngMul, scaleY: inst.rngMul }); arc.life = 10; arc.dmg = WEAPONS.sword.dmg * inst.dmgMul; arc.hitList = []; arc.update = function () { if (--this.life <= 0) { if (this.parent) { this.destroy(); } return; } var maxRange = WEAPONS.sword.range * inst.rngMul; for (var i = enemies.length - 1; i >= 0; i--) { var e = enemies[i]; // hem arc.intersects hem oyuncudan menzil kontrolü if (this.hitList.indexOf(e) === -1 && this.intersects(e) && isWithinRange(e, maxRange)) { this.hitList.push(e); damageEnemy(e, this.dmg); } } }; } } /* ---- boomerang ---- */ function fireBoomerang(inst) { // Bumerang ateşlendiğinde sesi çal if (LK.getSound && LK.getSound('bumerangSoundEffect')) { LK.getSound('bumerangSoundEffect').play(); } var target = nearestEnemy(); if (!target) { return; } var dir = unitDir(player, target); var maxRange = WEAPONS.boomerang.range * inst.rngMul; for (var n = 0; n < inst.amount; n++) { var p = new Container(); var boomerangAsset = p.attachAsset('boomerang', { anchorX: 0.5, anchorY: 0.5, scaleX: inst.rngMul, scaleY: inst.rngMul }); p._rotationAngle = 0; p._boomerangAsset = boomerangAsset; p.x = player.x; p.y = player.y; p._startX = player.x; p._startY = player.y; p._dirX = dir.dx; p._dirY = dir.dy; p._phase = "out"; // "out" = gidiş, "back" = dönüş p._distance = 0; p._maxRange = maxRange; p._speed = 18; p.dmg = WEAPONS.boomerang.dmg * inst.dmgMul; p.hitEnemies = []; p.update = function () { if (skillSelectionActive) { return; } // Boomerang assetini döndür if (this._boomerangAsset) { this._rotationAngle = (this._rotationAngle || 0) + 0.3; this._boomerangAsset.rotation = this._rotationAngle; } // Hareket if (this._phase === "out") { this.x += this._dirX * this._speed; this.y += this._dirY * this._speed; this._distance += this._speed; if (this._distance >= this._maxRange) { this._phase = "back"; } } else if (this._phase === "back") { // Dönüşte, oyuncuya doğru hareket et var dx = player.x - this.x; var dy = player.y - this.y; var d = Math.sqrt(dx * dx + dy * dy); if (d < this._speed) { // Oyuncuya ulaştıysa yok et if (this.parent) { this.destroy(); } var idx4 = bullets.indexOf(this); if (idx4 >= 0) { bullets.splice(idx4, 1); } return; } this.x += dx / d * this._speed; this.y += dy / d * this._speed; } // Düşmanlara çarpma kontrolü, menzil bakmaksızın, yok olmadan biçip geçmeli for (var i = enemies.length - 1; i >= 0; i--) { var e = enemies[i]; if (this.hitEnemies.indexOf(e) === -1 && this.intersects(e)) { damageEnemy(e, this.dmg); this.hitEnemies.push(e); } // Eğer boomerang artık düşmanla temas etmiyorsa, tekrar vurabilmesi için hitEnemies'den çıkar if (this.hitEnemies.indexOf(e) !== -1 && !this.intersects(e)) { this.hitEnemies.splice(this.hitEnemies.indexOf(e), 1); } } // Kameranın (görünen alanın) dışına çıktıysa sil (fail-safe) var camMinX = camera.x; var camMaxX = camera.x + 2048; var camMinY = camera.y; var camMaxY = camera.y + 2732; if (this.x < camMinX || this.x > camMaxX || this.y < camMinY || this.y > camMaxY) { if (this.parent) { this.destroy(); } var idx = bullets.indexOf(this); if (idx >= 0) { bullets.splice(idx, 1); } return; } }; bullets.push(p); gameContainer.addChild(p); } } /* ---- top (rebound ball) ---- */ function fireBall(inst) { var maxRange = WEAPONS.ball.range * (player.weapons && player.weapons.ball ? player.weapons.ball.rngMul : 1); var target = null; for (var i = 0; i < enemies.length; i++) { if (isWithinRange(enemies[i], maxRange)) { target = enemies[i]; break; } } // Eğer menzilde hiç düşman yoksa top fırlatma if (!target) { return; } // Top ateşlendiğinde sesi çal (yalnızca gerçekten top fırlatılıyorsa) if (LK.getSound && LK.getSound('ballSoundEffect')) { LK.getSound('ballSoundEffect').play(); } var dir = unitDir(player, target); for (var n = 0; n < inst.amount; n++) { var p = new Container(); // Attach the 'ball' asset, scale by range multiplier var ballGraphics = p.attachAsset('ball', { anchorX: 0.5, anchorY: 0.5, scaleX: inst.rngMul, scaleY: inst.rngMul }); p._rotationAngle = 0; p._ballAsset = ballGraphics; p.x = player.x; p.y = player.y; p.dirX = dir.dx; p.dirY = dir.dy; p.damage = WEAPONS.ball.dmg * inst.dmgMul; p.speed = 14; p.bounced = false; p.hitEnemies = []; p.update = function () { // Seviye seçim ekranı açıkken hiçbir şey yapma if (skillSelectionActive) { return; } // Hareket this.x += this.dirX * this.speed; this.y += this.dirY * this.speed; // Rotate ball asset if (this._ballAsset) { this._rotationAngle = (this._rotationAngle || 0) + 0.3; this._ballAsset.rotation = this._rotationAngle; } // Kamera (görünen alan) sınırlarını belirle var camMinX = camera.x; var camMaxX = camera.x + 2048; var camMinY = camera.y; var camMaxY = camera.y + 2732; // Kameranın dışına çıktıysa yok et if (this.x < camMinX || this.x > camMaxX || this.y < camMinY || this.y > camMaxY) { if (this.parent) { this.destroy(); } var bi = bullets.indexOf(this); if (bi >= 0) { bullets.splice(bi, 1); } return; } // Düşmana çarpma kontrolü (menzilsiz, ekranda nerde olursa olsun vurmalı ve sekmeli) for (var i = enemies.length - 1; i >= 0; i--) { var e = enemies[i]; if (this.hitEnemies.indexOf(e) === -1 && this.intersects(e)) { damageEnemy(e, this.damage); this.hitEnemies.push(e); // Sadece ilk çarpmada sekmeli if (!this.bounced) { this.dirX = -this.dirX; this.dirY = -this.dirY; this.bounced = true; } } // Eğer ball artık düşmanla temas etmiyorsa, tekrar vurabilmesi için hitEnemies'den çıkar if (this.hitEnemies.indexOf(e) !== -1 && !this.intersects(e)) { this.hitEnemies.splice(this.hitEnemies.indexOf(e), 1); } } }; bullets.push(p); gameContainer.addChild(p); } } /* ---- rocket ---- */ function fireRocket(inst) { // Roket ateşlendiğinde sesi çal if (LK.getSound && LK.getSound('rocketSoundEffect')) { LK.getSound('rocketSoundEffect').play(); } var target = nearestEnemy(); if (!target) { return; } var dir = unitDir(player, target); // Bu silahın menzili: var maxRange = WEAPONS.rocket.range * inst.rngMul; for (var n = 0; n < inst.amount; n++) { var p = gameContainer.addChild(new Container()); p.attachAsset('rocket', { anchorX: 0.5, anchorY: 0.5, scaleX: inst.rngMul, scaleY: inst.rngMul }); p.x = player.x; p.y = player.y; p.speed = 10; p.dmg = WEAPONS.rocket.dmg * inst.dmgMul; // Track target enemy at fire time p._targetEnemy = target; p._exploded = false; p.update = function () { if (skillSelectionActive) { return; } // Defensive: if target enemy is dead, just fly straight var targetEnemy = this._targetEnemy; if (!this._exploded && targetEnemy && enemies.indexOf(targetEnemy) !== -1) { // Home in on target enemy's center var dx = targetEnemy.x - this.x; var dy = targetEnemy.y - this.y; var dist = Math.sqrt(dx * dx + dy * dy); if (dist > 0) { this.x += dx / dist * this.speed; this.y += dy / dist * this.speed; } // Check if reached center (use <= speed for precision) if (dist <= this.speed) { this.x = targetEnemy.x; this.y = targetEnemy.y; this._exploded = true; LK.getSound('rocketBoom').play(); // a) Dairesel patlama efekti: rocketBoom asseti kullanılmalı var explosion = gameContainer.addChild(new Container()); var gfx = LK.getAsset('rocketBoom', { anchorX: 0.5, anchorY: 0.5, // Restore original scale (no 2x) scaleX: inst.rngMul * (80 / 70), scaleY: inst.rngMul * (80 / 70), alpha: 0.6 }); explosion.addChild(gfx); explosion.x = this.x; explosion.y = this.y; explosion.life = 30; explosion.update = function () { if (--this.life <= 0) { this.destroy(); } else { this.children[0].alpha = this.life / 30 * 0.6; } }; // b) AoE hasar: sadece menzil içindekileri vur for (var k = enemies.length - 1; k >= 0; k--) { var e2 = enemies[k]; // Restore original AoE radius (radius^2 = 16000) if (distSq(this, e2) < 16000) { // Rocket patlaması hasar versin damageEnemy(e2, this.dmg); } } // c) Bu roketi sil if (this.parent) { this.destroy(); } var bi = bullets.indexOf(this); if (bi >= 0) { bullets.splice(bi, 1); } return; } } else if (!this._exploded) { // If target is gone, fly straight in last direction if (typeof this.dirX === "undefined" || typeof this.dirY === "undefined") { // Set direction to original target var origDir = unitDir({ x: player.x, y: player.y }, { x: this.x, y: this.y }); this.dirX = origDir.dx; this.dirY = origDir.dy; } this.x += this.dirX * this.speed; this.y += this.dirY * this.speed; } // 3) Kameranın (görünen alanın) dışına çıkarsa sil var camMinX = camera.x; var camMaxX = camera.x + 2048; var camMinY = camera.y; var camMaxY = camera.y + 2732; if (this.x < camMinX || this.x > camMaxX || this.y < camMinY || this.y > camMaxY) { if (this.parent) { this.destroy(); } var bi2 = bullets.indexOf(this); if (bi2 >= 0) { bullets.splice(bi2, 1); } return; } }; bullets.push(p); } } /* ---- brick ---- */ function throwBrick(inst, angDeg) { // Tuğla ateşlendiğinde sesi çal if (LK.getSound && LK.getSound('brickSoundEffect')) { LK.getSound('brickSoundEffect').play(); } var rad = angDeg * Math.PI / 180; var p = gameContainer.addChild(new Container()); var brickAsset = p.attachAsset('brick', { anchorX: 0.5, anchorY: 0.5, scaleX: inst.rngMul, scaleY: inst.rngMul }); p._rotationAngle = 0; p._brickAsset = brickAsset; p.x = player.x; p.y = player.y; // Brick should be thrown upwards at 75 degrees (not downwards) p.vx = Math.cos(rad) * 14; p.vy = -Math.abs(Math.sin(rad) * 14); // always upwards p.g = 0.3; p.dmg = WEAPONS.brick.dmg * inst.dmgMul; // Bu silahın menzili: var maxRange = WEAPONS.brick.range * inst.rngMul; p._hitEnemies = []; p.update = function () { if (skillSelectionActive) { return; } // 1) Yerçekimi ve hareket this.vy += this.g; this.x += this.vx; this.y += this.vy; // Rotate brick asset if (this._brickAsset) { this._rotationAngle = (this._rotationAngle || 0) + 0.3; this._brickAsset.rotation = this._rotationAngle; } // 2) Düşmana çarpma kontrolü, brick yok olmasın, her düşmana bir kez vurabilsin, menzil bakmaksızın ekrandan çıkana kadar vurmalı for (var i = enemies.length - 1; i >= 0; i--) { var e = enemies[i]; if (this._hitEnemies.indexOf(e) === -1 && this.intersects(e)) { damageEnemy(e, this.dmg); this._hitEnemies.push(e); } // Eğer brick artık düşmanla temas etmiyorsa, tekrar vurabilmesi için _hitEnemies'den çıkar if (this._hitEnemies.indexOf(e) !== -1 && !this.intersects(e)) { this._hitEnemies.splice(this._hitEnemies.indexOf(e), 1); } } // 3) Kameranın (görünen alanın) dışına çıkarsa yok et var camMinX = camera.x; var camMaxX = camera.x + 2048; var camMinY = camera.y; var camMaxY = camera.y + 2732; if (this.x < camMinX || this.x > camMaxX || this.y < camMinY || this.y > camMaxY) { if (this.parent) { this.destroy(); } return; } }; } /* ---- lightning ---- */ function castLightning(inst) { // Yıldırım ateşlendiğinde sesi çal if (LK.getSound && LK.getSound('lightningSoundEffect')) { LK.getSound('lightningSoundEffect').play(); } // 1) Menzildeki düşmanları filtrele var maxRange = WEAPONS.lightning.range * inst.rngMul; var candidates = enemies.filter(function (e) { var dx = e.x - player.x; var dy = e.y - player.y; return dx * dx + dy * dy <= maxRange * maxRange; }); if (candidates.length === 0) { return; } // 2) Rastgele birini seç var victim = candidates[Math.floor(Math.random() * candidates.length)]; // 3) Efekti ekle var l = gameContainer.addChild(new Container()); l.attachAsset('lightning', { anchorX: 0.5, anchorY: 1, scaleX: inst.rngMul, scaleY: inst.rngMul }); l.x = victim.x; l.y = victim.y; l.life = 15; l.update = function () { if (skillSelectionActive) { return; } if (--this.life === 14) { damageEnemy(victim, WEAPONS.lightning.dmg * inst.dmgMul); } if (this.life <= 0) { if (this.parent) { this.destroy(); } } }; } /* ---- aura ---- */ var auraField = null; function ensureAura(inst) { // 1) Sadece ilk defa oluşturulacak: if (!auraField) { auraField = player.addChild(new Container()); auraField.attachAsset('auraField', { anchorX: 0.5, anchorY: 0.5, alpha: 0.3, scaleX: inst.rngMul, scaleY: inst.rngMul }); // Oyuncunun tam ortası (yerel koordinat): auraField.x = 0; auraField.y = 0; } // Her frame aura görselini güncelle (range buff alınırsa anında büyüsün/küçülsün) if (auraField && auraField.children && auraField.children.length > 0) { auraField.children[0].scaleX = inst.rngMul; auraField.children[0].scaleY = inst.rngMul; // Rotate aura asset if (typeof auraField._rotationAngle === "undefined") { auraField._rotationAngle = 0; } auraField._rotationAngle += 0.3; // 10 kat daha hızlı döndür auraField.children[0].rotation = auraField._rotationAngle; } // 2) Hasar uygulama kısmı (oyuncudan r uzaklıktaysa vur): var r = WEAPONS.aura.range * inst.rngMul; for (var i = enemies.length - 1; i >= 0; i--) { var e = enemies[i]; var dx = e.x - player.x; var dy = e.y - player.y; if (dx * dx + dy * dy < r * r) { damageEnemy(e, WEAPONS.aura.dmg * inst.dmgMul); } } } /* ---- molotov ---- */ function throwMolotov(inst) { // Molotov ateşlendiğinde sesi çal if (LK.getSound && LK.getSound('molotovSoundEffect')) { LK.getSound('molotovSoundEffect').play(); } // Find nearest enemy var target = nearestEnemy(); if (!target) { return; } var dir = unitDir(player, target); var p = gameContainer.addChild(new Container()); var molotovAsset = p.attachAsset('molotov', { anchorX: 0.5, anchorY: 0.5, scaleX: inst.rngMul, scaleY: inst.rngMul }); p._rotationAngle = 0; p._molotovAsset = molotovAsset; p.x = player.x; p.y = player.y; p.dirX = dir.dx; p.dirY = dir.dy; p.speed = 16; p.life = 120; // Give enough life to reach enemy p._targetEnemy = target; p.update = function () { if (skillSelectionActive) { return; } this.x += this.dirX * this.speed; this.y += this.dirY * this.speed; // Rotate molotov asset if (this._molotovAsset) { this._rotationAngle = (this._rotationAngle || 0) + 0.3; this._molotovAsset.rotation = this._rotationAngle; } // Check for collision with target enemy if (this._targetEnemy && this.intersects(this._targetEnemy)) { // Explode at enemy position spawnFlame(this._targetEnemy.x, this._targetEnemy.y, inst); if (this.parent) { this.destroy(); } return; } // If life runs out, just destroy (fail-safe) if (--this.life <= 0) { if (this.parent) { this.destroy(); } } }; bullets.push(p); } /* ---- molotov flame ---- */ function spawnFlame(x, y, inst) { // Molotov patladığında sesi çal (sadece bir kere, flame yaratılırken) if (LK.getSound && LK.getSound('molotovBoom')) { LK.getSound('molotovBoom').play(); } // 1) Alev container’ını oluşturup ekrana ekliyoruz: var f = gameContainer.addChild(new Container()); f.attachAsset('molotovFlame', { anchorX: 0.5, anchorY: 0.5, alpha: 0.4, scaleX: inst.rngMul, scaleY: inst.rngMul }); f.x = x; f.y = y; // 2) “Kaçıncı frame’deyiz?” ve “her düşmana kaç kez vuruldu?” bilgisini tutacak: f._flameTick = 0; // Şu anda kaçıncı güncellemede olduğumuz f._hitInfo = {}; // { [enemyId]: { enteredFrame, tickCount } } // 3) Alev ömrü: 61 frame (≈1 saniye + 1 frame). Böylece 0–60. frame’lerde // update() çalışır; 60 frame sonundaki üçüncü hasar da tetiklenir. f.life = 61; // 4) Her vuruş için 3 hasar verecek (WEAPONS.molotov.dmg = 9 ise /3 → 3). // inst.dmgMul varsa onunla da çarpalım. var singleHit = WEAPONS.molotov.dmg / 3 * inst.dmgMul; // 5) Alevin etki yarıçapı (radius) ve bunun karesini hesaplıyoruz: var radius = 70 * inst.rngMul; var radiusSq = radius * radius; f.update = function () { if (skillSelectionActive) { return; // Kart seçimi açıkken hiçbir şey yapma } this._flameTick++; // 6) Her düşman için: eğer bu frame’de alev yarıçapı içindeyse, // “enteredFrame” atamasını yap, ardından 0/30/60 frame kontrollerini yap. for (var i = enemies.length - 1; i >= 0; i--) { var e = enemies[i]; var dx = e.x - f.x; var dy = e.y - f.y; var distSq = dx * dx + dy * dy; if (distSq <= radiusSq) { // Düşman ilk kez alevin içine giriyorsa benzersiz bir ID atayalım: if (typeof e._molotovFlameId === "undefined") { e._molotovFlameId = Math.random().toString(36).substr(2, 9) + "_" + Math.floor(Math.random() * 1e6); } var id = e._molotovFlameId; // Eğer bu ID henüz kayıtlı değilse, “enteredFrame” ve “tickCount=0” set et: if (!this._hitInfo[id]) { this._hitInfo[id] = { enteredFrame: this._flameTick, tickCount: 0 }; // İlk temas anında bir kere **3 hasar** vur: damageEnemy(e, singleHit); this._hitInfo[id].tickCount = 1; } var info = this._hitInfo[id]; var framesSinceEnter = this._flameTick - info.enteredFrame; // 7) Eğer bir kez vuruldu (tickCount == 1) ve 30 frame geçtiyse → ikinci 3 hasar: if (info.tickCount === 1 && framesSinceEnter >= 30) { damageEnemy(e, singleHit); info.tickCount = 2; } // 8) Eğer iki kez vuruldu (tickCount == 2) ve 60 frame geçtiyse → üçüncü 3 hasar: else if (info.tickCount === 2 && framesSinceEnter >= 60) { damageEnemy(e, singleHit); info.tickCount = 3; } // 9) Üç vuruş da tamamlandıysa bu düşmanla ilgili kaydı silebiliriz: if (info.tickCount >= 3) { delete this._hitInfo[id]; } } // “else” bloğunda hiç silme veya sıfırlama yapmıyoruz, // böylece düşman kısa süre sonra çıksa bile sayaç devam eder. } // 10) Ömür bitti mi? Bittiğinde kendini yok et: this.life--; if (this.life <= 0) { if (this.parent) { this.destroy(); } return; } // 11) Fail-safe: Alev ekran dışına çıktıysa sil: var camMinX = camera.x; var camMaxX = camera.x + 2048; var camMinY = camera.y; var camMaxY = camera.y + 2732; if (this.x < camMinX || this.x > camMaxX || this.y < camMinY || this.y > camMaxY) { if (this.parent) { this.destroy(); } return; } }; } /* ---- shuriken ring ---- */ var shurikenObjs = []; // Shuriken now always visible, orbits player, speed = attack speed, range+ increases both orbit radius and asset size var _maintainShurikens = function maintainShurikens(inst) { // Always create shurikens if not present or if amount changed if (shurikenObjs.length !== inst.amount) { shurikenObjs.forEach(function (s) { if (s.parent) { s.destroy(); } }); shurikenObjs = []; var n = inst.amount; var r = WEAPONS.shuriken.range * inst.rngMul; var sizeMul = inst.rngMul; // Range+ increases both orbit and asset size for (var i = 0; i < n; i++) { var s = gameContainer.addChild(new Container()); s.attachAsset('shuriken', { anchorX: 0.5, anchorY: 0.5, scaleX: sizeMul, scaleY: sizeMul }); s.angle = i / n * Math.PI * 2; s.hitEnemies = []; s.update = function () { if (skillSelectionActive) { return; } var curR = WEAPONS.shuriken.range * inst.rngMul; var curSizeMul = inst.rngMul; // Orbit around player, speed = attack speed (cd) var baseCd = WEAPONS.shuriken.cd; var curCd = inst.cd || baseCd; // Higher attack speed = lower cd = faster orbit var speed = 0.15 * (baseCd / curCd); this.angle += speed; this.x = player.x + Math.cos(this.angle) * curR; this.y = player.y + Math.sin(this.angle) * curR; // Update asset size if range+ changes if (this.children && this.children.length > 0) { this.children[0].scaleX = curSizeMul; this.children[0].scaleY = curSizeMul; if (typeof this._rotationAngle === "undefined") { this._rotationAngle = 0; } this._rotationAngle += 0.4; this.children[0].rotation = this._rotationAngle; } // Damage enemies if intersecting and within range for (var k = enemies.length - 1; k >= 0; k--) { var e = enemies[k]; if (this.hitEnemies.indexOf(e) === -1 && this.intersects(e) && isWithinRange(e, curR)) { damageEnemy(e, WEAPONS.shuriken.dmg * inst.dmgMul); this.hitEnemies.push(e); } // Reset hitEnemies if not intersecting anymore (so shuriken can hit again on next contact) if (this.hitEnemies.indexOf(e) !== -1 && (!this.intersects(e) || !isWithinRange(e, curR))) { this.hitEnemies.splice(this.hitEnemies.indexOf(e), 1); } } }; shurikenObjs.push(s); } } // Always update shuriken positions even if already created for (var i = 0; i < shurikenObjs.length; i++) { if (typeof shurikenObjs[i].update === "function") { shurikenObjs[i].update(); } } }; /* ---- ana fireWeapon anahtarı ---- */ function fireWeapon(key, inst, meta) { switch (meta.type) { case 'melee': swingSword(inst); break; case 'boomerang': { // Only fire boomerang if there is an enemy in range var maxRange = WEAPONS.boomerang.range * inst.rngMul; var hasEnemy = false; for (var i = 0; i < enemies.length; i++) { var dx = enemies[i].x - player.x; var dy = enemies[i].y - player.y; if (dx * dx + dy * dy <= maxRange * maxRange) { hasEnemy = true; break; } } if (hasEnemy && player.weapons && player.weapons.boomerang) { fireBoomerang(inst); } } break; case 'ball': { // fireBall already checks for enemy in range and does not play sound if not fireBall(inst); } break; case 'rocket': { // Only fire rocket if there is an enemy in range var maxRange = WEAPONS.rocket.range * inst.rngMul; var hasEnemy = false; for (var i = 0; i < enemies.length; i++) { var dx = enemies[i].x - player.x; var dy = enemies[i].y - player.y; if (dx * dx + dy * dy <= maxRange * maxRange) { hasEnemy = true; break; } } if (hasEnemy) { fireRocket(inst); } } break; case 'brick': { // Only fire brick if there is an enemy in range var maxRange = WEAPONS.brick.range * inst.rngMul; var hasEnemy = false; for (var i = 0; i < enemies.length; i++) { var dx = enemies[i].x - player.x; var dy = enemies[i].y - player.y; if (dx * dx + dy * dy <= maxRange * maxRange) { hasEnemy = true; break; } } if (hasEnemy) { // Brick en yakındaki düşman ekranın sağında mı solunda mı ona göre fırlatılmalı var nearest = nearestEnemy(); var throwRight = true; if (nearest) { if (nearest.x < player.x) { throwRight = false; } else { throwRight = true; } } else { throwRight = true; } if (throwRight) { throwBrick(inst, 75); if (inst.upgrade > 0) { throwBrick(inst, 105); } if (inst.upgrade > 1) { throwBrick(inst, 45); } if (inst.upgrade > 2) { throwBrick(inst, 135); } } else { throwBrick(inst, 255); if (inst.upgrade > 0) { throwBrick(inst, 225); } if (inst.upgrade > 1) { throwBrick(inst, 285); } if (inst.upgrade > 2) { throwBrick(inst, 195); } } } } break; case 'lightning': { // Only fire lightning if there is an enemy in range var maxRange = WEAPONS.lightning.range * inst.rngMul; var hasEnemy = false; for (var i = 0; i < enemies.length; i++) { var dx = enemies[i].x - player.x; var dy = enemies[i].y - player.y; if (dx * dx + dy * dy <= maxRange * maxRange) { hasEnemy = true; break; } } if (hasEnemy) { for (var n = 0; n < inst.amount; n++) { castLightning(inst); } } } break; case 'aura': ensureAura(inst); break; case 'molotov': { // Only fire molotov if there is an enemy in range var maxRange = WEAPONS.molotov.range * inst.rngMul; var hasEnemy = false; for (var i = 0; i < enemies.length; i++) { var dx = enemies[i].x - player.x; var dy = enemies[i].y - player.y; if (dx * dx + dy * dy <= maxRange * maxRange) { hasEnemy = true; break; } } if (hasEnemy) { for (var n = 0; n < inst.amount; n++) { throwMolotov(inst); } } } break; case 'shuriken': _maintainShurikens(inst); break; } } function showSkillSelection() { skillSelectionActive = true; // 1) Yeni bir Overlay (seçim ekranı) oluşturuyoruz var ov = game.addChild(new Container()); ov.x = 1024; ov.y = 1366; // Add title text for skill selection var titleTxt = new Text2(t("weaponSelect"), { size: 64, fill: 0xffffff }); titleTxt.anchor.set(0.5, 0); titleTxt.y = -420; ov.addChild(titleTxt); var choices = []; // 2) Eğer oyuncu seviyesi 5'in katı ise (5,10,15,...) if (player.level % 5 === 0) { var ownedCount = Object.keys(player.weapons).length; // 2.a) Hâlâ < 6 silah sahibi ise: yeni silah açma seçenekleri if (ownedCount < 6) { // Kilidi açılmamış silahlar havuzunu al var pool = Object.keys(WEAPONS).filter(function (key) { return !player.weapons[key]; }); // Havuzdan rastgele 3 silah göster, her biri benzersiz while (choices.length < 3 && pool.length > 0) { var rnd = Math.floor(Math.random() * pool.length); choices.push(pool.splice(rnd, 1)[0]); } } else { // 2.b) Zaten 6 silah sahibi ise: önce silahların upgrade durumlarını kontrol et var upgradePool = []; Object.keys(player.weapons).forEach(function (key) { var inst = player.weapons[key]; if (inst.upgrade < 3) { // Eğer bu silah hâlâ < 3 aşama güçlendirilmişse, // “bu silahın bir sonraki upgrade aşaması” havuza eklenir. upgradePool.push({ w: key }); } }); if (upgradePool.length > 0) { // 2.b.i) En az bir silah yükseltmesi kalmış demektir: // Rastgele en fazla 3 tane “silah güçlendirme” seçeneği göster, her biri benzersiz while (choices.length < 3 && upgradePool.length > 0) { var rnd2 = Math.floor(Math.random() * upgradePool.length); choices.push({ w: upgradePool[rnd2].w, type: "upgrade" }); upgradePool.splice(rnd2, 1); } } else { // 2.b.ii) Tüm 6 silah zaten upgrade === 3 ise: // Bu aşamadan sonra artık sadece efsun kartı göster. // Her kart benzersiz olmalı (hem silah hem efsun kombinasyonu) var ownedKeys2 = Object.keys(player.weapons); var usedPairs = []; var attempts = 0; while (choices.length < 3 && attempts < 20) { var randomKey2 = ownedKeys2[Math.floor(Math.random() * ownedKeys2.length)]; var enchList = WEAPONS[randomKey2].ench; var rnd3 = enchList[Math.floor(Math.random() * enchList.length)]; var pairKey = randomKey2 + "|" + rnd3; if (usedPairs.indexOf(pairKey) === -1) { choices.push({ w: randomKey2, e: rnd3 }); usedPairs.push(pairKey); } attempts++; } } } } else { // 3) Seviye 5'in katı değilse: doğrudan efsun kartları göster // Her kart benzersiz olmalı (hem silah hem efsun kombinasyonu) var ownedKeys = Object.keys(player.weapons); var usedPairs2 = []; var attempts2 = 0; while (choices.length < 3 && attempts2 < 20) { var randomKey = ownedKeys[Math.floor(Math.random() * ownedKeys.length)]; var enchList = WEAPONS[randomKey].ench; var rnd4 = enchList[Math.floor(Math.random() * enchList.length)]; var pairKey2 = randomKey + "|" + rnd4; if (usedPairs2.indexOf(pairKey2) === -1) { choices.push({ w: randomKey, e: rnd4 }); usedPairs2.push(pairKey2); } attempts2++; } } // 4) choices dizisini “3 adet kart” olarak UI’da çizelim // --- BUFF havuzu --- // --- Buff/efsun/buff limit tracking --- if (!player.buffCounts) { player.buffCounts = {}; } if (!player.efsunCounts) { player.efsunCounts = {}; } // --- BUFF havuzu --- var buffPool = [{ key: "damageReduction", label: "Damage Reduction+", desc: "Take 10% less damage per level" }, { key: "moveSpeed", label: "Movement Speed Increase+", desc: "Move 20% faster per level" }, { key: "hpRegen", label: "HP Regeneration+", desc: "Regenerate 1% max HP every 5s per level" }, { key: "damageReflection", label: "Damage Reflection+", desc: "Reflect 5% damage per level" }, { key: "gritWound", label: "Grit Wound+", desc: "Reduce enemy regen by 10% per level" }, { key: "enemySlow", label: "Enemy Slow+", desc: "Slow enemies by 10% per level" }, { key: "enemyStun", label: "Enemy Stun+", desc: "Stun enemies for 0.1s per level" }, { key: "bleeding", label: "Bleeding+", desc: "Bleed: 4s, 0.5s tick, 5% max HP per tick per level" }, { key: "critChance", label: "Critical Hit Chance+", desc: "Increase critical hit chance by 15% per level" }, { key: "maxHpPlus", label: "Maximum HP+", desc: "Increase max HP by 25 per level" }, { key: "magnetPlus", label: "Magnet+", desc: "Nearby EXP orbs (150 radius, +150 per level) are quickly pulled to you." }]; // Remove buffs that have been taken 5 times buffPool = buffPool.filter(function (buff) { var k = buff.key; return (player.buffCounts[k] || 0) < 5; }); // Rastgele bir buff seç (her zaman 4. seçenek) var buffIdx = Math.floor(Math.random() * buffPool.length); var buffChoice = buffPool[buffIdx]; choices = choices.slice(0, 3); // Sadece ilk 3'ü tut choices.push({ type: "buff", buff: buffChoice }); // 4. seçenek buff // Remove efsun options that have been taken 5 times choices = choices.filter(function (c) { if (_typeof(c) === "object" && c.e) { var key = c.w + "|" + c.e; return (player.efsunCounts[key] || 0) < 5; } return true; }); choices.forEach(function (c, idx) { var card = ov.addChild(new Container()); // 4. kartı mavi yap if (idx === 3) { card.attachAsset('skillCard', { anchorX: 0.5, anchorY: 0.5, tint: 0x3399ff // mavi renk }); } else { card.attachAsset('skillCard', { anchorX: 0.5, anchorY: 0.5 }); } // Kartlar arası mesafeyi azalt (ör: 440px), dikey mesafeyi azalt (ör: 0) card.x = (idx - 1.5) * 440; card.y = 0; var label, fn, desc = ""; if (typeof c === "string") { // c, yeni kilidi açılacak silahın anahtarı (ör. "boomerang") var key = c; var own = !!player.weapons[key]; if (!own) { // Hâlâ envanterde yoksa “Unlock <silah adı>” label = t("unlock") + " " + key.charAt(0).toUpperCase() + key.slice(1); fn = function fn() { addWeapon(key); }; } else if (player.weapons[key].upgrade < 3) { // Silah zaten varsa ve upgrade < 3 ise: label = WEAPONS[key].up && WEAPONS[key].up[player.weapons[key].upgrade] ? WEAPONS[key].up[player.weapons[key].upgrade] : t("upgrade"); fn = function fn() { player.weapons[key].upgrade++; player.weapons[key].amount++; }; } else { // Eğer upgrade === 3’e ulaşmışsa: label = t("noOption"); fn = function fn() {}; } } else if (c.type === "upgrade") { // c = { w: "sword", type:"upgrade" } var wkey = c.w; label = WEAPONS[wkey].up && WEAPONS[wkey].up[player.weapons[wkey].upgrade] ? WEAPONS[wkey].up[player.weapons[wkey].upgrade] : t("upgrade"); fn = function fn() { player.weapons[wkey].upgrade++; player.weapons[wkey].amount++; }; } else if (c.type === "buff") { label = t("buffs")[c.buff.key] || c.buff.label; desc = t("buffDescs")[c.buff.key] || c.buff.desc; fn = function fn() { // Buff mantığı var k = c.buff.key; if (!player.buffCounts[k]) { player.buffCounts[k] = 0; } if (player.buffCounts[k] >= 5) { return; } // Defensive: do not allow more than 5 switch (k) { case "damageReduction": player.buffs.damageReduction += 10; break; case "moveSpeed": player.buffs.moveSpeed += 20; break; case "hpRegen": player.buffs.hpRegen += 1; break; case "damageReflection": player.buffs.damageReflection += 5; break; case "gritWound": player.buffs.gritWound += 10; break; case "enemySlow": player.buffs.enemySlow += 10; break; case "enemyStun": player.buffs.enemyStun += 0.1; break; case "bleeding": player.buffs.bleeding += 1; break; case "critChance": player.buffs.critChance += 15; player.critChance = 0.25 + player.buffs.critChance / 100; if (player.critChance > 1) { player.critChance = 1; } break; case "maxHpPlus": player.maxHp += 25; break; case "magnetPlus": // Magnet+ buff: increase magnet+ level, only affects EXP orb attraction if (!player.buffs.magnetPlus) { player.buffs.magnetPlus = 1; } else { player.buffs.magnetPlus++; } // No longer affects spawn rates of pickups break; } player.buffCounts[k]++; }; } else { // c = { w: "<silahAnahtarı>", e: "<SilahName> Damage+" veya "<SilahName> Attack Speed+" vb. } var w2 = c.w; var efsun = c.e; label = efsun; // Efsun açıklaması if (efsun.endsWith(" Damage+")) { desc = t("desc_damage"); } else if (efsun.endsWith(" Attack Speed+")) { desc = t("desc_atkspd"); } else if (efsun.endsWith(" Range+")) { desc = t("desc_range"); } else { desc = ""; } fn = function fn() { var key = w2 + "|" + efsun; if (!player.efsunCounts[key]) { player.efsunCounts[key] = 0; } if (player.efsunCounts[key] >= 5) { return; } // Defensive: do not allow more than 5 var inst = player.weapons[w2]; // Efsun metni "<SilahName> Damage+" ile bitiyorsa hasar çarpanını %20 artır (artık sabit): if (efsun.endsWith(" Damage+")) { if (!inst._baseDmgMul) { inst._baseDmgMul = 1; } if (!inst.dmgMul) { inst.dmgMul = 1; } inst.dmgMul = inst._baseDmgMul + 0.2 * (player.efsunCounts[key] + 1); } // Efsun metni "<SilahName> Attack Speed+" ile bitiyorsa cd süresini %20 azalt (artık sabit): if (efsun.endsWith(" Attack Speed+")) { var meta = WEAPONS[w2]; if (meta && typeof meta.cd === "number") { if (!inst._baseCd) { inst._baseCd = meta.cd; } if (!inst._atkSpdMul) { inst._atkSpdMul = 1; } inst._atkSpdMul = 1 - 0.2 * (player.efsunCounts[key] + 1); if (inst._atkSpdMul < 0.1) { inst._atkSpdMul = 0.1; } // min %90 hızlanma sınırı inst.cd = inst._baseCd * inst._atkSpdMul; } else { inst.cd = inst.cd || meta.cd || 1; inst.cd = inst.cd * (1 - 0.2 * (player.efsunCounts[key] + 1)); if (inst.cd < 1) { inst.cd = 1; } } // cdCnt’yi de yeni cd’ye çekiyoruz if (typeof inst.cdCnt === "number") { if (inst.cdCnt > inst.cd) { inst.cdCnt = inst.cd; } } else { inst.cdCnt = Math.floor(inst.cd); } } // Efsun metni "<SilahName> Range+" ile bitiyorsa hem menzil hem boyut çarpanını %20 artır (artık sabit): if (efsun.endsWith(" Range+")) { if (!inst._baseRngMul) { inst._baseRngMul = 1; } if (!inst._baseSizeMul) { inst._baseSizeMul = 1; } inst.rngMul = inst._baseRngMul + 0.2 * (player.efsunCounts[key] + 1); inst.sizeMul = inst._baseSizeMul + 0.2 * (player.efsunCounts[key] + 1); } // "Size+" efsunu artık yok, hiçbir şey yapma player.efsunCounts[key]++; }; } // --- Weapon and efsun name localization --- function localizeWeaponName(key) { // Turkish weapon names var trNames = { sword: "Kılıç", boomerang: "Bumerang", ball: "Top", rocket: "Roket", brick: "Tuğla", lightning: "Yıldırım", aura: "Aura", molotov: "Molotof", shuriken: "Ninja Yıldızı" }; if (currentLanguage === "tr" && trNames[key]) { return trNames[key]; } // Default: Capitalize first letter return key.charAt(0).toUpperCase() + key.slice(1); } function localizeEfsunName(efsun, wkey) { // efsun: e.g. "Boomerang Damage+", "Boomerang Range+", "Boomerang Attack Speed+" // wkey: e.g. "boomerang" var trNames = { sword: "Kılıç", boomerang: "Bumerang", ball: "Top", rocket: "Roket", brick: "Tuğla", lightning: "Yıldırım", aura: "Aura", molotov: "Molotof", shuriken: "Ninja Yıldızı" }; if (currentLanguage === "tr") { var base = wkey && trNames[wkey] ? trNames[wkey] : wkey.charAt(0).toUpperCase() + wkey.slice(1); if (efsun.endsWith(" Damage+")) { return base + " Hasar+"; } if (efsun.endsWith(" Range+")) { return base + " Menzil+"; } if (efsun.endsWith(" Attack Speed+")) { return base + " Saldırı Hızı+"; } // fallback return efsun; } return efsun; } var txtLabel = label; if (typeof c === "string") { // Weapon selection overlay: localize weapon name txtLabel = localizeWeaponName(c); } else if (c && c.type === "upgrade") { // Upgrade overlay: localize weapon name in upgrade label var wkey = c.w; if (WEAPONS[wkey] && WEAPONS[wkey].up && player.weapons[wkey]) { var upLabel = WEAPONS[wkey].up[player.weapons[wkey].upgrade]; if (currentLanguage === "tr") { // Try to localize upgrade label for Turkish // For +1, +2, +3 upgrades, just append to Turkish weapon name if (upLabel && upLabel.match(/^\w+ \+(\d)$/)) { var base = localizeWeaponName(wkey); var num = upLabel.match(/\+(\d)/)[1]; txtLabel = base + " +" + num; } else { txtLabel = upLabel; } } else { txtLabel = upLabel; } } } else if (c && !c.type && c.e && c.w) { // Efsun card: localize efsun name txtLabel = localizeEfsunName(c.e, c.w); } var txt = new Text2(txtLabel, { size: 40, fill: 0xffffff }); txt.anchor.set(0.5); card.addChild(txt); if (desc) { // Card width is 400, so max text width should be less (e.g. 340) var descTxt = new Text2(desc, { size: 28, fill: 0xcccccc, wordWrap: true, wordWrapWidth: 340, align: "center" }); descTxt.anchor.set(0.5, 0); descTxt.y = 60; card.addChild(descTxt); } card.down = function () { fn(); ov.destroy(); skillSelectionActive = false; levelText.setText('Level ' + player.level); }; }); } // Input handling // Reference point A: var refA = { x: 222, y: 222 }; // Draw a debug marker for A (optional, comment out if not needed) // var aMarker = LK.getAsset('ball', { anchorX: 0.5, anchorY: 0.5, x: refA.x, y: refA.y, scaleX: 0.5, scaleY: 0.5, tint: 0x00ff00 }); // game.addChild(aMarker); game.down = function (x, y, obj) { // If menu or language or death overlay is active, only allow their buttons if (gameState === GAME_STATE.MAIN_MENU && mainMenuContainer) { // Let mainMenuContainer children handle .down var local = mainMenuContainer.toLocal({ x: x, y: y }); for (var i = 0; i < mainMenuContainer.children.length; i++) { var ch = mainMenuContainer.children[i]; if (ch.down && ch.getBounds && ch.getBounds().contains(local.x, local.y)) { ch.down(); return; } } return; } if (gameState === GAME_STATE.MAIN_MENU && languageMenuContainer) { var local = languageMenuContainer.toLocal({ x: x, y: y }); for (var i = 0; i < languageMenuContainer.children.length; i++) { var ch = languageMenuContainer.children[i]; if (ch.down && ch.getBounds && ch.getBounds().contains(local.x, local.y)) { ch.down(); return; } } return; } if (gameState === GAME_STATE.DEATH && deathScreenContainer) { var local = deathScreenContainer.toLocal({ x: x, y: y }); for (var i = 0; i < deathScreenContainer.children.length; i++) { var ch = deathScreenContainer.children[i]; if (ch.down && ch.getBounds && ch.getBounds().contains(local.x, local.y)) { ch.down(); return; } } return; } // If not playing, block all input if (gameState !== GAME_STATE.PLAYING) { return; } if (skillSelectionActive) { return; } // 1) Dokunulan noktayı “LK.gui.bottom” GUI koordinatına çeviriyoruz. // Böylece refA ile aynı düzlemi paylaşacak. var guiPos = LK.gui.bottom.toLocal({ x: x, y: y }); // 2) refA’yı da “LK.gui.bottom” koordinatında sabit olarak düşünelim: // (refA zaten {x:222,y:222} ise, bu değer LK.gui.bottom içinde sabit bir nokta demektir.) // Eğer refA aslında canvas’ın başka bir yerindeyse, uygun container’a göre // refA’yı da dönüştürmeniz gerekir (örneğin `game.toLocal` ile). // Burada basitçe ikisini de GUI’e taşıdık: var dx = guiPos.x - refA.x; var dy = guiPos.y - refA.y; var dist = Math.sqrt(dx * dx + dy * dy); // 3) dx, dy vektörünü normalize edip joystick’e geçiriyoruz: var dirX = 0, dirY = 0; if (dist > 0) { dirX = dx / dist; dirY = dy / dist; } // 4) Joystick çubuğunu (knob) sabit noktaya göre hareket ettir: // Maksimum 60px mesafeye kadar sınır koyuyoruz (ör: joystick yarıçapı). joystick.active = true; var maxDist = 60; var knobDx = dx, knobDy = dy; if (dist > maxDist) { knobDx = dx / dist * maxDist; knobDy = dy / dist * maxDist; } joystick.setKnobPosition(knobDx, knobDy); // 5) Hareket yönünü saklamak için joystick.dirX / dirY’yi güncelle: joystick.dirX = dirX; joystick.dirY = dirY; }; game.move = function (x, y, obj) { if (gameState !== GAME_STATE.PLAYING) { return; } if (!joystick.active || skillSelectionActive) { return; } // Dokunulan noktayı yine “LK.gui.bottom” koordinatına çevir: var guiPos = LK.gui.bottom.toLocal({ x: x, y: y }); // joystick.x / joystick.y; bunlar da “LK.gui.bottom” koordinatında joystick’in merkezidir. var dx = guiPos.x - joystick.x; var dy = guiPos.y - joystick.y; joystick.setKnobPosition(dx, dy); // Hareket yönünü güncelle: var dist = Math.sqrt(dx * dx + dy * dy); if (dist > 0) { var dirX = dx / dist; var dirY = dy / dist; joystick.dirX = dirX; joystick.dirY = dirY; } else { joystick.dirX = 0; joystick.dirY = 0; } }; game.up = function (x, y, obj) { if (gameState !== GAME_STATE.PLAYING) { return; } joystick.reset(); }; // --- LOADING SCREEN & ASSET PRELOAD --- // Show a loading screen before main menu, preload all assets, then show main menu var loadingContainer = null; function showLoadingScreen() { if (loadingContainer) { loadingContainer.destroy(); } loadingContainer = game.addChild(new Container()); // Simple black background var bg = loadingContainer.attachAsset('mainMenuBackground', { anchorX: 0.5, anchorY: 0.5, x: 1024, y: 1366 }); // Loading text var loadingTxt = new Text2("Loading...", { size: 80, fill: 0xffffff }); loadingTxt.anchor.set(0.5, 0.5); loadingTxt.x = 1024; loadingTxt.y = 1366; loadingContainer.addChild(loadingTxt); // Optional: add a simple spinner effect var spinner = new Container(); var spinnerAsset = spinner.attachAsset('chickenLeg', { anchorX: 0.5, anchorY: 0.5, width: 120, height: 120 }); spinner.x = 1024; spinner.y = 1566; loadingContainer.addChild(spinner); spinner.update = function () { if (spinnerAsset) { spinnerAsset.rotation += 0.2; } }; loadingContainer.update = function () { if (spinner && spinner.update) { spinner.update(); } }; } // Preload all assets (images, sounds, music) before showing main menu function preloadAllAssets(onComplete) { // List of all asset keys to preload var assetKeys = [ // Images 'mainMenuBackground', 'startButton', 'languagesButton', 'langButton', 'returnMainMenu', 'deathBackground', 'player', 'hpBarRect', 'hpBarRectFg', 'enemyHpBarRect', 'enemyHpBarRectFg', 'seamlessBackGround', 'zombie', 'normalZombie', 'normalZombieAttack', 'rangedZombie', 'rangedZombieSaliva', 'fastZombie', 'fastZombieAttack', 'tankZombie', 'tankZombieAttack', 'bossZombie', 'bossZombieAttack1', 'bossZombieAttack2', 'auraField', 'ball', 'bomb', 'boomerang', 'brick', 'lightning', 'magnet', 'molotov', 'molotovFlame', 'rocket', 'rocketBoom', 'shuriken', 'skillCard', 'chickenLeg', // Sounds 'damageTaken', 'death', 'hit', 'levelup', 'pickup', 'rocketBoom', // Music 'backGroundMusic', 'deathScreenMusic', 'mainMenuMusic']; var loadedCount = 0; var totalCount = assetKeys.length; function assetLoaded() { loadedCount++; // Optionally update loading text if (loadingContainer && loadingContainer.children && loadingContainer.children.length > 1) { var txt = loadingContainer.children[1]; if (txt && txt.setText) { txt.setText("Loading... " + loadedCount + "/" + totalCount); } } if (loadedCount >= totalCount) { if (typeof onComplete === "function") { onComplete(); } } } // Defensive: if LK.getAsset returns synchronously, call assetLoaded immediately for (var i = 0; i < assetKeys.length; i++) { (function (key) { var asset = LK.getAsset(key, { anchorX: 0.5, anchorY: 0.5 }); // If asset is already loaded, call assetLoaded immediately if (asset && (asset.texture || asset.width)) { assetLoaded(); } else if (asset && typeof asset.once === "function") { // Listen for 'loaded' event if available asset.once('loaded', assetLoaded); } else { // Fallback: call assetLoaded after short delay LK.setTimeout(assetLoaded, 10); } })(assetKeys[i]); } } // At game start, show loading screen, preload all assets, then show main menu showLoadingScreen(); preloadAllAssets(function () { if (loadingContainer) { loadingContainer.destroy(); loadingContainer = null; } showMainMenu(); }); // Game update game.update = function () { // Block all game logic, UI, and sounds if not in PLAYING state if (gameState !== GAME_STATE.PLAYING) { setGameUIVisible(false); // Defensive: stop all music and sounds if not already stopped // Play correct music for each state if (gameState === GAME_STATE.MAIN_MENU) { // Music is handled in showMainMenu/red overlay logic, do not play here to avoid double playback } else if (gameState === GAME_STATE.DEATH) { if (currentMusic !== "deathScreenMusic") { LK.stopMusic(); window._mainMenuMusicPlaying = false; LK.playMusic('deathScreenMusic', { loop: true }); currentMusic = "deathScreenMusic"; } } else { LK.stopMusic(); window._mainMenuMusicPlaying = false; currentMusic = null; } if (typeof LK.stopAllSounds === "function") { LK.stopAllSounds(); } window._mainMenuMusicPlaying = false; // Defensive: always reset flag when stopping music return; } setGameUIVisible(true); // Ensure correct music is playing in PLAYING state if (currentMusic !== "backGroundMusic") { LK.stopMusic(); LK.playMusic('backGroundMusic', { loop: true }); currentMusic = "backGroundMusic"; } if (skillSelectionActive) { return; } gameTime++; // Update timer → geçen süreyi göster var elapsedSeconds = Math.floor(gameTime / 60); var minutes = Math.floor(elapsedSeconds / 60); var seconds = elapsedSeconds % 60; timerText.setText((minutes < 10 ? '0' + minutes : minutes) + ':' + (seconds < 10 ? '0' + seconds : seconds)); // Update UI hpText.setText(t("hpPrefix") + ": " + player.hp.toFixed(1) + '/' + player.maxHp.toFixed(1)); // Show exp orbs and required exp for next level levelText.setText(t("levelPrefix") + " " + player.level + " " + t("expPrefix") + ": " + player.exp + "/" + player.level); // Show current wave waveText.setText(t("wavePrefix") + " " + waveNumber); // Player movement if (joystick.active) { player.x += joystick.dirX * player.moveSpeed; player.y += joystick.dirY * player.moveSpeed; } /* Fire weapons / aura etc. */ player.update(); // --- Update player health bar --- if (player.hpBar && player.hpBarFg) { var hpRatio = Math.max(0, Math.min(1, player.hp / player.maxHp)); player.hpBarFg.width = 212 * hpRatio; // Move foreground bar's x so it depletes from right to left player.hpBarFg.x = 0; // Player HP bar color: green >80%, yellow 50-80%, orange 20-50%, red+blink <20% if (hpRatio > 0.8) { player.hpBarFg.tint = 0x00ff44; // green player.hpBarFg.alpha = 1; } else if (hpRatio > 0.5) { player.hpBarFg.tint = 0xffe066; // yellow player.hpBarFg.alpha = 1; } else if (hpRatio > 0.2) { player.hpBarFg.tint = 0xff9900; // orange player.hpBarFg.alpha = 1; } else { // <20%: red and blinking // Blink: alternate between red and transparent every 6 frames (~10Hz) player.hpBarFg.tint = 0xff0000; // pure red for critical if (gameTime % 12 < 6) { player.hpBarFg.alpha = 1; } else { player.hpBarFg.alpha = 0.3; } } } // --- Update enemy health bars --- for (var i = 0; i < enemies.length; i++) { var enemy = enemies[i]; if (enemy.hpBar && enemy.hpBarFg) { var ehpRatio = Math.max(0, Math.min(1, enemy.hp / enemy.maxHp)); enemy.hpBarFg.width = 152 * ehpRatio; enemy.hpBarFg.x = 0; // Enemy HP bar color: always red, regardless of HP enemy.hpBarFg.tint = 0xff4444; enemy.hpBarFg.alpha = 1; } } // Camera follow player camera.x = player.x - 1024; camera.y = player.y - 1366; gameContainer.x = -camera.x; gameContainer.y = -camera.y; // Update background tiles to always cover the visible area if (backgroundTiles && backgroundTiles.length) { var startX = Math.floor(camera.x / bgTileWidth) * bgTileWidth; var startY = Math.floor(camera.y / bgTileHeight) * bgTileHeight; var idx = 0; for (var i = 0; i < bgTilesX; i++) { for (var j = 0; j < bgTilesY; j++) { var tile = backgroundTiles[idx++]; tile.x = startX + i * bgTileWidth; tile.y = startY + j * bgTileHeight; } } } // Progress to next wave only when all enemies are cleared if (typeof nextWaveTimeout === "undefined") { nextWaveTimeout = null; } if (enemies.length === 0 && !nextWaveTimeout) { nextWaveTimeout = LK.setTimeout(function () { waveNumber++; spawnWave(); nextWaveTimeout = null; }, 3000); } if (enemies.length > 0 && nextWaveTimeout) { LK.clearTimeout(nextWaveTimeout); nextWaveTimeout = null; } // Update enemies (her düşman kendi update() metodunda saldırı efektini ve hasarı hallediyor) for (var i = 0; i < enemies.length; i++) { enemies[i].update(); } // Update exp orbs for (var i = expOrbs.length - 1; i >= 0; i--) { var orb = expOrbs[i]; var dx = orb.x - player.x; var dy = orb.y - player.y; var dist = Math.sqrt(dx * dx + dy * dy); // Magnet+ effect: pull orbs within magnetPlus radius quickly to player (no effect on pickup spawn rates) if (player.buffs && player.buffs.magnetPlus && player.buffs.magnetPlus > 0) { var levels = player.buffs.magnetPlus; var magnetRadius = 300 + (levels - 1) * 150; if (dist <= magnetRadius) { // Pull orbs much faster for Magnet+ (e.g. 12) if (orb.magnetSpeed < 12) { orb.magnetSpeed = 12; } } } // pickupRange artık aura ile büyümeyecek, sadece manyetik çekim için kullanılacak if (dist < player.pickupRange) { orb.magnetSpeed = 10; } // Sadece karakterin merkezinde 30 yarıçaplı daireye girerse alınabilir if (dist <= 30) { player.gainExp(orb.value); orb.destroy(); expOrbs.splice(i, 1); LK.getSound('pickup').play(); } } // Spawn pickups randomly if (gameTime - lastPickupSpawn > 1200) { // Increased spawn frequency (was 1800 frames, now 1200) // Calculate spawn chance and weights (increased overall rates) var baseChance = 0.25; // was 0.1, now 0.25 for more frequent spawns var bombMagnetWeight = 0.45; // was 0.4, now 0.45 var magnetWeight = 0.45; // was 0.4, now 0.45 var chickenWeight = 0.25; // was 0.2, now 0.25 // No longer affected by Magnet+ buff var totalWeight = bombMagnetWeight + magnetWeight + chickenWeight; var bombMagnetProb = bombMagnetWeight / totalWeight; var magnetProb = magnetWeight / totalWeight; var chickenProb = chickenWeight / totalWeight; if (Math.random() < baseChance) { lastPickupSpawn = gameTime; var pickup; var angle = Math.random() * Math.PI * 2; var distance = 300 + Math.random() * 500; var pickupX = player.x + Math.cos(angle) * distance; var pickupY = player.y + Math.sin(angle) * distance; var randVal = Math.random(); if (randVal < bombMagnetProb) { pickup = new Bomb(); } else if (randVal < bombMagnetProb + magnetProb) { pickup = new Magnet(); } else { pickup = new ChickenLeg(); } pickup.x = pickupX; pickup.y = pickupY; pickups.push(pickup); gameContainer.addChild(pickup); } } // Update pickups for (var i = pickups.length - 1; i >= 0; i--) { var pickup = pickups[i]; if (!pickup.active) { pickups.splice(i, 1); } } }; // Initial spawn // spawnWave();//{jb} // Delay first wave spawn until after weapon selection // Music is now handled in game.update based on gameState // Tank Zombie asset (bigger, armored) // Fast Zombie asset (slimmer, agile) // Poison Zombie asset (greenish, toxic) // Boss Zombie asset (huge, menacing)
/****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
var storage = LK.import("@upit/storage.v1");
/****
* Classes
****/
var Bomb = Container.expand(function () {
var self = Container.call(this);
self.active = true;
var bombGraphics = self.attachAsset('bomb', {
anchorX: 0.5,
anchorY: 0.5,
width: 60,
height: 60,
tint: 0xff0000
});
self.update = function () {
if (!player || !self.active) {
return;
}
var dx = player.x - self.x;
var dy = player.y - self.y;
var dist = Math.sqrt(dx * dx + dy * dy);
if (dist < 80) {
self.active = false;
// Deal 20 damage to all non-boss enemies on screen
for (var i = enemies.length - 1; i >= 0; i--) {
var enemy = enemies[i];
var screenDx = Math.abs(enemy.x - player.x);
var screenDy = Math.abs(enemy.y - player.y);
if (screenDx < 1024 && screenDy < 1366) {
// Deal 20 damage, drop exp and remove if dead
if (enemy.takeDamage(20)) {
dropExpOrb(enemy.x, enemy.y);
enemy.destroy();
enemies.splice(i, 1);
}
}
}
LK.effects.flashScreen(0xff0000, 300);
if (self.parent) {
self.destroy();
}
return true;
}
};
return self;
});
var Bullet = Container.expand(function () {
var self = Container.call(this);
self.damage;
self.speed;
self.dirX = 0;
self.dirY = 0;
self.piercing = 1;
self.hitEnemies = [];
var bulletGraphics = self.attachAsset('bullet', {
anchorX: 0.5,
anchorY: 0.5
});
self.update = function () {
// Seviye seçim ekranı açıkken hiçbir şey yapma
if (skillSelectionActive) {
return;
}
// Hareket
this.x += this.dirX * this.speed;
this.y += this.dirY * this.speed;
// Kameranın (görünen alanın) dışına çıktıysa sil
var camMinX = camera.x;
var camMaxX = camera.x + 2048;
var camMinY = camera.y;
var camMaxY = camera.y + 2732;
if (this.x < camMinX || this.x > camMaxX || this.y < camMinY || this.y > camMaxY) {
this.destroy();
var bi = bullets.indexOf(this);
if (bi >= 0) {
bullets.splice(bi, 1);
}
return;
}
// “Menzil” filtresi: bu top silahının menzili WEAPONS.ball.range
var maxRange = WEAPONS.ball.range * (player.weapons && player.weapons.ball ? player.weapons.ball.rngMul : 1);
// (Eğer o envanterde yoksa rngMul=1 varsaydık)
// Düşmana çarpma kontrolü & menzil filtresi
for (var i = enemies.length - 1; i >= 0; i--) {
var e = enemies[i];
if (this.intersects(e) && isWithinRange(e, maxRange)) {
damageEnemy(e, this.damage);
this.destroy();
var bi2 = bullets.indexOf(this);
if (bi2 >= 0) {
bullets.splice(bi2, 1);
}
return;
}
}
};
return self;
});
// ChickenLeg pickup class
var ChickenLeg = Container.expand(function () {
var self = Container.call(this);
self.active = true;
var chickenLegGraphics = self.attachAsset('chickenLeg', {
anchorX: 0.5,
anchorY: 0.5,
width: 60,
height: 60
});
self.update = function () {
if (!player || !self.active) {
return;
}
var dx = player.x - self.x;
var dy = player.y - self.y;
var dist = Math.sqrt(dx * dx + dy * dy);
if (dist < 80) {
self.active = false;
// Heal player for 30% of max HP, but not above max HP
var healAmount = Math.floor(player.maxHp * 0.3);
var oldHp = player.hp;
player.hp = Math.min(player.hp + healAmount, player.maxHp);
var actualHealed = player.hp - oldHp;
if (actualHealed > 0) {
LK.effects.flashObject(player, 0x00ff00, 300);
// Show green HP gain text with black border
var healText = new Text2('+' + actualHealed.toFixed(1), {
size: 48,
fill: 0x00ff00,
stroke: 0x000000,
strokeThickness: 4
});
healText.anchor.set(0.5);
healText.x = player.x;
healText.y = player.y - (player.height ? player.height / 2 : 60);
healText.alpha = 1;
gameContainer.addChild(healText);
tween(healText, {
y: healText.y - 60,
alpha: 0
}, {
duration: 500,
easing: tween.linear,
onFinish: function onFinish() {
if (healText.parent) {
healText.destroy();
}
}
});
}
LK.getSound('pickup').play();
if (self.parent) {
self.destroy();
}
return true;
}
};
return self;
});
// Enemy base class
var Enemy = Container.expand(function () {
var self = Container.call(this);
self.hp = 9999;
self.maxHp = 9999;
self.damage = 1;
self.speed = 0;
self.expValue = 1;
self.type = 'normal';
self.critChance = 0.25;
// Enemy does not attach a graphic directly; subclasses should do this
self.takeDamage = function (amount) {
self.hp -= amount;
LK.effects.flashObject(self, 0xff0000, 200);
if (self.hp <= 0) {
return true; // Enemy died
}
return false; // Enemy still alive
};
self.update = function () {
// Kart seçim ekranı aktifken düşman hareket etmesin ve saldırmasın
if (!player || skillSelectionActive) {
return;
}
// Subclasses should implement facing logic if needed
// Debuff: Stun
if (self.stunBuff && self.stunBuffTime !== undefined) {
var stunDuration = Math.floor(self.stunBuff * 60); // seconds to frames
if (gameTime - self.stunBuffTime < stunDuration) {
// Stunned: do not move or attack
return;
} else {
self.stunBuff = 0;
self.stunBuffTime = undefined;
}
}
// 1) Oyuncuya olan vektörü bul
var dx = player.x - self.x;
var dy = player.y - self.y;
var dist = Math.sqrt(dx * dx + dy * dy);
// Debuff: Slow
var speedMul = 1;
if (self.slowBuff && self.slowBuffTime !== undefined) {
var slowDuration = 120; // 2 seconds
if (gameTime - self.slowBuffTime < slowDuration) {
speedMul = Math.max(0, 1 - self.slowBuff / 100);
} else {
self.slowBuff = 0;
self.slowBuffTime = undefined;
}
}
// 2) Eğer düşman, oyuncuya 150 pikselden yakınsa hareket etme
if (dist > 150) {
// 3) Aksi halde 50px uzaktan hareket et
self.x += dx / dist * self.speed * speedMul;
self.y += dy / dist * self.speed * speedMul;
}
// Bleeding+
if (self.bleedBuff && self.bleedBuffTime !== undefined) {
var bleedDuration = 240; // 4 seconds (60fps)
if (gameTime - self.bleedBuffTime < bleedDuration) {
// Every 30 frames (0.5s), apply bleed damage
if (!self.bleedTicks) {
self.bleedTicks = 0;
}
var ticks = Math.floor((gameTime - self.bleedBuffTime) / 30);
while (self.bleedTicks < ticks) {
var bleedDmg = self.maxHp * (self.bleedBuff / 100); // allow fractional
self.hp -= bleedDmg;
LK.effects.flashObject(self, 0xaa0000, 100);
// Show damage text for bleeding (always, even if <1)
var dmgText = new Text2(bleedDmg.toFixed(1), {
size: 48,
fill: 0x00ff00,
// green for regen, but for bleed use red
stroke: 0x000000,
strokeThickness: 4
});
dmgText.setText('-' + bleedDmg.toFixed(1));
dmgText.fill = 0xff3333;
dmgText.anchor.set(0.5);
dmgText.x = self.x;
dmgText.y = self.y - (self.height ? self.height / 2 : 60);
dmgText.alpha = 1;
gameContainer.addChild(dmgText);
tween(dmgText, {
y: dmgText.y - 60,
alpha: 0
}, {
duration: 500,
easing: tween.linear,
onFinish: function onFinish() {
if (dmgText.parent) {
dmgText.destroy();
}
}
});
if (self.hp <= 0) {
if (self.parent) {
self.destroy();
}
break;
}
self.bleedTicks++;
}
} else {
self.bleedBuff = 0;
self.bleedBuffTime = undefined;
self.bleedTicks = 0;
}
}
// Grit Wound+
if (self.gritWound && self.lastGritWoundTime !== undefined) {
var gritDuration = 240; // 4 seconds
if (gameTime - self.lastGritWoundTime < gritDuration) {
// No regen logic here, but you can use self.gritWound for regen reduction elsewhere
// Example: If you add enemy regen, show green text here
if (self.enemyRegen && self.hp < self.maxHp) {
var regenAmount = self.maxHp * (self.enemyRegen / 100);
if (self.gritWound > 0) {
regenAmount = regenAmount * (1 - self.gritWound / 100);
}
var oldHp = self.hp;
self.hp = Math.min(self.hp + regenAmount, self.maxHp);
var actualHealed = self.hp - oldHp;
if (actualHealed > 0) {
// Show green HP gain text with black border
var healText = new Text2('+' + actualHealed.toFixed(1), {
size: 48,
fill: 0x00ff00,
stroke: 0x000000,
strokeThickness: 4
});
healText.anchor.set(0.5);
healText.x = self.x;
healText.y = self.y - (self.height ? self.height / 2 : 60);
healText.alpha = 1;
gameContainer.addChild(healText);
tween(healText, {
y: healText.y - 60,
alpha: 0
}, {
duration: 500,
easing: tween.linear,
onFinish: function onFinish() {
if (healText.parent) {
healText.destroy();
}
}
});
}
}
} else {
self.gritWound = 0;
self.lastGritWoundTime = undefined;
}
}
// --- Enemy HP regen after wave 5, scaling with wave ---
if (self.enemyRegen && self.enemyRegen > 0 && (!self.gritWound || self.gritWound === 0)) {
if (!self.lastEnemyRegenTime) {
self.lastEnemyRegenTime = 0;
}
if (gameTime - self.lastEnemyRegenTime >= 300) {
// every 5s
var regenAmount = self.maxHp * (self.enemyRegen / 100);
var oldHp = self.hp;
self.hp = Math.min(self.hp + regenAmount, self.maxHp);
var actualHealed = self.hp - oldHp;
if (actualHealed > 0) {
// Show green HP gain text with black border
var healText = new Text2('+' + actualHealed.toFixed(1), {
size: 48,
fill: 0x00ff00,
stroke: 0x000000,
strokeThickness: 4
});
healText.anchor.set(0.5);
healText.x = self.x;
healText.y = self.y - (self.height ? self.height / 2 : 60);
healText.alpha = 1;
gameContainer.addChild(healText);
tween(healText, {
y: healText.y - 60,
alpha: 0
}, {
duration: 500,
easing: tween.linear,
onFinish: function onFinish() {
if (healText.parent) {
healText.destroy();
}
}
});
}
self.lastEnemyRegenTime = gameTime;
}
}
// Burada asla destroy() yok; sadece hareketi sınırlıyoruz
};
return self;
});
// Tank Zombie subclass
var TankZombie = Enemy.expand(function () {
var self = Enemy.call(this);
var tankGraphics = self.attachAsset('tankZombie', {
anchorX: 0.5,
anchorY: 0.5
});
self.maxHp = 80;
self.hp = 80;
self.damage = 20;
self.speed = 0.8;
self.attackRange = 200;
self.attackCooldown = 180; // 3 saniye (60fps)
self.attackTimer = 0;
self.type = 'tankZombie';
self.update = function (origUpdate) {
return function () {
// Kart seçim ekranı aktifken tank zombi hareket etmesin ve saldırmasın
if (skillSelectionActive) {
return;
}
if (self.x < player.x) {
tankGraphics.scaleX = 1;
} else if (self.x > player.x) {
tankGraphics.scaleX = -1;
}
var dx = player.x - self.x;
var dy = player.y - self.y;
var dist = Math.sqrt(dx * dx + dy * dy);
if (dist > self.attackRange) {
var speedMul = 1;
if (self.slowBuff && self.slowBuffTime !== undefined) {
var slowDuration = 120;
if (gameTime - self.slowBuffTime < slowDuration) {
speedMul = Math.max(0, 1 - self.slowBuff / 100);
}
}
self.x += dx / dist * self.speed * speedMul;
self.y += dy / dist * self.speed * speedMul;
self.attackTimer = 0;
} else {
self.attackTimer++;
if (self.attackTimer >= self.attackCooldown) {
self.attackTimer = 0;
var atk = gameContainer.addChild(new Container());
atk.attachAsset('tankZombieAttack', {
anchorX: 0.5,
anchorY: 0.5
});
atk.x = self.x + dx / dist * 70;
atk.y = self.y + dy / dist * 70;
atk.life = 6; // 6 frame = 0.1 saniye
atk.hasHit = false; // Hasar verildi mi, onu izlemek için
atk.update = function () {
this.life--;
// 2) Eğer henüz hasar vermediysen, o anda intersection kontrol et:
if (!this.hasHit && this.intersects(player)) {
player.takeDamage(self.damage);
this.hasHit = true;
// NOT: Burada destroy() çağırmıyoruz, sadece hasHit flag’ini true yapıyoruz.
}
// 3) Süre (life) dolduğunda nesneyi sil:
if (this.life <= 0) {
if (this.parent) {
this.destroy();
}
}
};
}
}
origUpdate.call(self);
};
}(self.update);
return self;
});
// Ranged Zombie subclass
var RangedZombie = Enemy.expand(function () {
var self = Enemy.call(this);
var rangedGraphics = self.attachAsset('rangedZombie', {
anchorX: 0.5,
anchorY: 0.5
});
self.maxHp = 12;
self.hp = 12;
self.damage = 16;
self.speed = 1.2;
self.attackRange = 900;
self.attackCooldown = 300; // 5 saniye (60fps)
self.attackTimer = 0;
self.type = 'rangedZombie';
self.update = function (origUpdate) {
return function () {
// Kart seçim ekranı aktifken menzilli zombi hareket etmesin ve saldırmasın
if (skillSelectionActive) {
return;
}
// Flip to face player
if (self.x < player.x) {
rangedGraphics.scaleX = 1;
} else if (self.x > player.x) {
rangedGraphics.scaleX = -1;
}
var dx = player.x - self.x;
var dy = player.y - self.y;
var dist = Math.sqrt(dx * dx + dy * dy);
// Hareket ve saldırı state'i: menzile girince hareketi bırakıp saldırıya geç, çıkınca tekrar hareket et
if (typeof self.isAttacking === "undefined") {
self.isAttacking = false;
}
if (dist > self.attackRange) {
// === MENZİL DIŞI: sadece hareket et ve origUpdate’i çağır ===
var speedMul = 1;
if (self.slowBuff && self.slowBuffTime !== undefined) {
var slowDuration = 120;
if (gameTime - self.slowBuffTime < slowDuration) {
speedMul = Math.max(0, 1 - self.slowBuff / 100);
}
}
self.x += dx / dist * self.speed * speedMul;
self.y += dy / dist * self.speed * speedMul;
self.attackTimer = 0;
self.isAttacking = false;
// YALNIZCA BURADA Enemy.update’in (origUpdate’in) hareket+debuff kısımlarını çalıştırıyoruz
origUpdate.call(self);
} else {
// === MENZİL İÇİ: hareket etmeyi tamamen bırak, sadece saldırı yap ===
self.isAttacking = true;
self.attackTimer++;
if (self.attackTimer >= self.attackCooldown) {
self.attackTimer = 0;
// Fire saliva projectile towards player
var saliva = gameContainer.addChild(new Container());
saliva.attachAsset('rangedZombieSaliva', {
anchorX: 0.5,
anchorY: 0.5
});
saliva.x = self.x;
saliva.y = self.y;
var dirX = dx / dist;
var dirY = dy / dist;
saliva.speed = 18;
saliva.update = function () {
this.x += dirX * this.speed;
this.y += dirY * this.speed;
// If hits player, deal damage
if (this.intersects(player)) {
player.takeDamage(self.damage);
if (this.parent) {
this.destroy();
}
}
// Remove if out of camera
var camMinX = camera.x;
var camMaxX = camera.x + 2048;
var camMinY = camera.y;
var camMaxY = camera.y + 2732;
if (this.x < camMinX || this.x > camMaxX || this.y < camMinY || this.y > camMaxY) {
if (this.parent) {
this.destroy();
}
}
};
}
// Burada origUpdate çağrılmıyor → Enemy.update’in “hareket et” kısmı pasif kalacak
}
};
}(self.update);
return self;
});
// Normal Zombie subclass (renamed from Zombie)
var NormalZombie = Enemy.expand(function () {
var self = Enemy.call(this);
var zombieGraphics = self.attachAsset('normalZombie', {
anchorX: 0.5,
anchorY: 0.5
});
self.maxHp = 16;
self.hp = 16;
self.damage = 8;
self.speed = 1.0;
self.attackRange = 150;
self.attackCooldown = 90; // 1.5 saniye (60fps)
self.attackTimer = 0;
self.type = 'normalZombie';
self.update = function (origUpdate) {
return function () {
// Kart seçim ekranı aktifken normal zombi hareket etmesin ve saldırmasın
if (skillSelectionActive) {
return;
}
// Flip to face player
if (self.x < player.x) {
zombieGraphics.scaleX = 1;
} else if (self.x > player.x) {
zombieGraphics.scaleX = -1;
}
// Move or attack
var dx = player.x - self.x;
var dy = player.y - self.y;
var dist = Math.sqrt(dx * dx + dy * dy);
if (dist > self.attackRange) {
// Move towards player
var speedMul = 1;
if (self.slowBuff && self.slowBuffTime !== undefined) {
var slowDuration = 120;
if (gameTime - self.slowBuffTime < slowDuration) {
speedMul = Math.max(0, 1 - self.slowBuff / 100);
}
}
self.x += dx / dist * self.speed * speedMul;
self.y += dy / dist * self.speed * speedMul;
self.attackTimer = 0;
} else {
// Attack every 1.5s
self.attackTimer++;
if (self.attackTimer >= self.attackCooldown) {
self.attackTimer = 0;
// Attack: spawn attack asset towards player
var atk = gameContainer.addChild(new Container());
atk.attachAsset('normalZombieAttack', {
anchorX: 0.5,
anchorY: 0.5
});
atk.x = self.x + dx / dist * 60;
atk.y = self.y + dy / dist * 60;
atk.life = 6; // 6 frame = 0.1 saniye
atk.hasHit = false; // Hasar verildi mi, onu izlemek için
atk.update = function () {
// 1) Önce remaining life’ı azalt:
this.life--;
// 2) Eğer henüz hasar vermediysen, o anda intersection kontrol et:
if (!this.hasHit && this.intersects(player)) {
player.takeDamage(self.damage);
this.hasHit = true;
// NOT: Burada destroy() çağırmıyoruz, sadece hasHit flag’ini true yapıyoruz.
}
// 3) Süre (life) dolduğunda nesneyi sil:
if (this.life <= 0) {
if (this.parent) {
this.destroy();
}
}
};
}
}
origUpdate.call(self);
};
}(self.update);
return self;
});
// Fast Zombie subclass
var FastZombie = Enemy.expand(function () {
var self = Enemy.call(this);
var fastGraphics = self.attachAsset('fastZombie', {
anchorX: 0.5,
anchorY: 0.5
});
self.maxHp = 8;
self.hp = 8;
self.damage = 10;
self.speed = 2.0;
self.attackRange = 100;
self.attackCooldown = 30; // 0.5 saniye (60fps)
self.attackTimer = 0;
self.type = 'fastZombie';
self.update = function (origUpdate) {
return function () {
// Kart seçim ekranı aktifken hızlı zombi hareket etmesin ve saldırmasın
if (skillSelectionActive) {
return;
}
if (self.x < player.x) {
fastGraphics.scaleX = 1;
} else if (self.x > player.x) {
fastGraphics.scaleX = -1;
}
var dx = player.x - self.x;
var dy = player.y - self.y;
var dist = Math.sqrt(dx * dx + dy * dy);
if (dist > self.attackRange) {
var speedMul = 1;
if (self.slowBuff && self.slowBuffTime !== undefined) {
var slowDuration = 120;
if (gameTime - self.slowBuffTime < slowDuration) {
speedMul = Math.max(0, 1 - self.slowBuff / 100);
}
}
self.x += dx / dist * self.speed * speedMul;
self.y += dy / dist * self.speed * speedMul;
self.attackTimer = 0;
} else {
self.attackTimer++;
if (self.attackTimer >= self.attackCooldown) {
self.attackTimer = 0;
var atk = gameContainer.addChild(new Container());
atk.attachAsset('fastZombieAttack', {
anchorX: 0.5,
anchorY: 0.5
});
atk.x = self.x + dx / dist * 50;
atk.y = self.y + dy / dist * 50;
atk.life = 6; // 6 frame = 0.1 saniye
atk.hasHit = false; // Hasar verildi mi, onu izlemek için
atk.update = function () {
this.life--;
// 2) Eğer henüz hasar vermediysen, o anda intersection kontrol et:
if (!this.hasHit && this.intersects(player)) {
player.takeDamage(self.damage);
this.hasHit = true;
// NOT: Burada destroy() çağırmıyoruz, sadece hasHit flag’ini true yapıyoruz.
}
// 3) Süre (life) dolduğunda nesneyi sil:
if (this.life <= 0) {
if (this.parent) {
this.destroy();
}
}
};
}
}
origUpdate.call(self);
};
}(self.update);
return self;
});
// Boss Zombie subclass
var BossZombie = Enemy.expand(function () {
var self = Enemy.call(this);
var bossGraphics = self.attachAsset('bossZombie', {
anchorX: 0.5,
anchorY: 0.5
});
self.maxHp = 400;
self.hp = 400;
self.type = 'bossZombie';
self.mode = 'melee'; // 'melee' or 'ranged'
self.modeTimer = 0;
self.modeSwitchDelay = 0;
self.attackTimer = 0;
self.attackCooldownMelee = 180; // 3s
self.attackCooldownRanged = 24; // 0.4s
self.attackRangeMelee = 250;
self.attackRangeRanged = 1000;
self.damageMelee = 40;
self.damageRanged = 5;
self.speed = 0;
self.speedMelee = 1.2;
self.speedRanged = 1.8;
self.modeDurationMelee = 720; // 12s
self.modeDurationRanged = 240; // 4s
self.modeSwitchPause = 60; // 1s
self.lastWarningTime = 0;
self.warningCountdown = 0;
self.warningActive = false;
self.warningText = null;
self.update = function (origUpdate) {
return function () {
// Kart seçim ekranı aktifken boss hareket etmesin ve saldırmasın
if (skillSelectionActive) {
return;
}
// Flip to face player
if (self.x < player.x) {
bossGraphics.scaleX = 1;
} else if (self.x > player.x) {
bossGraphics.scaleX = -1;
}
// Mode switching logic
if (self.modeSwitchDelay > 0) {
self.modeSwitchDelay--;
// During pause, boss cannot move, but can attack if already in attack range
// Defensive: do not move, but allow attack animation/effects if in range
// For melee mode
var dx = player.x - self.x;
var dy = player.y - self.y;
var dist = Math.sqrt(dx * dx + dy * dy);
if (self.mode === 'melee' && dist <= self.attackRangeMelee) {
self.attackTimer++;
self.isAttacking = true;
if (self.attackTimer >= self.attackCooldownMelee) {
self.attackTimer = 0;
var atk = gameContainer.addChild(new Container());
atk.attachAsset('bossZombieAttack1', {
anchorX: 0.5,
anchorY: 0.5
});
atk.x = self.x + dx / dist * 90;
atk.y = self.y + dy / dist * 90;
atk.life = 6;
atk.hasHit = false;
atk.update = function () {
this.life--;
if (!this.hasHit) {
// Yakın dövüş saldırısı: doğrudan player.intersects ile kontrol et
if (player && typeof player.x === "number" && typeof player.y === "number") {
if (this.intersects(player)) {
player.takeDamage(self.damageMelee);
this.hasHit = true;
}
}
}
if (this.life <= 0) {
if (this.parent) {
this.destroy();
}
}
};
}
} else if (self.mode === 'ranged' && dist <= self.attackRangeRanged) {
self.attackTimer++;
self.isAttacking = true;
if (self.attackTimer % self.attackCooldownRanged === 0) {
var bone = gameContainer.addChild(new Container());
bone.attachAsset('bossZombieAttack2', {
anchorX: 0.5,
anchorY: 0.5
});
bone.x = self.x;
bone.y = self.y;
var dirX = dx / dist;
var dirY = dy / dist;
bone.speed = 28;
bone.update = function () {
this.x += dirX * bone.speed;
this.y += dirY * bone.speed;
if (player && typeof player.x === "number" && typeof player.y === "number") {
var dx2 = this.x - player.x;
var dy2 = this.y - player.y;
var dist2 = Math.sqrt(dx2 * dx2 + dy2 * dy2);
if (dist2 <= 60) {
player.takeDamage(self.damageRanged);
if (this.parent) {
this.destroy();
}
}
}
var camMinX = camera.x;
var camMaxX = camera.x + 2048;
var camMinY = camera.y;
var camMaxY = camera.y + 2732;
if (this.x < camMinX || this.x > camMaxX || this.y < camMinY || this.y > camMaxY) {
if (this.parent) {
this.destroy();
}
}
};
}
} else {
self.isAttacking = false;
self.attackTimer = 0;
}
// Do not move during pause
return;
}
self.modeTimer++;
if (self.mode === 'melee' && self.modeTimer >= self.modeDurationMelee) {
// --- Only teleport boss away from player if within 800 range ---
var dxTeleport = self.x - player.x;
var dyTeleport = self.y - player.y;
var distTeleport = Math.sqrt(dxTeleport * dxTeleport + dyTeleport * dyTeleport);
// Only teleport if boss is within 800 units of player
if (distTeleport <= 800 && distTeleport > 0) {
// Move boss 800 units away from player, in the direction away from player
self.x = player.x + dxTeleport / distTeleport * 800;
self.y = player.y + dyTeleport / distTeleport * 800;
self.mode = 'ranged';
self.modeTimer = 0;
self.modeSwitchDelay = 120; // 2 seconds pause when switching to ranged
self.isAttacking = false; // Defensive: reset attack state
} else {
// If not close enough, keep walking towards player (do not switch to ranged mode yet)
// Just reset modeTimer so boss keeps trying until close enough
self.modeTimer = self.modeDurationMelee; // keep at threshold, so check again next frame
}
} else if (self.mode === 'ranged' && self.modeTimer >= self.modeDurationRanged) {
self.mode = 'melee';
self.modeTimer = 0;
self.modeSwitchDelay = self.modeSwitchPause;
self.isAttacking = false; // Defensive: reset attack state
}
var dx = player.x - self.x;
var dy = player.y - self.y;
var dist = Math.sqrt(dx * dx + dy * dy);
// Boss warning if player is too far
var bossDist = Math.sqrt((self.x - player.x) * (self.x - player.x) + (self.y - player.y) * (self.y - player.y));
if (bossDist > 2000) {
if (!self.warningActive) {
self.warningActive = true;
self.warningCountdown = 5;
self.lastWarningTime = gameTime;
if (!self.warningText) {
self.warningText = new Text2(t("bossWarning") + "5", {
size: 80,
fill: 0xff0000
});
self.warningText.anchor.set(0.5, 0);
self.warningText.y = 300;
LK.gui.top.addChild(self.warningText);
}
} else {
if (gameTime - self.lastWarningTime >= 60) {
self.warningCountdown--;
self.lastWarningTime = gameTime;
if (self.warningText) {
self.warningText.setText(t("bossWarning") + self.warningCountdown);
}
}
if (self.warningCountdown <= 0) {
if (self.warningText && self.warningText.parent) {
self.warningText.destroy();
}
LK.showGameOver();
return;
}
}
} else if (self.warningActive) {
self.warningActive = false;
if (self.warningText && self.warningText.parent) {
self.warningText.destroy();
}
self.warningText = null;
}
// Movement and attack logic
if (self.mode === 'melee') {
// Melee mode: slow, short range, high damage
if (typeof self.isAttacking === "undefined") {
self.isAttacking = false;
}
// Boss zombi yakın dövüşçü modunda saldırı menziline girdiğinde hareket etmeye devam etsin ve saldırı yapabilsin
var speedMul = 1;
if (self.slowBuff && self.slowBuffTime !== undefined) {
var slowDuration = 120;
if (gameTime - self.slowBuffTime < slowDuration) {
speedMul = Math.max(0, 1 - self.slowBuff / 100);
}
}
// Sadece saldırı menziline kadar hareket etsin, daha fazla yaklaşmasın
if (dist > self.attackRangeMelee) {
self.x += dx / dist * self.speedMelee * speedMul;
self.y += dy / dist * self.speedMelee * speedMul;
}
// Saldırı menziline girildiyse saldırı yapabilsin
if (dist <= self.attackRangeMelee) {
self.attackTimer++;
self.isAttacking = true;
if (self.attackTimer >= self.attackCooldownMelee) {
self.attackTimer = 0;
var atk = gameContainer.addChild(new Container());
atk.attachAsset('bossZombieAttack1', {
anchorX: 0.5,
anchorY: 0.5
});
atk.x = self.x + dx / dist * 90;
atk.y = self.y + dy / dist * 90;
atk.life = 6; // 6 frame = 0.1 saniye
atk.hasHit = false; // Hasar verildi mi, onu izlemek için
atk.update = function () {
this.life--;
// Her frame'de intersection kontrolü yap, böylece animasyon karaktere her zaman hasar verebilir
if (!this.hasHit && player && typeof player.x === "number" && typeof player.y === "number") {
if (this.intersects(player)) {
player.takeDamage(self.damageMelee);
this.hasHit = true;
}
}
if (this.life <= 0) {
if (this.parent) {
this.destroy();
}
}
};
}
} else {
self.attackTimer = 0;
self.isAttacking = false;
}
} else if (self.mode === 'ranged') {
// Ranged mode: normal speed, long range, low damage, fires bones
if (typeof self.isAttacking === "undefined") {
self.isAttacking = false;
}
if (dist > self.attackRangeRanged) {
var speedMul = 1;
if (self.slowBuff && self.slowBuffTime !== undefined) {
var slowDuration = 120;
if (gameTime - self.slowBuffTime < slowDuration) {
speedMul = Math.max(0, 1 - self.slowBuff / 100);
}
}
// Only move if not attacking
if (!self.isAttacking) {
self.x += dx / dist * self.speedRanged * speedMul;
self.y += dy / dist * self.speedRanged * speedMul;
}
self.attackTimer = 0;
self.isAttacking = false;
} else {
self.attackTimer++;
self.isAttacking = true;
if (self.attackTimer % self.attackCooldownRanged === 0) {
// Fire bone projectile
var bone = gameContainer.addChild(new Container());
bone.attachAsset('bossZombieAttack2', {
anchorX: 0.5,
anchorY: 0.5
});
bone.x = self.x;
bone.y = self.y;
var dirX = dx / dist;
var dirY = dy / dist;
bone.speed = 28;
bone.update = function () {
this.x += dirX * bone.speed;
this.y += dirY * bone.speed;
// --- Use circular hitbox for player (radius 60) ---
if (player && typeof player.x === "number" && typeof player.y === "number") {
var dx = this.x - player.x;
var dy = this.y - player.y;
var dist = Math.sqrt(dx * dx + dy * dy);
if (dist <= 60) {
player.takeDamage(self.damageRanged);
if (this.parent) {
this.destroy();
}
}
}
var camMinX = camera.x;
var camMaxX = camera.x + 2048;
var camMinY = camera.y;
var camMaxY = camera.y + 2732;
if (this.x < camMinX || this.x > camMaxX || this.y < camMinY || this.y > camMaxY) {
if (this.parent) {
this.destroy();
}
}
};
}
}
}
origUpdate.call(self);
};
}(self.update);
return self;
});
var ExpOrb = Container.expand(function () {
var self = Container.call(this);
// 1) spawnTime kaydet
self.spawnTime = gameTime;
self.value = 1; // Each orb always gives 1 EXP
self.magnetSpeed = 0;
var orbGraphics = self.attachAsset('expOrb', {
anchorX: 0.5,
anchorY: 0.5
});
self.update = function () {
// ORB YAŞI (frame cinsinden)
var age = gameTime - self.spawnTime;
// 2) Eğer 20 saniye geçmişse (1200 frame), orb’u yok et ve listeden çıkar
if (age >= 1200) {
// yok et
if (self.parent) {
self.destroy();
}
var idx = expOrbs.indexOf(self);
if (idx >= 0) {
expOrbs.splice(idx, 1);
}
return;
}
// 3) Son 5 saniyede (age >= 900), yanıp sönme uygulaması
if (age >= 900) {
// kalan süre
var remaining = 1200 - age; // 300’den 0’a kadar azalır
// blink periyodu: remaining / 50’ye bağlı (en hızlı 1 frame’lik periyot)
var blinkInterval = Math.ceil(remaining / 50);
if (blinkInterval < 1) {
blinkInterval = 1;
}
// yaşa göre çift/tek periyoda bölerek görünürlüğü değiştir
var phase = Math.floor(age / blinkInterval);
self.visible = phase % 2 === 0;
} else {
// son 5 saniye değilse görünür olsun
self.visible = true;
}
// 4) Normal manyetik çekim hareketi
if (!player || self.magnetSpeed === 0) {
return;
}
var dx = player.x - self.x;
var dy = player.y - self.y;
var dist = Math.sqrt(dx * dx + dy * dy);
if (dist > 0) {
self.x += dx / dist * self.magnetSpeed;
self.y += dy / dist * self.magnetSpeed;
}
};
return self;
});
var Joystick = Container.expand(function () {
var self = Container.call(this);
self.active = false;
self.dirX = 0;
self.dirY = 0;
var base = self.attachAsset('joystickBase', {
anchorX: 0.5,
anchorY: 0.5
});
base.alpha = 0.5;
var knob = self.attachAsset('joystickKnob', {
anchorX: 0.5,
anchorY: 0.5
});
knob.alpha = 0.7;
// Manuel knob merkez offset'i (ör: knob assetinin görsel merkezi ile gerçek merkez arasındaki fark)
// Bu değerleri elle ayarlayabilirsin. Örneğin, knob görselinin merkezi 10px sağda ve 5px yukarıda ise:
self.knobCenterOffsetX = 0; // Buraya elle düzeltmek istediğin değeri gir
self.knobCenterOffsetY = 0; // Buraya elle düzeltmek istediğin değeri gir
self.setKnobPosition = function (x, y) {
// x, y: joystick base'in merkezine göre offset (dokunulan nokta - joystick merkezi)
// Knob'un merkezi joystick base'in merkeziyle tam hizalı olmalı
// Hareket yönü, knob merkezinden dokunulan pozisyona doğru olmalı
// dx, dy: knob'un merkezinden dokunulan noktaya vektör
// Manuel offset uygula
var dx = x - self.knobCenterOffsetX;
var dy = y - self.knobCenterOffsetY;
var dist = Math.sqrt(dx * dx + dy * dy);
var maxDist = 60;
if (dist > maxDist) {
dx = dx / dist * maxDist;
dy = dy / dist * maxDist;
}
knob.x = dx;
knob.y = dy;
// Defensive: avoid division by zero
if (maxDist === 0) {
self.dirX = 0;
self.dirY = 0;
} else {
// Hareket yönü: knob'un merkezinden dokunulan noktaya doğru normalize edilmiş vektör
self.dirX = dx / maxDist;
self.dirY = dy / maxDist;
}
};
self.reset = function () {
knob.x = 0;
knob.y = 0;
self.dirX = 0;
self.dirY = 0;
self.active = false;
};
return self;
});
var Magnet = Container.expand(function () {
var self = Container.call(this);
self.active = true;
var magnetGraphics = self.attachAsset('magnet', {
anchorX: 0.5,
anchorY: 0.5,
width: 60,
height: 60,
tint: 0x0099ff
});
self.update = function () {
if (!player || !self.active) {
return;
}
var dx = player.x - self.x;
var dy = player.y - self.y;
var dist = Math.sqrt(dx * dx + dy * dy);
if (dist < 80) {
self.active = false;
// Magnetize all exp orbs on screen
for (var i = 0; i < expOrbs.length; i++) {
var orb = expOrbs[i];
var orbDx = Math.abs(orb.x - player.x);
var orbDy = Math.abs(orb.y - player.y);
if (orbDx < 1024 && orbDy < 1366) {
orb.magnetSpeed = 20;
}
}
if (self.parent) {
self.destroy();
}
return true;
}
};
return self;
});
var Player = Container.expand(function () {
var self = Container.call(this);
self.level = 1;
self.exp = 0;
self.expToNext = 10;
self.hp = 100;
self.maxHp = 100;
self.damage = 10.0;
self.fireRate = 30;
self.bulletSpeed = 15;
self.moveSpeed = 5;
self.pickupRange = 100;
self.lastFire = 0;
self.critChance = 0.25; // Oyuncu başlangıçta %25 kritik vuruş şansı
/* ===== BUFFS ===== */
self.buffs = {
damageReduction: 0,
// percent, e.g. 10 means 10%
moveSpeed: 0,
// percent, e.g. 20 means 20%
hpRegen: 0,
// percent per 5s
damageReflection: 0,
// percent
gritWound: 0,
// percent
enemySlow: 0,
// percent
enemyStun: 0,
// seconds
bleeding: 0,
// percent per tick
critChance: 0 // Critical Hit Chance+ buff, percent (15 means +15%)
};
self.lastRegenTime = 0;
/* ===== WEAPON INVENTORY ===== */
self.weapons = {}; // key → instance
var playerGraphics = self.attachAsset('player', {
anchorX: 0.5,
anchorY: 0.5
});
self.takeDamage = function (amount) {
// Damage Reduction+
var finalAmount = amount;
if (self.buffs.damageReduction > 0) {
finalAmount = Math.ceil(amount * (1 - self.buffs.damageReduction / 100));
}
self.hp -= finalAmount;
LK.effects.flashObject(self, 0xff0000, 300);
LK.getSound('damageTaken').play();
// Damage Reflection+
if (self.buffs.damageReflection > 0 && enemies.length > 0) {
// Find nearest enemy in contact (or just first in range)
for (var i = 0; i < enemies.length; i++) {
var e = enemies[i];
var dx = e.x - self.x;
var dy = e.y - self.y;
var distSq = dx * dx + dy * dy;
if (distSq <= 22500) {
// same as contact check
var reflectDmg = Math.ceil(finalAmount * self.buffs.damageReflection / 100);
if (reflectDmg > 0) {
damageEnemy(e, reflectDmg);
// Show damage text for reflection
var dmgText = new Text2(reflectDmg.toFixed(1), {
size: 48,
fill: 0xffffff,
stroke: 0x000000,
strokeThickness: 4
});
dmgText.anchor.set(0.5);
dmgText.x = e.x;
dmgText.y = e.y - (e.height ? e.height / 2 : 60);
dmgText.alpha = 1;
gameContainer.addChild(dmgText);
tween(dmgText, {
y: dmgText.y - 60,
alpha: 0
}, {
duration: 500,
easing: tween.linear,
onFinish: function onFinish() {
if (dmgText.parent) {
dmgText.destroy();
}
}
});
}
break;
}
}
}
if (self.hp <= 0) {
// Show death screen and block game update
showDeathScreen();
return;
}
};
self.gainExp = function (amount) {
// Add exp orbs
self.exp += amount;
// Level up if exp orbs >= current level
while (self.exp >= self.level) {
self.exp -= self.level;
self.levelUp();
}
};
self.update = function () {
// Kart seçim ekranı aktifken karakter hareket etmesin ve saldırmasın
if (skillSelectionActive) {
return;
}
// Flip player graphic based on movement direction
if (self.lastX === undefined) {
self.lastX = self.x;
}
if (self.x > self.lastX) {
playerGraphics.scaleX = 1;
} else if (self.x < self.lastX) {
playerGraphics.scaleX = -1;
}
self.lastX = self.x;
// Movement Speed+
if (self.buffs.moveSpeed > 0) {
self.moveSpeed = 5 * (1 + self.buffs.moveSpeed / 100);
} else {
self.moveSpeed = 5;
}
// HP Regeneration+
if (self.buffs.hpRegen > 0 && gameTime - (self.lastRegenTime || 0) >= 300) {
var regenAmount = self.maxHp * (self.buffs.hpRegen / 100); // allow fractional regen
if (regenAmount > 0 && self.hp < self.maxHp) {
var oldHp = self.hp;
self.hp = Math.min(self.hp + regenAmount, self.maxHp);
var actualHealed = self.hp - oldHp;
if (actualHealed > 0) {
LK.effects.flashObject(self, 0x00ff00, 200);
// Show green HP gain text with black border
var healText = new Text2('+' + actualHealed.toFixed(1), {
size: 48,
fill: 0x00ff00,
stroke: 0x000000,
strokeThickness: 4
});
healText.anchor.set(0.5);
healText.x = self.x;
healText.y = self.y - (self.height ? self.height / 2 : 60);
healText.alpha = 1;
gameContainer.addChild(healText);
tween(healText, {
y: healText.y - 60,
alpha: 0
}, {
duration: 500,
easing: tween.linear,
onFinish: function onFinish() {
if (healText.parent) {
healText.destroy();
}
}
});
}
}
self.lastRegenTime = gameTime;
}
for (var k in self.weapons) {
var inst = self.weapons[k],
meta = WEAPONS[k];
if (--inst.cdCnt <= 0) {
fireWeapon(k, inst, meta);
// inst.cd muhtemelen kesirsiz olmayabilir; istersen Math.floor ile yuvarla:
inst.cdCnt = Math.floor(inst.cd);
}
}
};
self.levelUp = function () {
self.level++;
// self.exp is now handled in gainExp, do not reset here
self.expToNext = self.level * 15;
// self.maxHp += 10; // Artık seviye atlayınca max HP artmıyor
// Do NOT restore full health on level up
// self.hp = self.maxHp;
// Increase movement speed by 4% per level up
self.moveSpeed *= 1.04;
LK.getSound('levelup').play();
showSkillSelection();
};
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0x111111
});
/****
* Game Code
****/
/* ==== UNIVERSAL HELPERS ==== */
// --- SOUND/MUSIC BLOCKER: Only allow introSpeech while red overlay is visible ---
(function () {
// Patch LK.getSound and LK.playMusic to block all except introSpeech/mainMenuMusic when window._blockAllSounds is true
var origGetSound = LK.getSound;
var origPlayMusic = LK.playMusic;
// Track if introSpeech is currently playing
window._introSpeechPlaying = false;
LK.getSound = function (key) {
var sound = origGetSound.call(LK, key);
if (!sound) return sound;
// Patch .play() to block all except introSpeech if _blockAllSounds is set or introSpeech is playing
var origPlay = sound.play;
sound.play = function () {
// Block all sounds except introSpeech if _blockAllSounds or introSpeech is playing
if ((window._blockAllSounds || window._introSpeechPlaying) && key !== 'introSpeech') {
return;
}
// If introSpeech is about to play, set flag and set up end event
if (key === 'introSpeech') {
// Only set if not already playing
if (!window._introSpeechPlaying) {
window._introSpeechPlaying = true;
// Try to listen for 'ended' event to clear the flag
if (typeof sound.once === "function") {
sound.once('ended', function () {
window._introSpeechPlaying = false;
});
} else {
// Fallback: clear after 10s (length of introSpeech)
LK.setTimeout(function () {
window._introSpeechPlaying = false;
}, 10000);
}
}
}
return origPlay.apply(sound, arguments);
};
return sound;
};
LK.playMusic = function (key, opts) {
// Block all music except mainMenuMusic if _blockAllSounds, or block all music if introSpeech is playing
// Also block mainMenuMusic if red overlay is visible (mainMenuRedBlocker exists and is visible)
if (window._blockAllSounds && key !== 'mainMenuMusic' || window._introSpeechPlaying || key === 'mainMenuMusic' && typeof mainMenuRedBlocker !== "undefined" && mainMenuRedBlocker && mainMenuRedBlocker.visible) {
return;
}
return origPlayMusic.apply(LK, arguments);
};
})();
// This is normal for large files or slow connections and cannot be fixed in code.
// Note: If music starts late, it may be due to the time required to load the music asset on first play.
// --- MENU & LANGUAGE STATE ---
var GAME_STATE = {
MAIN_MENU: 0,
PLAYING: 1,
DEATH: 2
};
var gameState = GAME_STATE.MAIN_MENU;
var currentLanguage = "en"; // "en" or "tr"
var mainMenuContainer = null;
var languageMenuContainer = null;
var deathScreenContainer = null;
// --- TEXTS ---
var TEXTS = {
en: {
start: "Start Game",
languages: "Languages",
returnMainMenu: "Return to Main Menu",
death: "You Died!",
lang_tr: "Türkçe",
lang_en: "English",
hp: "HP",
wave: "Wave",
level: "Level",
exp: "EXP",
expShort: "EXP",
weaponSelect: "Select Your Weapon",
unlock: "Unlock",
noOption: "No Option",
buffs: {
damageReduction: "Damage Reduction+",
moveSpeed: "Movement Speed Increase+",
hpRegen: "HP Regeneration+",
damageReflection: "Damage Reflection+",
gritWound: "Grit Wound+",
enemySlow: "Enemy Slow+",
enemyStun: "Enemy Stun+",
bleeding: "Bleeding+",
critChance: "Critical Hit Chance+",
maxHpPlus: "Maximum HP+",
magnetPlus: "Magnet+"
},
buffDescs: {
damageReduction: "Take 10% less damage per level",
moveSpeed: "Move 20% faster per level",
hpRegen: "Regenerate 1% max HP every 5s per level",
damageReflection: "Reflect 5% damage per level",
gritWound: "Reduce enemy regen by 10% per level",
enemySlow: "Slow enemies by 10% per level",
enemyStun: "Stun enemies for 0.1s per level",
bleeding: "Bleed: 4s, 0.5s tick, 5% max HP per tick per level",
critChance: "Increase critical hit chance by 15% per level",
maxHpPlus: "Increase max HP by 25 per level",
magnetPlus: "Nearby EXP orbs (150 radius, +150 per level) are quickly pulled to you."
},
upgrade: "Upgrade",
desc_damage: "Increases weapon damage by 20%",
desc_atkspd: "Increases weapon attack speed by 20%",
desc_range: "Increases weapon range and size by 20%",
bossWarning: "You are too far from the Boss! ",
expGain: "+",
wavePrefix: "Wave",
levelPrefix: "Level",
expPrefix: "EXP",
hpPrefix: "HP"
},
tr: {
start: "Oyuna Başla",
languages: "Diller",
returnMainMenu: "Ana Menüye Dön",
death: "Öldün!",
lang_tr: "Türkçe",
lang_en: "English",
hp: "Can",
wave: "Dalga",
level: "Seviye",
exp: "EXP",
expShort: "EXP",
weaponSelect: "Silahını Seç",
unlock: "Aç",
noOption: "Seçenek Yok",
buffs: {
damageReduction: "Hasar Azaltma+",
moveSpeed: "Hareket Hızı Artışı+",
hpRegen: "Can Yenileme+",
damageReflection: "Hasar Yansıtma+",
gritWound: "Derin Yara+",
enemySlow: "Düşman Yavaşlatma+",
enemyStun: "Düşman Sersemletme+",
bleeding: "Kanama+",
critChance: "Kritik Vuruş Şansı+",
maxHpPlus: "Maksimum Can+",
magnetPlus: "Mıknatıs+"
},
buffDescs: {
damageReduction: "Her seviyede %10 daha az hasar alırsın",
moveSpeed: "Her seviyede %20 daha hızlı hareket edersin",
hpRegen: "Her seviyede 5 saniyede bir maksimum canının %1'i kadar yenilenirsin",
damageReflection: "Her seviyede %5 hasarı yansıtırsın",
gritWound: "Her seviyede düşman yenilenmesini %10 azaltır",
enemySlow: "Her seviyede düşmanları %10 yavaşlatır",
enemyStun: "Her seviyede düşmanları 0.1s sersemletir",
bleeding: "Her seviyede 4s boyunca, 0.5s arayla, her tikte maksimum canın %5'i kadar kanama",
critChance: "Her seviyede kritik vuruş şansını %15 artırır",
maxHpPlus: "Her seviyede maksimum canı 25 artırır",
magnetPlus: "Yakındaki EXP küreleri (150 yarıçap, seviye başına +150) hızlıca sana çekilir."
},
upgrade: "Yükselt",
desc_damage: "Silah hasarını %20 artırır",
desc_atkspd: "Silah saldırı hızını %20 artırır",
desc_range: "Silah menzilini ve boyutunu %20 artırır",
bossWarning: "Boss'tan çok uzaktasın! ",
expGain: "+",
wavePrefix: "Dalga",
levelPrefix: "Seviye",
expPrefix: "EXP",
hpPrefix: "Can"
}
};
// --- Language helper ---
function t(key) {
if (TEXTS[currentLanguage] && TEXTS[currentLanguage][key]) {
return TEXTS[currentLanguage][key];
}
// fallback
return TEXTS["en"][key] || key;
}
// --- Menu helpers ---
function showMainMenu() {
gameState = GAME_STATE.MAIN_MENU;
// Hide all game UI and stop all game sounds/music
setGameUIVisible(false);
LK.stopMusic();
window._mainMenuMusicPlaying = false;
if (typeof LK.stopAllSounds === "function") {
LK.stopAllSounds();
}
// Remove other overlays if any
if (mainMenuContainer) {
mainMenuContainer.destroy();
}
if (languageMenuContainer) {
languageMenuContainer.destroy();
}
if (deathScreenContainer) {
deathScreenContainer.destroy();
}
// Music is now handled in game.update based on gameState
if (typeof currentMusic === "undefined") {
currentMusic = null;
}
// Always play main menu music immediately when showing main menu
if (currentMusic !== "mainMenuMusic") {
LK.stopMusic();
// Defensive: ensure music is loaded and play only once
if (typeof LK.playMusic === "function") {
if (!window._mainMenuMusicPlaying) {
window._mainMenuMusicPlaying = true;
var musicObj = LK.playMusic('mainMenuMusic', {
loop: true
});
// Listen for 'ended' event to clear flag (should not happen for loop, but defensive)
if (musicObj && typeof musicObj.once === "function") {
musicObj.once('ended', function () {
window._mainMenuMusicPlaying = false;
});
}
}
}
currentMusic = "mainMenuMusic";
} else {
// Defensive: if music is already playing, do not start again
}
// === RED OVERLAY BLOCKER ===
// Show the red overlay only on first app launch, not when returning to main menu after death
if (typeof window._hasShownRedOverlay === "undefined") {
window._hasShownRedOverlay = false;
}
if (!window._hasShownRedOverlay) {
window._hasShownRedOverlay = true;
if (typeof mainMenuRedBlocker !== "undefined" && mainMenuRedBlocker && mainMenuRedBlocker.destroy) {
mainMenuRedBlocker.destroy();
}
// Play intro speech sound when red loading screen appears
if (typeof LK.getSound === "function" && LK.getSound('introSpeech')) {
// Stop all music and all sounds before playing introSpeech
if (typeof LK.stopMusic === "function") {
LK.stopMusic();
}
if (typeof LK.stopAllSounds === "function") {
LK.stopAllSounds();
}
// Play introSpeech only once
LK.getSound('introSpeech').play();
// Block all other sounds/music while red overlay is visible
window._blockAllSounds = true;
}
mainMenuRedBlocker = game.addChild(new Container());
// Create a solid red rectangle using a shape asset
var redRect = mainMenuRedBlocker.attachAsset('hpBarRect', {
anchorX: 0,
anchorY: 0,
x: 0,
y: 0,
width: 2048,
height: 2732,
color: 0xff0000,
tint: 0xff0000,
alpha: 1
});
mainMenuRedBlocker.x = 0;
mainMenuRedBlocker.y = 0;
mainMenuRedBlocker.visible = true;
mainMenuRedBlocker.interactive = true;
mainMenuRedBlocker.down = function () {/* block all input */};
mainMenuRedBlocker.move = function () {/* block all input */};
mainMenuRedBlocker.up = function () {/* block all input */};
// --- Add "Loading..." text and spinner to red overlay ---
// Add a logo image asset above Loading... text
var logoArea = new Container();
logoArea.x = 1024;
logoArea.y = 1000;
// Replace this with your actual logo asset key and dimensions
var logoImg = logoArea.attachAsset('myLogo', {
anchorX: 0.5,
anchorY: 0.5,
width: 600,
height: 220
});
mainMenuRedBlocker.addChild(logoArea);
var redLoadingText = new Text2("Loading...", {
size: 100,
fill: 0xffffff
});
redLoadingText.anchor.set(0.5, 0.5);
redLoadingText.x = 1024;
redLoadingText.y = 1200;
mainMenuRedBlocker.addChild(redLoadingText);
var redSpinner = new Container();
var redSpinnerAsset = redSpinner.attachAsset('chickenLeg', {
anchorX: 0.5,
anchorY: 0.5,
width: 120,
height: 120
});
redSpinner.x = 1024;
redSpinner.y = 1350;
mainMenuRedBlocker.addChild(redSpinner);
redSpinner.update = function () {
if (redSpinnerAsset) {
redSpinnerAsset.rotation += 0.2;
}
};
mainMenuRedBlocker.update = function () {
if (redSpinner && redSpinner.update) {
redSpinner.update();
}
};
// Preload main menu container and its children, but keep them hidden until red overlay is removed
mainMenuContainer = game.addChild(new Container());
// Background
var bg = mainMenuContainer.attachAsset('mainMenuBackground', {
anchorX: 0.5,
anchorY: 0.5,
x: 1024,
y: 1366
});
// Start Button
var startBtn = mainMenuContainer.addChild(new Container());
startBtn.attachAsset('startButton', {
anchorX: 0.5,
anchorY: 0.5
});
startBtn.x = 1024;
startBtn.y = 1200;
var startTxt = new Text2(t("start"), {
size: 72,
fill: 0xffffff
});
startTxt.anchor.set(0.5);
startBtn.addChild(startTxt);
startBtn.visible = true;
startBtn.interactive = true;
startBtn.down = function () {
mainMenuContainer.destroy();
mainMenuContainer = null;
startGame();
};
// Languages Button
var langBtn = mainMenuContainer.addChild(new Container());
langBtn.attachAsset('languagesButton', {
anchorX: 0.5,
anchorY: 0.5
});
langBtn.x = 1024;
langBtn.y = 1600;
var langTxt = new Text2(t("languages"), {
size: 60,
fill: 0xffffff
});
langTxt.anchor.set(0.5);
langBtn.addChild(langTxt);
langBtn.visible = true;
langBtn.interactive = true;
langBtn.down = function () {
// Hide start and lang buttons and make them untouchable
startBtn.visible = false;
startBtn.interactive = false;
langBtn.visible = false;
langBtn.interactive = false;
showLanguageMenu(function () {
// After language selection, show buttons again and update texts
startBtn.visible = true;
startBtn.interactive = true;
langBtn.visible = true;
langBtn.interactive = true;
// Update button texts to new language
if (startTxt && startTxt.setText) {
startTxt.setText(t("start"));
}
if (langTxt && langTxt.setText) {
langTxt.setText(t("languages"));
}
});
};
// Hide main menu container and its children while red overlay is visible
if (mainMenuContainer && mainMenuContainer.visible !== undefined) {
mainMenuContainer.visible = false;
for (var i = 0; i < mainMenuContainer.children.length; i++) {
if (mainMenuContainer.children[i] && mainMenuContainer.children[i].visible !== undefined) {
mainMenuContainer.children[i].visible = false;
}
}
}
// Remove after 10 seconds and show main menu container
LK.setTimeout(function () {
// Remove loading text and spinner from red overlay
if (mainMenuRedBlocker) {
// Defensive: remove children if not already destroyed
if (typeof redLoadingText !== "undefined" && redLoadingText && redLoadingText.parent) {
redLoadingText.destroy();
}
if (typeof redSpinner !== "undefined" && redSpinner && redSpinner.parent) {
redSpinner.destroy();
}
}
if (mainMenuRedBlocker && mainMenuRedBlocker.parent) {
mainMenuRedBlocker.destroy();
mainMenuRedBlocker = null;
}
window._mainMenuMusicPlaying = false;
// Unblock all sounds/music after red overlay is gone
window._blockAllSounds = false;
// Show main menu container and its children after red overlay is gone
if (mainMenuContainer && mainMenuContainer.visible !== undefined) {
mainMenuContainer.visible = true;
for (var i = 0; i < mainMenuContainer.children.length; i++) {
if (mainMenuContainer.children[i] && mainMenuContainer.children[i].visible !== undefined) {
mainMenuContainer.children[i].visible = true;
}
}
// Also ensure background is visible
if (bg && bg.visible !== undefined) {
bg.visible = true;
}
}
// Play main menu music after red overlay is gone, but only if introSpeech is NOT playing
if (typeof LK.playMusic === "function") {
if (!window._introSpeechPlaying) {
if (!window._mainMenuMusicPlaying) {
window._mainMenuMusicPlaying = true;
var musicObj = LK.playMusic('mainMenuMusic', {
loop: true
});
if (musicObj && typeof musicObj.once === "function") {
musicObj.once('ended', function () {
window._mainMenuMusicPlaying = false;
});
}
}
currentMusic = "mainMenuMusic";
} else {
// If introSpeech is still playing, wait until it ends before playing mainMenuMusic
if (typeof LK.getSound === "function" && LK.getSound('introSpeech') && typeof LK.getSound('introSpeech').once === "function") {
LK.getSound('introSpeech').once('ended', function () {
if (typeof LK.playMusic === "function") {
if (!window._mainMenuMusicPlaying) {
window._mainMenuMusicPlaying = true;
var musicObj = LK.playMusic('mainMenuMusic', {
loop: true
});
if (musicObj && typeof musicObj.once === "function") {
musicObj.once('ended', function () {
window._mainMenuMusicPlaying = false;
});
}
}
currentMusic = "mainMenuMusic";
}
});
} else {
// Fallback: set a timeout to check again after 500ms
(function waitForIntroSpeech() {
if (!window._introSpeechPlaying) {
if (typeof LK.playMusic === "function") {
if (!window._mainMenuMusicPlaying) {
window._mainMenuMusicPlaying = true;
var musicObj = LK.playMusic('mainMenuMusic', {
loop: true
});
if (musicObj && typeof musicObj.once === "function") {
musicObj.once('ended', function () {
window._mainMenuMusicPlaying = false;
});
}
}
currentMusic = "mainMenuMusic";
}
} else {
LK.setTimeout(waitForIntroSpeech, 500);
}
})();
}
}
}
}, 10000);
// === END RED OVERLAY BLOCKER ===
return;
}
// If not first launch, show main menu instantly (no red overlay)
mainMenuContainer = game.addChild(new Container());
// Background
var bg = mainMenuContainer.attachAsset('mainMenuBackground', {
anchorX: 0.5,
anchorY: 0.5,
x: 1024,
y: 1366
});
// Start Button
var startBtn = mainMenuContainer.addChild(new Container());
startBtn.attachAsset('startButton', {
anchorX: 0.5,
anchorY: 0.5
});
startBtn.x = 1024;
startBtn.y = 1200;
var startTxt = new Text2(t("start"), {
size: 72,
fill: 0xffffff
});
startTxt.anchor.set(0.5);
startBtn.addChild(startTxt);
startBtn.visible = true;
startBtn.interactive = true;
startBtn.down = function () {
mainMenuContainer.destroy();
mainMenuContainer = null;
startGame();
};
// Languages Button
var langBtn = mainMenuContainer.addChild(new Container());
langBtn.attachAsset('languagesButton', {
anchorX: 0.5,
anchorY: 0.5
});
langBtn.x = 1024;
langBtn.y = 1600;
var langTxt = new Text2(t("languages"), {
size: 60,
fill: 0xffffff
});
langTxt.anchor.set(0.5);
langBtn.addChild(langTxt);
langBtn.visible = true;
langBtn.interactive = true;
langBtn.down = function () {
// Hide start and lang buttons and make them untouchable
startBtn.visible = false;
startBtn.interactive = false;
langBtn.visible = false;
langBtn.interactive = false;
showLanguageMenu(function () {
// After language selection, show buttons again and update texts
startBtn.visible = true;
startBtn.interactive = true;
langBtn.visible = true;
langBtn.interactive = true;
// Update button texts to new language
if (startTxt && startTxt.setText) {
startTxt.setText(t("start"));
}
if (langTxt && langTxt.setText) {
langTxt.setText(t("languages"));
}
});
};
if (mainMenuContainer && mainMenuContainer.visible !== undefined) {
mainMenuContainer.visible = true;
for (var i = 0; i < mainMenuContainer.children.length; i++) {
if (mainMenuContainer.children[i] && mainMenuContainer.children[i].visible !== undefined) {
mainMenuContainer.children[i].visible = true;
}
}
}
if (bg && bg.visible !== undefined) {
bg.visible = true;
}
}
function showLanguageMenu(onClose) {
if (languageMenuContainer) {
languageMenuContainer.destroy();
}
languageMenuContainer = game.addChild(new Container());
languageMenuContainer.x = 1024;
languageMenuContainer.y = 1366;
// Türkçe button
var trBtn = languageMenuContainer.addChild(new Container());
trBtn.attachAsset('langButton', {
anchorX: 0.5,
anchorY: 0.5
});
trBtn.x = 0;
trBtn.y = -120;
var trTxt = new Text2(t("lang_tr"), {
size: 56,
fill: 0x222222
});
trTxt.anchor.set(0.5);
trBtn.addChild(trTxt);
trBtn.down = function () {
currentLanguage = "tr";
languageMenuContainer.destroy();
languageMenuContainer = null;
window._mainMenuMusicPlaying = false; // Defensive: always reset flag on language menu
// Do not stop or restart music here; showMainMenu will handle music logic
// Do not stop or restart music here; let showMainMenu handle music logic
// Instead of showMainMenu, just refresh mainMenuContainer text and language
if (mainMenuContainer) {
// Update start and language button texts
for (var i = 0; i < mainMenuContainer.children.length; i++) {
var ch = mainMenuContainer.children[i];
if (ch && ch.children && ch.children.length > 0) {
var txt = ch.children[0];
if (txt && txt.setText) {
if (ch === mainMenuContainer.children[0]) {
txt.setText(t("start"));
} else if (ch === mainMenuContainer.children[1]) {
txt.setText(t("languages"));
}
}
}
}
}
if (typeof onClose === "function") {
onClose();
}
};
// English button
var enBtn = languageMenuContainer.addChild(new Container());
enBtn.attachAsset('langButton', {
anchorX: 0.5,
anchorY: 0.5
});
enBtn.x = 0;
enBtn.y = 120;
var enTxt = new Text2(t("lang_en"), {
size: 56,
fill: 0x222222
});
enTxt.anchor.set(0.5);
enBtn.addChild(enTxt);
enBtn.down = function () {
currentLanguage = "en";
languageMenuContainer.destroy();
languageMenuContainer = null;
window._mainMenuMusicPlaying = false; // Defensive: always reset flag on language menu
// Do not stop or restart music here; let showMainMenu handle music logic
// Instead of showMainMenu, just refresh mainMenuContainer text and language
if (mainMenuContainer) {
// Update start and language button texts
for (var i = 0; i < mainMenuContainer.children.length; i++) {
var ch = mainMenuContainer.children[i];
if (ch && ch.children && ch.children.length > 0) {
var txt = ch.children[0];
if (txt && txt.setText) {
if (ch === mainMenuContainer.children[0]) {
txt.setText(t("start"));
} else if (ch === mainMenuContainer.children[1]) {
txt.setText(t("languages"));
}
}
}
}
}
if (typeof onClose === "function") {
onClose();
}
};
}
function showDeathScreen() {
gameState = GAME_STATE.DEATH;
// Hide all game UI and stop all game sounds/music
setGameUIVisible(false);
LK.stopMusic();
window._mainMenuMusicPlaying = false;
if (typeof LK.stopAllSounds === "function") {
LK.stopAllSounds();
}
// Play death sound when death screen is shown
if (LK.getSound && LK.getSound('death')) {
LK.getSound('death').play();
}
// Remove overlays if any
if (mainMenuContainer) {
mainMenuContainer.destroy();
}
if (languageMenuContainer) {
languageMenuContainer.destroy();
}
if (deathScreenContainer) {
deathScreenContainer.destroy();
}
// Destroy all enemies and clear enemy array
if (typeof enemies !== "undefined" && enemies.length > 0) {
for (var i = enemies.length - 1; i >= 0; i--) {
if (enemies[i] && typeof enemies[i].destroy === "function") {
enemies[i].destroy();
}
enemies.splice(i, 1);
}
}
// Music is now handled in game.update based on gameState
if (typeof currentMusic === "undefined") {
currentMusic = null;
}
deathScreenContainer = game.addChild(new Container());
// Background
var bg = deathScreenContainer.attachAsset('deathBackground', {
anchorX: 0.5,
anchorY: 0.5,
x: 1024,
y: 1366
});
// Death text
var deathTxt = new Text2(t("death"), {
size: 120,
fill: 0xffffff
});
deathTxt.anchor.set(0.5);
deathTxt.x = 1024;
deathTxt.y = 1100;
deathScreenContainer.addChild(deathTxt);
// Return Main Menu Button
var retBtn = deathScreenContainer.addChild(new Container());
retBtn.attachAsset('returnMainMenu', {
anchorX: 0.5,
anchorY: 0.5
});
retBtn.x = 1024;
retBtn.y = 1450;
var retTxt = new Text2(t("returnMainMenu"), {
size: 60,
fill: 0xffffff
});
retTxt.anchor.set(0.5);
retBtn.addChild(retTxt);
retBtn.down = function () {
// Reset everything and go to main menu
deathScreenContainer.destroy();
deathScreenContainer = null;
resetGameToMainMenu();
};
}
// --- Game start/reset helpers ---
function startGame() {
gameState = GAME_STATE.PLAYING;
// Show all game UI and resume game sounds/music
setGameUIVisible(true);
LK.stopMusic();
window._mainMenuMusicPlaying = false;
if (typeof LK.stopAllSounds === "function") {
LK.stopAllSounds();
}
if (typeof currentMusic === "undefined") {
currentMusic = null;
}
// Music is now handled in game.update based on gameState
// Reset all game variables and containers
resetGameWorld();
}
function resetGameToMainMenu() {
// Destroy all overlays and containers
setGameUIVisible(false);
LK.stopMusic();
window._mainMenuMusicPlaying = false;
if (typeof LK.stopAllSounds === "function") {
LK.stopAllSounds();
}
// Music is now handled in game.update based on gameState
// Destroy all overlays if exist
if (mainMenuContainer && mainMenuContainer.destroy) {
mainMenuContainer.destroy();
}
if (languageMenuContainer && languageMenuContainer.destroy) {
languageMenuContainer.destroy();
}
if (deathScreenContainer && deathScreenContainer.destroy) {
deathScreenContainer.destroy();
}
mainMenuContainer = null;
languageMenuContainer = null;
deathScreenContainer = null;
// Destroy all game world objects and arrays
if (typeof enemies !== "undefined" && enemies.length > 0) {
for (var i = enemies.length - 1; i >= 0; i--) {
if (enemies[i] && typeof enemies[i].destroy === "function") {
enemies[i].destroy();
}
enemies.splice(i, 1);
}
}
if (typeof bullets !== "undefined" && bullets.length > 0) {
for (var i = bullets.length - 1; i >= 0; i--) {
if (bullets[i] && typeof bullets[i].destroy === "function") {
bullets[i].destroy();
}
bullets.splice(i, 1);
}
}
if (typeof expOrbs !== "undefined" && expOrbs.length > 0) {
for (var i = expOrbs.length - 1; i >= 0; i--) {
if (expOrbs[i] && typeof expOrbs[i].destroy === "function") {
expOrbs[i].destroy();
}
expOrbs.splice(i, 1);
}
}
if (typeof pickups !== "undefined" && pickups.length > 0) {
for (var i = pickups.length - 1; i >= 0; i--) {
if (pickups[i] && typeof pickups[i].destroy === "function") {
pickups[i].destroy();
}
pickups.splice(i, 1);
}
}
if (typeof shurikenObjs !== "undefined" && shurikenObjs.length > 0) {
for (var i = shurikenObjs.length - 1; i >= 0; i--) {
if (shurikenObjs[i] && typeof shurikenObjs[i].destroy === "function") {
shurikenObjs[i].destroy();
}
shurikenObjs.splice(i, 1);
}
}
if (typeof backgroundTiles !== "undefined" && backgroundTiles.length > 0) {
for (var i = backgroundTiles.length - 1; i >= 0; i--) {
if (backgroundTiles[i] && typeof backgroundTiles[i].destroy === "function") {
backgroundTiles[i].destroy();
}
backgroundTiles.splice(i, 1);
}
}
// Remove all children from gameContainer
if (typeof gameContainer !== "undefined" && gameContainer && gameContainer.children) {
for (var i = gameContainer.children.length - 1; i >= 0; i--) {
var ch = gameContainer.children[i];
if (ch && ch.destroy) {
ch.destroy();
}
}
gameContainer.children = [];
}
// Remove overlays
if (typeof weaponSelectionOverlay !== "undefined" && weaponSelectionOverlay && weaponSelectionOverlay.destroy) {
weaponSelectionOverlay.destroy();
weaponSelectionOverlay = null;
}
if (typeof ov !== "undefined" && ov && ov.destroy) {
ov.destroy();
}
window._mainMenuMusicPlaying = false; // Defensive: always reset flag on overlays
// Remove joystick from GUI
if (typeof joystick !== "undefined" && joystick && joystick.parent && joystick.parent.removeChild) {
joystick.parent.removeChild(joystick);
}
joystick = null;
// Reset all global variables to initial state
enemies = [];
bullets = [];
expOrbs = [];
pickups = [];
shurikenObjs = [];
backgroundTiles = [];
player = undefined;
gameTime = 0;
waveNumber = 1;
nextWaveTimeout = null;
skillSelectionActive = false;
weaponSelectionActive = false;
weaponSelectionOverlay = null;
auraField = null;
lastPickupSpawn = 0;
camera = {
x: 0,
y: 0
};
selectedSkills = [];
// UI elements
timerText = null;
waveText = null;
levelText = null;
hpText = null;
// Remove any global buff counters
if (typeof window.magnetPlusBuffCount !== "undefined") {
window.magnetPlusBuffCount = undefined;
}
// Reset game state to main menu
gameState = GAME_STATE.MAIN_MENU;
currentMusic = null;
// Show main menu
showMainMenu();
}
// --- Reset game world (player, enemies, etc) ---
function resetGameWorld() {
// Remove all children from gameContainer (or create if not exists)
if (!gameContainer) {
gameContainer = game.addChild(new Container());
} else if (gameContainer.children) {
for (var i = gameContainer.children.length - 1; i >= 0; i--) {
var ch = gameContainer.children[i];
if (ch && ch.destroy) {
ch.destroy();
}
}
gameContainer.children = [];
}
// Reset arrays and variables
enemies = [];
bullets = [];
expOrbs = [];
pickups = [];
shurikenObjs = [];
auraField = null;
waveNumber = 1;
gameTime = 0;
nextWaveTimeout = null;
skillSelectionActive = false;
weaponSelectionActive = true;
// Remove overlays
if (weaponSelectionOverlay) {
weaponSelectionOverlay.destroy();
weaponSelectionOverlay = null;
}
window._mainMenuMusicPlaying = false; // Defensive: always reset flag on world reset
// Remove background tiles
if (backgroundTiles && backgroundTiles.length) {
for (var i = 0; i < backgroundTiles.length; i++) {
if (backgroundTiles[i] && backgroundTiles[i].destroy) {
backgroundTiles[i].destroy();
}
}
backgroundTiles = [];
}
// Recreate background tiles
for (var i = 0; i < bgTilesX; i++) {
for (var j = 0; j < bgTilesY; j++) {
var tile = LK.getAsset('seamlessBackGround', {
anchorX: 0,
anchorY: 0
});
tile.x = i * bgTileWidth;
tile.y = j * bgTileHeight;
gameContainer.addChild(tile);
backgroundTiles.push(tile);
}
}
// Create player
player = gameContainer.addChild(new Player());
player.x = 1024;
player.y = 1366;
// Health bar for player
player.hpBar = new Container();
player.hpBarBg = LK.getAsset('hpBarRect', {
anchorX: 0,
anchorY: 0.5
});
player.hpBarBg.alpha = 0.3;
player.hpBarFg = LK.getAsset('hpBarRectFg', {
anchorX: -0.02,
anchorY: 0.5
});
player.hpBar.addChild(player.hpBarBg);
player.hpBar.addChild(player.hpBarFg);
player.hpBar.y = -140;
player.hpBar.x = -110;
player.addChild(player.hpBar);
// Create joystick if not exists
if (!joystick) {
joystick = new Joystick();
joystick.x = 0;
joystick.y = -200;
LK.gui.bottom.addChild(joystick);
} else {
joystick.reset();
}
// UI: create if not exists, else reset text
if (!timerText) {
timerText = new Text2('00:00', {
size: 80,
fill: 0xFFFFFF
});
timerText.anchor.set(0.5, 0);
LK.gui.top.addChild(timerText);
}
timerText.setText('00:00');
if (!waveText) {
waveText = new Text2(t("wavePrefix") + " 1", {
size: 80,
fill: 0x00FFFF
});
waveText.anchor.set(0.5, 0);
waveText.y = 100;
LK.gui.top.addChild(waveText);
}
waveText.setText(t("wavePrefix") + " 1");
if (!levelText) {
levelText = new Text2(t("levelPrefix") + " 1", {
size: 60,
fill: 0xFFFF00
});
levelText.anchor.set(0, 0);
levelText.y = 10;
levelText.x = 200;
LK.gui.top.addChild(levelText);
}
levelText.setText(t("levelPrefix") + " 1");
if (!hpText) {
hpText = new Text2(t("hpPrefix") + ": 100/100", {
size: 60,
fill: 0xFF0000
});
hpText.anchor.set(1, 0);
hpText.y = 10;
hpText.x = -200;
LK.gui.top.addChild(hpText);
}
hpText.setText(t("hpPrefix") + ": 100/100");
// Remove skill overlays
if (typeof ov !== "undefined" && ov && ov.destroy) {
ov.destroy();
}
window._mainMenuMusicPlaying = false; // Defensive: always reset flag on overlays (world reset)
// Weapon selection overlay
weaponSelectionActive = true;
weaponSelectionOverlay = game.addChild(new Container());
weaponSelectionOverlay.x = 1024;
weaponSelectionOverlay.y = 1366;
var allWeaponKeys = Object.keys(WEAPONS);
for (var i = 0; i < allWeaponKeys.length; i++) {
(function (idx) {
var key = allWeaponKeys[idx];
var card = weaponSelectionOverlay.addChild(new Container());
card.attachAsset('skillCard', {
anchorX: 0.5,
anchorY: 0.5
});
var col = idx % 3;
var row = Math.floor(idx / 3);
card.x = (col - 1) * 420;
card.y = (row - 1) * 700;
// Localize weapon name for selection overlay
var trWeaponNames = {
sword: "Kılıç",
boomerang: "Bumerang",
ball: "Top",
rocket: "Roket",
brick: "Tuğla",
lightning: "Yıldırım",
aura: "Aura",
molotov: "Molotof",
shuriken: "Ninja Yıldızı"
};
var weaponLabel = currentLanguage === "tr" && trWeaponNames[key] ? trWeaponNames[key] : key.charAt(0).toUpperCase() + key.slice(1);
var txt = new Text2(weaponLabel, {
size: 48,
fill: 0xffffff
});
txt.anchor.set(0.5);
card.addChild(txt);
card.down = function () {
player.weapons = {};
addWeapon(key);
weaponSelectionActive = false;
weaponSelectionOverlay.destroy();
skillSelectionActive = false;
spawnWave();
};
})(i);
}
skillSelectionActive = true;
}
// --- New zombie attack/projectile assets ---
function _typeof(o) {
"@babel/helpers - typeof";
return _typeof = "function" == typeof Symbol && "symbol" == typeof Symbol.iterator ? function (o) {
return typeof o;
} : function (o) {
return o && "function" == typeof Symbol && o.constructor === Symbol && o !== Symbol.prototype ? "symbol" : typeof o;
}, _typeof(o);
}
function isWithinRange(e, maxRange) {
var dx = e.x - player.x;
var dy = e.y - player.y;
return dx * dx + dy * dy <= maxRange * maxRange;
}
function addWeapon(key) {
// <-- artık global
if (player && Object.keys(player.weapons).length >= 6) {
return; // Zaten 6 silah varsa yeni eklemeye izin verme
}
var m = WEAPONS[key];
var target = player ? player : {
weapons: {}
}; // sınıf kurulumunda da çalışsın
target.weapons[key] = {
cd: m.cd,
cdCnt: m.cd,
dmgMul: 1,
rngMul: 1,
amount: 1,
upgrade: 0,
enchTaken: []
};
}
function rand(min, max) {
return min + Math.random() * (max - min);
}
function distSq(a, b) {
var dx = a.x - b.x,
dy = a.y - b.y;
return dx * dx + dy * dy;
}
function unitDir(from, to) {
var dx = to.x - from.x,
dy = to.y - from.y,
d = Math.sqrt(dx * dx + dy * dy);
return {
dx: dx / d,
dy: dy / d,
len: d
};
}
/* ==== WEAPON META TABLE ==== */
var WEAPONS = {
sword: {
cd: 120,
dmg: 10,
range: 200,
type: 'melee',
ench: ["Sword Damage+", "Sword Attack Speed+", "Sword Range+"],
up: ["Sword +1", "Sword +2", "Sword +3"]
},
boomerang: {
cd: 240,
dmg: 6,
range: 650,
type: 'boomerang',
ench: ["Boomerang Damage+", "Boomerang Attack Speed+", "Boomerang Range+"],
up: ["Boomerang +1", "Boomerang +2", "Boomerang +3"]
},
ball: {
cd: 240,
dmg: 6,
range: 650,
type: 'ball',
ench: ["Ball Damage+", "Ball Attack Speed+", "Ball Range+"],
up: ["Ball +1", "Ball +2", "Ball +3"]
},
rocket: {
cd: 480,
dmg: 16,
range: 800,
type: 'rocket',
ench: ["Rocket Damage+", "Rocket Attack Speed+", "Rocket Range+"],
up: ["Rocket +1", "Rocket +2", "Rocket +3"]
},
brick: {
cd: 240,
dmg: 6,
range: 450,
type: 'brick',
ench: ["Brick Damage+", "Brick Attack Speed+", "Brick Range+"],
up: ["Back 75°", "Front 45°", "Back 45°"]
},
lightning: {
cd: 180,
dmg: 10,
range: 900,
type: 'lightning',
ench: ["Lightning Damage+", "Lightning Attack Speed+", "Lightning Range+"],
up: ["Lightning +1", "Lightning +2", "Lightning +3"]
},
aura: {
cd: 60,
dmg: 3,
range: 250,
type: 'aura',
ench: ["Aura Damage+", "Aura Attack Speed+", "Aura Range+"],
up: ["Slow 25%", "Slow 50%", "Slow 75%"]
},
molotov: {
cd: 180,
dmg: 9,
range: 550,
type: 'molotov',
ench: ["Molotov Damage+", "Molotov Attack Speed+", "Molotov Range+"],
up: ["Molotov +1", "Molotov +2", "Molotov +3"]
},
shuriken: {
cd: 180,
dmg: 6,
range: 300,
type: 'shuriken',
ench: ["Shuriken Damage+", "Shuriken Attack Speed+", "Shuriken Range+"],
up: ["Shuriken +1", "Shuriken +2", "Shuriken +3"]
}
};
var player;
var enemies = [];
var bullets = [];
var expOrbs = [];
var joystick;
var gameTime = 0;
var waveNumber = 1;
// bossSpawned variable removed; boss is now spawned every 10th wave
var selectedSkills = [];
var camera = {
x: 0,
y: 0
};
var gameContainer;
var skillSelectionActive = false;
var pickups = [];
var lastPickupSpawn = 0;
// --- Game world variables (do not initialize world here, only declare) ---
var gameContainer;
var bgTileAsset = LK.getAsset('seamlessBackGround', {
anchorX: 0,
anchorY: 0
});
var bgTileWidth = bgTileAsset.width;
var bgTileHeight = bgTileAsset.height;
var bgTilesX = Math.ceil(2048 / bgTileWidth) + 2; // +2 for safe margin
var bgTilesY = Math.ceil(2732 / bgTileHeight) + 2;
var backgroundTiles = [];
var player;
var weaponSelectionActive = false;
var weaponSelectionOverlay = null;
var joystick = null;
var timerText = null;
var waveText = null;
var levelText = null;
var hpText = null;
// Helper to show/hide all game UI elements
function setGameUIVisible(visible) {
if (timerText) {
timerText.visible = visible;
}
if (waveText) {
waveText.visible = visible;
}
if (levelText) {
levelText.visible = visible;
}
if (hpText) {
hpText.visible = visible;
}
// Hide joystick as well if not playing
if (joystick) {
joystick.visible = visible;
}
if (!visible) {
window._mainMenuMusicPlaying = false;
}
}
// Spawn functions
function spawnEnemy(type, x, y) {
var enemy;
if (type === 'normal' || type === 'normalZombie') {
enemy = new NormalZombie();
} else if (type === 'rangedZombie') {
enemy = new RangedZombie();
} else if (type === 'fastZombie') {
enemy = new FastZombie();
} else if (type === 'tankZombie') {
enemy = new TankZombie();
} else if (type === 'bossZombie') {
enemy = new BossZombie();
} else {
enemy = new NormalZombie();
}
enemy.x = x;
enemy.y = y;
// --- Düşman can yenileme: 5. dalgadan sonra, her 5 dalgada +1% artar ---
if (waveNumber > 5) {
var regenPercent = 5 + Math.floor((waveNumber - 6) / 5) + 0;
enemy.enemyRegen = regenPercent;
} else {
enemy.enemyRegen = 0;
}
enemies.push(enemy);
gameContainer.addChild(enemy);
// --- Health bar for enemy ---
enemy.hpBar = new Container();
enemy.hpBarBg = LK.getAsset('enemyHpBarRect', {
anchorX: 0.0,
anchorY: 0.5
});
enemy.hpBarBg.alpha = 0.3;
enemy.hpBarFg = LK.getAsset('enemyHpBarRectFg', {
anchorX: -0.04,
anchorY: 0.5
});
enemy.hpBar.addChild(enemy.hpBarBg);
enemy.hpBar.addChild(enemy.hpBarFg);
enemy.hpBar.y = -110;
enemy.hpBar.x = -80;
enemy.addChild(enemy.hpBar);
}
function spawnWave() {
var spawnRadius = 1500;
var spawnList = [];
// Eski dalga sistemi: her dalgada normal, ranged, fast, tank ve boss (her 10. dalgada) zombi
var normalCount = 5 + (waveNumber - 1);
var rangedCount = waveNumber >= 3 ? waveNumber - 2 : 0;
var fastCount = waveNumber >= 6 ? waveNumber - 5 : 0;
var tankCount = waveNumber >= 9 ? waveNumber - 8 : 0;
var bossCount = waveNumber % 10 === 0 ? Math.floor(waveNumber / 10) : 0;
for (var i = 0; i < normalCount; i++) {
var angle = Math.random() * Math.PI * 2;
var x = player.x + Math.cos(angle) * spawnRadius;
var y = player.y + Math.sin(angle) * spawnRadius;
spawnList.push({
type: 'normalZombie',
x: x,
y: y
});
}
for (var i = 0; i < rangedCount; i++) {
var angle = Math.random() * Math.PI * 2;
var x = player.x + Math.cos(angle) * spawnRadius;
var y = player.y + Math.sin(angle) * spawnRadius;
spawnList.push({
type: 'rangedZombie',
x: x,
y: y
});
}
for (var i = 0; i < fastCount; i++) {
var angle = Math.random() * Math.PI * 2;
var x = player.x + Math.cos(angle) * spawnRadius;
var y = player.y + Math.sin(angle) * spawnRadius;
spawnList.push({
type: 'fastZombie',
x: x,
y: y
});
}
for (var i = 0; i < tankCount; i++) {
var angle = Math.random() * Math.PI * 2;
var x = player.x + Math.cos(angle) * spawnRadius;
var y = player.y + Math.sin(angle) * spawnRadius;
spawnList.push({
type: 'tankZombie',
x: x,
y: y
});
}
for (var i = 0; i < bossCount; i++) {
var angle = Math.random() * Math.PI * 2;
var x = player.x + Math.cos(angle) * spawnRadius;
var y = player.y + Math.sin(angle) * spawnRadius;
spawnList.push({
type: 'bossZombie',
x: x,
y: y
});
}
// Shuffle spawnList for random order
for (var i = spawnList.length - 1; i > 0; i--) {
var j = Math.floor(Math.random() * (i + 1));
var temp = spawnList[i];
spawnList[i] = spawnList[j];
spawnList[j] = temp;
}
// Staggered spawn with 0.1s (6 frames) delay between each
var spawnIdx = 0;
function spawnNextEnemy() {
if (spawnIdx < spawnList.length) {
var info = spawnList[spawnIdx];
spawnEnemy(info.type, info.x, info.y);
spawnIdx++;
if (spawnIdx < spawnList.length) {
LK.setTimeout(spawnNextEnemy, 100); // 0.1s = 100ms
}
}
}
spawnNextEnemy();
}
function dropExpOrb(x, y) {
var orb = new ExpOrb();
orb.x = x;
orb.y = y;
// orb.value is always 1 in ExpOrb class
expOrbs.push(orb);
gameContainer.addChild(orb);
}
/* =============================================================== */
/* NINE-WEAPON FIRE / PROJECTILES */
/* =============================================================== */
function nearestEnemy() {
var best = null,
bestD = 1e9;
for (var i = 0; i < enemies.length; i++) {
var d = distSq(player, enemies[i]);
if (d < bestD) {
best = enemies[i];
bestD = d;
}
}
return best;
}
function damageEnemy(e, dmg, attacker) {
// attacker: optional, can be player or enemy
// Play hit sound on every hit
LK.getSound('hit').play();
// Determine crit chance and crit type
var critChance = 0.25; // default
var critType = null; // null, "low", "high"
var isPlayerAttack = false;
if (attacker === undefined) {
attacker = player;
}
if (attacker === player) {
isPlayerAttack = true;
critChance = player.critChance !== undefined ? player.critChance : 0.25;
} else if (attacker && attacker.type === 'normal') {
critChance = attacker.critChance !== undefined ? attacker.critChance : 0.25;
}
var critRoll = Math.random();
var critValue = 1;
if (critRoll < critChance) {
// Crit! Now decide low or high
if (Math.random() < 0.5) {
critType = "low";
critValue = 1.2;
} else {
critType = "high";
critValue = 1.4;
}
}
var finalDmg = dmg * critValue;
// Apply debuffs if player has buffs
if (player && player.buffs) {
// Grit Wound+
if (player.buffs.gritWound > 0) {
e.gritWound = player.buffs.gritWound;
e.lastGritWoundTime = gameTime;
}
// Enemy Slow+
if (player.buffs.enemySlow > 0) {
e.slowBuff = player.buffs.enemySlow;
e.slowBuffTime = gameTime;
}
// Enemy Stun+
if (player.buffs.enemyStun > 0) {
e.stunBuff = player.buffs.enemyStun;
e.stunBuffTime = gameTime;
}
// Bleeding+
if (player.buffs.bleeding > 0) {
e.bleedBuff = player.buffs.bleeding;
e.bleedBuffTime = gameTime;
e.bleedTicks = 0;
}
}
// Hasar yazısı göster
if (typeof finalDmg === "number") {
var fillColor = 0xffffff;
var strokeColor = 0x000000;
if (isPlayerAttack) {
if (critType === "low") {
fillColor = 0xffe066; // sarı
} else if (critType === "high") {
fillColor = 0xff3333; // kırmızı
} else {
fillColor = 0xffffff; // beyaz
}
} else {
// Enemy attack, use default
fillColor = 0xff3333;
}
var dmgText = new Text2(finalDmg.toFixed(1), {
size: 48,
fill: fillColor,
stroke: strokeColor,
strokeThickness: 4
});
dmgText.anchor.set(0.5);
dmgText.x = e.x;
dmgText.y = e.y - (e.height ? e.height / 2 : 60);
dmgText.alpha = 1;
gameContainer.addChild(dmgText);
// Yavaşça yukarı çıkıp kaybolsun
tween(dmgText, {
y: dmgText.y - 60,
alpha: 0
}, {
duration: 500,
easing: tween.linear,
onFinish: function onFinish() {
if (dmgText.parent) {
dmgText.destroy();
}
}
});
}
if (e.takeDamage(finalDmg)) {
// Play death sound (if you have a death sound asset, e.g. 'death')
if (LK.getSound('death')) {
LK.getSound('death').play();
}
// Ölünce exp orb düşür:
dropExpOrb(e.x, e.y);
// Sonra düşmanı kaldır ve yok et:
var idx = enemies.indexOf(e);
enemies.splice(idx, 1);
e.destroy();
}
}
/* ---- sword (melee slash) ---- */
function swingSword(inst) {
// Kılıç ateşlendiğinde sesi çal
if (LK.getSound && LK.getSound('swordSoundEffect')) {
LK.getSound('swordSoundEffect').play();
}
var sorted = enemies.slice().sort(function (a, b) {
return distSq(player, a) - distSq(player, b);
});
if (sorted.length === 0) {
return;
}
for (var n = 0; n < inst.amount; n++) {
var idx = n < sorted.length ? n : 0;
var target = sorted[idx];
var dir = unitDir(player, target);
var arc = gameContainer.addChild(new Container());
arc.x = player.x;
arc.y = player.y;
arc.attachAsset('swordHit', {
anchorX: 0,
anchorY: 0.5,
rotation: Math.atan2(dir.dy, dir.dx),
scaleX: inst.rngMul,
scaleY: inst.rngMul
});
arc.life = 10;
arc.dmg = WEAPONS.sword.dmg * inst.dmgMul;
arc.hitList = [];
arc.update = function () {
if (--this.life <= 0) {
if (this.parent) {
this.destroy();
}
return;
}
var maxRange = WEAPONS.sword.range * inst.rngMul;
for (var i = enemies.length - 1; i >= 0; i--) {
var e = enemies[i];
// hem arc.intersects hem oyuncudan menzil kontrolü
if (this.hitList.indexOf(e) === -1 && this.intersects(e) && isWithinRange(e, maxRange)) {
this.hitList.push(e);
damageEnemy(e, this.dmg);
}
}
};
}
}
/* ---- boomerang ---- */
function fireBoomerang(inst) {
// Bumerang ateşlendiğinde sesi çal
if (LK.getSound && LK.getSound('bumerangSoundEffect')) {
LK.getSound('bumerangSoundEffect').play();
}
var target = nearestEnemy();
if (!target) {
return;
}
var dir = unitDir(player, target);
var maxRange = WEAPONS.boomerang.range * inst.rngMul;
for (var n = 0; n < inst.amount; n++) {
var p = new Container();
var boomerangAsset = p.attachAsset('boomerang', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: inst.rngMul,
scaleY: inst.rngMul
});
p._rotationAngle = 0;
p._boomerangAsset = boomerangAsset;
p.x = player.x;
p.y = player.y;
p._startX = player.x;
p._startY = player.y;
p._dirX = dir.dx;
p._dirY = dir.dy;
p._phase = "out"; // "out" = gidiş, "back" = dönüş
p._distance = 0;
p._maxRange = maxRange;
p._speed = 18;
p.dmg = WEAPONS.boomerang.dmg * inst.dmgMul;
p.hitEnemies = [];
p.update = function () {
if (skillSelectionActive) {
return;
}
// Boomerang assetini döndür
if (this._boomerangAsset) {
this._rotationAngle = (this._rotationAngle || 0) + 0.3;
this._boomerangAsset.rotation = this._rotationAngle;
}
// Hareket
if (this._phase === "out") {
this.x += this._dirX * this._speed;
this.y += this._dirY * this._speed;
this._distance += this._speed;
if (this._distance >= this._maxRange) {
this._phase = "back";
}
} else if (this._phase === "back") {
// Dönüşte, oyuncuya doğru hareket et
var dx = player.x - this.x;
var dy = player.y - this.y;
var d = Math.sqrt(dx * dx + dy * dy);
if (d < this._speed) {
// Oyuncuya ulaştıysa yok et
if (this.parent) {
this.destroy();
}
var idx4 = bullets.indexOf(this);
if (idx4 >= 0) {
bullets.splice(idx4, 1);
}
return;
}
this.x += dx / d * this._speed;
this.y += dy / d * this._speed;
}
// Düşmanlara çarpma kontrolü, menzil bakmaksızın, yok olmadan biçip geçmeli
for (var i = enemies.length - 1; i >= 0; i--) {
var e = enemies[i];
if (this.hitEnemies.indexOf(e) === -1 && this.intersects(e)) {
damageEnemy(e, this.dmg);
this.hitEnemies.push(e);
}
// Eğer boomerang artık düşmanla temas etmiyorsa, tekrar vurabilmesi için hitEnemies'den çıkar
if (this.hitEnemies.indexOf(e) !== -1 && !this.intersects(e)) {
this.hitEnemies.splice(this.hitEnemies.indexOf(e), 1);
}
}
// Kameranın (görünen alanın) dışına çıktıysa sil (fail-safe)
var camMinX = camera.x;
var camMaxX = camera.x + 2048;
var camMinY = camera.y;
var camMaxY = camera.y + 2732;
if (this.x < camMinX || this.x > camMaxX || this.y < camMinY || this.y > camMaxY) {
if (this.parent) {
this.destroy();
}
var idx = bullets.indexOf(this);
if (idx >= 0) {
bullets.splice(idx, 1);
}
return;
}
};
bullets.push(p);
gameContainer.addChild(p);
}
}
/* ---- top (rebound ball) ---- */
function fireBall(inst) {
var maxRange = WEAPONS.ball.range * (player.weapons && player.weapons.ball ? player.weapons.ball.rngMul : 1);
var target = null;
for (var i = 0; i < enemies.length; i++) {
if (isWithinRange(enemies[i], maxRange)) {
target = enemies[i];
break;
}
}
// Eğer menzilde hiç düşman yoksa top fırlatma
if (!target) {
return;
}
// Top ateşlendiğinde sesi çal (yalnızca gerçekten top fırlatılıyorsa)
if (LK.getSound && LK.getSound('ballSoundEffect')) {
LK.getSound('ballSoundEffect').play();
}
var dir = unitDir(player, target);
for (var n = 0; n < inst.amount; n++) {
var p = new Container();
// Attach the 'ball' asset, scale by range multiplier
var ballGraphics = p.attachAsset('ball', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: inst.rngMul,
scaleY: inst.rngMul
});
p._rotationAngle = 0;
p._ballAsset = ballGraphics;
p.x = player.x;
p.y = player.y;
p.dirX = dir.dx;
p.dirY = dir.dy;
p.damage = WEAPONS.ball.dmg * inst.dmgMul;
p.speed = 14;
p.bounced = false;
p.hitEnemies = [];
p.update = function () {
// Seviye seçim ekranı açıkken hiçbir şey yapma
if (skillSelectionActive) {
return;
}
// Hareket
this.x += this.dirX * this.speed;
this.y += this.dirY * this.speed;
// Rotate ball asset
if (this._ballAsset) {
this._rotationAngle = (this._rotationAngle || 0) + 0.3;
this._ballAsset.rotation = this._rotationAngle;
}
// Kamera (görünen alan) sınırlarını belirle
var camMinX = camera.x;
var camMaxX = camera.x + 2048;
var camMinY = camera.y;
var camMaxY = camera.y + 2732;
// Kameranın dışına çıktıysa yok et
if (this.x < camMinX || this.x > camMaxX || this.y < camMinY || this.y > camMaxY) {
if (this.parent) {
this.destroy();
}
var bi = bullets.indexOf(this);
if (bi >= 0) {
bullets.splice(bi, 1);
}
return;
}
// Düşmana çarpma kontrolü (menzilsiz, ekranda nerde olursa olsun vurmalı ve sekmeli)
for (var i = enemies.length - 1; i >= 0; i--) {
var e = enemies[i];
if (this.hitEnemies.indexOf(e) === -1 && this.intersects(e)) {
damageEnemy(e, this.damage);
this.hitEnemies.push(e);
// Sadece ilk çarpmada sekmeli
if (!this.bounced) {
this.dirX = -this.dirX;
this.dirY = -this.dirY;
this.bounced = true;
}
}
// Eğer ball artık düşmanla temas etmiyorsa, tekrar vurabilmesi için hitEnemies'den çıkar
if (this.hitEnemies.indexOf(e) !== -1 && !this.intersects(e)) {
this.hitEnemies.splice(this.hitEnemies.indexOf(e), 1);
}
}
};
bullets.push(p);
gameContainer.addChild(p);
}
}
/* ---- rocket ---- */
function fireRocket(inst) {
// Roket ateşlendiğinde sesi çal
if (LK.getSound && LK.getSound('rocketSoundEffect')) {
LK.getSound('rocketSoundEffect').play();
}
var target = nearestEnemy();
if (!target) {
return;
}
var dir = unitDir(player, target);
// Bu silahın menzili:
var maxRange = WEAPONS.rocket.range * inst.rngMul;
for (var n = 0; n < inst.amount; n++) {
var p = gameContainer.addChild(new Container());
p.attachAsset('rocket', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: inst.rngMul,
scaleY: inst.rngMul
});
p.x = player.x;
p.y = player.y;
p.speed = 10;
p.dmg = WEAPONS.rocket.dmg * inst.dmgMul;
// Track target enemy at fire time
p._targetEnemy = target;
p._exploded = false;
p.update = function () {
if (skillSelectionActive) {
return;
}
// Defensive: if target enemy is dead, just fly straight
var targetEnemy = this._targetEnemy;
if (!this._exploded && targetEnemy && enemies.indexOf(targetEnemy) !== -1) {
// Home in on target enemy's center
var dx = targetEnemy.x - this.x;
var dy = targetEnemy.y - this.y;
var dist = Math.sqrt(dx * dx + dy * dy);
if (dist > 0) {
this.x += dx / dist * this.speed;
this.y += dy / dist * this.speed;
}
// Check if reached center (use <= speed for precision)
if (dist <= this.speed) {
this.x = targetEnemy.x;
this.y = targetEnemy.y;
this._exploded = true;
LK.getSound('rocketBoom').play();
// a) Dairesel patlama efekti: rocketBoom asseti kullanılmalı
var explosion = gameContainer.addChild(new Container());
var gfx = LK.getAsset('rocketBoom', {
anchorX: 0.5,
anchorY: 0.5,
// Restore original scale (no 2x)
scaleX: inst.rngMul * (80 / 70),
scaleY: inst.rngMul * (80 / 70),
alpha: 0.6
});
explosion.addChild(gfx);
explosion.x = this.x;
explosion.y = this.y;
explosion.life = 30;
explosion.update = function () {
if (--this.life <= 0) {
this.destroy();
} else {
this.children[0].alpha = this.life / 30 * 0.6;
}
};
// b) AoE hasar: sadece menzil içindekileri vur
for (var k = enemies.length - 1; k >= 0; k--) {
var e2 = enemies[k];
// Restore original AoE radius (radius^2 = 16000)
if (distSq(this, e2) < 16000) {
// Rocket patlaması hasar versin
damageEnemy(e2, this.dmg);
}
}
// c) Bu roketi sil
if (this.parent) {
this.destroy();
}
var bi = bullets.indexOf(this);
if (bi >= 0) {
bullets.splice(bi, 1);
}
return;
}
} else if (!this._exploded) {
// If target is gone, fly straight in last direction
if (typeof this.dirX === "undefined" || typeof this.dirY === "undefined") {
// Set direction to original target
var origDir = unitDir({
x: player.x,
y: player.y
}, {
x: this.x,
y: this.y
});
this.dirX = origDir.dx;
this.dirY = origDir.dy;
}
this.x += this.dirX * this.speed;
this.y += this.dirY * this.speed;
}
// 3) Kameranın (görünen alanın) dışına çıkarsa sil
var camMinX = camera.x;
var camMaxX = camera.x + 2048;
var camMinY = camera.y;
var camMaxY = camera.y + 2732;
if (this.x < camMinX || this.x > camMaxX || this.y < camMinY || this.y > camMaxY) {
if (this.parent) {
this.destroy();
}
var bi2 = bullets.indexOf(this);
if (bi2 >= 0) {
bullets.splice(bi2, 1);
}
return;
}
};
bullets.push(p);
}
}
/* ---- brick ---- */
function throwBrick(inst, angDeg) {
// Tuğla ateşlendiğinde sesi çal
if (LK.getSound && LK.getSound('brickSoundEffect')) {
LK.getSound('brickSoundEffect').play();
}
var rad = angDeg * Math.PI / 180;
var p = gameContainer.addChild(new Container());
var brickAsset = p.attachAsset('brick', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: inst.rngMul,
scaleY: inst.rngMul
});
p._rotationAngle = 0;
p._brickAsset = brickAsset;
p.x = player.x;
p.y = player.y;
// Brick should be thrown upwards at 75 degrees (not downwards)
p.vx = Math.cos(rad) * 14;
p.vy = -Math.abs(Math.sin(rad) * 14); // always upwards
p.g = 0.3;
p.dmg = WEAPONS.brick.dmg * inst.dmgMul;
// Bu silahın menzili:
var maxRange = WEAPONS.brick.range * inst.rngMul;
p._hitEnemies = [];
p.update = function () {
if (skillSelectionActive) {
return;
}
// 1) Yerçekimi ve hareket
this.vy += this.g;
this.x += this.vx;
this.y += this.vy;
// Rotate brick asset
if (this._brickAsset) {
this._rotationAngle = (this._rotationAngle || 0) + 0.3;
this._brickAsset.rotation = this._rotationAngle;
}
// 2) Düşmana çarpma kontrolü, brick yok olmasın, her düşmana bir kez vurabilsin, menzil bakmaksızın ekrandan çıkana kadar vurmalı
for (var i = enemies.length - 1; i >= 0; i--) {
var e = enemies[i];
if (this._hitEnemies.indexOf(e) === -1 && this.intersects(e)) {
damageEnemy(e, this.dmg);
this._hitEnemies.push(e);
}
// Eğer brick artık düşmanla temas etmiyorsa, tekrar vurabilmesi için _hitEnemies'den çıkar
if (this._hitEnemies.indexOf(e) !== -1 && !this.intersects(e)) {
this._hitEnemies.splice(this._hitEnemies.indexOf(e), 1);
}
}
// 3) Kameranın (görünen alanın) dışına çıkarsa yok et
var camMinX = camera.x;
var camMaxX = camera.x + 2048;
var camMinY = camera.y;
var camMaxY = camera.y + 2732;
if (this.x < camMinX || this.x > camMaxX || this.y < camMinY || this.y > camMaxY) {
if (this.parent) {
this.destroy();
}
return;
}
};
}
/* ---- lightning ---- */
function castLightning(inst) {
// Yıldırım ateşlendiğinde sesi çal
if (LK.getSound && LK.getSound('lightningSoundEffect')) {
LK.getSound('lightningSoundEffect').play();
}
// 1) Menzildeki düşmanları filtrele
var maxRange = WEAPONS.lightning.range * inst.rngMul;
var candidates = enemies.filter(function (e) {
var dx = e.x - player.x;
var dy = e.y - player.y;
return dx * dx + dy * dy <= maxRange * maxRange;
});
if (candidates.length === 0) {
return;
}
// 2) Rastgele birini seç
var victim = candidates[Math.floor(Math.random() * candidates.length)];
// 3) Efekti ekle
var l = gameContainer.addChild(new Container());
l.attachAsset('lightning', {
anchorX: 0.5,
anchorY: 1,
scaleX: inst.rngMul,
scaleY: inst.rngMul
});
l.x = victim.x;
l.y = victim.y;
l.life = 15;
l.update = function () {
if (skillSelectionActive) {
return;
}
if (--this.life === 14) {
damageEnemy(victim, WEAPONS.lightning.dmg * inst.dmgMul);
}
if (this.life <= 0) {
if (this.parent) {
this.destroy();
}
}
};
}
/* ---- aura ---- */
var auraField = null;
function ensureAura(inst) {
// 1) Sadece ilk defa oluşturulacak:
if (!auraField) {
auraField = player.addChild(new Container());
auraField.attachAsset('auraField', {
anchorX: 0.5,
anchorY: 0.5,
alpha: 0.3,
scaleX: inst.rngMul,
scaleY: inst.rngMul
});
// Oyuncunun tam ortası (yerel koordinat):
auraField.x = 0;
auraField.y = 0;
}
// Her frame aura görselini güncelle (range buff alınırsa anında büyüsün/küçülsün)
if (auraField && auraField.children && auraField.children.length > 0) {
auraField.children[0].scaleX = inst.rngMul;
auraField.children[0].scaleY = inst.rngMul;
// Rotate aura asset
if (typeof auraField._rotationAngle === "undefined") {
auraField._rotationAngle = 0;
}
auraField._rotationAngle += 0.3; // 10 kat daha hızlı döndür
auraField.children[0].rotation = auraField._rotationAngle;
}
// 2) Hasar uygulama kısmı (oyuncudan r uzaklıktaysa vur):
var r = WEAPONS.aura.range * inst.rngMul;
for (var i = enemies.length - 1; i >= 0; i--) {
var e = enemies[i];
var dx = e.x - player.x;
var dy = e.y - player.y;
if (dx * dx + dy * dy < r * r) {
damageEnemy(e, WEAPONS.aura.dmg * inst.dmgMul);
}
}
}
/* ---- molotov ---- */
function throwMolotov(inst) {
// Molotov ateşlendiğinde sesi çal
if (LK.getSound && LK.getSound('molotovSoundEffect')) {
LK.getSound('molotovSoundEffect').play();
}
// Find nearest enemy
var target = nearestEnemy();
if (!target) {
return;
}
var dir = unitDir(player, target);
var p = gameContainer.addChild(new Container());
var molotovAsset = p.attachAsset('molotov', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: inst.rngMul,
scaleY: inst.rngMul
});
p._rotationAngle = 0;
p._molotovAsset = molotovAsset;
p.x = player.x;
p.y = player.y;
p.dirX = dir.dx;
p.dirY = dir.dy;
p.speed = 16;
p.life = 120; // Give enough life to reach enemy
p._targetEnemy = target;
p.update = function () {
if (skillSelectionActive) {
return;
}
this.x += this.dirX * this.speed;
this.y += this.dirY * this.speed;
// Rotate molotov asset
if (this._molotovAsset) {
this._rotationAngle = (this._rotationAngle || 0) + 0.3;
this._molotovAsset.rotation = this._rotationAngle;
}
// Check for collision with target enemy
if (this._targetEnemy && this.intersects(this._targetEnemy)) {
// Explode at enemy position
spawnFlame(this._targetEnemy.x, this._targetEnemy.y, inst);
if (this.parent) {
this.destroy();
}
return;
}
// If life runs out, just destroy (fail-safe)
if (--this.life <= 0) {
if (this.parent) {
this.destroy();
}
}
};
bullets.push(p);
}
/* ---- molotov flame ---- */
function spawnFlame(x, y, inst) {
// Molotov patladığında sesi çal (sadece bir kere, flame yaratılırken)
if (LK.getSound && LK.getSound('molotovBoom')) {
LK.getSound('molotovBoom').play();
}
// 1) Alev container’ını oluşturup ekrana ekliyoruz:
var f = gameContainer.addChild(new Container());
f.attachAsset('molotovFlame', {
anchorX: 0.5,
anchorY: 0.5,
alpha: 0.4,
scaleX: inst.rngMul,
scaleY: inst.rngMul
});
f.x = x;
f.y = y;
// 2) “Kaçıncı frame’deyiz?” ve “her düşmana kaç kez vuruldu?” bilgisini tutacak:
f._flameTick = 0; // Şu anda kaçıncı güncellemede olduğumuz
f._hitInfo = {}; // { [enemyId]: { enteredFrame, tickCount } }
// 3) Alev ömrü: 61 frame (≈1 saniye + 1 frame). Böylece 0–60. frame’lerde
// update() çalışır; 60 frame sonundaki üçüncü hasar da tetiklenir.
f.life = 61;
// 4) Her vuruş için 3 hasar verecek (WEAPONS.molotov.dmg = 9 ise /3 → 3).
// inst.dmgMul varsa onunla da çarpalım.
var singleHit = WEAPONS.molotov.dmg / 3 * inst.dmgMul;
// 5) Alevin etki yarıçapı (radius) ve bunun karesini hesaplıyoruz:
var radius = 70 * inst.rngMul;
var radiusSq = radius * radius;
f.update = function () {
if (skillSelectionActive) {
return; // Kart seçimi açıkken hiçbir şey yapma
}
this._flameTick++;
// 6) Her düşman için: eğer bu frame’de alev yarıçapı içindeyse,
// “enteredFrame” atamasını yap, ardından 0/30/60 frame kontrollerini yap.
for (var i = enemies.length - 1; i >= 0; i--) {
var e = enemies[i];
var dx = e.x - f.x;
var dy = e.y - f.y;
var distSq = dx * dx + dy * dy;
if (distSq <= radiusSq) {
// Düşman ilk kez alevin içine giriyorsa benzersiz bir ID atayalım:
if (typeof e._molotovFlameId === "undefined") {
e._molotovFlameId = Math.random().toString(36).substr(2, 9) + "_" + Math.floor(Math.random() * 1e6);
}
var id = e._molotovFlameId;
// Eğer bu ID henüz kayıtlı değilse, “enteredFrame” ve “tickCount=0” set et:
if (!this._hitInfo[id]) {
this._hitInfo[id] = {
enteredFrame: this._flameTick,
tickCount: 0
};
// İlk temas anında bir kere **3 hasar** vur:
damageEnemy(e, singleHit);
this._hitInfo[id].tickCount = 1;
}
var info = this._hitInfo[id];
var framesSinceEnter = this._flameTick - info.enteredFrame;
// 7) Eğer bir kez vuruldu (tickCount == 1) ve 30 frame geçtiyse → ikinci 3 hasar:
if (info.tickCount === 1 && framesSinceEnter >= 30) {
damageEnemy(e, singleHit);
info.tickCount = 2;
}
// 8) Eğer iki kez vuruldu (tickCount == 2) ve 60 frame geçtiyse → üçüncü 3 hasar:
else if (info.tickCount === 2 && framesSinceEnter >= 60) {
damageEnemy(e, singleHit);
info.tickCount = 3;
}
// 9) Üç vuruş da tamamlandıysa bu düşmanla ilgili kaydı silebiliriz:
if (info.tickCount >= 3) {
delete this._hitInfo[id];
}
}
// “else” bloğunda hiç silme veya sıfırlama yapmıyoruz,
// böylece düşman kısa süre sonra çıksa bile sayaç devam eder.
}
// 10) Ömür bitti mi? Bittiğinde kendini yok et:
this.life--;
if (this.life <= 0) {
if (this.parent) {
this.destroy();
}
return;
}
// 11) Fail-safe: Alev ekran dışına çıktıysa sil:
var camMinX = camera.x;
var camMaxX = camera.x + 2048;
var camMinY = camera.y;
var camMaxY = camera.y + 2732;
if (this.x < camMinX || this.x > camMaxX || this.y < camMinY || this.y > camMaxY) {
if (this.parent) {
this.destroy();
}
return;
}
};
}
/* ---- shuriken ring ---- */
var shurikenObjs = [];
// Shuriken now always visible, orbits player, speed = attack speed, range+ increases both orbit radius and asset size
var _maintainShurikens = function maintainShurikens(inst) {
// Always create shurikens if not present or if amount changed
if (shurikenObjs.length !== inst.amount) {
shurikenObjs.forEach(function (s) {
if (s.parent) {
s.destroy();
}
});
shurikenObjs = [];
var n = inst.amount;
var r = WEAPONS.shuriken.range * inst.rngMul;
var sizeMul = inst.rngMul; // Range+ increases both orbit and asset size
for (var i = 0; i < n; i++) {
var s = gameContainer.addChild(new Container());
s.attachAsset('shuriken', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: sizeMul,
scaleY: sizeMul
});
s.angle = i / n * Math.PI * 2;
s.hitEnemies = [];
s.update = function () {
if (skillSelectionActive) {
return;
}
var curR = WEAPONS.shuriken.range * inst.rngMul;
var curSizeMul = inst.rngMul;
// Orbit around player, speed = attack speed (cd)
var baseCd = WEAPONS.shuriken.cd;
var curCd = inst.cd || baseCd;
// Higher attack speed = lower cd = faster orbit
var speed = 0.15 * (baseCd / curCd);
this.angle += speed;
this.x = player.x + Math.cos(this.angle) * curR;
this.y = player.y + Math.sin(this.angle) * curR;
// Update asset size if range+ changes
if (this.children && this.children.length > 0) {
this.children[0].scaleX = curSizeMul;
this.children[0].scaleY = curSizeMul;
if (typeof this._rotationAngle === "undefined") {
this._rotationAngle = 0;
}
this._rotationAngle += 0.4;
this.children[0].rotation = this._rotationAngle;
}
// Damage enemies if intersecting and within range
for (var k = enemies.length - 1; k >= 0; k--) {
var e = enemies[k];
if (this.hitEnemies.indexOf(e) === -1 && this.intersects(e) && isWithinRange(e, curR)) {
damageEnemy(e, WEAPONS.shuriken.dmg * inst.dmgMul);
this.hitEnemies.push(e);
}
// Reset hitEnemies if not intersecting anymore (so shuriken can hit again on next contact)
if (this.hitEnemies.indexOf(e) !== -1 && (!this.intersects(e) || !isWithinRange(e, curR))) {
this.hitEnemies.splice(this.hitEnemies.indexOf(e), 1);
}
}
};
shurikenObjs.push(s);
}
}
// Always update shuriken positions even if already created
for (var i = 0; i < shurikenObjs.length; i++) {
if (typeof shurikenObjs[i].update === "function") {
shurikenObjs[i].update();
}
}
};
/* ---- ana fireWeapon anahtarı ---- */
function fireWeapon(key, inst, meta) {
switch (meta.type) {
case 'melee':
swingSword(inst);
break;
case 'boomerang':
{
// Only fire boomerang if there is an enemy in range
var maxRange = WEAPONS.boomerang.range * inst.rngMul;
var hasEnemy = false;
for (var i = 0; i < enemies.length; i++) {
var dx = enemies[i].x - player.x;
var dy = enemies[i].y - player.y;
if (dx * dx + dy * dy <= maxRange * maxRange) {
hasEnemy = true;
break;
}
}
if (hasEnemy && player.weapons && player.weapons.boomerang) {
fireBoomerang(inst);
}
}
break;
case 'ball':
{
// fireBall already checks for enemy in range and does not play sound if not
fireBall(inst);
}
break;
case 'rocket':
{
// Only fire rocket if there is an enemy in range
var maxRange = WEAPONS.rocket.range * inst.rngMul;
var hasEnemy = false;
for (var i = 0; i < enemies.length; i++) {
var dx = enemies[i].x - player.x;
var dy = enemies[i].y - player.y;
if (dx * dx + dy * dy <= maxRange * maxRange) {
hasEnemy = true;
break;
}
}
if (hasEnemy) {
fireRocket(inst);
}
}
break;
case 'brick':
{
// Only fire brick if there is an enemy in range
var maxRange = WEAPONS.brick.range * inst.rngMul;
var hasEnemy = false;
for (var i = 0; i < enemies.length; i++) {
var dx = enemies[i].x - player.x;
var dy = enemies[i].y - player.y;
if (dx * dx + dy * dy <= maxRange * maxRange) {
hasEnemy = true;
break;
}
}
if (hasEnemy) {
// Brick en yakındaki düşman ekranın sağında mı solunda mı ona göre fırlatılmalı
var nearest = nearestEnemy();
var throwRight = true;
if (nearest) {
if (nearest.x < player.x) {
throwRight = false;
} else {
throwRight = true;
}
} else {
throwRight = true;
}
if (throwRight) {
throwBrick(inst, 75);
if (inst.upgrade > 0) {
throwBrick(inst, 105);
}
if (inst.upgrade > 1) {
throwBrick(inst, 45);
}
if (inst.upgrade > 2) {
throwBrick(inst, 135);
}
} else {
throwBrick(inst, 255);
if (inst.upgrade > 0) {
throwBrick(inst, 225);
}
if (inst.upgrade > 1) {
throwBrick(inst, 285);
}
if (inst.upgrade > 2) {
throwBrick(inst, 195);
}
}
}
}
break;
case 'lightning':
{
// Only fire lightning if there is an enemy in range
var maxRange = WEAPONS.lightning.range * inst.rngMul;
var hasEnemy = false;
for (var i = 0; i < enemies.length; i++) {
var dx = enemies[i].x - player.x;
var dy = enemies[i].y - player.y;
if (dx * dx + dy * dy <= maxRange * maxRange) {
hasEnemy = true;
break;
}
}
if (hasEnemy) {
for (var n = 0; n < inst.amount; n++) {
castLightning(inst);
}
}
}
break;
case 'aura':
ensureAura(inst);
break;
case 'molotov':
{
// Only fire molotov if there is an enemy in range
var maxRange = WEAPONS.molotov.range * inst.rngMul;
var hasEnemy = false;
for (var i = 0; i < enemies.length; i++) {
var dx = enemies[i].x - player.x;
var dy = enemies[i].y - player.y;
if (dx * dx + dy * dy <= maxRange * maxRange) {
hasEnemy = true;
break;
}
}
if (hasEnemy) {
for (var n = 0; n < inst.amount; n++) {
throwMolotov(inst);
}
}
}
break;
case 'shuriken':
_maintainShurikens(inst);
break;
}
}
function showSkillSelection() {
skillSelectionActive = true;
// 1) Yeni bir Overlay (seçim ekranı) oluşturuyoruz
var ov = game.addChild(new Container());
ov.x = 1024;
ov.y = 1366;
// Add title text for skill selection
var titleTxt = new Text2(t("weaponSelect"), {
size: 64,
fill: 0xffffff
});
titleTxt.anchor.set(0.5, 0);
titleTxt.y = -420;
ov.addChild(titleTxt);
var choices = [];
// 2) Eğer oyuncu seviyesi 5'in katı ise (5,10,15,...)
if (player.level % 5 === 0) {
var ownedCount = Object.keys(player.weapons).length;
// 2.a) Hâlâ < 6 silah sahibi ise: yeni silah açma seçenekleri
if (ownedCount < 6) {
// Kilidi açılmamış silahlar havuzunu al
var pool = Object.keys(WEAPONS).filter(function (key) {
return !player.weapons[key];
});
// Havuzdan rastgele 3 silah göster, her biri benzersiz
while (choices.length < 3 && pool.length > 0) {
var rnd = Math.floor(Math.random() * pool.length);
choices.push(pool.splice(rnd, 1)[0]);
}
} else {
// 2.b) Zaten 6 silah sahibi ise: önce silahların upgrade durumlarını kontrol et
var upgradePool = [];
Object.keys(player.weapons).forEach(function (key) {
var inst = player.weapons[key];
if (inst.upgrade < 3) {
// Eğer bu silah hâlâ < 3 aşama güçlendirilmişse,
// “bu silahın bir sonraki upgrade aşaması” havuza eklenir.
upgradePool.push({
w: key
});
}
});
if (upgradePool.length > 0) {
// 2.b.i) En az bir silah yükseltmesi kalmış demektir:
// Rastgele en fazla 3 tane “silah güçlendirme” seçeneği göster, her biri benzersiz
while (choices.length < 3 && upgradePool.length > 0) {
var rnd2 = Math.floor(Math.random() * upgradePool.length);
choices.push({
w: upgradePool[rnd2].w,
type: "upgrade"
});
upgradePool.splice(rnd2, 1);
}
} else {
// 2.b.ii) Tüm 6 silah zaten upgrade === 3 ise:
// Bu aşamadan sonra artık sadece efsun kartı göster.
// Her kart benzersiz olmalı (hem silah hem efsun kombinasyonu)
var ownedKeys2 = Object.keys(player.weapons);
var usedPairs = [];
var attempts = 0;
while (choices.length < 3 && attempts < 20) {
var randomKey2 = ownedKeys2[Math.floor(Math.random() * ownedKeys2.length)];
var enchList = WEAPONS[randomKey2].ench;
var rnd3 = enchList[Math.floor(Math.random() * enchList.length)];
var pairKey = randomKey2 + "|" + rnd3;
if (usedPairs.indexOf(pairKey) === -1) {
choices.push({
w: randomKey2,
e: rnd3
});
usedPairs.push(pairKey);
}
attempts++;
}
}
}
} else {
// 3) Seviye 5'in katı değilse: doğrudan efsun kartları göster
// Her kart benzersiz olmalı (hem silah hem efsun kombinasyonu)
var ownedKeys = Object.keys(player.weapons);
var usedPairs2 = [];
var attempts2 = 0;
while (choices.length < 3 && attempts2 < 20) {
var randomKey = ownedKeys[Math.floor(Math.random() * ownedKeys.length)];
var enchList = WEAPONS[randomKey].ench;
var rnd4 = enchList[Math.floor(Math.random() * enchList.length)];
var pairKey2 = randomKey + "|" + rnd4;
if (usedPairs2.indexOf(pairKey2) === -1) {
choices.push({
w: randomKey,
e: rnd4
});
usedPairs2.push(pairKey2);
}
attempts2++;
}
}
// 4) choices dizisini “3 adet kart” olarak UI’da çizelim
// --- BUFF havuzu ---
// --- Buff/efsun/buff limit tracking ---
if (!player.buffCounts) {
player.buffCounts = {};
}
if (!player.efsunCounts) {
player.efsunCounts = {};
}
// --- BUFF havuzu ---
var buffPool = [{
key: "damageReduction",
label: "Damage Reduction+",
desc: "Take 10% less damage per level"
}, {
key: "moveSpeed",
label: "Movement Speed Increase+",
desc: "Move 20% faster per level"
}, {
key: "hpRegen",
label: "HP Regeneration+",
desc: "Regenerate 1% max HP every 5s per level"
}, {
key: "damageReflection",
label: "Damage Reflection+",
desc: "Reflect 5% damage per level"
}, {
key: "gritWound",
label: "Grit Wound+",
desc: "Reduce enemy regen by 10% per level"
}, {
key: "enemySlow",
label: "Enemy Slow+",
desc: "Slow enemies by 10% per level"
}, {
key: "enemyStun",
label: "Enemy Stun+",
desc: "Stun enemies for 0.1s per level"
}, {
key: "bleeding",
label: "Bleeding+",
desc: "Bleed: 4s, 0.5s tick, 5% max HP per tick per level"
}, {
key: "critChance",
label: "Critical Hit Chance+",
desc: "Increase critical hit chance by 15% per level"
}, {
key: "maxHpPlus",
label: "Maximum HP+",
desc: "Increase max HP by 25 per level"
}, {
key: "magnetPlus",
label: "Magnet+",
desc: "Nearby EXP orbs (150 radius, +150 per level) are quickly pulled to you."
}];
// Remove buffs that have been taken 5 times
buffPool = buffPool.filter(function (buff) {
var k = buff.key;
return (player.buffCounts[k] || 0) < 5;
});
// Rastgele bir buff seç (her zaman 4. seçenek)
var buffIdx = Math.floor(Math.random() * buffPool.length);
var buffChoice = buffPool[buffIdx];
choices = choices.slice(0, 3); // Sadece ilk 3'ü tut
choices.push({
type: "buff",
buff: buffChoice
}); // 4. seçenek buff
// Remove efsun options that have been taken 5 times
choices = choices.filter(function (c) {
if (_typeof(c) === "object" && c.e) {
var key = c.w + "|" + c.e;
return (player.efsunCounts[key] || 0) < 5;
}
return true;
});
choices.forEach(function (c, idx) {
var card = ov.addChild(new Container());
// 4. kartı mavi yap
if (idx === 3) {
card.attachAsset('skillCard', {
anchorX: 0.5,
anchorY: 0.5,
tint: 0x3399ff // mavi renk
});
} else {
card.attachAsset('skillCard', {
anchorX: 0.5,
anchorY: 0.5
});
}
// Kartlar arası mesafeyi azalt (ör: 440px), dikey mesafeyi azalt (ör: 0)
card.x = (idx - 1.5) * 440;
card.y = 0;
var label,
fn,
desc = "";
if (typeof c === "string") {
// c, yeni kilidi açılacak silahın anahtarı (ör. "boomerang")
var key = c;
var own = !!player.weapons[key];
if (!own) {
// Hâlâ envanterde yoksa “Unlock <silah adı>”
label = t("unlock") + " " + key.charAt(0).toUpperCase() + key.slice(1);
fn = function fn() {
addWeapon(key);
};
} else if (player.weapons[key].upgrade < 3) {
// Silah zaten varsa ve upgrade < 3 ise:
label = WEAPONS[key].up && WEAPONS[key].up[player.weapons[key].upgrade] ? WEAPONS[key].up[player.weapons[key].upgrade] : t("upgrade");
fn = function fn() {
player.weapons[key].upgrade++;
player.weapons[key].amount++;
};
} else {
// Eğer upgrade === 3’e ulaşmışsa:
label = t("noOption");
fn = function fn() {};
}
} else if (c.type === "upgrade") {
// c = { w: "sword", type:"upgrade" }
var wkey = c.w;
label = WEAPONS[wkey].up && WEAPONS[wkey].up[player.weapons[wkey].upgrade] ? WEAPONS[wkey].up[player.weapons[wkey].upgrade] : t("upgrade");
fn = function fn() {
player.weapons[wkey].upgrade++;
player.weapons[wkey].amount++;
};
} else if (c.type === "buff") {
label = t("buffs")[c.buff.key] || c.buff.label;
desc = t("buffDescs")[c.buff.key] || c.buff.desc;
fn = function fn() {
// Buff mantığı
var k = c.buff.key;
if (!player.buffCounts[k]) {
player.buffCounts[k] = 0;
}
if (player.buffCounts[k] >= 5) {
return;
} // Defensive: do not allow more than 5
switch (k) {
case "damageReduction":
player.buffs.damageReduction += 10;
break;
case "moveSpeed":
player.buffs.moveSpeed += 20;
break;
case "hpRegen":
player.buffs.hpRegen += 1;
break;
case "damageReflection":
player.buffs.damageReflection += 5;
break;
case "gritWound":
player.buffs.gritWound += 10;
break;
case "enemySlow":
player.buffs.enemySlow += 10;
break;
case "enemyStun":
player.buffs.enemyStun += 0.1;
break;
case "bleeding":
player.buffs.bleeding += 1;
break;
case "critChance":
player.buffs.critChance += 15;
player.critChance = 0.25 + player.buffs.critChance / 100;
if (player.critChance > 1) {
player.critChance = 1;
}
break;
case "maxHpPlus":
player.maxHp += 25;
break;
case "magnetPlus":
// Magnet+ buff: increase magnet+ level, only affects EXP orb attraction
if (!player.buffs.magnetPlus) {
player.buffs.magnetPlus = 1;
} else {
player.buffs.magnetPlus++;
}
// No longer affects spawn rates of pickups
break;
}
player.buffCounts[k]++;
};
} else {
// c = { w: "<silahAnahtarı>", e: "<SilahName> Damage+" veya "<SilahName> Attack Speed+" vb. }
var w2 = c.w;
var efsun = c.e;
label = efsun;
// Efsun açıklaması
if (efsun.endsWith(" Damage+")) {
desc = t("desc_damage");
} else if (efsun.endsWith(" Attack Speed+")) {
desc = t("desc_atkspd");
} else if (efsun.endsWith(" Range+")) {
desc = t("desc_range");
} else {
desc = "";
}
fn = function fn() {
var key = w2 + "|" + efsun;
if (!player.efsunCounts[key]) {
player.efsunCounts[key] = 0;
}
if (player.efsunCounts[key] >= 5) {
return;
} // Defensive: do not allow more than 5
var inst = player.weapons[w2];
// Efsun metni "<SilahName> Damage+" ile bitiyorsa hasar çarpanını %20 artır (artık sabit):
if (efsun.endsWith(" Damage+")) {
if (!inst._baseDmgMul) {
inst._baseDmgMul = 1;
}
if (!inst.dmgMul) {
inst.dmgMul = 1;
}
inst.dmgMul = inst._baseDmgMul + 0.2 * (player.efsunCounts[key] + 1);
}
// Efsun metni "<SilahName> Attack Speed+" ile bitiyorsa cd süresini %20 azalt (artık sabit):
if (efsun.endsWith(" Attack Speed+")) {
var meta = WEAPONS[w2];
if (meta && typeof meta.cd === "number") {
if (!inst._baseCd) {
inst._baseCd = meta.cd;
}
if (!inst._atkSpdMul) {
inst._atkSpdMul = 1;
}
inst._atkSpdMul = 1 - 0.2 * (player.efsunCounts[key] + 1);
if (inst._atkSpdMul < 0.1) {
inst._atkSpdMul = 0.1;
} // min %90 hızlanma sınırı
inst.cd = inst._baseCd * inst._atkSpdMul;
} else {
inst.cd = inst.cd || meta.cd || 1;
inst.cd = inst.cd * (1 - 0.2 * (player.efsunCounts[key] + 1));
if (inst.cd < 1) {
inst.cd = 1;
}
}
// cdCnt’yi de yeni cd’ye çekiyoruz
if (typeof inst.cdCnt === "number") {
if (inst.cdCnt > inst.cd) {
inst.cdCnt = inst.cd;
}
} else {
inst.cdCnt = Math.floor(inst.cd);
}
}
// Efsun metni "<SilahName> Range+" ile bitiyorsa hem menzil hem boyut çarpanını %20 artır (artık sabit):
if (efsun.endsWith(" Range+")) {
if (!inst._baseRngMul) {
inst._baseRngMul = 1;
}
if (!inst._baseSizeMul) {
inst._baseSizeMul = 1;
}
inst.rngMul = inst._baseRngMul + 0.2 * (player.efsunCounts[key] + 1);
inst.sizeMul = inst._baseSizeMul + 0.2 * (player.efsunCounts[key] + 1);
}
// "Size+" efsunu artık yok, hiçbir şey yapma
player.efsunCounts[key]++;
};
}
// --- Weapon and efsun name localization ---
function localizeWeaponName(key) {
// Turkish weapon names
var trNames = {
sword: "Kılıç",
boomerang: "Bumerang",
ball: "Top",
rocket: "Roket",
brick: "Tuğla",
lightning: "Yıldırım",
aura: "Aura",
molotov: "Molotof",
shuriken: "Ninja Yıldızı"
};
if (currentLanguage === "tr" && trNames[key]) {
return trNames[key];
}
// Default: Capitalize first letter
return key.charAt(0).toUpperCase() + key.slice(1);
}
function localizeEfsunName(efsun, wkey) {
// efsun: e.g. "Boomerang Damage+", "Boomerang Range+", "Boomerang Attack Speed+"
// wkey: e.g. "boomerang"
var trNames = {
sword: "Kılıç",
boomerang: "Bumerang",
ball: "Top",
rocket: "Roket",
brick: "Tuğla",
lightning: "Yıldırım",
aura: "Aura",
molotov: "Molotof",
shuriken: "Ninja Yıldızı"
};
if (currentLanguage === "tr") {
var base = wkey && trNames[wkey] ? trNames[wkey] : wkey.charAt(0).toUpperCase() + wkey.slice(1);
if (efsun.endsWith(" Damage+")) {
return base + " Hasar+";
}
if (efsun.endsWith(" Range+")) {
return base + " Menzil+";
}
if (efsun.endsWith(" Attack Speed+")) {
return base + " Saldırı Hızı+";
}
// fallback
return efsun;
}
return efsun;
}
var txtLabel = label;
if (typeof c === "string") {
// Weapon selection overlay: localize weapon name
txtLabel = localizeWeaponName(c);
} else if (c && c.type === "upgrade") {
// Upgrade overlay: localize weapon name in upgrade label
var wkey = c.w;
if (WEAPONS[wkey] && WEAPONS[wkey].up && player.weapons[wkey]) {
var upLabel = WEAPONS[wkey].up[player.weapons[wkey].upgrade];
if (currentLanguage === "tr") {
// Try to localize upgrade label for Turkish
// For +1, +2, +3 upgrades, just append to Turkish weapon name
if (upLabel && upLabel.match(/^\w+ \+(\d)$/)) {
var base = localizeWeaponName(wkey);
var num = upLabel.match(/\+(\d)/)[1];
txtLabel = base + " +" + num;
} else {
txtLabel = upLabel;
}
} else {
txtLabel = upLabel;
}
}
} else if (c && !c.type && c.e && c.w) {
// Efsun card: localize efsun name
txtLabel = localizeEfsunName(c.e, c.w);
}
var txt = new Text2(txtLabel, {
size: 40,
fill: 0xffffff
});
txt.anchor.set(0.5);
card.addChild(txt);
if (desc) {
// Card width is 400, so max text width should be less (e.g. 340)
var descTxt = new Text2(desc, {
size: 28,
fill: 0xcccccc,
wordWrap: true,
wordWrapWidth: 340,
align: "center"
});
descTxt.anchor.set(0.5, 0);
descTxt.y = 60;
card.addChild(descTxt);
}
card.down = function () {
fn();
ov.destroy();
skillSelectionActive = false;
levelText.setText('Level ' + player.level);
};
});
}
// Input handling
// Reference point A:
var refA = {
x: 222,
y: 222
};
// Draw a debug marker for A (optional, comment out if not needed)
// var aMarker = LK.getAsset('ball', { anchorX: 0.5, anchorY: 0.5, x: refA.x, y: refA.y, scaleX: 0.5, scaleY: 0.5, tint: 0x00ff00 });
// game.addChild(aMarker);
game.down = function (x, y, obj) {
// If menu or language or death overlay is active, only allow their buttons
if (gameState === GAME_STATE.MAIN_MENU && mainMenuContainer) {
// Let mainMenuContainer children handle .down
var local = mainMenuContainer.toLocal({
x: x,
y: y
});
for (var i = 0; i < mainMenuContainer.children.length; i++) {
var ch = mainMenuContainer.children[i];
if (ch.down && ch.getBounds && ch.getBounds().contains(local.x, local.y)) {
ch.down();
return;
}
}
return;
}
if (gameState === GAME_STATE.MAIN_MENU && languageMenuContainer) {
var local = languageMenuContainer.toLocal({
x: x,
y: y
});
for (var i = 0; i < languageMenuContainer.children.length; i++) {
var ch = languageMenuContainer.children[i];
if (ch.down && ch.getBounds && ch.getBounds().contains(local.x, local.y)) {
ch.down();
return;
}
}
return;
}
if (gameState === GAME_STATE.DEATH && deathScreenContainer) {
var local = deathScreenContainer.toLocal({
x: x,
y: y
});
for (var i = 0; i < deathScreenContainer.children.length; i++) {
var ch = deathScreenContainer.children[i];
if (ch.down && ch.getBounds && ch.getBounds().contains(local.x, local.y)) {
ch.down();
return;
}
}
return;
}
// If not playing, block all input
if (gameState !== GAME_STATE.PLAYING) {
return;
}
if (skillSelectionActive) {
return;
}
// 1) Dokunulan noktayı “LK.gui.bottom” GUI koordinatına çeviriyoruz.
// Böylece refA ile aynı düzlemi paylaşacak.
var guiPos = LK.gui.bottom.toLocal({
x: x,
y: y
});
// 2) refA’yı da “LK.gui.bottom” koordinatında sabit olarak düşünelim:
// (refA zaten {x:222,y:222} ise, bu değer LK.gui.bottom içinde sabit bir nokta demektir.)
// Eğer refA aslında canvas’ın başka bir yerindeyse, uygun container’a göre
// refA’yı da dönüştürmeniz gerekir (örneğin `game.toLocal` ile).
// Burada basitçe ikisini de GUI’e taşıdık:
var dx = guiPos.x - refA.x;
var dy = guiPos.y - refA.y;
var dist = Math.sqrt(dx * dx + dy * dy);
// 3) dx, dy vektörünü normalize edip joystick’e geçiriyoruz:
var dirX = 0,
dirY = 0;
if (dist > 0) {
dirX = dx / dist;
dirY = dy / dist;
}
// 4) Joystick çubuğunu (knob) sabit noktaya göre hareket ettir:
// Maksimum 60px mesafeye kadar sınır koyuyoruz (ör: joystick yarıçapı).
joystick.active = true;
var maxDist = 60;
var knobDx = dx,
knobDy = dy;
if (dist > maxDist) {
knobDx = dx / dist * maxDist;
knobDy = dy / dist * maxDist;
}
joystick.setKnobPosition(knobDx, knobDy);
// 5) Hareket yönünü saklamak için joystick.dirX / dirY’yi güncelle:
joystick.dirX = dirX;
joystick.dirY = dirY;
};
game.move = function (x, y, obj) {
if (gameState !== GAME_STATE.PLAYING) {
return;
}
if (!joystick.active || skillSelectionActive) {
return;
}
// Dokunulan noktayı yine “LK.gui.bottom” koordinatına çevir:
var guiPos = LK.gui.bottom.toLocal({
x: x,
y: y
});
// joystick.x / joystick.y; bunlar da “LK.gui.bottom” koordinatında joystick’in merkezidir.
var dx = guiPos.x - joystick.x;
var dy = guiPos.y - joystick.y;
joystick.setKnobPosition(dx, dy);
// Hareket yönünü güncelle:
var dist = Math.sqrt(dx * dx + dy * dy);
if (dist > 0) {
var dirX = dx / dist;
var dirY = dy / dist;
joystick.dirX = dirX;
joystick.dirY = dirY;
} else {
joystick.dirX = 0;
joystick.dirY = 0;
}
};
game.up = function (x, y, obj) {
if (gameState !== GAME_STATE.PLAYING) {
return;
}
joystick.reset();
};
// --- LOADING SCREEN & ASSET PRELOAD ---
// Show a loading screen before main menu, preload all assets, then show main menu
var loadingContainer = null;
function showLoadingScreen() {
if (loadingContainer) {
loadingContainer.destroy();
}
loadingContainer = game.addChild(new Container());
// Simple black background
var bg = loadingContainer.attachAsset('mainMenuBackground', {
anchorX: 0.5,
anchorY: 0.5,
x: 1024,
y: 1366
});
// Loading text
var loadingTxt = new Text2("Loading...", {
size: 80,
fill: 0xffffff
});
loadingTxt.anchor.set(0.5, 0.5);
loadingTxt.x = 1024;
loadingTxt.y = 1366;
loadingContainer.addChild(loadingTxt);
// Optional: add a simple spinner effect
var spinner = new Container();
var spinnerAsset = spinner.attachAsset('chickenLeg', {
anchorX: 0.5,
anchorY: 0.5,
width: 120,
height: 120
});
spinner.x = 1024;
spinner.y = 1566;
loadingContainer.addChild(spinner);
spinner.update = function () {
if (spinnerAsset) {
spinnerAsset.rotation += 0.2;
}
};
loadingContainer.update = function () {
if (spinner && spinner.update) {
spinner.update();
}
};
}
// Preload all assets (images, sounds, music) before showing main menu
function preloadAllAssets(onComplete) {
// List of all asset keys to preload
var assetKeys = [
// Images
'mainMenuBackground', 'startButton', 'languagesButton', 'langButton', 'returnMainMenu', 'deathBackground', 'player', 'hpBarRect', 'hpBarRectFg', 'enemyHpBarRect', 'enemyHpBarRectFg', 'seamlessBackGround', 'zombie', 'normalZombie', 'normalZombieAttack', 'rangedZombie', 'rangedZombieSaliva', 'fastZombie', 'fastZombieAttack', 'tankZombie', 'tankZombieAttack', 'bossZombie', 'bossZombieAttack1', 'bossZombieAttack2', 'auraField', 'ball', 'bomb', 'boomerang', 'brick', 'lightning', 'magnet', 'molotov', 'molotovFlame', 'rocket', 'rocketBoom', 'shuriken', 'skillCard', 'chickenLeg',
// Sounds
'damageTaken', 'death', 'hit', 'levelup', 'pickup', 'rocketBoom',
// Music
'backGroundMusic', 'deathScreenMusic', 'mainMenuMusic'];
var loadedCount = 0;
var totalCount = assetKeys.length;
function assetLoaded() {
loadedCount++;
// Optionally update loading text
if (loadingContainer && loadingContainer.children && loadingContainer.children.length > 1) {
var txt = loadingContainer.children[1];
if (txt && txt.setText) {
txt.setText("Loading... " + loadedCount + "/" + totalCount);
}
}
if (loadedCount >= totalCount) {
if (typeof onComplete === "function") {
onComplete();
}
}
}
// Defensive: if LK.getAsset returns synchronously, call assetLoaded immediately
for (var i = 0; i < assetKeys.length; i++) {
(function (key) {
var asset = LK.getAsset(key, {
anchorX: 0.5,
anchorY: 0.5
});
// If asset is already loaded, call assetLoaded immediately
if (asset && (asset.texture || asset.width)) {
assetLoaded();
} else if (asset && typeof asset.once === "function") {
// Listen for 'loaded' event if available
asset.once('loaded', assetLoaded);
} else {
// Fallback: call assetLoaded after short delay
LK.setTimeout(assetLoaded, 10);
}
})(assetKeys[i]);
}
}
// At game start, show loading screen, preload all assets, then show main menu
showLoadingScreen();
preloadAllAssets(function () {
if (loadingContainer) {
loadingContainer.destroy();
loadingContainer = null;
}
showMainMenu();
});
// Game update
game.update = function () {
// Block all game logic, UI, and sounds if not in PLAYING state
if (gameState !== GAME_STATE.PLAYING) {
setGameUIVisible(false);
// Defensive: stop all music and sounds if not already stopped
// Play correct music for each state
if (gameState === GAME_STATE.MAIN_MENU) {
// Music is handled in showMainMenu/red overlay logic, do not play here to avoid double playback
} else if (gameState === GAME_STATE.DEATH) {
if (currentMusic !== "deathScreenMusic") {
LK.stopMusic();
window._mainMenuMusicPlaying = false;
LK.playMusic('deathScreenMusic', {
loop: true
});
currentMusic = "deathScreenMusic";
}
} else {
LK.stopMusic();
window._mainMenuMusicPlaying = false;
currentMusic = null;
}
if (typeof LK.stopAllSounds === "function") {
LK.stopAllSounds();
}
window._mainMenuMusicPlaying = false; // Defensive: always reset flag when stopping music
return;
}
setGameUIVisible(true);
// Ensure correct music is playing in PLAYING state
if (currentMusic !== "backGroundMusic") {
LK.stopMusic();
LK.playMusic('backGroundMusic', {
loop: true
});
currentMusic = "backGroundMusic";
}
if (skillSelectionActive) {
return;
}
gameTime++;
// Update timer → geçen süreyi göster
var elapsedSeconds = Math.floor(gameTime / 60);
var minutes = Math.floor(elapsedSeconds / 60);
var seconds = elapsedSeconds % 60;
timerText.setText((minutes < 10 ? '0' + minutes : minutes) + ':' + (seconds < 10 ? '0' + seconds : seconds));
// Update UI
hpText.setText(t("hpPrefix") + ": " + player.hp.toFixed(1) + '/' + player.maxHp.toFixed(1));
// Show exp orbs and required exp for next level
levelText.setText(t("levelPrefix") + " " + player.level + " " + t("expPrefix") + ": " + player.exp + "/" + player.level);
// Show current wave
waveText.setText(t("wavePrefix") + " " + waveNumber);
// Player movement
if (joystick.active) {
player.x += joystick.dirX * player.moveSpeed;
player.y += joystick.dirY * player.moveSpeed;
}
/* Fire weapons / aura etc. */
player.update();
// --- Update player health bar ---
if (player.hpBar && player.hpBarFg) {
var hpRatio = Math.max(0, Math.min(1, player.hp / player.maxHp));
player.hpBarFg.width = 212 * hpRatio;
// Move foreground bar's x so it depletes from right to left
player.hpBarFg.x = 0;
// Player HP bar color: green >80%, yellow 50-80%, orange 20-50%, red+blink <20%
if (hpRatio > 0.8) {
player.hpBarFg.tint = 0x00ff44; // green
player.hpBarFg.alpha = 1;
} else if (hpRatio > 0.5) {
player.hpBarFg.tint = 0xffe066; // yellow
player.hpBarFg.alpha = 1;
} else if (hpRatio > 0.2) {
player.hpBarFg.tint = 0xff9900; // orange
player.hpBarFg.alpha = 1;
} else {
// <20%: red and blinking
// Blink: alternate between red and transparent every 6 frames (~10Hz)
player.hpBarFg.tint = 0xff0000; // pure red for critical
if (gameTime % 12 < 6) {
player.hpBarFg.alpha = 1;
} else {
player.hpBarFg.alpha = 0.3;
}
}
}
// --- Update enemy health bars ---
for (var i = 0; i < enemies.length; i++) {
var enemy = enemies[i];
if (enemy.hpBar && enemy.hpBarFg) {
var ehpRatio = Math.max(0, Math.min(1, enemy.hp / enemy.maxHp));
enemy.hpBarFg.width = 152 * ehpRatio;
enemy.hpBarFg.x = 0;
// Enemy HP bar color: always red, regardless of HP
enemy.hpBarFg.tint = 0xff4444;
enemy.hpBarFg.alpha = 1;
}
}
// Camera follow player
camera.x = player.x - 1024;
camera.y = player.y - 1366;
gameContainer.x = -camera.x;
gameContainer.y = -camera.y;
// Update background tiles to always cover the visible area
if (backgroundTiles && backgroundTiles.length) {
var startX = Math.floor(camera.x / bgTileWidth) * bgTileWidth;
var startY = Math.floor(camera.y / bgTileHeight) * bgTileHeight;
var idx = 0;
for (var i = 0; i < bgTilesX; i++) {
for (var j = 0; j < bgTilesY; j++) {
var tile = backgroundTiles[idx++];
tile.x = startX + i * bgTileWidth;
tile.y = startY + j * bgTileHeight;
}
}
}
// Progress to next wave only when all enemies are cleared
if (typeof nextWaveTimeout === "undefined") {
nextWaveTimeout = null;
}
if (enemies.length === 0 && !nextWaveTimeout) {
nextWaveTimeout = LK.setTimeout(function () {
waveNumber++;
spawnWave();
nextWaveTimeout = null;
}, 3000);
}
if (enemies.length > 0 && nextWaveTimeout) {
LK.clearTimeout(nextWaveTimeout);
nextWaveTimeout = null;
}
// Update enemies (her düşman kendi update() metodunda saldırı efektini ve hasarı hallediyor)
for (var i = 0; i < enemies.length; i++) {
enemies[i].update();
}
// Update exp orbs
for (var i = expOrbs.length - 1; i >= 0; i--) {
var orb = expOrbs[i];
var dx = orb.x - player.x;
var dy = orb.y - player.y;
var dist = Math.sqrt(dx * dx + dy * dy);
// Magnet+ effect: pull orbs within magnetPlus radius quickly to player (no effect on pickup spawn rates)
if (player.buffs && player.buffs.magnetPlus && player.buffs.magnetPlus > 0) {
var levels = player.buffs.magnetPlus;
var magnetRadius = 300 + (levels - 1) * 150;
if (dist <= magnetRadius) {
// Pull orbs much faster for Magnet+ (e.g. 12)
if (orb.magnetSpeed < 12) {
orb.magnetSpeed = 12;
}
}
}
// pickupRange artık aura ile büyümeyecek, sadece manyetik çekim için kullanılacak
if (dist < player.pickupRange) {
orb.magnetSpeed = 10;
}
// Sadece karakterin merkezinde 30 yarıçaplı daireye girerse alınabilir
if (dist <= 30) {
player.gainExp(orb.value);
orb.destroy();
expOrbs.splice(i, 1);
LK.getSound('pickup').play();
}
}
// Spawn pickups randomly
if (gameTime - lastPickupSpawn > 1200) {
// Increased spawn frequency (was 1800 frames, now 1200)
// Calculate spawn chance and weights (increased overall rates)
var baseChance = 0.25; // was 0.1, now 0.25 for more frequent spawns
var bombMagnetWeight = 0.45; // was 0.4, now 0.45
var magnetWeight = 0.45; // was 0.4, now 0.45
var chickenWeight = 0.25; // was 0.2, now 0.25
// No longer affected by Magnet+ buff
var totalWeight = bombMagnetWeight + magnetWeight + chickenWeight;
var bombMagnetProb = bombMagnetWeight / totalWeight;
var magnetProb = magnetWeight / totalWeight;
var chickenProb = chickenWeight / totalWeight;
if (Math.random() < baseChance) {
lastPickupSpawn = gameTime;
var pickup;
var angle = Math.random() * Math.PI * 2;
var distance = 300 + Math.random() * 500;
var pickupX = player.x + Math.cos(angle) * distance;
var pickupY = player.y + Math.sin(angle) * distance;
var randVal = Math.random();
if (randVal < bombMagnetProb) {
pickup = new Bomb();
} else if (randVal < bombMagnetProb + magnetProb) {
pickup = new Magnet();
} else {
pickup = new ChickenLeg();
}
pickup.x = pickupX;
pickup.y = pickupY;
pickups.push(pickup);
gameContainer.addChild(pickup);
}
}
// Update pickups
for (var i = pickups.length - 1; i >= 0; i--) {
var pickup = pickups[i];
if (!pickup.active) {
pickups.splice(i, 1);
}
}
};
// Initial spawn
// spawnWave();//{jb} // Delay first wave spawn until after weapon selection
// Music is now handled in game.update based on gameState
// Tank Zombie asset (bigger, armored)
// Fast Zombie asset (slimmer, agile)
// Poison Zombie asset (greenish, toxic)
// Boss Zombie asset (huge, menacing)
Survivor.io style 2D sword swing effect made by HABBY PTE. LTD.. In-Game asset. 2d. High contrast. No shadows. It should only have a slash effect, no swords. The slash effect should also be in the shape of a half moon.
Survivor.io style 2D round soccer ball made by HABBY PTE. LTD.. In-Game asset. 2d. High contrast. No shadows
A 2D Survivor.io style lightning strike from a cloud in the sky to the ground, made by HABBY PTE. LTD.. In-Game asset. 2d. High contrast. No shadows
A red and blue Survivor.io style 2D U-shaped (with N and S) magnet made by HABBY PTE. LTD.. In-Game asset. 2d. High contrast. No shadows
Survivor.io style 2D shuriken made by HABBY PTE. LTD.. In-Game asset. 2d. High contrast. No shadows
Survivor.io style 2D brick made by HABBY PTE. LTD.. In-Game asset. 2d. High contrast. No shadows
Survivor.io style 2D missile rocket made by HABBY PTE. LTD.
A 2D green radiating circular aura in the Survivor.io style made by HABBY PTE. LTD.. In-Game asset. 2d. High contrast. No shadows
A 2D bomb in the style of Survivor.io, made by HABBY PTE. LTD.. In-Game asset. 2d. High contrast. No shadows
A 2D circular burning effect in Survivor.io style made by HABBY PTE. LTD. (not only the surroundings but also the inside burns) In-Game asset. 2d. High contrast. No shadows
A 2D molotov in the Survivor.io style made by HABBY PTE. LTD.. In-Game asset. 2d. High contrast. No shadows
Survivor.io style 2D greenish exp sphere made by HABBY PTE. LTD. No exp written on it. In-Game asset. 2d. High contrast. No shadows
Survivor.io style 2D half-moon orange boomerang made by HABBY PTE. LTD. In-Game asset. 2d. High contrast. No shadows
Survivor.io style 2D 1 chicken leg.. In-Game asset. 2d. High contrast. No shadows
2D survivor.io game style atomic boom effect front view. No text written on it.
2D. Ranged zombie. attacks with poisonous saliva. In-Game asset. 2d. High contrast. No shadows
2D. Child (small) zombie. He has a small saw in his hand.. In-Game asset. 2d. High contrast. No shadows
2D. Fat zombie. His hands are too big.. In-Game asset. 2d. High contrast. No shadows
Poisonous green circular saliva. 2D. Top View.. In-Game asset. 2d. High contrast. No shadows
Small green claw slash effect. 2D. Top View.. In-Game asset. 2d. High contrast. No shadows
Giant boss angry reddish zombie. 2D.. In-Game asset. 2d. High contrast. No shadows
Small saw slash effect. 2D. Top View.. In-Game asset. 2d. High contrast. No shadows
Big red fist slash effect. 2D. Top View.. In-Game asset. 2d. High contrast. No shadows
Kanlı kemik 2D. Top View.. In-Game asset. 2d. High contrast. No shadows
2D character that looks like a prophet and holds a holy book in his hand.. In-Game asset. 2d. High contrast. No shadows
A background image (wallpaper) representing an old prophet-like man with white hair and beard, wearing a priest's robe (hooded) and holding a holy book (christianity, cross) in his hand, fighting against zombies.. In-Game asset. 2d. High contrast. No shadows
Zombie flesh and bone themed 2D cardboard hollow (without text) horizontal rectangular button.. In-Game asset. 2d. High contrast. No shadows
2D. Healer zombie. Like a female zombie in a healer costume.. In-Game asset. 2d. High contrast. No shadows
2D. Brain illustrated healing potion.. In-Game asset. 2d. High contrast. No shadows
A healing blood pool with circular zombie brain and bone particles. Green + (healing) symbols on top. 2D.. In-Game asset. 2d. High contrast. No shadows
2D. Survivor.io game style skill card. No text written on it. No symbols on it. Just the blank card. Green.. In-Game asset. 2d. High contrast. No shadows
2D. Cartoon. The rise of the zombie ghost spirit from the ground.. In-Game asset. 2d. High contrast. No shadows
levelup
Sound effect
hit
Sound effect
rocketBoom
Sound effect
pickup
Sound effect
backGroundMusic
Music
death
Sound effect
damageTaken
Sound effect
mainMenuMusic
Music
deathScreenMusic
Music
swordSoundEffect
Sound effect
bumerangSoundEffect
Sound effect
brickSoundEffect
Sound effect
lightningSoundEffect
Sound effect
ballSoundEffect
Sound effect
rocketSoundEffect
Sound effect
auraSoundEffect
Sound effect
molotovSoundEffect
Sound effect
molotovBoom
Sound effect
introSpeech
Sound effect
bombBoomSound
Sound effect