User prompt
1. enemyData → mermi-başı isabet & hız & ölçek diff Kopyala Düzenle var enemyData = [ - { name:"Greenhorn", minReact:2.8, maxReact:3.2, accuracy:0.70 }, - { name:"Billy the Kid", minReact:2.1, maxReact:2.4, accuracy:0.75 }, - { name:"Doc Holliday", minReact:1.5, maxReact:1.8, accuracy:0.80 }, - { name:"Calamity Jane", minReact:1.0, maxReact:1.3, accuracy:0.85 }, - { name:"Wild Bill", minReact:0.7, maxReact:1.0, accuracy:0.90 }, - { name:"The Undertaker",minReact:0.5, maxReact:0.7, accuracy:0.95 } + { // ROUND 1 + name:"Greenhorn", + minReact:3.0, maxReact:3.4, // en yavaş + bulletAcc:[.20,.40,.60,.80,.90,1.00] + }, + { // ROUND 2 + name:"Billy the Kid", + minReact:2.4, maxReact:2.7, + bulletAcc:[.50,.60,.70,.90,.95,1.00] + }, + { // ROUND 3 + name:"Doc Holliday", + minReact:1.6, maxReact:1.9, + bulletAcc:[.70,.80,.80,.90,.90,1.00] + }, + { // ROUND 4 + name:"Calamity Jane", + minReact:1.0, maxReact:1.3, + bulletAcc:[.75,.80,.85,.90,.95,1.00] + }, + { // ROUND 5 – Boss, %50 daha hızlı tepki + 1.5× büyük + name:"The Undertaker", + minReact:0.6, maxReact:0.8, + bulletAcc:[.90,.90,.90,.95,.95,1.00], + scale:1.5 + } ]; (İstersen min/max değerlerini ince ayarla — mantık hazır.) 2. Müzik seçimi resetDuel() içindeki müzik satırını değiştir: diff Kopyala Düzenle - var roundMusic = roundNum === 1 ? 'duelmusic' : roundNum === 2 ? 'round2music' : 'round3music'; + const roundMusic = + roundNum === 1 ? 'duelmusic' + : roundNum === 2 ? 'round2music' + : roundNum === 3 ? 'round3music' + : roundNum === 4 ? 'round4music' + : 'round5music'; 3. Boss’u büyütmek resetDuel() sonunda (enemy pozisyonunu ayarladıktan hemen sonra) ekle: js Kopyala Düzenle enemy.scaleX = enemy.scaleY = currentEnemy.scale || 1; (Boy büyükken ayakları yere basmaya devam eder — çünkü pozisyonu y zaten ayrı tutuluyor.) 4. Mermi başına isabet hesabı enemyFire() fonksiyonundaki isabet kodunu güncelle: diff Kopyala Düzenle - var acc = 0.8; // default accuracy fallback - if (currentEnemy && typeof currentEnemy.accuracy === "number") { - acc = currentEnemy.accuracy; - } - if (enemyShotsFiredCnt === 6) { - acc = 1.0; // 6’ncı mermi kesin vurur - } + // seçili raundun mermi-bazlı isabet tablosu + let acc = .8; + if (currentEnemy && Array.isArray(currentEnemy.bulletAcc)) { + const idx = Math.min(enemyShotsFiredCnt-1, currentEnemy.bulletAcc.length-1); + acc = currentEnemy.bulletAcc[idx]; + } Not: eski “6’ncı mermi kesin vurur” satırlarını artık sil – tablo zaten 1.00 ile bitiyor.
User prompt
1. Raund-başına müzik seçimi resetDuel içinde startZoomIn çağrısını tek satırla değiştiriyoruz: diff Kopyala Düzenle - startZoomIn(roundNum === 1 ? 'duelmusic' : 'round2music'); + const roundMusic = (roundNum === 1) + ? 'duelmusic' + : (roundNum === 2 ? 'round2music' : 'round3music'); // 3+ raund + startZoomIn(roundMusic); İstersen sonraki raundlara yeni parça eklemek için aynı mantığa bir case 4: vb. ekleyebilirsin. 2. Daha yavaş (kolay) tepki süreleri enemyData dizisini güncelledim. Temel fikir: en kolay düşman ≈ 3 sn, her raundda ~0.5-0.6 sn kısalıyor. diff Kopyala Düzenle var enemyData = [ - { name:"Greenhorn", minReact:1.5, maxReact:1.5, accuracy:0.75 }, - { name:"Billy the Kid", minReact:0.8, maxReact:0.8, accuracy:0.8 }, - { name:"Doc Holliday", minReact:0.6, maxReact:0.6, accuracy:0.85 }, - { name:"Calamity Jane", minReact:0.4, maxReact:0.4, accuracy:0.9 }, - { name:"Wild Bill", minReact:0.2, maxReact:0.2, accuracy:0.93 }, - { name:"The Undertaker",minReact:0.1, maxReact:0.1, accuracy:0.97 } + { name:"Greenhorn", minReact:2.8, maxReact:3.2, accuracy:0.70 }, + { name:"Billy the Kid", minReact:2.1, maxReact:2.4, accuracy:0.75 }, + { name:"Doc Holliday", minReact:1.5, maxReact:1.8, accuracy:0.80 }, + { name:"Calamity Jane", minReact:1.0, maxReact:1.3, accuracy:0.85 }, + { name:"Wild Bill", minReact:0.7, maxReact:1.0, accuracy:0.90 }, + { name:"The Undertaker",minReact:0.5, maxReact:0.7, accuracy:0.95 } ]; Avantajı: Daha geniş min-max aralığı ufak bir rastgelelik katar; oyunu “ezberlenmiş” hissettirmez. 3. (İsteğe bağlı) Reticle hızını da düşürmek istersen diff Kopyala Düzenle - var aimSpeed = 0.025; + var aimSpeed = 0.018; // %30 daha yavaş
User prompt
Aşağıdaki üç küçük ekleme 2. (ve sonraki) raundları tamamen toparlar. diff Kopyala Düzenle /***** resetDuel *****/ function resetDuel(){ ... holsterHoldTime = 0; + duelStarted = false; // <-- YENİ: her raunda nötr gir + + cancelHold(); // 1. raunddan kalma musicTimeout varsa temizle zoomFinished = false; startZoomIn( roundNum === 1 ? 'duelmusic' : 'round2music' ); statusTxt.setText("Wait for the music..."); ... } /***** input – holster’a basma *****/ game.down = function(x,y,obj){ - if(!duelStarted && holsterBox.contains(x,y)){ + if( zoomFinished && !duelStarted && holsterBox.contains(x,y) ){ playerReady = true; holdingHolster= true; beginHold(); return; } ... }; Neler değişti? Satır Etki duelStarted = false 2. raunda girildiğinde holster kontrollerini yeniden etkinleştirir. cancelHold() Önceki raunddan kalabilecek musicTimeout’u temizler; gereksiz zamanlayıcılar durur. zoomFinished && kontrolü Oyuncu zoom-in animasyonu bitmeden holstere basarsa istenmeyen “early lose” tetiklenmez.
User prompt
1 6-mermi göstergesi diff Kopyala Düzenle function setupAmmoUI(){ ... - game.addChild(bulletP); - game.addChild(bulletE); + // HUD’u world içine koy ki zoom animasyonuyla birlikte büyüsün + world.addChild(bulletP); + world.addChild(bulletE); ... } playerAmmoUI / enemyAmmoUI dizileri zaten tutuluyor; playerFire() ve enemyFire() içinde ilk görünen elemanı visible = false yaptığımız için her atışta ikon eksilir. 2 “Düşman 6. kurşun kesin vurur” ve oyuncunun ardışık atış yapabilmesi a) Vuruş kontrolündeki eski engelleri kaldır diff Kopyala Düzenle // (checkBullets içinde) – oyuncuya isabet -if(enemyBullet && !playerShot){ +if(enemyBullet){ ... } // – düşmana isabet -if(playerBullet && !enemyShot){ +if(playerBullet){ ... } Artık mermi, oyuncu daha önce ateş etmiş olsa bile çarpışma yakalayabilir. b) 6. mermi isabeti Kodda js Kopyala Düzenle if (enemyShotsFiredCnt === 6){ acc = 1.0; } // spread = 0 zaten doğru; engeli kaldırınca isabet gerçekleşir. c) Oyuncunun art arda ateş edebilmesi diff Kopyala Düzenle function playerFire(x,y){ - if(!canShoot || playerShot) return; + if(!canShoot) return; // playerShot kısıtlamasını kaldır playerShot = true; ... } // Mermi sahneyi terk edince flag’i düşür if(playerBulletOffScreen){ ... playerShot = false; // yeni atışa izin ver } Aynısını enemyBullet için de yapın (enemyShot = false;). 3 Her raundda yeniden zoom-in ve farklı müzik a) Genel zoom fonksiyonunu parametrik yap diff Kopyala Düzenle -function startZoomIn(){ +function startZoomIn(music){ ... // world.scale = 0.4 vb. - LK.playMusic('duelmusic'); + LK.playMusic(music || 'duelmusic'); ... } b) Yeni raund başlatılırken çağır diff Kopyala Düzenle function resetDuel(){ ... - statusTxt.setText("Wait for the music..."); + zoomFinished = false; // yeni animasyona hazırlan + startZoomIn(roundNum === 1 ? 'duelmusic' : 'round2music'); + statusTxt.setText("Wait for the music..."); } zoomFinished tekrar false olduğu için HUD yazıları, holster kontrolleri ve hedefleme ancak zoom bitince yeniden aktif hâle gelir – ilk raunddaki davranış korunur.
User prompt
earlyLose() hâlâ duelStarted = false iken tetikleniyor. Müziği kestikten sonra (stopMusicAndDraw → startDraw) duelState zaten STATE_DRAW oluyor fakat duelStarted true yapılmadığından scss Kopyala Düzenle if( zoomFinished && !duelStarted ){ ... earlyLose(); } bloğu devrede kalıyor; imleç holster-kutusundan çıkar çıkmaz oyun “kural dışı” sanıp fail + game-over çalıyor. 1. stopMusicAndDraw() içinde duelStarted’i aç diff Kopyala Düzenle function stopMusicAndDraw(){ LK.stopMusic(); // müzik sustu + duelStarted = true; // <-- ERKEN KAYIP KONTROLÜ ARTIK PASİF + holsterHoldTime = 0; // güvenlik: sayaç sıfırla startDraw(); // DRAW fazına geç } Bu tek satır bile hatayı keser; imleci dilediğiniz gibi dolaştırabilirsiniz, artık earlyLose() çağrılmaz. 2. (Tercih) 4-saniyelik holsterHoldTime eşik kodunu sil / yoruma al Yeni kuralda “en az 4 sn bekle” şartı yok, sadece müziğin bitimine kadar holster’ı bırakmamak yeterli. Aşağıdaki blok artık gereksiz; tutarsanız da zarar vermez ama temizlemek kod akışını sadeleştirir: js Kopyala Düzenle // if (holsterHoldTime >= 4000 && !duelStarted) { // duelStarted = true; // holsterHoldTime = 0; // resetDuel(); // } 3. resetDuel() çağrısı değişmiyor Holster’a ilk bastığınız anda (game.down içinde) hâlâ beginHold() öncesi resetDuel() çağrılı olduğu için round hazırlıkları (mermi UI’si, “Wait for the music...” yazısı v.b.) normal şekilde çalışmaya devam eder.
User prompt
1 ) resetDuel() ⇒ holster bayraklarını temizle Round başlarken hâlâ holdingHolster / playerReady “true” kalınca, silahı ateş ettikten sonra “holster’den elini çekti” sanılıp earlyLose() çalışıyor. diff Kopyala Düzenle function resetDuel(){ ... + playerReady = false; // yeni ← round’a nötr gir + holdingHolster = false; // yeni + holsterHoldTime = 0; // güvenlik ... } 2 ) game.up içinde early-lose kontrolünü round başlamadan önceye sınırla Elini kaldırdığında artık duelStarted == true ise, earlyLose() tetiklenmemeli. diff Kopyala Düzenle game.up = function(x,y,obj){ holdingHolster = false; cancelHold(); - if(playerReady && duelState === STATE_WAIT){ + if(!duelStarted && playerReady && duelState === STATE_WAIT){ earlyLose(); return; } ... }; Not: Aynı korumayı game.move tarafında da tutmak istiyorsanız, if(zoomFinished && !duelStarted){ ... earlyLose(); } bloğu zaten güvenli. Bu iki değişiklikten sonra: Rakibi vurduğunuzda earlyLose asla çalışmaz; “fail” sesi de çalmaz, normal You Win / Next Round akışı gelir. Holster’den erken çektiğiniz gerçek durumlarda davranış değişmez.
User prompt
1 ) Zamanlayıcı yalnızca basılı tutma başladığında kurulmalı drawTimeout’ı startZoomIn içinde kurmak yanlıştı; eldeki süre sayacı kılıf tutulmadan akmaya başlıyordu. diff Kopyala Düzenle // (1) -- startZoomIn(): drawTimeout KALKSIN -onFinish:function(){ - zoomFinished = true; - var extra = 4000 + Math.random()*6000; // 4-10 s - drawTimeout = LK.setTimeout(startDraw, extra); -} +onFinish:function(){ zoomFinished = true; } 2 ) Basılı tutmayı başlat / iptal et Basarken süreyi başlat, çekince iptal et. ( holdingHolster ve musicTimeout ) js Kopyala Düzenle var holdingHolster = false, musicTimeout = null; // yeni function beginHold(){ if(musicTimeout){ LK.clearTimeout(musicTimeout); } var extra = 4000 + Math.random()*6000; // 4-10 s musicTimeout = LK.setTimeout(stopMusicAndDraw, extra); } function cancelHold(){ if(musicTimeout){ LK.clearTimeout(musicTimeout); } musicTimeout = null; } function stopMusicAndDraw(){ LK.stopMusic(); // müzik sustu startDraw(); // DRAW fazına geç } game.down diff Kopyala Düzenle if(!duelStarted && holsterBox.contains(x,y)){ playerReady = true; holdingHolster = true; + beginHold(); // HOLD başlatılırken zamanlayıcı kuruluyor return; } game.up diff Kopyala Düzenle holdingHolster = false; cancelHold(); // el çekildi, zamanlayıcı iptal 3 ) Hareketle kutudan çıkınca da iptal / FAIL El kutudan çıkarsa hem zamanlayıcı iptal olsun hem Early Lose tetiklensin. diff Kopyala Düzenle if(zoomFinished && !duelStarted){ if(holsterBox.contains(x,y)){ /* sorun yok */ }else if(holdingHolster){ // eli hâlâ basılı ama kutu dışında + cancelHold(); earlyLose(); } } 4 ) earlyLose() – yere ateş efekti & fail Elini erken çekmek “silâhı yere boşaltmak” gibi duyulsun. diff Kopyala Düzenle function earlyLose(){ ... + LK.getSound('gunshot').play(); // yere ateş LK.getSound('fail').play(); // FAIL ... } (İsterseniz küçük bir Bullet örneği de -90° açıyla yere gidecek şekilde ekleyebilirsiniz.) Sonuç Müzik yalnızca kılıf basılıyken (holdingHolster) geri sayar; 4-10 sn sonra durur ve DRAW! gelir. Elinizi erken çekersen cancelHold() → müzik durmaz, silâh “boşa” patlar, FAIL. El kutudayken boş gezmek müziği durdurmaz – sadece gerçek basılı tutma süresi sayılır.
User prompt
Aşağıdaki üç “yanlış mağlubiyet” tetikleyicisi (FAIL/Game Over) oyunu beklenmedik anda bozuyor: # Nerede tetikleniyor? Neden hatalı? Nasıl düzeltirim? 1 update() döngüsünde if (holsterBox.contains(px,py)) holsterHoldTime += ... else if (holsterHoldTime>0) earlyLose(); Sadece imleci kılıfın üstünden sağ–sol gezdirmek bile holsterHoldTime > 0 yapıyor. Tuşa basmanıza gerek kalmadan kutudan çıkınca earlyLose çağrılıyor. Aşağıdaki iki satırı değiştirin: if (holsterBox.contains(px,py) && holdingHolster) { holsterHoldTime += dtMs; } else if (holsterHoldTime>0 && holdingHolster){ earlyLose(); } 2 game.move içinde else if (playerReady) earlyLose(); playerReady sadece “kutuda bir kere tıkladı” demek. Parmağı / fareyi basılı tutmadığınız hâlde kutudan çıkar çıkmaz kaybettiriyor. Aynı kontrolü holdingHolster ile değiştirin: else if (holdingHolster){ earlyLose(); } 3 earlyLose() fonksiyonu zoomFinished = false; Kaybettikten sonra zoomFinished false olunca, game.update ekran metinlerini gizliyor → kara ekran + ikinci kez erken-kaybetme olabiliyor. Bu satırı silin veya şu şekilde değiştirin: if (duelState!==STATE_RESULT) zoomFinished = false; Adım Adım Kalıcı Çözüm 1. Global tutuş (holding) bayrağı ekleyin js Kopyala Düzenle var holdingHolster = false; // en başta 2. game.down / game.up diff Kopyala Düzenle game.down = function(x,y){ if(!duelStarted && holsterBox.contains(x,y)){ - playerReady = true; + playerReady = true; + holdingHolster = true; // tuş bastı return; } ... }; game.up = function(x,y){ + holdingHolster = false; // tuşu bıraktı if(playerReady && duelState===STATE_WAIT){ earlyLose(); return; } ... }; 3. update() içinde holster süresi diff Kopyala Düzenle if (holsterBox.contains(px,py) && holdingHolster){ holsterHoldTime += dtMs; }else{ - if (holsterHoldTime>0){ earlyLose(); } + if (holsterHoldTime>0 && holdingHolster){ earlyLose(); } holsterHoldTime = 0; } 4. game.move erken–bırakma kontrolü diff Kopyala Düzenle }else if (holdingHolster){ earlyLose(); } 5. earlyLose() diff Kopyala Düzenle -function earlyLose(){ - zoomFinished = false; +function earlyLose(){ + if(duelState===STATE_RESULT) return; // tek seferlik holdingHolster = false; (Zoom’u sıfırlamıyorsanız zoomFinished = false; satırını tamamen atabilirsiniz.) Neler Değişti? İmleci kutu üzerinde gezdirmek artık sayılmıyor – tuş basılı iken dışarı çıkarsanız kaybedersiniz. Tuşu bırakınca holdingHolster false olduğu için gereksiz erken-lose tetiklenmez. earlyLose bir kez çalınır, ekran metinleri kalır, “failed” animasyonundan sonra doğrudan LK.showGameOver()’a geçer.
User prompt
fail isimli ses çaldıktan 1 saniye sonra gameover olsun
User prompt
Holster’ı basılı tutarken kutunun dışına çıkarsan –ya da tuşu bırakıp tekrar basarsan– müzik bitmeden hemen kaybedersin.
Müzik 4-10 sn sonra otomatik kesilir, “DRAW!” gelir.
Hem sen hem de rakip “tak-tak” toplam 6 mermi atabilirsiniz. Rakibin 6’ncı mermisi %100 isabet.
1. Global sayaçlar
// Game state bölümünde şu ikisini ekle:
js
Kopyala
Düzenle
var playerShotsFiredCnt = 0;
var enemyShotsFiredCnt = 0;
2. resetDuel → sayaçları sıfırla
diff
Kopyala
Düzenle
function resetDuel(){
...
+ playerShotsFiredCnt = 0;
+ enemyShotsFiredCnt = 0;
...
}
3. Player fire – tekrar ateşlenebilir
Bul function playerFire içindeki ilk 20 satırı, şu şekilde değiştir:
diff
Kopyala
Düzenle
-function playerFire(x,y){
- // Count player shots fired
- var playerShotsFired = 0;
- for(var i=0;i
User prompt
Please fix the bug: 'Timeout.tick error: Cannot read properties of null (reading 'accuracy')' in or related to this line: 'var acc = currentEnemy.accuracy;' Line Number: 571
User prompt
Please fix the bug: 'Timeout.tick error: Cannot read properties of null (reading 'minReact')' in or related to this line: 'var reactTime = 1000 * (currentEnemy.minReact + Math.random() * (currentEnemy.maxReact - currentEnemy.minReact));' Line Number: 453
User prompt
1. ResetDuel: artık drawTimeout kurma diff Kopyala Düzenle - // Random time before "DRAW!" (3-14s) - var waitTime = 3000 + Math.floor(Math.random() * 11000); - ... - drawTimeout = LK.setTimeout(startDraw, waitTime); + // burada drawTimeout KURMA – müzik süresi zoom’dan sonra belirlenecek 2. startZoomIn: müzik süresini rastgele ayarla js Kopyala Düzenle function startZoomIn(){ var startScale = 0.4; world.scaleX = world.scaleY = startScale; world.x = centerX*(1-startScale); world.y = 2732/2*(1-startScale); LK.playMusic('duelmusic'); tween(world,{scaleX:1,scaleY:1,x:0,y:0},{ duration:4000, easing:tween.quadInOut, onFinish:()=>{ zoomFinished = true; /* --- YENİ EK --- */ var extra = 4000 + Math.random()*6000; // 4-10 sn drawTimeout = LK.setTimeout(startDraw, extra); } }); } 3. game.up: holster’ı erken bırakma kontrolü diff Kopyala Düzenle game.up = function(x,y){ - if(!duelStarted){ - ... - } - if(duelState===STATE_DRAW && canShoot){ ... } + /* HOLSTER ERKEN BIRAKMA */ + if(playerReady && duelState===STATE_WAIT){ + earlyLose(); + return; + } + + if(duelState===STATE_DRAW && canShoot){ + playerFire(x,y); + return; + } }; (artık ‟holster içinde miydi?” kontrolüne gerek kalmadı; bırakma=yenilgi) 4. earlyLose: ufak kozmetik diff Kopyala Düzenle function earlyLose(){ ... - statusTxt.setText("Too Early!\nYou Lose!"); + statusTxt.setText("Too early!\nYou lose!"); LK.getSound('fail').play(); + // ister ses efekti istersen: + // LK.getSound('gunshot').play(); } Ne değişti? Durum Ekran / Ses Ne olur? Zoom biter (yazı yok) müzik 4-10 sn çalar Holster’a bas “Hold holster to start duel” → “...music to end... then aim & release” Basılıyken erken bırak “Too early! You lose!” + fail kaybedersin Müzik kesilir (startDraw) “Aim and SHOOT!” + draw sfx artık imleci çek - fire Fire veya rakibin ateşi mevcut düzen
User prompt
# Ne Değişiyor? Nereye Koy? 1 Zoom sırasında metin gizli game.update başına if(!zoomFinished){statusTxt.visible=false;return;} 2 Müzik sadece zoom-in’de başlar, resetDuel() bir daha çalmaz resetDuel() içindeki LK.playMusic('duelmusic'); satırını sil 3 playerReady bayrağı (4 sn sayaç gitti) var playerReady=false; (global) 4 Metin akışı (game.update sonunda) js if(!duelStarted){ if(!playerReady) statusTxt.setText("Hold holster to start duel"); else statusTxt.setText("Waiting for music to end...\nthen aim & release"); } else if(duelState===STATE_DRAW){ statusTxt.setText("Aim and SHOOT!"); } 5 Holster tıklanınca sadece playerReady olur game.down içine: js if(!duelStarted && holsterBox.contains(x,y)){ playerReady=true; return; } 6 Müzik bittiğinde oyuncu hazır mı kontrolü startDraw() başına ekle: js if(!playerReady){ earlyLose(); return; } Akış Zoom-in Müzik çalar, ekranda yazı yok. Zoom biter ➜ “Hold holster to start duel” Oyuncu kutuya basmasa & müzik biterse → earlyLose (enemy vurur). Kutuya basınca ➜ “Waiting for music to end... then aim & release” Parmak/imiç holster dışına çıkarsa yine earlyLose. Müzik kesilince (startDraw) Eğer playerReady == false ➜ earlyLose Eğer basılı tutuyorsa ➜ status “Aim and SHOOT!”, duelState = DRAW, oyuncu imleci çektiğinde (game.up) ateş eder. ↪💡 Consider importing and using the following plugins: @upit/tween.v1
User prompt
Zoom’un hâlâ sola-yukarı “kaçmasının” sebebi pivot yerine ölçek + kaydırma ikilisini aynı anda güncellememen. LK konteynerlerinde pivot her zaman beklediğimiz gibi çalışmıyor; en güvenlisi ‘kamerayı’ (world) “küçült ve merkeze doğru çek” formülüyle yakınlaştırmak. 1 – Kesin çalışan zoom formülü Özet: – world’u (oyun sahnesi) scale 0.4’e çekerken – x ve y’sini, ekran merkezine göre offset = center * (1-scale) kadar ötele. Böylece ölçek 1’e tween’lenirken x ve y de 0’a tween’lenir → görüntü tam ortadan yakınlaşır. js Kopyala Düzenle // BOOT : world kapsayıcısı var world = new Container(); game.addChild(world); world.addChild(player); world.addChild(enemy); /* diğer world içi nesneler (holsterVisual, aimReticle vb.) de world.addChild(...) olmalı ki zoom hepsine beraber uygunsun. */ // Ekran merkezleri var screenW = 2048; var screenH = 2732; var centerX = screenW/2; var centerY = screenH/2; // ---------- ZOOM FONKSİYONU TAMAMEN DEĞİŞTİR ---------- function startZoomIn(){ const startScale = 0.4; world.scaleX = world.scaleY = startScale; world.x = centerX*(1-startScale); // ≡ centerX - centerX*scale world.y = centerY*(1-startScale); LK.playMusic('duelmusic'); tween(world,{ scaleX:1, scaleY:1, x:0, y:0 },{ duration:4000, easing:tween.quadInOut, onFinish:()=>{ zoomFinished = true; } }); } Neden çalışır? Ölçek küçülünce sahne sol-üstte kalsın istemiyoruz; center*(1-scale) kadar sağ-aşağı kaydırırsak tam orta noktayı sabit tutmuş oluruz. Tween sırasında scale 1’e yaklaşırken offset de 0’a iner → çakışmasız, pürüzsüz zoom. 2 – Holster kutusunu dünyaya ekle & konum güncelle Önce kutuyu world’a ekle: js Kopyala Düzenle world.addChild(holsterVisual); // game.addChild yerine Ve her frame’de oyuncu ayağını referans al: js Kopyala Düzenle function updateHolsterBox(){ holsterBox.x = player.x - 200; holsterBox.y = player.y + player.body.height/2 + 250; holsterVisual.x = holsterBox.x; holsterVisual.y = holsterBox.y; } game.update içinde ilk satırlarda updateHolsterBox(); çağır; zoom sırasında da doğru yerde kalır. 3 – Metin & tetik akışı sağlam Metin kısmı çalışıyordu; world değişimi bunu etkilemez. Ateş hâlâ game.up’ta tetikleniyor olmalı. ↪💡 Consider importing and using the following plugins: @upit/tween.v1
User prompt
1) Düello sahnesini “world” kapsayıcısına al + pivotla js Kopyala Düzenle // ==== FIX #1 : SAHNENİN BAŞINDA ==== var world = new Container(); game.addChild(world); // oyuncu ve düşmanı ekrana eklerken: world.addChild(player); world.addChild(enemy); js Kopyala Düzenle // ==== FIX #1b : zoom fonksiyonunda ==== function startZoomIn(){ world.scaleX = world.scaleY = 0.4; world.pivotX = centerX; // ekranın tam ortası world.pivotY = 2732/2; world.x = centerX; world.y = 2732/2; LK.playMusic('duelmusic'); tween(world,{scaleX:1,scaleY:1}, { duration:4000, easing:tween.quadInOut, onFinish:()=>zoomFinished=true }); } Sonuç: Artık ilk karede tam ekran görünür; zoom, merkezden kusursuz yakınlaşır. 2) Holster kutusunu oyuncu ayaklarına göre hesapla js Kopyala Düzenle // ==== FIX #2 : holster konumu ==== var holsterOffset = 250; var holsterBox = new Rectangle( player.x - 200, player.y + player.body.height/2 + holsterOffset, 400, 200 ); holsterVisual.x = holsterBox.x; holsterVisual.y = holsterBox.y; Kutu tam ayak altına iner; zoom sırasında da yerini korur (çünkü GUI değil world içinde). 3) Metin akışını sadeleştir js Kopyala Düzenle // ==== FIX #3 : statusTxt ayarı ==== if(!duelStarted){ statusTxt.setText( holsterHoldTime === 0 ? "Tap holster to start" : "Hold holster..." ); }else{ if(duelState===STATE_WAIT) statusTxt.setText("Waiting for music..."); if(duelState===STATE_DRAW) statusTxt.setText("DRAW and SHOOT!"); } statusTxt.visible = true; Artık tam istediğin sırayla üç mesaj görülecek. 4) Kol & silah imleci takip etsin, ateş “up”ta patlasın js Kopyala Düzenle // ==== FIX #4a : imleç açısı ==== function rotateArmTo(x,y){ let gx = player.x + player.gun.x; let gy = player.y + player.gun.y; let ang = Math.atan2(y-gy, x-gx); player.gun.rotation = ang; player.rarm.rotation = ang + 0.15; } js Kopyala Düzenle // ==== FIX #4b : game.move sonunda ==== if(duelState>=STATE_DRAW) rotateArmTo(x,y); js Kopyala Düzenle // ==== FIX #4c : tetikleyici ===== game.up = (x,y)=>{ if(!duelStarted) return; // hâlâ bekleme safhası if(duelState===STATE_DRAW && canShoot) playerFire(x,y); }; 5) Holster 4 saniye sayacı güvenilir olsun js Kopyala Düzenle // ==== FIX #5 : update içinde ==== var dtMs = typeof dt==="number" ? dt : 16.7; if(holsterBox.contains(px,py)){ holsterHoldTime += dtMs; }else{ holsterHoldTime = 0; // parmak çıktı -> sayaç sıfır } if(holsterHoldTime >= 4000){ // 4 sn dolunca duelStarted = true; resetDuel(); } Erken bırakırsa earlyLose() çalışıyor; tutarsa 4 sn sonunda duel başlıyor. ↪💡 Consider importing and using the following plugins: @upit/tween.v1
User prompt
Aşağıdaki dört adım – her biri küçük, net yamalar – istediğin görünümü ve akışı tam olarak verir: # Sorun / İstek Çözüm (yalnızca ekle-değiştir satırları) 1 Zoom in soldan yukarı kayıyor Oyun sahnesini bir “world” kapsayıcıya alıp pivot merkezleyerek yakınlaştır: js // BOOT sonrası sadece bir kez: var world = new Container(); game.addChild(world); world.addChild(player); world.addChild(enemy); ... /* pivot noktası */ world.pivotX = centerX; world.pivotY = 2732/2; // ekran ortası // zoom fonksiyonu tween(world,{ scaleX:1,scaleY:1 },...); GUI elemanları (LK.gui.top, LK.gui.center) aynen kalır. 2 Holster kutusu yanlış yerde Kutuyu oyuncunun gövde altına göre hesapla: js var holsterOffset = 250; // ayaklardan aşağı var holsterBox = new Rectangle( player.x - 200, player.y + player.body.height/2 + holsterOffset, 400, 200); holsterVisual.x = holsterBox.x; holsterVisual.y = holsterBox.y; 3 Metin akışı (start → waiting → draw) game.update içindeki mesaj kısmını değiştir: js if(!duelStarted){ if(holsterHoldTime===0){ statusTxt.setText("Tap holster to start"); } ... } else if(duelState===STATE_WAIT){ statusTxt.setText("Waiting for music to end..."); } else if(duelState===STATE_DRAW){ statusTxt.setText("DRAW and SHOOT!"); } 4 Silah kolu nişanı takip etsin, tetik “up”ta patlasın 1) game.move sonunda kol/gun rotasyonu: js if(duelState>=STATE_DRAW){ var ang = Math.atan2(y-player.y,gx-player.x); // gx = player.gun global pos tween(player.gun,{rotation:ang},0); tween(player.rarm,{rotation:ang+0.15},0); } 2) Ateşleme tetikleyicisini game.up’a taşı: js game.up = (x,y)=>{ if(duelStarted && duelState===STATE_DRAW && canShoot) playerFire(x,y); } Kısaca Akış startZoomIn() world .scale = 0.4 ➜ 1, pivot merkez → ekran ortalı zoom. Müzik hemen başlar. Holster Oyuncu 400 × 200’lük kutuda 4 s basılı tutar → duelStarted=true. Ekranda “Waiting for music...” görünür. Müzik kesildiğinde startDraw() çağrılır → “DRAW and SHOOT!” yazısı, silah + kol imleci takip eder. Parmağı/kursörü bıraktığı an playerFire() tetiklenir. Erken bırakırsa earlyLose() → “Too Early! You Lose”. Bu dört mini yama dışında kalan kodu ellemene gerek yok; derleyip çalıştırdığında: Zoom sinematik olarak tam merkezden gelir. Holster kutusu oyuncu ayaklarının ~250 px altında durur. Metinler doğru sırayla güncellenir. Silah kolu imleci takip eder, “bırak” olayıyla ateşler. Takıldığın bir satır olursa doğrudan o bölümü paylaşırsan nokta at ↪💡 Consider importing and using the following plugins: @upit/tween.v1
User prompt
Kod şu anda zoom-in’i holster kutusuna ilk dokunuşa bağladığı için akış karışıyor. İstediğin davranışı aşağıdaki üç adımda çözebilirsin: Adım Ne değişecek? Nasıl? 1. Oyun yüklenir yüklenmez zoom-in + müzik startZoomIn() otomatik çağrılır, fonksiyon müziği başlatır - playerReady mantığını kaldır. - startZoomIn() içinde LK.playMusic('duelmusic'); satırını tween başlamadan önce ekle. - startZoomIn() fonksiyonunu dosya sonunda doğrudan çağır: startZoomIn(); 2. Zoom tamamlandıktan sonra “holster bekletme” sayacı Oyuncu kutuya girer ve 4 sn boyunca içeride kalırsa resetDuel() çağrılır, ekranda “Wait for the music...” görünür - Yeni değişken: var holsterHoldTime = 0; - game.move / game.down içinde if(holsterBox.contains(x,y)) { holsterHoldTime += dt; } else { holsterHoldTime = 0; } şeklinde güncelle (dt = frame delta). - game.update sonunda if(!duelStarted && holsterHoldTime >= 4000){ resetDuel(); } ekle. 3. Kutudan erken çıkma → “Too Early – You Lose” holsterHoldTime > 0 && !holsterBox.contains(...) olursa kaybet - game.move veya game.up’ta if(!holsterBox.contains(x,y) && holsterHoldTime>0 && !duelStarted){ earlyLose(); }. - earlyLose() fonksiyonu içinde müziği durdur, statusTxt.setText("Too Early!\nYou Lose"); ve LK.getSound('fail').play(); Örnek kod parçaları js Kopyala Düzenle /***** BOOT *****/ startZoomIn(); // Oyunun en başında çağır /***** START ZOOM IN *****/ function startZoomIn(){ LK.playMusic('duelmusic'); // ⬅ müzik hemen başlasın tween(game,{ scaleX:1, scaleY:1, y:0 }, { duration:4000, easing:tween.quadInOut, onFinish(){ zoomFinished=true; } // sadece zoom bitti işareti koy }); } /***** UPDATE DÖNGÜSÜ *****/ var zoomFinished=false, duelStarted=false, holsterHoldTime=0; game.update = function(dt){ if(zoomFinished && !duelStarted){ if(holsterBox.contains(game.pointerX, game.pointerY)){ holsterHoldTime += dt; if(holsterHoldTime>=4000){ // 4 sn doldu duelStarted=true; resetDuel(); // “Wait for the music...” burada çıkacak } }else if(holsterHoldTime>0){ // kutudan erken çıktı earlyLose(); } } ... }; /***** ERKEN KAYBET *****/ function earlyLose(){ zoomFinished=false; LK.stopMusic(); statusTxt.setText("Too Early!\nYou Lose!"); statusTxt.visible=true; LK.getSound('fail').play(); }
User prompt
Oldukça sağlam bir iskelet çıkmış — müzik-bekleme, “DRAW!”, mermi UI’si ve ragdoll akışı oturmuş. Yine de tam istediğin davranışı yakalamak için birkaç kritik noktayı elden geçirmeni öneririm: Alan Durum Eksik / İyileştirme 1. Düşman tepki süreleri enemyData hâlâ 0.45 → 0.18 sn aralığında Brief’te istediğin 1.5 → 0.1 sn aralığını tabloya yansıt (min=max=1.5 / 0.8 / 0.6 / 0.4 / 0.2 / 0.1). 2. Müzik başlatma startZoomIn.onFinish() ve resetDuel() ikisi de LK.playMusic('duelmusic') çağırıyor Çifte çalmayı önlemek için sadece resetDuel() içinde bırak, diğeri kaldır. 3. Holster “basılı tut” Şu an tek tıklamayla “ready” oluyor - Pointer down olduğunda playerReady = true - Pointer up’ta playerReady = false, müzik ve zamanlayıcıyı durdur (erken çekme sayılmaz, “beklet” gereksinimi sağlanır). 4. İlk zoom-out sahnesi Oyun yüklenir yüklenmez ölçek 0.4 Sinematik etkiyi pekiştirmek için kamera y konumunu da uzaklaştır (mesela game.y = -500) ve tween’de hem scale hem y değerini animasyonla normale getir. 5. Holster kutusunu göstermek Sadece Rectangle objesi (render edilmez) Küçük yarı saydam kutu veya silüet sprite ekle; oyuncu “nereye basacağını” sezsin. 6. 6. mermi kesin isabet Mantık doğru ama raunt geçince sayaç sıfırlanıyor mu? enemyAmmoUI sıfırlanınca currentBulletCount da sıfırlanır ➜ Tamam. Yine de enemyFire() sonunda mermisi kalmadıysa otomatik reload/raunt bitir kontrolü ekle. 7. Oyuncu mermi limiti UI’den eksiltiyorsun ama ateş logic’i sayaç kontrol etmiyor Basit sayaç playerShotsFired >= 6 → canShoot = false; ile “şarjör boş” hatası ver. 8. Müzik süresi 3–14 saniye rastgele; ✅ İstediğin aralıkta. (Dilersen Math.random()*11000 yerine Phaser.Math.Between(3000,14000) gibi net sayı). 9. Erken ateş diskalifiye Çalışıyor fakat ilk basış holster dışında olursa devreye giriyor playerFire() çağrısını playerReady === true şartına bağla; böylece “holster’a basmadan ateş” de diskalifiye sebebi olur. Örnek yama: tek satırlık kritik değişiklikler js Kopyala Düzenle // 1) enemyData tepki süresi { name: "Greenhorn", minReact: 1.5, maxReact: 1.5, accuracy: 0.75 }, // ... // 2) startZoomIn'de müzik satırını çıkar onFinish: () => { resetDuel(); // sadece burada başlatılacak } // 3) basılı tutma mekaniği game.down = (x,y)=>{ if(holsterBox.contains(x,y)){playerReady=true;} ... } game.up = ()=>{ if(playerReady && duelState===STATE_WAIT){ LK.stopMusic(); LK.clearTimeout(drawTimeout); playerReady=false; statusTxt.setText("Hold holster!"); } } if(!playerReady) return; // playerFire içinde // 7) oyuncu mermi limiti if(playerShotsFired >= 6){ return; } // canShoot=false de ekle Son olarak Debug: Tüm zamanlayıcıların (drawTimeout, duelTimer) temizlendiğinden emin ol. Performans: Ragdoll tween’lerinde duration:600 tüm parçalara aynı anda → mobilde droplara sebep olursa delay: i*30 gibi hafif kaydır. Ses: Müzik bitişinde küçük bir “vinyl stop” efekti eklemek gerilimi artırır. ↪💡 Consider importing and using the following plugins: @upit/tween.v1
User prompt
1. 🕹️ Kılıf Bölgesi Bekletme Sistemi (Holster Box) Kullanıcının parmağı/kursörü kutuya girmeden oyun başlamasın: js Kopyala Düzenle var playerReady = false; var holsterBox = new Rectangle(centerX - 200, playerY + 200, 400, 200); game.down = function (x, y, obj) { if (!playerReady && holsterBox.contains(x, y)) { playerReady = true; startZoomIn(); // Aşağıda tanımlayacağız } if (duelState === STATE_DRAW && canShoot && aimActive) { playerFire(x, y); } else if (duelState === STATE_WAIT && !playerShot) { playerFire(x, y); } }; 2. 🎥 Zoom Out → Zoom In Geçişi Kamera başlangıçta uzak, sonra 4 saniyede yaklaşsın: js Kopyala Düzenle function startZoomIn() { game.scaleX = 0.4; game.scaleY = 0.4; tween(game, { scaleX: 1, scaleY: 1 }, { duration: 4000, easing: tween.quadInOut, onComplete: () => { LK.playMusic('duelmusic'); resetDuel(); // buradan sonra mevcut sistem işliyor } }); } Oyun resetDuel() yerine bu zoom animasyonundan sonra başlatılmalı. 3. 🎵 Müzik Süresini 3–14 Saniye Arası Ayarla resetDuel() içinde şu satırı değiştir: js Kopyala Düzenle // Eski var waitTime = 2500 + Math.floor(Math.random() * 2000); // Yeni var waitTime = 3000 + Math.floor(Math.random() * 11000); 4. 🔫 6 Mermilik UI Eklemek Her karakterin altında 6 adet dolu mermi simgesi çiz: js Kopyala Düzenle var playerAmmoUI = []; var enemyAmmoUI = []; function setupAmmoUI() { for (var i = 0; i < 6; i++) { let px = player.x - 120 + i * 40; let ex = enemy.x - 120 + i * 40; let bulletP = LK.getAsset('bullet', {scaleX:0.6, scaleY:0.6}); let bulletE = LK.getAsset('bullet', {scaleX:0.6, scaleY:0.6}); bulletP.y = player.y + 250; bulletE.y = enemy.y - 250; bulletP.x = px; bulletE.x = ex; game.addChild(bulletP); game.addChild(bulletE); playerAmmoUI.push(bulletP); enemyAmmoUI.push(bulletE); } } Ve her ateşte bu diziden visible = false yaparak eksilt. 5. 🎯 6. Mermide %100 İsabet Zorunluluğu js Kopyala Düzenle // Enemy fires kısmına ekle: var currentBulletCount = 6 - enemyAmmoUI.filter(b => b.visible === true).length; if (currentBulletCount === 5) { acc = 1.0; // Son mermi garanti vuruş } ↪💡 Consider importing and using the following plugins: @upit/tween.v1
Code edit (1 edits merged)
Please save this source code
User prompt
The Last Beat
Initial prompt
1. Core Concept A tense 1v1 western duel game where the player and the opponent wait as music plays. The duel begins the moment the music abruptly stops. Shooting early leads to instant disqualification. When shot, the enemy reacts with ragdoll-style physics (per body part) to make the experience visceral and reactive. --- 2. Game Flow Overview 1. Main Menu Title logo: "The Last Beat" One button: Play 2. Intro to Round Camera starts zoomed out, showing full duel scene. In 4 seconds, camera zooms into duel position. Player and enemy are aligned horizontally (same y-axis). Duel begins once player holds finger or mouse cursor inside the holster box below them. Only after this, music begins. Instructions shown briefly: "Hold on your holster to begin. Don't shoot until the music stops!" 3. Duel Phase GameState: waiting Background music plays between 3 to 14 seconds (random duration). Player must keep mouse/finger in the holster box. If player shoots before music ends: DISQUALIFIED. Enemies have a reaction time depending on their level: Level 1: 1.5s Level 2: 0.8s Level 3: 0.6s Level 4: 0.4s Level 5: 0.2s Final Boss: 0.1s (larger in size, more intimidating visuals) 4. Shootout Resolution When music stops: GameState changes to ready Enemy begins aiming after their designated reaction time. Player can aim and shoot. Both player and enemy have 6 bullets (represented as a 6-round revolver under each character). Each shot removes one bullet from revolver UI. Enemies can miss shots with increasing accuracy per level: Level 1–5: Random chance of miss (decreasing chance per level). Level 6: Last bullet (6th shot) is always a hit (100% accuracy). 5. Restart Prompt Show score and time to fire (if won) Option: Play Again --- 3. Game States menu: Show title and Play button. intro: Characters shown; instruction overlay; zoom in. waiting: Music plays; shooting is locked. ready: Music stops; duel can start. resolution: One side wins; ragdoll physics triggered. result: Score screen and replay option. --- 4. Core Mechanics • Shooting Mechanic When gameState === ready, player can click anywhere on enemy's body. Bullet travel is instant (hitscan). Each body part triggers different ragdoll reaction. • Early Fire (Disqualification) Clicking before ready state triggers immediate loss: Message: "You fired too early!" No ragdoll triggered; just static outcome. • Ragdoll System Each character (enemy and player) is a composite of parts: Head Torso Left Arm, Right Arm Left Leg, Right Leg On hit, appropriate part is detached or applies torque and gravity-based animation. Physics simulation begins once character is hit. --- 5. Characters • Player Always at left side of screen. Can aim via mouse when duel begins. Idle pose: hand near holster, blinking occasionally. Must hold cursor/finger inside holster box for duel to start. 6-round revolver UI shown below character. • Enemies (Duelists) Series of progressively harder opponents. Faster reaction time, smarter movement (head bobbing, fake gestures). Random miss chance on first 5 bullets. 6th bullet is always 100% accurate. Larger hitbox and more imposing visuals for boss. Example opponents: Billy "Deadeye" Sheriff Mae The Undertaker Widow Black --- 6. User Interface Main Menu: Title + Play In-game HUD: Simple crosshair (shown only in ready state) Timer (shows reaction time after shot) Revolver UI (6 bullets for player and enemy) End Screen: Message: "Victory!" / "Defeat" / "You fired too early!" Display reaction time (ms) Play Again button --- 7. Audio Design Multiple loopable western-themed tracks. Each duel uses a different one. Volume drop-off and sudden silence at random time (3-14s). Gunshot sounds vary depending on hit location. Ragdoll impact sounds: thud, body fall, metal jingles. --- 8. Art & Animation Stylized pixel art or vector characters. Body parts rigged separately to support physics. Idle animations: breathing, blinking. Ragdoll physics: applied using physics engine (e.g., Matter.js in Phaser). Backgrounds: Western saloon, desert outpost, train station. Camera starts zoomed out; zooms in over 4 seconds when duel begins. --- 9. Scoring System Score = Reaction time (lower is better). Bonus for accuracy (headshot, torso, limb). Cumulative score if multiple duels planned.
/**** * Plugins ****/ var tween = LK.import("@upit/tween.v1"); /**** * Classes ****/ // Bullet class var Bullet = Container.expand(function () { var self = Container.call(this); var bullet = self.attachAsset('bullet', { anchorX: 0.5, anchorY: 0.5 }); self.speed = 0; self.dirX = 0; self.dirY = 0; self.update = function () { self.x += self.dirX * self.speed; self.y += self.dirY * self.speed; }; return self; }); // Enemy class var Enemy = Container.expand(function () { var self = Container.call(this); // Attach enemybottomthing as a child of enemy var enemybottomthing = self.attachAsset('bottomthingenemy', { anchorX: 0.5, anchorY: 0.5, x: 0, y: 80 // moved 10px up from previous (90 -> 80) }); // Legs var lleg = self.attachAsset('enemyLeg', { anchorX: 0.5, anchorY: 0, x: -140 / 4 + 5 - 5, // moved 5px left // use width of enemyBody (140) since body not yet defined y: 180 / 2 - 15 - 3 // use height of enemyBody (180) }); var rleg = self.attachAsset('enemyLeg', { anchorX: 0.5, anchorY: 0, x: 140 / 4 + 5 - 5, // moved 5px left y: 180 / 2 - 15 - 3 }); // Left Arm var larm = self.attachAsset('enemyArm', { anchorX: 0.5, anchorY: 0, x: -140 / 2 + 5 + 10, // use width of enemyBody (140) since body not yet defined // 10px right // 5px closer to body, 10px right y: -80 //{t} // 30px up (10px further down) }); // Body var body = self.attachAsset('enemyBody', { anchorX: 0.5, anchorY: 0.5, x: 10, y: -15 //{h} // 5px daha aşağı, 10px sağa taşındı }); // Head var head = self.attachAsset('enemyHead', { anchorX: 0.5, anchorY: 0.5, x: -4 + 5 - 4, // 4px left from previous // 5px right, 4px left y: -body.height / 2 - 45 - 4 - 5 - 4 // 4px up from previous }); // Right Arm (gun hand) var rarm = self.attachAsset('enemyArm', { anchorX: 0.5, anchorY: 0, x: body.width / 2 - 5, // 5px closer to body y: -80 //{z} // 30px up (10px further down) }); // Legs var lleg = self.attachAsset('enemyLeg', { anchorX: 0.5, anchorY: 0, x: -body.width / 4 + 5 - 5 + 3, // moved 5px left, now 3px right y: body.height / 2 - 15 - 3 //{z} // 3px up }); var rleg = self.attachAsset('enemyLeg', { anchorX: 0.5, anchorY: 0, x: body.width / 4 + 5 - 5 + 3, // moved 5px left, now 3px right y: body.height / 2 - 15 - 3 //{E} // 3px up }); // Gun (in hand) var gun = self.attachAsset('enemyGun', { anchorX: 0.5, // 0.5 yerine 0.5 anchorY: 0.5, x: rarm.x, y: rarm.y + rarm.height, rotation: 0 }); // Holster gun (on hip, always visible unless drawn) var hipGun = self.attachAsset('enemyGun', { anchorX: 0.5, anchorY: 0.5, x: body.width / 2 + 8 - 10 - 5 - 10, // 5px further left // 10px further left, 5px more left, 10px more left y: body.height / 2 - 22 + 15 + 10 + 3, // 3px further down // 10px further down, 3px more down rotation: Math.PI / 2 }); // Start with gun in holster, hand empty gun.visible = false; hipGun.visible = true; // Helper methods to switch gun/holster visibility self.toHolster = function () { hipGun.visible = true; gun.visible = false; }; self.toHand = function () { hipGun.visible = false; gun.visible = true; }; self.bodyParts = [body, head, larm, rarm, lleg, rleg, gun]; self.gun = gun; self.rarm = rarm; self.head = head; self.body = body; self.ragdoll = function (hitX, hitY) { for (var i = 0; i < self.bodyParts.length; i++) { var part = self.bodyParts[i]; tween(part, { rotation: (Math.random() - 0.5) * 2.5, x: part.x + (Math.random() - 0.5) * 200, y: part.y + (Math.random() - 0.5) * 200 }, { duration: 600, delay: i * 30, easing: tween.elasticOut }); } }; self.resetPose = function () { // Body body.x = 10; body.y = -15; body.rotation = 0; // Head (use constant offset, not accumulated) head.x = 0; head.y = -body.height / 2 - 45; // SABİT offset, yukarı kayma engellendi head.rotation = 0; // Left Arm larm.x = -body.width / 2 + 5 + 10; larm.y = -80; larm.rotation = 0; // Right Arm rarm.x = body.width / 2 - 5; rarm.y = -80; rarm.rotation = 0; // Left Leg lleg.x = -body.width / 4 + 5 - 5 + 3; lleg.y = body.height / 2 - 15 - 3; lleg.rotation = 0; // Right Leg rleg.x = body.width / 4 + 5 - 5 + 3; rleg.y = body.height / 2 - 15 - 3; rleg.rotation = 0; // Gun (in hand) gun.x = rarm.x; gun.y = rarm.y + rarm.height; gun.rotation = 0; // Hip gun (holster) if (typeof hipGun !== "undefined") { hipGun.x = body.width / 2 + 8 - 10 - 5 - 10; hipGun.y = body.height / 2 - 22 + 15 + 10 + 3; hipGun.rotation = Math.PI / 2; } self.toHolster(); // always start with gun in holster }; return self; }); // Player class var Player = Container.expand(function () { var self = Container.call(this); // Head var head = self.attachAsset('playerHead', { anchorX: 0.5, anchorY: 0.5, x: 0, y: -160 / 2 - 45 - 20 // use playerBody height (160) since body not yet defined, moved 25px up }); // Body var body = self.attachAsset('playerBody', { anchorX: 0.5, anchorY: 0.5, x: -12, y: 10 //{1U} // moved 15px down, 5px left from previous }); // bottomthingplayer as child of player, 75px down (moved 15px further down) var bottomthingplayer = self.attachAsset('bottomthingplayer', { anchorX: 0.5, anchorY: 0.5, x: -5, //{21} // moved 5px left y: 101 // moved 6px further down (95+6) }); // Left Arm var larm = self.attachAsset('playerArm', { anchorX: 0.5, anchorY: 0, x: -body.width / 2 - 10 - 5 + 5 + 7 + 3, // move 3px right // move 5px right, 7px more right, 3px more right y: -100 + 10 + 10 + 15, // move 15px down // move 10px down, 15px more down zIndex: -1 // ensure arm is behind body (if zIndex is supported) }); // Move left arm behind body in display list if (typeof self.setChildIndex === "function") { self.setChildIndex(larm, 0); } // Right Arm (gun hand) var rarm = self.attachAsset('playerArm', { anchorX: 0.5, anchorY: 0, x: body.width / 2 + 10 - 5 - 20 - 5 - 10, // 10px more left y: -100 + 10 + 15 + 10 //{1Y} // 10px more down, 15px more down, 10px more down }); // Legs var lleg = self.attachAsset('playerLeg', { anchorX: 0.5, anchorY: 0, x: -body.width / 4 - 10 + 3 + 10, // moved 10px right // move 3px right (total 6px closer) // 10px left, 3px right, 10px right y: body.height / 2 - 10 + 10 // 10px down }); var rleg = self.attachAsset('playerLeg', { anchorX: 0.5, anchorY: 0, x: body.width / 4 - 10 - 3, // move 3px left (total 6px closer) // 10px left, 3px left y: body.height / 2 - 10 + 10 // 10px down }); // Gun (in hand) var gun = self.attachAsset('playerGun', { anchorX: 0.5, // 0.5 yerine 0.5 anchorY: 0.5, x: rarm.x - 20, //{2m} // 20px left y: rarm.y + rarm.height + 5, //{2n} // 5px down rotation: 0 }); // Holster gun (on hip, always visible unless drawn) var hipGun = self.attachAsset('playerGun', { anchorX: 0.5, anchorY: 0.5, x: body.width / 2 + -28, y: body.height / 2 - -12, rotation: Math.PI / 2 }); // Start with gun in holster, hand empty gun.visible = false; hipGun.visible = true; // Helper methods to switch gun/holster visibility self.toHolster = function () { hipGun.visible = true; gun.visible = false; }; self.toHand = function () { hipGun.visible = false; gun.visible = true; }; // Used for ragdoll effect self.bodyParts = [body, head, larm, rarm, lleg, rleg, gun]; // Used for aiming self.gun = gun; self.rarm = rarm; // Used for hit detection self.head = head; self.body = body; // Used for ragdoll self.ragdoll = function (hitX, hitY) { // Animate all parts to random positions/rotations for (var i = 0; i < self.bodyParts.length; i++) { var part = self.bodyParts[i]; tween(part, { rotation: (Math.random() - 0.5) * 2.5, x: part.x + (Math.random() - 0.5) * 200, y: part.y + (Math.random() - 0.5) * 200 }, { duration: 600, delay: i * 30, easing: tween.elasticOut }); } }; // Reset pose self.resetPose = function () { // Body body.x = -12; body.y = 10; body.rotation = 0; // Head (use constant offset, not accumulated) head.x = 0; head.y = -body.height / 2 - 45; // SABİT offset, yukarı kayma engellendi head.rotation = 0; // Left Arm larm.x = -body.width / 2 - 10 - 5 + 5 + 7 + 3; larm.y = -100 + 10 + 10 + 15; larm.rotation = 0; // Move left arm behind body in display list if (typeof self.setChildIndex === "function") { self.setChildIndex(larm, 0); } // Right Arm rarm.x = body.width / 2 + 10 - 5 - 20 - 5 - 10; rarm.y = -100 + 10 + 15 + 10; rarm.rotation = 0; // Left Leg lleg.x = -body.width / 4 - 10 + 3 + 10; lleg.y = body.height / 2 - 10 + 10; lleg.rotation = 0; // Right Leg rleg.x = body.width / 4 - 10 - 3; rleg.y = body.height / 2 - 10 + 10; rleg.rotation = 0; // Gun (in hand) gun.x = rarm.x - 20; gun.y = rarm.y + rarm.height + 5; gun.rotation = 0; // Move bottomthingplayer 6px further down and 5px left if (typeof bottomthingplayer !== "undefined") { bottomthingplayer.x = -5; bottomthingplayer.y = 101; } self.toHolster(); // always start with gun in holster }; return self; }); /**** * Initialize Game ****/ var game = new LK.Game({ backgroundColor: 0x2e1a09 }); /**** * Game Code ****/ // Player and enemy bodies (torso, head, arms, legs) as simple shapes // Game state var STATE_WAIT = 0; var STATE_DRAW = 1; var STATE_SHOT = 2; var STATE_RESULT = 3; var duelState = STATE_WAIT; var canShoot = false; var playerShot = false; var enemyShot = false; var playerBullet = null; var enemyBullet = null; var duelTimer = null; var drawTimeout = null; var resultTimeout = null; var duelStartTime = 0; var playerShotTime = 0; var enemyShotTime = 0; var aimX = 0; var aimY = 0; var aimRadius = 180; var aimAngle = 0; var aimSpeed = 0.018; // %30 daha yavaş var aimDir = 1; var aimActive = false; var playerScore = 0; var roundNum = 1; var maxRounds = 5; // === GLOBAL SHOT COUNTERS === var playerShotsFiredCnt = 0; var enemyShotsFiredCnt = 0; // === PLAYER SHOT COOLDOWN === var playerShotCooldown = 0; // ms left until next shot allowed var enemyData = [{ name: "Greenhorn", minReact: 3.0, maxReact: 3.4, coneStart: 42, coneEnd: 0 }, { name: "Billy the Kid", minReact: 2.4, maxReact: 2.7, coneStart: 36, coneEnd: 0 }, { name: "Doc Holliday", minReact: 1.6, maxReact: 1.9, coneStart: 30, coneEnd: 0 }, { name: "Calamity Jane", minReact: 1.0, maxReact: 1.3, coneStart: 24, coneEnd: 0 }, { name: "The Undertaker", minReact: 0.6, maxReact: 0.8, coneStart: 18, coneEnd: 0, scale: 1.5 }]; var currentEnemy = null; // ==== FIX #1 : SAHNENİN BAŞINDA ==== var world = new Container(); game.addChild(world); // Add arkaplan background image centered in the screen var arkaplan = LK.getAsset('arkaplan', { anchorX: 0.5, anchorY: 0.5 }); arkaplan.x = 2048 / 2; arkaplan.y = 2732 / 2; world.addChild(arkaplan); // Player and enemy var player = new Player(); var enemy = new Enemy(); world.addChild(player); world.addChild(enemy); // Center positions var centerX = 2048 / 2; var playerY = 2732 * 0.7; var enemyY = 2732 * 0.3; // 6-bullet UI for player and enemy var playerAmmoUI = []; var enemyAmmoUI = []; function setupAmmoUI() { // Remove old UI if any for (var i = 0; i < playerAmmoUI.length; i++) { if (playerAmmoUI[i].parent) { playerAmmoUI[i].parent.removeChild(playerAmmoUI[i]); } } for (var i = 0; i < enemyAmmoUI.length; i++) { if (enemyAmmoUI[i].parent) { enemyAmmoUI[i].parent.removeChild(enemyAmmoUI[i]); } } playerAmmoUI = []; enemyAmmoUI = []; for (var i = 0; i < 6; i++) { var px = player.x - 120 + i * 40; var ex = enemy.x - 120 + i * 40; var bulletP = LK.getAsset('bullet', { scaleX: 0.6, scaleY: 0.6, anchorX: 0.5, anchorY: 0.5 }); var bulletE = LK.getAsset('bullet', { scaleX: 0.6, scaleY: 0.6, anchorX: 0.5, anchorY: 0.5 }); bulletP.y = player.y + 250 + 60; // Düşman: yalnızca 5. raundda 75 px yukarı var enemyExtraY = roundNum === 5 ? -75 : 0; bulletE.y = enemy.y - 250 + enemyExtraY; bulletP.x = px; bulletE.x = ex; // HUD’u world içine koy ki zoom animasyonuyla birlikte büyüsün world.addChild(bulletP); world.addChild(bulletE); playerAmmoUI.push(bulletP); enemyAmmoUI.push(bulletE); } } // Position player and enemy player.x = centerX - 500; player.y = playerY; enemy.x = centerX + 500; enemy.y = enemyY; // Score text var scoreTxt = new Text2('Score: 0', { size: 90, fill: 0xFFF7D6 }); scoreTxt.anchor.set(0.5, 0); LK.gui.top.addChild(scoreTxt); // Round text var roundTxt = new Text2('', { size: 70, fill: 0xFFE4B5 }); roundTxt.anchor.set(0.5, 0); LK.gui.top.addChild(roundTxt); roundTxt.y = 110; // Duel status text var statusTxt = new Text2('', { size: 110, fill: 0xFFECB3 }); statusTxt.anchor.set(0.5, 0.5); LK.gui.center.addChild(statusTxt); // (Aim reticle removed) // Helper: reset all state for a new round function resetDuel() { duelState = STATE_WAIT; canShoot = false; playerShot = false; enemyShot = false; playerBullet = null; enemyBullet = null; duelStartTime = 0; playerShotTime = 0; enemyShotTime = 0; aimAngle = Math.random() * Math.PI * 2; aimDir = Math.random() > 0.5 ? 1 : -1; aimActive = false; // (aimReticle removed) player.resetPose(); enemy.resetPose(); player.toHolster(); enemy.toHolster(); holsterFull.visible = true; holsterEmpty.visible = false; setupAmmoUI(); // === RESET SHOT COUNTERS === playerShotsFiredCnt = 0; enemyShotsFiredCnt = 0; // === RESET HOLSTER FLAGS === playerReady = false; // yeni ← round’a nötr gir holdingHolster = false; // yeni holsterHoldTime = 0; // güvenlik duelStarted = false; // <-- YENİ: her raunda nötr gir cancelHold(); // 1. raunddan kalma musicTimeout varsa temizle zoomFinished = false; // yeni animasyona hazırlan var roundMusic = roundNum === 1 ? 'duelmusic' : roundNum === 2 ? 'round2music' : roundNum === 3 ? 'round3music' : roundNum === 4 ? 'round4music' : 'round5music'; startZoomIn(roundMusic); statusTxt.setText("Wait for the music..."); statusTxt.visible = true; // Set up enemy for this round currentEnemy = enemyData[roundNum - 1]; roundTxt.setText("Round " + roundNum + ": " + currentEnemy.name); // Boss scale (if any) enemy.scaleX = enemy.scaleY = currentEnemy.scale || 1; // Play duel music // (music now only plays during zoom-in, do not play here) // drawTimeout is now set after zoom-in, not here if (drawTimeout) { LK.clearTimeout(drawTimeout); } } // Start the "DRAW!" phase function startDraw() { if (!playerReady) { earlyLose(); return; } duelState = STATE_DRAW; canShoot = true; aimActive = true; // (aimReticle removed) statusTxt.setText("DRAW!"); statusTxt.visible = true; LK.stopMusic(); LK.getSound('draw').play(); player.toHand(); // draw from holster enemy.toHand(); // enemy draws too aimEnemyGunAt(player.x, player.y - 40); // hemen hizala duelStartTime = Date.now(); // Enemy will shoot after their reaction time var reactTime = 1200; // fallback default if (currentEnemy && typeof currentEnemy.minReact === "number" && typeof currentEnemy.maxReact === "number") { reactTime = 1000 * (currentEnemy.minReact + Math.random() * (currentEnemy.maxReact - currentEnemy.minReact)); reactTime *= 0.425; // %57.5 daha hızlı tepki (eski 0.5 ve yeni 0.35 arası) } if (duelTimer) { LK.clearTimeout(duelTimer); } duelTimer = LK.setTimeout(enemyFire, reactTime); } // Player fires function playerFire(x, y) { // 6 mermi limiti if (playerShotsFiredCnt >= 6) { statusTxt.setText("Out of ammo!"); statusTxt.visible = true; return; } if (!canShoot) { return; } // === COOLDOWN: Block if cooldown active === if (playerShotCooldown > 0) { return; } playerShot = true; playerShotsFiredCnt++; // sayaç ↑ playerShotTime = Date.now(); // === COOLDOWN: Set cooldown after shot === playerShotCooldown = 300; // 0.3 seconds in ms // Animate gun tween(player.gun, { rotation: -0.5 }, { duration: 80, easing: tween.cubicOut }); tween(player.rarm, { rotation: -0.3 }, { duration: 80, easing: tween.cubicOut }); LK.getSound('gunshot').play(); // Create bullet playerBullet = new Bullet(); // Muzzle position helper function muzzlePos() { // Use full gun width/height for muzzle (center anchor) var gunW = player.gun.width || 60; var gunH = player.gun.height || 15; return { x: player.x + player.gun.x + Math.cos(player.gun.rotation) * gunW, y: player.y + player.gun.y + Math.sin(player.gun.rotation) * gunH }; } var muzzle = muzzlePos(); playerBullet.x = muzzle.x; playerBullet.y = muzzle.y; // Ateş anındaki imleç (x, y) fonksiyona zaten parametre olarak geliyor var dx = x - muzzle.x; var dy = y - muzzle.y; var dist = Math.sqrt(dx * dx + dy * dy); playerBullet.dirX = dx / dist; playerBullet.dirY = dy / dist; playerBullet.speed = 60; game.addChild(playerBullet); // Decrement player bullet UI for (var i = 0; i < playerAmmoUI.length; i++) { if (playerAmmoUI[i].visible !== false) { playerAmmoUI[i].visible = false; break; } } // If shot before draw, instant loss if (duelState === STATE_WAIT) { duelState = STATE_RESULT; statusTxt.setText("You shot too early!\nYou Lose!"); statusTxt.visible = true; LK.getSound('fail').play(); player.ragdoll(); enemy.ragdoll(); endRound(false); return; } // bir sonraki mermiye izin ver if (duelState === STATE_DRAW && playerShotsFiredCnt < 6) { canShoot = true; aimActive = true; // (aimReticle removed) } // DRAW fazı açık kalsın ki 6 mermi bitene kadar tekrar tekrar ateş edebilelim } // Enemy fires function enemyFire() { var _currentEnemy; if (enemyShotsFiredCnt >= 6 || duelState === STATE_RESULT) { return; } if (!enemyShot) { enemyShot = true; } // ilk mermi için eski bayrak enemyShotsFiredCnt++; enemyShotTime = Date.now(); // Ateşten önce hedefe çevir aimEnemyGunAt(player.x, player.y - 40); // Animate gun tween(enemy.gun, { rotation: 0.5 }, { duration: 80, easing: tween.cubicOut }); tween(enemy.rarm, { rotation: 0.3 }, { duration: 80, easing: tween.cubicOut }); LK.getSound('gunshot').play(); // Create bullet enemyBullet = new Bullet(); var gunW = enemy.gun.width || 60; var gunH = enemy.gun.height || 18; enemyBullet.x = enemy.x + enemy.gun.x + Math.cos(enemy.gun.rotation) * gunW; enemyBullet.y = enemy.y + enemy.gun.y + Math.sin(enemy.gun.rotation) * gunH; // Decrement enemy bullet UI for (var i = 0; i < enemyAmmoUI.length; i++) { if (enemyAmmoUI[i].visible !== false) { enemyAmmoUI[i].visible = false; break; } } // Konik isabet modeliyle hedef seçimi // 1️⃣ hedef vektörü var baseDX = player.x - enemyBullet.x; var baseDY = player.y - 40 - enemyBullet.y; // gövdenin biraz üstü var baseAng = Math.atan2(baseDY, baseDX); // 2️⃣ saçılma açısı (daralan koni) var sIdx = enemyShotsFiredCnt; // 1-6 var sMax = 6; var cStart = currentEnemy && typeof currentEnemy.coneStart === "number" ? currentEnemy.coneStart : 24; // fallback var cEnd = currentEnemy && typeof currentEnemy.coneEnd === "number" ? currentEnemy.coneEnd : 0; var coneDeg = cStart + (cEnd - cStart) * ((sIdx - 1) / (sMax - 1)); var coneRad = coneDeg * Math.PI / 180; var isLast = sIdx === sMax; // son kurşun var spread = isLast ? 0 : Math.random() * coneRad - coneRad / 2; // 3️⃣ hedef noktasını diagramdaki yayı keserek bul var range = 1500; // ekran dışına yeter var targetX = enemyBullet.x + Math.cos(baseAng + spread) * range; var targetY = enemyBullet.y + Math.sin(baseAng + spread) * range; var dx = targetX - enemyBullet.x; var dy = targetY - enemyBullet.y; var dist = Math.sqrt(dx * dx + dy * dy); enemyBullet.dirX = dx / dist; enemyBullet.dirY = dy / dist; enemyBullet.speed = 60; game.addChild(enemyBullet); // sonraki mermi (tak-tak) 0.6 sn sonra if (enemyShotsFiredCnt < 6) { LK.setTimeout(enemyFire, 600); } // Round oyuncu ateş etmediyse ANCAK bütün 6 mermi atıldıktan sonra if (enemyShotsFiredCnt === 6 && !playerShot) { // Son kurşunun ekrandan çıkması için çok kısa bir gecikme bırak LK.setTimeout(function () { if (duelState !== STATE_RESULT) { duelState = STATE_RESULT; statusTxt.setText("You Survived!"); statusTxt.visible = true; endRound(true); } }, 350); } } // End round, win: true/false function endRound(win) { canShoot = false; aimActive = false; // (aimReticle removed) if (drawTimeout) { LK.clearTimeout(drawTimeout); } if (duelTimer) { LK.clearTimeout(duelTimer); } if (resultTimeout) { LK.clearTimeout(resultTimeout); } drawTimeout = null; duelTimer = null; resultTimeout = null; // Next round or end game resultTimeout = LK.setTimeout(function () { if (win) { playerScore += 1; scoreTxt.setText("Score: " + playerScore); roundNum += 1; if (roundNum > maxRounds) { statusTxt.setText("You Win!\nScore: " + playerScore); statusTxt.visible = true; LK.showYouWin(); } else { resetDuel(); } } else { statusTxt.setText("You Lose!\nScore: " + playerScore); statusTxt.visible = true; LK.showGameOver(); } }, 1600); } // Handle aiming reticle movement (circular motion) function updateAimReticle() { // (aim reticle removed) } // Handle bullet collisions and duel result function checkBullets() { // Player bullet hits enemy if (playerBullet) { // Check if bullet reached enemy var dx = playerBullet.x - enemy.x; var dy = playerBullet.y - enemy.y; var dist = Math.sqrt(dx * dx + dy * dy); if (dist < 120) { // Hit! LK.getSound('hit').play(); enemy.ragdoll(playerBullet.x, playerBullet.y); game.removeChild(playerBullet); playerBullet = null; duelState = STATE_RESULT; statusTxt.setText("You Win the Duel!"); statusTxt.visible = true; endRound(true); } } // Enemy bullet hits player if (enemyBullet) { var dx = enemyBullet.x - player.x; var dy = enemyBullet.y - player.y; var dist = Math.sqrt(dx * dx + dy * dy); if (dist < 120) { LK.getSound('hit').play(); player.ragdoll(enemyBullet.x, enemyBullet.y); game.removeChild(enemyBullet); enemyBullet = null; duelState = STATE_RESULT; statusTxt.setText("You were shot!"); statusTxt.visible = true; endRound(false); } } // Both bullets exist (simultaneous fire) if (playerBullet && enemyBullet) { // Check which bullet hits first var pdx = playerBullet.x - enemy.x; var pdy = playerBullet.y - enemy.y; var pdist = Math.sqrt(pdx * pdx + pdy * pdy); var edx = enemyBullet.x - player.x; var edy = enemyBullet.y - player.y; var edist = Math.sqrt(edx * edx + edy * edy); if (pdist < 120 && edist < 120) { // Both hit at same time: draw LK.getSound('hit').play(); player.ragdoll(); enemy.ragdoll(); game.removeChild(playerBullet); game.removeChild(enemyBullet); playerBullet = null; enemyBullet = null; duelState = STATE_RESULT; statusTxt.setText("Draw!\nBoth shot!"); statusTxt.visible = true; endRound(false); } else if (pdist < 120) { LK.getSound('hit').play(); enemy.ragdoll(playerBullet.x, playerBullet.y); game.removeChild(playerBullet); playerBullet = null; duelState = STATE_RESULT; statusTxt.setText("You Win the Duel!"); statusTxt.visible = true; endRound(true); } else if (edist < 120) { LK.getSound('hit').play(); player.ragdoll(enemyBullet.x, enemyBullet.y); game.removeChild(enemyBullet); enemyBullet = null; duelState = STATE_RESULT; statusTxt.setText("You were shot!"); statusTxt.visible = true; endRound(false); } } // Remove bullets if off screen if (playerBullet && (playerBullet.x < 0 || playerBullet.x > 2048 || playerBullet.y < 0 || playerBullet.y > 2732)) { game.removeChild(playerBullet); playerBullet = null; // === COOLDOWN: Only allow new shot if cooldown expired === if (playerShotCooldown <= 0) { canShoot = true; } } if (enemyBullet && (enemyBullet.x < 0 || enemyBullet.x > 2048 || enemyBullet.y < 0 || enemyBullet.y > 2732)) { game.removeChild(enemyBullet); enemyBullet = null; enemyShot = false; // yeni atışa izin ver } } // ==== FIX #2 : holster konumu ==== // Use two holster sprites: full and empty, toggle visibility var holsterFull = LK.getAsset('kilif', { width: 300, height: 300, anchorX: -0.2, anchorY: 0 }); var holsterEmpty = LK.getAsset('kilifbos', { width: 300, height: 300, anchorX: -0.2, anchorY: 0 }); holsterFull.visible = true; holsterEmpty.visible = false; var holsterOffset = 250; holsterFull.x = player.x - 200; holsterFull.y = player.y + player.body.height / 2 + holsterOffset; holsterEmpty.x = holsterFull.x; holsterEmpty.y = holsterFull.y; world.addChild(holsterFull); world.addChild(holsterEmpty); // === Place enemygunpocket in the middle of the screen === var enemygunpocket = LK.getAsset('enemygunpocket', { anchorX: 0.5, anchorY: 0.5 }); enemygunpocket.x = 2048 / 2 + 550 + 50 - 30 - 5 - 5; enemygunpocket.y = 2732 / 2 - 600 + 150 + 10; world.addChild(enemygunpocket); // Helper: check if global point is inside holsterFull's bounds (always use holsterFull for hit area) function holsterContains(globalX, globalY) { // Global (stage) → world-içi (local) koordinata çevir var localX = (globalX - world.x) / world.scaleX; var localY = (globalY - world.y) / world.scaleY; // kilif sprite’ının local kutusu var b = { x: holsterFull.x, y: holsterFull.y, w: holsterFull.width, h: holsterFull.height }; return localX >= b.x && localX <= b.x + b.w && localY >= b.y && localY <= b.y + b.h; } // Holster hold/zoom/duel state var zoomFinished = false, duelStarted = false, holsterHoldTime = 0; var playerReady = false; var holdingHolster = false; // Track if holster is being held var musicTimeout = null; // Timer for music/draw phase function beginHold() { if (musicTimeout) { LK.clearTimeout(musicTimeout); } var extra = 4000 + Math.random() * 6000; // 4-10 s musicTimeout = LK.setTimeout(stopMusicAndDraw, extra); } function cancelHold() { if (musicTimeout) { LK.clearTimeout(musicTimeout); } musicTimeout = null; } function stopMusicAndDraw() { LK.stopMusic(); // müzik sustu if (enemygunpocket && enemygunpocket.visible !== false) { enemygunpocket.visible = false; } duelStarted = true; // <-- ERKEN KAYIP KONTROLÜ ARTIK PASİF holsterHoldTime = 0; // güvenlik: sayaç sıfırla startDraw(); // DRAW fazına geç } // Touch/click to fire or start holster hold game.down = function (x, y, obj) { // No fire until duel actually started if (zoomFinished && !duelStarted && holsterContains(x, y)) { playerReady = true; holdingHolster = true; // holster pressed player.toHand(); // ← kılıftan çek (hemen ele ver) // Play holdsound when gun is drawn to hand LK.getSound('holdsound').play(); // --- holster assetini kilifbos ile değiştir (Çözüm 2: iki sprite, görünürlük) --- holsterFull.visible = false; holsterEmpty.visible = true; pointGunAt(x, y); // artık global ve erişilebilir beginHold(); // HOLD başlatılırken zamanlayıcı kuruluyor return; } if (!duelStarted) { return; } if (duelState === STATE_DRAW && canShoot && aimActive) { playerFire(x, y); } else if (duelState === STATE_WAIT && !playerShot) { playerFire(x, y); } }; // On pointer up, check for early leave from holster // ==== FIX #4c : tetikleyici ===== game.up = function (x, y, obj) { // HOLSTER EARLY RELEASE CHECK holdingHolster = false; // holster released cancelHold(); // el çekildi, zamanlayıcı iptal if (!duelStarted && playerReady && duelState === STATE_WAIT) { earlyLose(); return; } if (duelState === STATE_DRAW && canShoot) { playerFire(x, y); return; } }; // Move reticle with finger (optional: drag to aim) game.move = function (x, y, obj) { // Holster hold/early leave logic if (zoomFinished && !duelStarted) { if (holsterContains(x, y)) { // handled in update for dt-accurate timing } else if (holdingHolster) { cancelHold(); earlyLose(); } } // (aim reticle removed) // Namlu her zaman fareyi izlesin (DRAW’tan önce holster tutulurken de) pointGunAt(x, y); }; /**** GLOBAL YARDIMCI : Silahı imlece çevir ****/ function pointGunAt(globalX, globalY) { // 1) Omuz pivotu – dünya koordinatı var shX = player.x + player.rarm.x; var shY = player.y + player.rarm.y; // 2) Omuz → imleç vektörü ve açı (0 rad = sağ) var dx = globalX - shX; var dy = globalY - shY; var ang = Math.atan2(dy, dx); // 3) Kol sprite’ı AŞAĞI bakıyor → –90° (-π/2) düzelt var armRot = ang - Math.PI / 2; player.rarm.rotation = armRot; // 4) El / silah konumu (player LOCAL) var L = player.rarm.height; // ≈110 px // ❗️ Doğru ofset: (-sin, +cos) player.gun.x = player.rarm.x - Math.sin(armRot) * L; player.gun.y = player.rarm.y + Math.cos(armRot) * L; // 5) Silah namlu yönü if (!duelStarted && holsterContains(globalX, globalY)) { player.gun.rotation = Math.PI / 2; // kılıfta ⇒ namlu yere } else { player.gun.rotation = ang; // hedefe bak } } /**** GLOBAL : Enemy silahını hedefe çevir ****/ function aimEnemyGunAt(globalX, globalY) { // 1) Omuz pivotu (world) var shX = enemy.x + enemy.rarm.x; var shY = enemy.y + enemy.rarm.y; // 2) Omuz→hedef vektörü var dx = globalX - shX; var dy = globalY - shY; var ang = Math.atan2(dy, dx); // 3) Kol sprite’ı AŞAĞI bakıyor → –90° (–π/2) düzeltme var armRot = ang - Math.PI / 2; enemy.rarm.rotation = armRot; // 4) El / silah konumu (enemy LOCAL) **ayna!** var L = enemy.rarm.height; // ≈145 px enemy.gun.x = enemy.rarm.x - Math.sin(armRot) * L; enemy.gun.y = enemy.rarm.y + Math.cos(armRot) * L; // 5) Namluyu hedefe döndür enemy.gun.rotation = ang; } // Helper to update holster box position under player's feet function updateHolsterBox() { holsterFull.x = player.x - 200; holsterFull.y = player.y + player.body.height / 2 + holsterOffset; holsterEmpty.x = holsterFull.x; holsterEmpty.y = holsterFull.y; } // Main update loop game.update = function (dt) { // Move enemygun assets in pocket 5px down and 10px left when music is not stopped if (LK.musicPlaying && enemygunpocket) { enemygunpocket.x = 2048 / 2 + 550 + 50 - 30 - 5 - 5 - 10; // 10px more left enemygunpocket.y = 2732 / 2 - 600 + 150 + 10 + 5; // 5px more down } // Hide status text during zoom-in if (!zoomFinished) { statusTxt.visible = false; return; } // Always update holster position to follow player updateHolsterBox(); // (aim reticle removed) // Rakip silahı eldeyse oyuncuyu takip etsin if (enemy.gun.visible) { aimEnemyGunAt(player.x, player.y - 40); } // Animate bullets if (playerBullet) { playerBullet.update(); } if (enemyBullet) { enemyBullet.update(); } // === PLAYER SHOT COOLDOWN TIMER === if (playerShotCooldown > 0) { var dtMs = typeof dt === "number" ? dt : 16.7; playerShotCooldown -= dtMs; if (playerShotCooldown < 0) { playerShotCooldown = 0; } } // Check for bullet collisions checkBullets(); // ==== FIX #3 : statusTxt ayarı ==== // Holster hold logic after zoom if (zoomFinished && !duelStarted) { // Use pointerX/Y for current pointer var px = typeof game.pointerX === "number" ? game.pointerX : 0; var py = typeof game.pointerY === "number" ? game.pointerY : 0; var dtMs = typeof dt === "number" ? dt : 16.7; if (holsterContains(px, py) && holdingHolster) { holsterHoldTime += dtMs; } else { if (holsterHoldTime > 0 && holdingHolster) { earlyLose(); } holsterHoldTime = 0; } if (!duelStarted) { if (!playerReady) { statusTxt.setText("Hold holster to start duel"); } else { statusTxt.setText("Wait for music to end…\nthen aim & release"); } } // if (holsterHoldTime >= 4000 && !duelStarted) { // duelStarted = true; // holsterHoldTime = 0; // resetDuel(); // } statusTxt.visible = true; } else { if (duelState === STATE_DRAW) { statusTxt.setText("Aim and SHOOT!"); } statusTxt.visible = true; } }; // Early lose function function earlyLose() { if (duelState === STATE_RESULT) { return; } // only trigger once holdingHolster = false; holsterHoldTime = 0; duelStarted = false; LK.stopMusic(); if (drawTimeout) { LK.clearTimeout(drawTimeout); } statusTxt.setText("Too early!\nYou lose!"); statusTxt.visible = true; LK.getSound('gunshot').play(); // yere ateş LK.getSound('fail').play(); // Optionally, also play gunshot sound: // LK.getSound('gunshot').play(); // Show game over after 1 second LK.setTimeout(function () { LK.showGameOver(); }, 1000); } // Camera zoom-in before duel starts function startZoomIn(music) { // ==== FIX #1b : zoom fonksiyonunda ==== var startScale = 0.4; world.scaleX = world.scaleY = startScale; world.x = centerX * (1 - startScale); world.y = 2732 / 2 * (1 - startScale); LK.playMusic(music || 'duelmusic'); tween(world, { scaleX: 1, scaleY: 1, x: 0, y: 0 }, { duration: 4000, easing: tween.quadInOut, onFinish: function onFinish() { zoomFinished = true; } }); } // Start zoom-in and music immediately on boot startZoomIn();
/****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
/****
* Classes
****/
// Bullet class
var Bullet = Container.expand(function () {
var self = Container.call(this);
var bullet = self.attachAsset('bullet', {
anchorX: 0.5,
anchorY: 0.5
});
self.speed = 0;
self.dirX = 0;
self.dirY = 0;
self.update = function () {
self.x += self.dirX * self.speed;
self.y += self.dirY * self.speed;
};
return self;
});
// Enemy class
var Enemy = Container.expand(function () {
var self = Container.call(this);
// Attach enemybottomthing as a child of enemy
var enemybottomthing = self.attachAsset('bottomthingenemy', {
anchorX: 0.5,
anchorY: 0.5,
x: 0,
y: 80 // moved 10px up from previous (90 -> 80)
});
// Legs
var lleg = self.attachAsset('enemyLeg', {
anchorX: 0.5,
anchorY: 0,
x: -140 / 4 + 5 - 5,
// moved 5px left
// use width of enemyBody (140) since body not yet defined
y: 180 / 2 - 15 - 3 // use height of enemyBody (180)
});
var rleg = self.attachAsset('enemyLeg', {
anchorX: 0.5,
anchorY: 0,
x: 140 / 4 + 5 - 5,
// moved 5px left
y: 180 / 2 - 15 - 3
});
// Left Arm
var larm = self.attachAsset('enemyArm', {
anchorX: 0.5,
anchorY: 0,
x: -140 / 2 + 5 + 10,
// use width of enemyBody (140) since body not yet defined
// 10px right
// 5px closer to body, 10px right
y: -80 //{t} // 30px up (10px further down)
});
// Body
var body = self.attachAsset('enemyBody', {
anchorX: 0.5,
anchorY: 0.5,
x: 10,
y: -15 //{h} // 5px daha aşağı, 10px sağa taşındı
});
// Head
var head = self.attachAsset('enemyHead', {
anchorX: 0.5,
anchorY: 0.5,
x: -4 + 5 - 4,
// 4px left from previous
// 5px right, 4px left
y: -body.height / 2 - 45 - 4 - 5 - 4 // 4px up from previous
});
// Right Arm (gun hand)
var rarm = self.attachAsset('enemyArm', {
anchorX: 0.5,
anchorY: 0,
x: body.width / 2 - 5,
// 5px closer to body
y: -80 //{z} // 30px up (10px further down)
});
// Legs
var lleg = self.attachAsset('enemyLeg', {
anchorX: 0.5,
anchorY: 0,
x: -body.width / 4 + 5 - 5 + 3,
// moved 5px left, now 3px right
y: body.height / 2 - 15 - 3 //{z} // 3px up
});
var rleg = self.attachAsset('enemyLeg', {
anchorX: 0.5,
anchorY: 0,
x: body.width / 4 + 5 - 5 + 3,
// moved 5px left, now 3px right
y: body.height / 2 - 15 - 3 //{E} // 3px up
});
// Gun (in hand)
var gun = self.attachAsset('enemyGun', {
anchorX: 0.5,
// 0.5 yerine 0.5
anchorY: 0.5,
x: rarm.x,
y: rarm.y + rarm.height,
rotation: 0
});
// Holster gun (on hip, always visible unless drawn)
var hipGun = self.attachAsset('enemyGun', {
anchorX: 0.5,
anchorY: 0.5,
x: body.width / 2 + 8 - 10 - 5 - 10,
// 5px further left
// 10px further left, 5px more left, 10px more left
y: body.height / 2 - 22 + 15 + 10 + 3,
// 3px further down
// 10px further down, 3px more down
rotation: Math.PI / 2
});
// Start with gun in holster, hand empty
gun.visible = false;
hipGun.visible = true;
// Helper methods to switch gun/holster visibility
self.toHolster = function () {
hipGun.visible = true;
gun.visible = false;
};
self.toHand = function () {
hipGun.visible = false;
gun.visible = true;
};
self.bodyParts = [body, head, larm, rarm, lleg, rleg, gun];
self.gun = gun;
self.rarm = rarm;
self.head = head;
self.body = body;
self.ragdoll = function (hitX, hitY) {
for (var i = 0; i < self.bodyParts.length; i++) {
var part = self.bodyParts[i];
tween(part, {
rotation: (Math.random() - 0.5) * 2.5,
x: part.x + (Math.random() - 0.5) * 200,
y: part.y + (Math.random() - 0.5) * 200
}, {
duration: 600,
delay: i * 30,
easing: tween.elasticOut
});
}
};
self.resetPose = function () {
// Body
body.x = 10;
body.y = -15;
body.rotation = 0;
// Head (use constant offset, not accumulated)
head.x = 0;
head.y = -body.height / 2 - 45; // SABİT offset, yukarı kayma engellendi
head.rotation = 0;
// Left Arm
larm.x = -body.width / 2 + 5 + 10;
larm.y = -80;
larm.rotation = 0;
// Right Arm
rarm.x = body.width / 2 - 5;
rarm.y = -80;
rarm.rotation = 0;
// Left Leg
lleg.x = -body.width / 4 + 5 - 5 + 3;
lleg.y = body.height / 2 - 15 - 3;
lleg.rotation = 0;
// Right Leg
rleg.x = body.width / 4 + 5 - 5 + 3;
rleg.y = body.height / 2 - 15 - 3;
rleg.rotation = 0;
// Gun (in hand)
gun.x = rarm.x;
gun.y = rarm.y + rarm.height;
gun.rotation = 0;
// Hip gun (holster)
if (typeof hipGun !== "undefined") {
hipGun.x = body.width / 2 + 8 - 10 - 5 - 10;
hipGun.y = body.height / 2 - 22 + 15 + 10 + 3;
hipGun.rotation = Math.PI / 2;
}
self.toHolster(); // always start with gun in holster
};
return self;
});
// Player class
var Player = Container.expand(function () {
var self = Container.call(this);
// Head
var head = self.attachAsset('playerHead', {
anchorX: 0.5,
anchorY: 0.5,
x: 0,
y: -160 / 2 - 45 - 20 // use playerBody height (160) since body not yet defined, moved 25px up
});
// Body
var body = self.attachAsset('playerBody', {
anchorX: 0.5,
anchorY: 0.5,
x: -12,
y: 10 //{1U} // moved 15px down, 5px left from previous
});
// bottomthingplayer as child of player, 75px down (moved 15px further down)
var bottomthingplayer = self.attachAsset('bottomthingplayer', {
anchorX: 0.5,
anchorY: 0.5,
x: -5,
//{21} // moved 5px left
y: 101 // moved 6px further down (95+6)
});
// Left Arm
var larm = self.attachAsset('playerArm', {
anchorX: 0.5,
anchorY: 0,
x: -body.width / 2 - 10 - 5 + 5 + 7 + 3,
// move 3px right
// move 5px right, 7px more right, 3px more right
y: -100 + 10 + 10 + 15,
// move 15px down
// move 10px down, 15px more down
zIndex: -1 // ensure arm is behind body (if zIndex is supported)
});
// Move left arm behind body in display list
if (typeof self.setChildIndex === "function") {
self.setChildIndex(larm, 0);
}
// Right Arm (gun hand)
var rarm = self.attachAsset('playerArm', {
anchorX: 0.5,
anchorY: 0,
x: body.width / 2 + 10 - 5 - 20 - 5 - 10,
// 10px more left
y: -100 + 10 + 15 + 10 //{1Y} // 10px more down, 15px more down, 10px more down
});
// Legs
var lleg = self.attachAsset('playerLeg', {
anchorX: 0.5,
anchorY: 0,
x: -body.width / 4 - 10 + 3 + 10,
// moved 10px right
// move 3px right (total 6px closer)
// 10px left, 3px right, 10px right
y: body.height / 2 - 10 + 10 // 10px down
});
var rleg = self.attachAsset('playerLeg', {
anchorX: 0.5,
anchorY: 0,
x: body.width / 4 - 10 - 3,
// move 3px left (total 6px closer)
// 10px left, 3px left
y: body.height / 2 - 10 + 10 // 10px down
});
// Gun (in hand)
var gun = self.attachAsset('playerGun', {
anchorX: 0.5,
// 0.5 yerine 0.5
anchorY: 0.5,
x: rarm.x - 20,
//{2m} // 20px left
y: rarm.y + rarm.height + 5,
//{2n} // 5px down
rotation: 0
});
// Holster gun (on hip, always visible unless drawn)
var hipGun = self.attachAsset('playerGun', {
anchorX: 0.5,
anchorY: 0.5,
x: body.width / 2 + -28,
y: body.height / 2 - -12,
rotation: Math.PI / 2
});
// Start with gun in holster, hand empty
gun.visible = false;
hipGun.visible = true;
// Helper methods to switch gun/holster visibility
self.toHolster = function () {
hipGun.visible = true;
gun.visible = false;
};
self.toHand = function () {
hipGun.visible = false;
gun.visible = true;
};
// Used for ragdoll effect
self.bodyParts = [body, head, larm, rarm, lleg, rleg, gun];
// Used for aiming
self.gun = gun;
self.rarm = rarm;
// Used for hit detection
self.head = head;
self.body = body;
// Used for ragdoll
self.ragdoll = function (hitX, hitY) {
// Animate all parts to random positions/rotations
for (var i = 0; i < self.bodyParts.length; i++) {
var part = self.bodyParts[i];
tween(part, {
rotation: (Math.random() - 0.5) * 2.5,
x: part.x + (Math.random() - 0.5) * 200,
y: part.y + (Math.random() - 0.5) * 200
}, {
duration: 600,
delay: i * 30,
easing: tween.elasticOut
});
}
};
// Reset pose
self.resetPose = function () {
// Body
body.x = -12;
body.y = 10;
body.rotation = 0;
// Head (use constant offset, not accumulated)
head.x = 0;
head.y = -body.height / 2 - 45; // SABİT offset, yukarı kayma engellendi
head.rotation = 0;
// Left Arm
larm.x = -body.width / 2 - 10 - 5 + 5 + 7 + 3;
larm.y = -100 + 10 + 10 + 15;
larm.rotation = 0;
// Move left arm behind body in display list
if (typeof self.setChildIndex === "function") {
self.setChildIndex(larm, 0);
}
// Right Arm
rarm.x = body.width / 2 + 10 - 5 - 20 - 5 - 10;
rarm.y = -100 + 10 + 15 + 10;
rarm.rotation = 0;
// Left Leg
lleg.x = -body.width / 4 - 10 + 3 + 10;
lleg.y = body.height / 2 - 10 + 10;
lleg.rotation = 0;
// Right Leg
rleg.x = body.width / 4 - 10 - 3;
rleg.y = body.height / 2 - 10 + 10;
rleg.rotation = 0;
// Gun (in hand)
gun.x = rarm.x - 20;
gun.y = rarm.y + rarm.height + 5;
gun.rotation = 0;
// Move bottomthingplayer 6px further down and 5px left
if (typeof bottomthingplayer !== "undefined") {
bottomthingplayer.x = -5;
bottomthingplayer.y = 101;
}
self.toHolster(); // always start with gun in holster
};
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0x2e1a09
});
/****
* Game Code
****/
// Player and enemy bodies (torso, head, arms, legs) as simple shapes
// Game state
var STATE_WAIT = 0;
var STATE_DRAW = 1;
var STATE_SHOT = 2;
var STATE_RESULT = 3;
var duelState = STATE_WAIT;
var canShoot = false;
var playerShot = false;
var enemyShot = false;
var playerBullet = null;
var enemyBullet = null;
var duelTimer = null;
var drawTimeout = null;
var resultTimeout = null;
var duelStartTime = 0;
var playerShotTime = 0;
var enemyShotTime = 0;
var aimX = 0;
var aimY = 0;
var aimRadius = 180;
var aimAngle = 0;
var aimSpeed = 0.018; // %30 daha yavaş
var aimDir = 1;
var aimActive = false;
var playerScore = 0;
var roundNum = 1;
var maxRounds = 5;
// === GLOBAL SHOT COUNTERS ===
var playerShotsFiredCnt = 0;
var enemyShotsFiredCnt = 0;
// === PLAYER SHOT COOLDOWN ===
var playerShotCooldown = 0; // ms left until next shot allowed
var enemyData = [{
name: "Greenhorn",
minReact: 3.0,
maxReact: 3.4,
coneStart: 42,
coneEnd: 0
}, {
name: "Billy the Kid",
minReact: 2.4,
maxReact: 2.7,
coneStart: 36,
coneEnd: 0
}, {
name: "Doc Holliday",
minReact: 1.6,
maxReact: 1.9,
coneStart: 30,
coneEnd: 0
}, {
name: "Calamity Jane",
minReact: 1.0,
maxReact: 1.3,
coneStart: 24,
coneEnd: 0
}, {
name: "The Undertaker",
minReact: 0.6,
maxReact: 0.8,
coneStart: 18,
coneEnd: 0,
scale: 1.5
}];
var currentEnemy = null;
// ==== FIX #1 : SAHNENİN BAŞINDA ====
var world = new Container();
game.addChild(world);
// Add arkaplan background image centered in the screen
var arkaplan = LK.getAsset('arkaplan', {
anchorX: 0.5,
anchorY: 0.5
});
arkaplan.x = 2048 / 2;
arkaplan.y = 2732 / 2;
world.addChild(arkaplan);
// Player and enemy
var player = new Player();
var enemy = new Enemy();
world.addChild(player);
world.addChild(enemy);
// Center positions
var centerX = 2048 / 2;
var playerY = 2732 * 0.7;
var enemyY = 2732 * 0.3;
// 6-bullet UI for player and enemy
var playerAmmoUI = [];
var enemyAmmoUI = [];
function setupAmmoUI() {
// Remove old UI if any
for (var i = 0; i < playerAmmoUI.length; i++) {
if (playerAmmoUI[i].parent) {
playerAmmoUI[i].parent.removeChild(playerAmmoUI[i]);
}
}
for (var i = 0; i < enemyAmmoUI.length; i++) {
if (enemyAmmoUI[i].parent) {
enemyAmmoUI[i].parent.removeChild(enemyAmmoUI[i]);
}
}
playerAmmoUI = [];
enemyAmmoUI = [];
for (var i = 0; i < 6; i++) {
var px = player.x - 120 + i * 40;
var ex = enemy.x - 120 + i * 40;
var bulletP = LK.getAsset('bullet', {
scaleX: 0.6,
scaleY: 0.6,
anchorX: 0.5,
anchorY: 0.5
});
var bulletE = LK.getAsset('bullet', {
scaleX: 0.6,
scaleY: 0.6,
anchorX: 0.5,
anchorY: 0.5
});
bulletP.y = player.y + 250 + 60;
// Düşman: yalnızca 5. raundda 75 px yukarı
var enemyExtraY = roundNum === 5 ? -75 : 0;
bulletE.y = enemy.y - 250 + enemyExtraY;
bulletP.x = px;
bulletE.x = ex;
// HUD’u world içine koy ki zoom animasyonuyla birlikte büyüsün
world.addChild(bulletP);
world.addChild(bulletE);
playerAmmoUI.push(bulletP);
enemyAmmoUI.push(bulletE);
}
}
// Position player and enemy
player.x = centerX - 500;
player.y = playerY;
enemy.x = centerX + 500;
enemy.y = enemyY;
// Score text
var scoreTxt = new Text2('Score: 0', {
size: 90,
fill: 0xFFF7D6
});
scoreTxt.anchor.set(0.5, 0);
LK.gui.top.addChild(scoreTxt);
// Round text
var roundTxt = new Text2('', {
size: 70,
fill: 0xFFE4B5
});
roundTxt.anchor.set(0.5, 0);
LK.gui.top.addChild(roundTxt);
roundTxt.y = 110;
// Duel status text
var statusTxt = new Text2('', {
size: 110,
fill: 0xFFECB3
});
statusTxt.anchor.set(0.5, 0.5);
LK.gui.center.addChild(statusTxt);
// (Aim reticle removed)
// Helper: reset all state for a new round
function resetDuel() {
duelState = STATE_WAIT;
canShoot = false;
playerShot = false;
enemyShot = false;
playerBullet = null;
enemyBullet = null;
duelStartTime = 0;
playerShotTime = 0;
enemyShotTime = 0;
aimAngle = Math.random() * Math.PI * 2;
aimDir = Math.random() > 0.5 ? 1 : -1;
aimActive = false;
// (aimReticle removed)
player.resetPose();
enemy.resetPose();
player.toHolster();
enemy.toHolster();
holsterFull.visible = true;
holsterEmpty.visible = false;
setupAmmoUI();
// === RESET SHOT COUNTERS ===
playerShotsFiredCnt = 0;
enemyShotsFiredCnt = 0;
// === RESET HOLSTER FLAGS ===
playerReady = false; // yeni ← round’a nötr gir
holdingHolster = false; // yeni
holsterHoldTime = 0; // güvenlik
duelStarted = false; // <-- YENİ: her raunda nötr gir
cancelHold(); // 1. raunddan kalma musicTimeout varsa temizle
zoomFinished = false; // yeni animasyona hazırlan
var roundMusic = roundNum === 1 ? 'duelmusic' : roundNum === 2 ? 'round2music' : roundNum === 3 ? 'round3music' : roundNum === 4 ? 'round4music' : 'round5music';
startZoomIn(roundMusic);
statusTxt.setText("Wait for the music...");
statusTxt.visible = true;
// Set up enemy for this round
currentEnemy = enemyData[roundNum - 1];
roundTxt.setText("Round " + roundNum + ": " + currentEnemy.name);
// Boss scale (if any)
enemy.scaleX = enemy.scaleY = currentEnemy.scale || 1;
// Play duel music
// (music now only plays during zoom-in, do not play here)
// drawTimeout is now set after zoom-in, not here
if (drawTimeout) {
LK.clearTimeout(drawTimeout);
}
}
// Start the "DRAW!" phase
function startDraw() {
if (!playerReady) {
earlyLose();
return;
}
duelState = STATE_DRAW;
canShoot = true;
aimActive = true;
// (aimReticle removed)
statusTxt.setText("DRAW!");
statusTxt.visible = true;
LK.stopMusic();
LK.getSound('draw').play();
player.toHand(); // draw from holster
enemy.toHand(); // enemy draws too
aimEnemyGunAt(player.x, player.y - 40); // hemen hizala
duelStartTime = Date.now();
// Enemy will shoot after their reaction time
var reactTime = 1200; // fallback default
if (currentEnemy && typeof currentEnemy.minReact === "number" && typeof currentEnemy.maxReact === "number") {
reactTime = 1000 * (currentEnemy.minReact + Math.random() * (currentEnemy.maxReact - currentEnemy.minReact));
reactTime *= 0.425; // %57.5 daha hızlı tepki (eski 0.5 ve yeni 0.35 arası)
}
if (duelTimer) {
LK.clearTimeout(duelTimer);
}
duelTimer = LK.setTimeout(enemyFire, reactTime);
}
// Player fires
function playerFire(x, y) {
// 6 mermi limiti
if (playerShotsFiredCnt >= 6) {
statusTxt.setText("Out of ammo!");
statusTxt.visible = true;
return;
}
if (!canShoot) {
return;
}
// === COOLDOWN: Block if cooldown active ===
if (playerShotCooldown > 0) {
return;
}
playerShot = true;
playerShotsFiredCnt++; // sayaç ↑
playerShotTime = Date.now();
// === COOLDOWN: Set cooldown after shot ===
playerShotCooldown = 300; // 0.3 seconds in ms
// Animate gun
tween(player.gun, {
rotation: -0.5
}, {
duration: 80,
easing: tween.cubicOut
});
tween(player.rarm, {
rotation: -0.3
}, {
duration: 80,
easing: tween.cubicOut
});
LK.getSound('gunshot').play();
// Create bullet
playerBullet = new Bullet();
// Muzzle position helper
function muzzlePos() {
// Use full gun width/height for muzzle (center anchor)
var gunW = player.gun.width || 60;
var gunH = player.gun.height || 15;
return {
x: player.x + player.gun.x + Math.cos(player.gun.rotation) * gunW,
y: player.y + player.gun.y + Math.sin(player.gun.rotation) * gunH
};
}
var muzzle = muzzlePos();
playerBullet.x = muzzle.x;
playerBullet.y = muzzle.y;
// Ateş anındaki imleç (x, y) fonksiyona zaten parametre olarak geliyor
var dx = x - muzzle.x;
var dy = y - muzzle.y;
var dist = Math.sqrt(dx * dx + dy * dy);
playerBullet.dirX = dx / dist;
playerBullet.dirY = dy / dist;
playerBullet.speed = 60;
game.addChild(playerBullet);
// Decrement player bullet UI
for (var i = 0; i < playerAmmoUI.length; i++) {
if (playerAmmoUI[i].visible !== false) {
playerAmmoUI[i].visible = false;
break;
}
}
// If shot before draw, instant loss
if (duelState === STATE_WAIT) {
duelState = STATE_RESULT;
statusTxt.setText("You shot too early!\nYou Lose!");
statusTxt.visible = true;
LK.getSound('fail').play();
player.ragdoll();
enemy.ragdoll();
endRound(false);
return;
}
// bir sonraki mermiye izin ver
if (duelState === STATE_DRAW && playerShotsFiredCnt < 6) {
canShoot = true;
aimActive = true;
// (aimReticle removed)
}
// DRAW fazı açık kalsın ki 6 mermi bitene kadar tekrar tekrar ateş edebilelim
}
// Enemy fires
function enemyFire() {
var _currentEnemy;
if (enemyShotsFiredCnt >= 6 || duelState === STATE_RESULT) {
return;
}
if (!enemyShot) {
enemyShot = true;
} // ilk mermi için eski bayrak
enemyShotsFiredCnt++;
enemyShotTime = Date.now();
// Ateşten önce hedefe çevir
aimEnemyGunAt(player.x, player.y - 40);
// Animate gun
tween(enemy.gun, {
rotation: 0.5
}, {
duration: 80,
easing: tween.cubicOut
});
tween(enemy.rarm, {
rotation: 0.3
}, {
duration: 80,
easing: tween.cubicOut
});
LK.getSound('gunshot').play();
// Create bullet
enemyBullet = new Bullet();
var gunW = enemy.gun.width || 60;
var gunH = enemy.gun.height || 18;
enemyBullet.x = enemy.x + enemy.gun.x + Math.cos(enemy.gun.rotation) * gunW;
enemyBullet.y = enemy.y + enemy.gun.y + Math.sin(enemy.gun.rotation) * gunH;
// Decrement enemy bullet UI
for (var i = 0; i < enemyAmmoUI.length; i++) {
if (enemyAmmoUI[i].visible !== false) {
enemyAmmoUI[i].visible = false;
break;
}
}
// Konik isabet modeliyle hedef seçimi
// 1️⃣ hedef vektörü
var baseDX = player.x - enemyBullet.x;
var baseDY = player.y - 40 - enemyBullet.y; // gövdenin biraz üstü
var baseAng = Math.atan2(baseDY, baseDX);
// 2️⃣ saçılma açısı (daralan koni)
var sIdx = enemyShotsFiredCnt; // 1-6
var sMax = 6;
var cStart = currentEnemy && typeof currentEnemy.coneStart === "number" ? currentEnemy.coneStart : 24; // fallback
var cEnd = currentEnemy && typeof currentEnemy.coneEnd === "number" ? currentEnemy.coneEnd : 0;
var coneDeg = cStart + (cEnd - cStart) * ((sIdx - 1) / (sMax - 1));
var coneRad = coneDeg * Math.PI / 180;
var isLast = sIdx === sMax; // son kurşun
var spread = isLast ? 0 : Math.random() * coneRad - coneRad / 2;
// 3️⃣ hedef noktasını diagramdaki yayı keserek bul
var range = 1500; // ekran dışına yeter
var targetX = enemyBullet.x + Math.cos(baseAng + spread) * range;
var targetY = enemyBullet.y + Math.sin(baseAng + spread) * range;
var dx = targetX - enemyBullet.x;
var dy = targetY - enemyBullet.y;
var dist = Math.sqrt(dx * dx + dy * dy);
enemyBullet.dirX = dx / dist;
enemyBullet.dirY = dy / dist;
enemyBullet.speed = 60;
game.addChild(enemyBullet);
// sonraki mermi (tak-tak) 0.6 sn sonra
if (enemyShotsFiredCnt < 6) {
LK.setTimeout(enemyFire, 600);
}
// Round oyuncu ateş etmediyse ANCAK bütün 6 mermi atıldıktan sonra
if (enemyShotsFiredCnt === 6 && !playerShot) {
// Son kurşunun ekrandan çıkması için çok kısa bir gecikme bırak
LK.setTimeout(function () {
if (duelState !== STATE_RESULT) {
duelState = STATE_RESULT;
statusTxt.setText("You Survived!");
statusTxt.visible = true;
endRound(true);
}
}, 350);
}
}
// End round, win: true/false
function endRound(win) {
canShoot = false;
aimActive = false;
// (aimReticle removed)
if (drawTimeout) {
LK.clearTimeout(drawTimeout);
}
if (duelTimer) {
LK.clearTimeout(duelTimer);
}
if (resultTimeout) {
LK.clearTimeout(resultTimeout);
}
drawTimeout = null;
duelTimer = null;
resultTimeout = null;
// Next round or end game
resultTimeout = LK.setTimeout(function () {
if (win) {
playerScore += 1;
scoreTxt.setText("Score: " + playerScore);
roundNum += 1;
if (roundNum > maxRounds) {
statusTxt.setText("You Win!\nScore: " + playerScore);
statusTxt.visible = true;
LK.showYouWin();
} else {
resetDuel();
}
} else {
statusTxt.setText("You Lose!\nScore: " + playerScore);
statusTxt.visible = true;
LK.showGameOver();
}
}, 1600);
}
// Handle aiming reticle movement (circular motion)
function updateAimReticle() {
// (aim reticle removed)
}
// Handle bullet collisions and duel result
function checkBullets() {
// Player bullet hits enemy
if (playerBullet) {
// Check if bullet reached enemy
var dx = playerBullet.x - enemy.x;
var dy = playerBullet.y - enemy.y;
var dist = Math.sqrt(dx * dx + dy * dy);
if (dist < 120) {
// Hit!
LK.getSound('hit').play();
enemy.ragdoll(playerBullet.x, playerBullet.y);
game.removeChild(playerBullet);
playerBullet = null;
duelState = STATE_RESULT;
statusTxt.setText("You Win the Duel!");
statusTxt.visible = true;
endRound(true);
}
}
// Enemy bullet hits player
if (enemyBullet) {
var dx = enemyBullet.x - player.x;
var dy = enemyBullet.y - player.y;
var dist = Math.sqrt(dx * dx + dy * dy);
if (dist < 120) {
LK.getSound('hit').play();
player.ragdoll(enemyBullet.x, enemyBullet.y);
game.removeChild(enemyBullet);
enemyBullet = null;
duelState = STATE_RESULT;
statusTxt.setText("You were shot!");
statusTxt.visible = true;
endRound(false);
}
}
// Both bullets exist (simultaneous fire)
if (playerBullet && enemyBullet) {
// Check which bullet hits first
var pdx = playerBullet.x - enemy.x;
var pdy = playerBullet.y - enemy.y;
var pdist = Math.sqrt(pdx * pdx + pdy * pdy);
var edx = enemyBullet.x - player.x;
var edy = enemyBullet.y - player.y;
var edist = Math.sqrt(edx * edx + edy * edy);
if (pdist < 120 && edist < 120) {
// Both hit at same time: draw
LK.getSound('hit').play();
player.ragdoll();
enemy.ragdoll();
game.removeChild(playerBullet);
game.removeChild(enemyBullet);
playerBullet = null;
enemyBullet = null;
duelState = STATE_RESULT;
statusTxt.setText("Draw!\nBoth shot!");
statusTxt.visible = true;
endRound(false);
} else if (pdist < 120) {
LK.getSound('hit').play();
enemy.ragdoll(playerBullet.x, playerBullet.y);
game.removeChild(playerBullet);
playerBullet = null;
duelState = STATE_RESULT;
statusTxt.setText("You Win the Duel!");
statusTxt.visible = true;
endRound(true);
} else if (edist < 120) {
LK.getSound('hit').play();
player.ragdoll(enemyBullet.x, enemyBullet.y);
game.removeChild(enemyBullet);
enemyBullet = null;
duelState = STATE_RESULT;
statusTxt.setText("You were shot!");
statusTxt.visible = true;
endRound(false);
}
}
// Remove bullets if off screen
if (playerBullet && (playerBullet.x < 0 || playerBullet.x > 2048 || playerBullet.y < 0 || playerBullet.y > 2732)) {
game.removeChild(playerBullet);
playerBullet = null;
// === COOLDOWN: Only allow new shot if cooldown expired ===
if (playerShotCooldown <= 0) {
canShoot = true;
}
}
if (enemyBullet && (enemyBullet.x < 0 || enemyBullet.x > 2048 || enemyBullet.y < 0 || enemyBullet.y > 2732)) {
game.removeChild(enemyBullet);
enemyBullet = null;
enemyShot = false; // yeni atışa izin ver
}
}
// ==== FIX #2 : holster konumu ====
// Use two holster sprites: full and empty, toggle visibility
var holsterFull = LK.getAsset('kilif', {
width: 300,
height: 300,
anchorX: -0.2,
anchorY: 0
});
var holsterEmpty = LK.getAsset('kilifbos', {
width: 300,
height: 300,
anchorX: -0.2,
anchorY: 0
});
holsterFull.visible = true;
holsterEmpty.visible = false;
var holsterOffset = 250;
holsterFull.x = player.x - 200;
holsterFull.y = player.y + player.body.height / 2 + holsterOffset;
holsterEmpty.x = holsterFull.x;
holsterEmpty.y = holsterFull.y;
world.addChild(holsterFull);
world.addChild(holsterEmpty);
// === Place enemygunpocket in the middle of the screen ===
var enemygunpocket = LK.getAsset('enemygunpocket', {
anchorX: 0.5,
anchorY: 0.5
});
enemygunpocket.x = 2048 / 2 + 550 + 50 - 30 - 5 - 5;
enemygunpocket.y = 2732 / 2 - 600 + 150 + 10;
world.addChild(enemygunpocket);
// Helper: check if global point is inside holsterFull's bounds (always use holsterFull for hit area)
function holsterContains(globalX, globalY) {
// Global (stage) → world-içi (local) koordinata çevir
var localX = (globalX - world.x) / world.scaleX;
var localY = (globalY - world.y) / world.scaleY;
// kilif sprite’ının local kutusu
var b = {
x: holsterFull.x,
y: holsterFull.y,
w: holsterFull.width,
h: holsterFull.height
};
return localX >= b.x && localX <= b.x + b.w && localY >= b.y && localY <= b.y + b.h;
}
// Holster hold/zoom/duel state
var zoomFinished = false,
duelStarted = false,
holsterHoldTime = 0;
var playerReady = false;
var holdingHolster = false; // Track if holster is being held
var musicTimeout = null; // Timer for music/draw phase
function beginHold() {
if (musicTimeout) {
LK.clearTimeout(musicTimeout);
}
var extra = 4000 + Math.random() * 6000; // 4-10 s
musicTimeout = LK.setTimeout(stopMusicAndDraw, extra);
}
function cancelHold() {
if (musicTimeout) {
LK.clearTimeout(musicTimeout);
}
musicTimeout = null;
}
function stopMusicAndDraw() {
LK.stopMusic(); // müzik sustu
if (enemygunpocket && enemygunpocket.visible !== false) {
enemygunpocket.visible = false;
}
duelStarted = true; // <-- ERKEN KAYIP KONTROLÜ ARTIK PASİF
holsterHoldTime = 0; // güvenlik: sayaç sıfırla
startDraw(); // DRAW fazına geç
}
// Touch/click to fire or start holster hold
game.down = function (x, y, obj) {
// No fire until duel actually started
if (zoomFinished && !duelStarted && holsterContains(x, y)) {
playerReady = true;
holdingHolster = true; // holster pressed
player.toHand(); // ← kılıftan çek (hemen ele ver)
// Play holdsound when gun is drawn to hand
LK.getSound('holdsound').play();
// --- holster assetini kilifbos ile değiştir (Çözüm 2: iki sprite, görünürlük) ---
holsterFull.visible = false;
holsterEmpty.visible = true;
pointGunAt(x, y); // artık global ve erişilebilir
beginHold(); // HOLD başlatılırken zamanlayıcı kuruluyor
return;
}
if (!duelStarted) {
return;
}
if (duelState === STATE_DRAW && canShoot && aimActive) {
playerFire(x, y);
} else if (duelState === STATE_WAIT && !playerShot) {
playerFire(x, y);
}
};
// On pointer up, check for early leave from holster
// ==== FIX #4c : tetikleyici =====
game.up = function (x, y, obj) {
// HOLSTER EARLY RELEASE CHECK
holdingHolster = false; // holster released
cancelHold(); // el çekildi, zamanlayıcı iptal
if (!duelStarted && playerReady && duelState === STATE_WAIT) {
earlyLose();
return;
}
if (duelState === STATE_DRAW && canShoot) {
playerFire(x, y);
return;
}
};
// Move reticle with finger (optional: drag to aim)
game.move = function (x, y, obj) {
// Holster hold/early leave logic
if (zoomFinished && !duelStarted) {
if (holsterContains(x, y)) {
// handled in update for dt-accurate timing
} else if (holdingHolster) {
cancelHold();
earlyLose();
}
}
// (aim reticle removed)
// Namlu her zaman fareyi izlesin (DRAW’tan önce holster tutulurken de)
pointGunAt(x, y);
};
/**** GLOBAL YARDIMCI : Silahı imlece çevir ****/
function pointGunAt(globalX, globalY) {
// 1) Omuz pivotu – dünya koordinatı
var shX = player.x + player.rarm.x;
var shY = player.y + player.rarm.y;
// 2) Omuz → imleç vektörü ve açı (0 rad = sağ)
var dx = globalX - shX;
var dy = globalY - shY;
var ang = Math.atan2(dy, dx);
// 3) Kol sprite’ı AŞAĞI bakıyor → –90° (-π/2) düzelt
var armRot = ang - Math.PI / 2;
player.rarm.rotation = armRot;
// 4) El / silah konumu (player LOCAL)
var L = player.rarm.height; // ≈110 px
// ❗️ Doğru ofset: (-sin, +cos)
player.gun.x = player.rarm.x - Math.sin(armRot) * L;
player.gun.y = player.rarm.y + Math.cos(armRot) * L;
// 5) Silah namlu yönü
if (!duelStarted && holsterContains(globalX, globalY)) {
player.gun.rotation = Math.PI / 2; // kılıfta ⇒ namlu yere
} else {
player.gun.rotation = ang; // hedefe bak
}
}
/**** GLOBAL : Enemy silahını hedefe çevir ****/
function aimEnemyGunAt(globalX, globalY) {
// 1) Omuz pivotu (world)
var shX = enemy.x + enemy.rarm.x;
var shY = enemy.y + enemy.rarm.y;
// 2) Omuz→hedef vektörü
var dx = globalX - shX;
var dy = globalY - shY;
var ang = Math.atan2(dy, dx);
// 3) Kol sprite’ı AŞAĞI bakıyor → –90° (–π/2) düzeltme
var armRot = ang - Math.PI / 2;
enemy.rarm.rotation = armRot;
// 4) El / silah konumu (enemy LOCAL) **ayna!**
var L = enemy.rarm.height; // ≈145 px
enemy.gun.x = enemy.rarm.x - Math.sin(armRot) * L;
enemy.gun.y = enemy.rarm.y + Math.cos(armRot) * L;
// 5) Namluyu hedefe döndür
enemy.gun.rotation = ang;
}
// Helper to update holster box position under player's feet
function updateHolsterBox() {
holsterFull.x = player.x - 200;
holsterFull.y = player.y + player.body.height / 2 + holsterOffset;
holsterEmpty.x = holsterFull.x;
holsterEmpty.y = holsterFull.y;
}
// Main update loop
game.update = function (dt) {
// Move enemygun assets in pocket 5px down and 10px left when music is not stopped
if (LK.musicPlaying && enemygunpocket) {
enemygunpocket.x = 2048 / 2 + 550 + 50 - 30 - 5 - 5 - 10; // 10px more left
enemygunpocket.y = 2732 / 2 - 600 + 150 + 10 + 5; // 5px more down
}
// Hide status text during zoom-in
if (!zoomFinished) {
statusTxt.visible = false;
return;
}
// Always update holster position to follow player
updateHolsterBox();
// (aim reticle removed)
// Rakip silahı eldeyse oyuncuyu takip etsin
if (enemy.gun.visible) {
aimEnemyGunAt(player.x, player.y - 40);
}
// Animate bullets
if (playerBullet) {
playerBullet.update();
}
if (enemyBullet) {
enemyBullet.update();
}
// === PLAYER SHOT COOLDOWN TIMER ===
if (playerShotCooldown > 0) {
var dtMs = typeof dt === "number" ? dt : 16.7;
playerShotCooldown -= dtMs;
if (playerShotCooldown < 0) {
playerShotCooldown = 0;
}
}
// Check for bullet collisions
checkBullets();
// ==== FIX #3 : statusTxt ayarı ====
// Holster hold logic after zoom
if (zoomFinished && !duelStarted) {
// Use pointerX/Y for current pointer
var px = typeof game.pointerX === "number" ? game.pointerX : 0;
var py = typeof game.pointerY === "number" ? game.pointerY : 0;
var dtMs = typeof dt === "number" ? dt : 16.7;
if (holsterContains(px, py) && holdingHolster) {
holsterHoldTime += dtMs;
} else {
if (holsterHoldTime > 0 && holdingHolster) {
earlyLose();
}
holsterHoldTime = 0;
}
if (!duelStarted) {
if (!playerReady) {
statusTxt.setText("Hold holster to start duel");
} else {
statusTxt.setText("Wait for music to end…\nthen aim & release");
}
}
// if (holsterHoldTime >= 4000 && !duelStarted) {
// duelStarted = true;
// holsterHoldTime = 0;
// resetDuel();
// }
statusTxt.visible = true;
} else {
if (duelState === STATE_DRAW) {
statusTxt.setText("Aim and SHOOT!");
}
statusTxt.visible = true;
}
};
// Early lose function
function earlyLose() {
if (duelState === STATE_RESULT) {
return;
} // only trigger once
holdingHolster = false;
holsterHoldTime = 0;
duelStarted = false;
LK.stopMusic();
if (drawTimeout) {
LK.clearTimeout(drawTimeout);
}
statusTxt.setText("Too early!\nYou lose!");
statusTxt.visible = true;
LK.getSound('gunshot').play(); // yere ateş
LK.getSound('fail').play();
// Optionally, also play gunshot sound:
// LK.getSound('gunshot').play();
// Show game over after 1 second
LK.setTimeout(function () {
LK.showGameOver();
}, 1000);
}
// Camera zoom-in before duel starts
function startZoomIn(music) {
// ==== FIX #1b : zoom fonksiyonunda ====
var startScale = 0.4;
world.scaleX = world.scaleY = startScale;
world.x = centerX * (1 - startScale);
world.y = 2732 / 2 * (1 - startScale);
LK.playMusic(music || 'duelmusic');
tween(world, {
scaleX: 1,
scaleY: 1,
x: 0,
y: 0
}, {
duration: 4000,
easing: tween.quadInOut,
onFinish: function onFinish() {
zoomFinished = true;
}
});
}
// Start zoom-in and music immediately on boot
startZoomIn();