User prompt
1 – Düşman isabet olasılığı (gerçek %’ler) enemyFire() fonksiyonunu bulun, hedef/hata payı kısmını değiştirin ⤵ diff Kopyala Düzenle - // seçili raundun mermi-bazlı isabet tablosu - var acc = 0.8; - if (currentEnemy && Array.isArray(currentEnemy.bulletAcc)) { - var idx = Math.min(enemyShotsFiredCnt - 1, currentEnemy.bulletAcc.length - 1); - acc = currentEnemy.bulletAcc[idx]; - } - var spread = (1 - acc) * 200; - targetX += (Math.random() - 0.5) * spread; - targetY += (Math.random() - 0.5) * spread; + // ❶ mermiye “vuracak mıyım?” kararı + let acc = 0.8; + if (Array.isArray(currentEnemy?.bulletAcc)) { + const idx = Math.min(enemyShotsFiredCnt - 1, currentEnemy.bulletAcc.length - 1); + acc = currentEnemy.bulletAcc[idx]; + } + const mustHit = enemyShotsFiredCnt === 6; // 6. mermi her zaman vurur + const willHit = mustHit || Math.random() < acc; // olasılıksal isabet + + if (!willHit) { // KAÇIR → geniş sapma ver + targetX += (Math.random() > .5 ? 1 : -1) * (220 + Math.random() * 200); + targetY += (Math.random() > .5 ? 1 : -1) * (220 + Math.random() * 200); + } (Şansa göre tamamen ıskalayan mermiler artık bariz dışarı gider; düşük %’lerde tek atış mucize olmayacak.) 2 – Oyuncu art arda ateş edebilsin playerFire() fonksiyonunun en sonundaki satırı sil / yoruma al ⤵ diff Kopyala Düzenle - // If shot after draw, check hit - duelState = STATE_SHOT; + // DRAW fazı açık kalsın ki 6 mermi bitene kadar tekrar tekrar ateş edebilelim (Her atıştan sonra canShoot zaten true kalıyor; başka değişiklik gerekmez.)
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); // Body var body = self.attachAsset('enemyBody', { anchorX: 0.5, anchorY: 0.5, x: 0, y: 0 }); // Head var head = self.attachAsset('enemyHead', { anchorX: 0.5, anchorY: 0.5, x: 0, y: -body.height / 2 - 45 }); // Left Arm var larm = self.attachAsset('enemyArm', { anchorX: 0.5, anchorY: 0, x: -body.width / 2 - 10, y: -40 }); // Right Arm (gun hand) var rarm = self.attachAsset('enemyArm', { anchorX: 0.5, anchorY: 0, x: body.width / 2 + 10, y: -40 }); // Legs var lleg = self.attachAsset('enemyLeg', { anchorX: 0.5, anchorY: 0, x: -body.width / 4, y: body.height / 2 - 10 }); var rleg = self.attachAsset('enemyLeg', { anchorX: 0.5, anchorY: 0, x: body.width / 4, y: body.height / 2 - 10 }); // Gun var gun = self.attachAsset('enemyGun', { anchorX: 0.1, anchorY: 0.5, x: body.width / 2 + 40, y: 10, rotation: 0 }); 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.x = 0; body.y = 0; body.rotation = 0; head.x = 0; head.y = -body.height / 2 - 45; head.rotation = 0; larm.x = -body.width / 2 - 10; larm.y = -40; larm.rotation = 0; rarm.x = body.width / 2 + 10; rarm.y = -40; rarm.rotation = 0; lleg.x = -body.width / 4; lleg.y = body.height / 2 - 10; lleg.rotation = 0; rleg.x = body.width / 4; rleg.y = body.height / 2 - 10; rleg.rotation = 0; gun.x = body.width / 2 + 40; gun.y = 10; gun.rotation = 0; }; return self; }); // Player class var Player = Container.expand(function () { var self = Container.call(this); // Body var body = self.attachAsset('playerBody', { anchorX: 0.5, anchorY: 0.5, x: 0, y: 0 }); // Head var head = self.attachAsset('playerHead', { anchorX: 0.5, anchorY: 0.5, x: 0, y: -body.height / 2 - 45 }); // Left Arm var larm = self.attachAsset('playerArm', { anchorX: 0.5, anchorY: 0, x: -body.width / 2 - 10, y: -40 }); // Right Arm (gun hand) var rarm = self.attachAsset('playerArm', { anchorX: 0.5, anchorY: 0, x: body.width / 2 + 10, y: -40 }); // Legs var lleg = self.attachAsset('playerLeg', { anchorX: 0.5, anchorY: 0, x: -body.width / 4, y: body.height / 2 - 10 }); var rleg = self.attachAsset('playerLeg', { anchorX: 0.5, anchorY: 0, x: body.width / 4, y: body.height / 2 - 10 }); // Gun var gun = self.attachAsset('playerGun', { anchorX: 0.1, anchorY: 0.5, x: body.width / 2 + 40, y: 10, rotation: 0 }); // 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.x = 0; body.y = 0; body.rotation = 0; head.x = 0; head.y = -body.height / 2 - 45; head.rotation = 0; larm.x = -body.width / 2 - 10; larm.y = -40; larm.rotation = 0; rarm.x = body.width / 2 + 10; rarm.y = -40; rarm.rotation = 0; lleg.x = -body.width / 4; lleg.y = body.height / 2 - 10; lleg.rotation = 0; rleg.x = body.width / 4; rleg.y = body.height / 2 - 10; rleg.rotation = 0; gun.x = body.width / 2 + 40; gun.y = 10; gun.rotation = 0; }; return self; }); /**** * Initialize Game ****/ var game = new LK.Game({ backgroundColor: 0x2e1a09 }); /**** * Game Code ****/ // Game state // Player and enemy bodies (torso, head, arms, legs) as simple shapes 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; var enemyData = [{ // ROUND 1 name: "Greenhorn", minReact: 3.0, maxReact: 3.4, bulletAcc: [0.20, 0.40, 0.60, 0.80, 0.90, 1.00] }, { // ROUND 2 name: "Billy the Kid", minReact: 2.4, maxReact: 2.7, bulletAcc: [0.50, 0.60, 0.70, 0.90, 0.95, 1.00] }, { // ROUND 3 name: "Doc Holliday", minReact: 1.6, maxReact: 1.9, bulletAcc: [0.70, 0.80, 0.80, 0.90, 0.90, 1.00] }, { // ROUND 4 name: "Calamity Jane", minReact: 1.0, maxReact: 1.3, bulletAcc: [0.75, 0.80, 0.85, 0.90, 0.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: [0.90, 0.90, 0.90, 0.95, 0.95, 1.00], scale: 1.5 }]; var currentEnemy = null; // ==== FIX #1 : SAHNENİN BAŞINDA ==== var world = new Container(); game.addChild(world); // 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; bulletE.y = enemy.y - 250; 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 var aimReticle = LK.getAsset('bullet', { anchorX: 0.5, anchorY: 0.5, scaleX: 1.5, scaleY: 1.5, tint: 0xff0000 }); aimReticle.visible = false; game.addChild(aimReticle); // 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.visible = false; player.resetPose(); enemy.resetPose(); 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.visible = true; statusTxt.setText("DRAW!"); statusTxt.visible = true; LK.stopMusic(); LK.getSound('draw').play(); 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)); } 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; // playerShot kısıtlamasını kaldır playerShot = true; playerShotsFiredCnt++; // sayaç ↑ playerShotTime = Date.now(); // 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(); playerBullet.x = player.x + player.gun.x + Math.cos(player.gun.rotation) * 60; playerBullet.y = player.y + player.gun.y + Math.sin(player.gun.rotation) * 18; // Direction: from gun to aimReticle var dx = aimReticle.x - playerBullet.x; var dy = aimReticle.y - playerBullet.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.visible = true; } // 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(); // 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(); enemyBullet.x = enemy.x + enemy.gun.x + Math.cos(enemy.gun.rotation) * 60; enemyBullet.y = enemy.y + enemy.gun.y + Math.sin(enemy.gun.rotation) * 18; // Decrement enemy bullet UI for (var i = 0; i < enemyAmmoUI.length; i++) { if (enemyAmmoUI[i].visible !== false) { enemyAmmoUI[i].visible = false; break; } } // Direction: from gun to player's body, with some inaccuracy var targetX = player.x; var targetY = player.y - 40; // ❶ mermiye “vuracak mıyım?” kararı var acc = 0.8; if (Array.isArray((_currentEnemy = currentEnemy) === null || _currentEnemy === void 0 ? void 0 : _currentEnemy.bulletAcc)) { var idx = Math.min(enemyShotsFiredCnt - 1, currentEnemy.bulletAcc.length - 1); acc = currentEnemy.bulletAcc[idx]; } var mustHit = enemyShotsFiredCnt === 6; // 6. mermi her zaman vurur var willHit = mustHit || Math.random() < acc; // olasılıksal isabet if (!willHit) { // KAÇIR → geniş sapma ver targetX += (Math.random() > .5 ? 1 : -1) * (220 + Math.random() * 200); targetY += (Math.random() > .5 ? 1 : -1) * (220 + Math.random() * 200); } 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); } // If all enemy bullets are gone, end round (player survived) var allGone = true; for (var i = 0; i < enemyAmmoUI.length; i++) { if (enemyAmmoUI[i].visible === true) { allGone = false; break; } } if (allGone && !playerShot) { // Player survived all shots, win round duelState = STATE_RESULT; statusTxt.setText("You Survived!"); statusTxt.visible = true; endRound(true); } } // End round, win: true/false function endRound(win) { canShoot = false; aimActive = false; aimReticle.visible = false; 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() { if (!aimActive) return; aimAngle += aimSpeed * aimDir; var r = aimRadius; var ex = enemy.x; var ey = enemy.y; // Reticle orbits enemy's body aimReticle.x = ex + Math.cos(aimAngle) * r; aimReticle.y = ey + Math.sin(aimAngle) * r * 0.7; } // 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; playerShot = false; // yeni atışa izin ver } 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 ==== var holsterOffset = 250; var holsterBox = new Rectangle(player.x - 200, player.y + player.body.height / 2 + holsterOffset, 400, 200); // Show holster box as a semi-transparent rectangle var holsterVisual = LK.getAsset('playerBody', { width: 400, height: 200, color: 0x333333, alpha: 0.25, anchorX: 0, anchorY: 0 }); holsterVisual.x = holsterBox.x; holsterVisual.y = holsterBox.y; world.addChild(holsterVisual); // 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 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 && holsterBox.contains(x, y)) { playerReady = true; holdingHolster = true; // holster pressed 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 (holsterBox.contains(x, y)) { // handled in update for dt-accurate timing } else if (holdingHolster) { cancelHold(); earlyLose(); } } if (aimActive) { // Clamp reticle to a circle around enemy var ex = enemy.x; var ey = enemy.y; var dx = x - ex; var dy = y - ey; var d = Math.sqrt(dx * dx + dy * dy); if (d > aimRadius) { dx = dx * (aimRadius / d); dy = dy * (aimRadius / d); } aimReticle.x = ex + dx; aimReticle.y = ey + dy; // Update angle for next frame aimAngle = Math.atan2(aimReticle.y - ey, aimReticle.x - ex); } // ==== FIX #4b : imleç açısı ==== if (duelState >= STATE_DRAW) { var rotateArmTo = function rotateArmTo(x, y) { var gx = player.x + player.gun.x; var gy = player.y + player.gun.y; var ang = Math.atan2(y - gy, x - gx); player.gun.rotation = ang; player.rarm.rotation = ang + 0.15; }; rotateArmTo(x, y); } }; // Helper to update holster box position under player's feet function updateHolsterBox() { holsterBox.x = player.x - 200; holsterBox.y = player.y + player.body.height / 2 + holsterOffset; holsterVisual.x = holsterBox.x; holsterVisual.y = holsterBox.y; } // Main update loop game.update = function (dt) { // Hide status text during zoom-in if (!zoomFinished) { statusTxt.visible = false; return; } // Always update holster position to follow player updateHolsterBox(); // Animate aim reticle updateAimReticle(); // Animate bullets if (playerBullet) playerBullet.update(); if (enemyBullet) enemyBullet.update(); // 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 (holsterBox.contains(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("Waiting 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();
===================================================================
--- original.js
+++ change.js
@@ -509,13 +509,13 @@
canShoot = true;
aimActive = true;
aimReticle.visible = true;
}
- // If shot after draw, check hit
- duelState = STATE_SHOT;
+ // 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();
@@ -546,17 +546,21 @@
}
// Direction: from gun to player's body, with some inaccuracy
var targetX = player.x;
var targetY = player.y - 40;
- // seçili raundun mermi-bazlı isabet tablosu
+ // ❶ mermiye “vuracak mıyım?” kararı
var acc = 0.8;
- if (currentEnemy && Array.isArray(currentEnemy.bulletAcc)) {
+ if (Array.isArray((_currentEnemy = currentEnemy) === null || _currentEnemy === void 0 ? void 0 : _currentEnemy.bulletAcc)) {
var idx = Math.min(enemyShotsFiredCnt - 1, currentEnemy.bulletAcc.length - 1);
acc = currentEnemy.bulletAcc[idx];
}
- var spread = (1 - acc) * 200;
- targetX += (Math.random() - 0.5) * spread;
- targetY += (Math.random() - 0.5) * spread;
+ var mustHit = enemyShotsFiredCnt === 6; // 6. mermi her zaman vurur
+ var willHit = mustHit || Math.random() < acc; // olasılıksal isabet
+ if (!willHit) {
+ // KAÇIR → geniş sapma ver
+ targetX += (Math.random() > .5 ? 1 : -1) * (220 + Math.random() * 200);
+ targetY += (Math.random() > .5 ? 1 : -1) * (220 + Math.random() * 200);
+ }
var dx = targetX - enemyBullet.x;
var dy = targetY - enemyBullet.y;
var dist = Math.sqrt(dx * dx + dy * dy);
enemyBullet.dirX = dx / dist;