/****
* 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.lastX = typeof self.x === "number" ? self.x : 0;
self.lastY = typeof self.y === "number" ? self.y : 0;
self.update = function () {
var oldX = self.x,
oldY = self.y;
self.x += self.dirX * self.speed;
self.y += self.dirY * self.speed;
// Gapless bullet trail: spawn particles along the path
if (self.parent) {
var dx = self.x - oldX,
dy = self.y - oldY;
var dist = Math.sqrt(dx * dx + dy * dy);
var steps = Math.ceil(dist / 4);
for (var i = 0; i < steps; i++) {
var t = i / steps;
var px = oldX + dx * t;
var py = oldY + dy * t;
var p = new Particle();
p.x = px;
p.y = py;
var idx = self.parent.getChildIndex ? self.parent.getChildIndex(self) : -1;
if (idx > 0) self.parent.addChildAt(p, idx);else self.parent.addChild(p);
}
}
};
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.2,
// tetiğe yakın pivot
anchorY: -0.4,
// sapına yakın pivot
x: rarm.x + 15,
// 4px sağa kaydır
y: rarm.y + 15,
// 2px alta kaydır
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];
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 + 4; // 4px sağa kaydır
gun.y = rarm.y + 2; // 2px alta kaydır
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;
});
// Particle class for bullet trail
var Particle = Container.expand(function () {
var self = Container.call(this);
// Use an even smaller, still bright ellipse for smoke puff/trail (size ~5)
var size = 5 + Math.random() * 5; // much smaller than before
var color = 0xFFFFFF; // pure white for max brightness
// Alternate between 25% and 75% opacity for striped effect, starting with 25%
if (typeof Particle._stripeToggle === "undefined") Particle._stripeToggle = 0;
Particle._stripeToggle = 1 - Particle._stripeToggle;
var alpha = Particle._stripeToggle ? 0.25 : 0.75;
var puff = self.attachAsset('centerCircle', {
width: size,
height: size * (0.8 + Math.random() * 0.5),
color: color,
anchorX: 0.5,
anchorY: 0.5
});
puff.alpha = alpha;
// Fade out and destroy after a much shorter time for even faster trail disappearance
tween(puff, {
alpha: 0
}, {
duration: 70 + Math.random() * 40,
onFinish: function onFinish() {
if (self.parent) self.parent.removeChild(self);
}
});
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.3,
// tetiğe yakın pivot
anchorY: 1.2,
// sapına yakın pivot
x: rarm.x + 5,
// 5px sağa kaydır
y: rarm.y + 3,
// 3px alta kaydır
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];
// 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 + 5; // 5px sağa kaydır
gun.y = rarm.y + 3; // 3px alta kaydır
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
****/
// Helper: getChildIndex and addChildAt for Container (PIXI compatibility)
var game = new LK.Game({
backgroundColor: 0x000000
});
/****
* Game Code
****/
// Game state
// Player and enemy bodies (torso, head, arms, legs) as simple shapes
// Helper: getChildIndex and addChildAt for Container (PIXI compatibility)
if (typeof Container.prototype.getChildIndex !== "function") {
Container.prototype.getChildIndex = function (child) {
if (!this.children) return -1;
for (var i = 0; i < this.children.length; i++) {
if (this.children[i] === child) return i;
}
return -1;
};
}
if (typeof Container.prototype.addChildAt !== "function") {
Container.prototype.addChildAt = function (child, idx) {
if (!this.children) this.children = [];
if (child.parent) child.parent.removeChild(child);
this.children.splice(idx, 0, child);
child.parent = this;
// If LK engine needs, also call addChild for proper registration
if (typeof this.addChild === "function" && this.children.length === 1) {
this.addChild(child);
}
};
}
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
// === GUN RECOIL CONSTANTS ===
var RECOIL_ANGLE = 0.4; // tip the barrel a little farther (≈23° instead of 17°)
var RECOIL_TIME = 200; // total recoil lasts 0.2s instead of 0.5s
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.3
}];
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 recoil only (no arm kick)
{
// capture the gun’s base rotation:
var baseRot = player.gun.rotation;
// tween up half-time:
tween(player.gun, {
rotation: baseRot - RECOIL_ANGLE
}, {
duration: RECOIL_TIME / 2,
easing: tween.cubicOut,
onFinish: function onFinish() {
// tween back down the other half:
tween(player.gun, {
rotation: baseRot
}, {
duration: RECOIL_TIME / 2,
easing: tween.cubicIn
});
}
});
}
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 recoil only (no arm kick)
{
var baseRotE = enemy.gun.rotation;
tween(enemy.gun, {
rotation: baseRotE + RECOIL_ANGLE
}, {
duration: RECOIL_TIME / 2,
easing: tween.cubicOut,
onFinish: function onFinish() {
tween(enemy.gun, {
rotation: baseRotE
}, {
duration: RECOIL_TIME / 2,
easing: tween.cubicIn
});
}
});
}
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)
// Clear all timers immediately
cancelHold(); // musicTimeout'u iptal eder
if (duelTimer) {
LK.clearTimeout(duelTimer);
duelTimer = null;
}
if (drawTimeout) {
LK.clearTimeout(drawTimeout);
drawTimeout = null;
}
if (resultTimeout) {
LK.clearTimeout(resultTimeout);
resultTimeout = null;
}
drawTimeout = null;
duelTimer = null;
resultTimeout = null;
duelState = STATE_RESULT;
// 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() {
// Hem bullet’lar var mı diye bak:
if (playerBullet && enemyBullet) {
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);
// 1) Önce oyuncuya gelen mermiyi kontrol et:
if (edist < 120) {
// Player vurulmuş, her durumda kaybeder
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);
return;
}
// 2) Sonra düşmana geleni:
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);
return;
}
}
// 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);
}
}
// 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 = 2000 + Math.random() * 4000; // 2-6 s
musicTimeout = LK.setTimeout(stopMusicAndDraw, extra);
}
function cancelHold() {
if (musicTimeout) {
LK.clearTimeout(musicTimeout);
}
musicTimeout = null;
}
function stopMusicAndDraw() {
if (duelState !== STATE_WAIT) return;
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) {
if (duelState === STATE_RESULT) return;
// 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) {
if (duelState === STATE_RESULT) return;
// 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) {
if (duelState === STATE_RESULT) return;
// 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) {
if (duelState === STATE_RESULT) return;
// 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 + 5; // +5px x-offset
player.gun.y = player.rarm.y + Math.cos(armRot) * L + 3; // +3px y-offset
// 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) {
if (duelState === STATE_RESULT) return;
// 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 + 4; // +4px x-offset
enemy.gun.y = enemy.rarm.y + Math.cos(armRot) * L + 2; // +2px y-offset
// 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) {
if (duelState === STATE_RESULT) {
// Optionally, statusTxt can still be shown
statusTxt.visible = true;
return;
}
// 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) {
// Sadece roundNum 1–3 arası ipuçlarını göster
if (roundNum <= 3) {
if (!playerReady) {
statusTxt.setText("Hold holster to start duel");
} else {
statusTxt.setText("Wait for music to end…\nthen aim & release");
}
statusTxt.visible = true;
} else {
// 4. raund ve sonrası için hiç göstermeme
statusTxt.visible = false;
}
}
// if (holsterHoldTime >= 4000 && !duelStarted) {
// duelStarted = true;
// holsterHoldTime = 0;
// resetDuel();
// }
} else {
if (duelState === STATE_DRAW) {
if (roundNum <= 3) {
statusTxt.setText("Aim and SHOOT!");
statusTxt.visible = true;
} else {
statusTxt.visible = false;
}
} else {
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.lastX = typeof self.x === "number" ? self.x : 0;
self.lastY = typeof self.y === "number" ? self.y : 0;
self.update = function () {
var oldX = self.x,
oldY = self.y;
self.x += self.dirX * self.speed;
self.y += self.dirY * self.speed;
// Gapless bullet trail: spawn particles along the path
if (self.parent) {
var dx = self.x - oldX,
dy = self.y - oldY;
var dist = Math.sqrt(dx * dx + dy * dy);
var steps = Math.ceil(dist / 4);
for (var i = 0; i < steps; i++) {
var t = i / steps;
var px = oldX + dx * t;
var py = oldY + dy * t;
var p = new Particle();
p.x = px;
p.y = py;
var idx = self.parent.getChildIndex ? self.parent.getChildIndex(self) : -1;
if (idx > 0) self.parent.addChildAt(p, idx);else self.parent.addChild(p);
}
}
};
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.2,
// tetiğe yakın pivot
anchorY: -0.4,
// sapına yakın pivot
x: rarm.x + 15,
// 4px sağa kaydır
y: rarm.y + 15,
// 2px alta kaydır
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];
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 + 4; // 4px sağa kaydır
gun.y = rarm.y + 2; // 2px alta kaydır
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;
});
// Particle class for bullet trail
var Particle = Container.expand(function () {
var self = Container.call(this);
// Use an even smaller, still bright ellipse for smoke puff/trail (size ~5)
var size = 5 + Math.random() * 5; // much smaller than before
var color = 0xFFFFFF; // pure white for max brightness
// Alternate between 25% and 75% opacity for striped effect, starting with 25%
if (typeof Particle._stripeToggle === "undefined") Particle._stripeToggle = 0;
Particle._stripeToggle = 1 - Particle._stripeToggle;
var alpha = Particle._stripeToggle ? 0.25 : 0.75;
var puff = self.attachAsset('centerCircle', {
width: size,
height: size * (0.8 + Math.random() * 0.5),
color: color,
anchorX: 0.5,
anchorY: 0.5
});
puff.alpha = alpha;
// Fade out and destroy after a much shorter time for even faster trail disappearance
tween(puff, {
alpha: 0
}, {
duration: 70 + Math.random() * 40,
onFinish: function onFinish() {
if (self.parent) self.parent.removeChild(self);
}
});
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.3,
// tetiğe yakın pivot
anchorY: 1.2,
// sapına yakın pivot
x: rarm.x + 5,
// 5px sağa kaydır
y: rarm.y + 3,
// 3px alta kaydır
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];
// 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 + 5; // 5px sağa kaydır
gun.y = rarm.y + 3; // 3px alta kaydır
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
****/
// Helper: getChildIndex and addChildAt for Container (PIXI compatibility)
var game = new LK.Game({
backgroundColor: 0x000000
});
/****
* Game Code
****/
// Game state
// Player and enemy bodies (torso, head, arms, legs) as simple shapes
// Helper: getChildIndex and addChildAt for Container (PIXI compatibility)
if (typeof Container.prototype.getChildIndex !== "function") {
Container.prototype.getChildIndex = function (child) {
if (!this.children) return -1;
for (var i = 0; i < this.children.length; i++) {
if (this.children[i] === child) return i;
}
return -1;
};
}
if (typeof Container.prototype.addChildAt !== "function") {
Container.prototype.addChildAt = function (child, idx) {
if (!this.children) this.children = [];
if (child.parent) child.parent.removeChild(child);
this.children.splice(idx, 0, child);
child.parent = this;
// If LK engine needs, also call addChild for proper registration
if (typeof this.addChild === "function" && this.children.length === 1) {
this.addChild(child);
}
};
}
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
// === GUN RECOIL CONSTANTS ===
var RECOIL_ANGLE = 0.4; // tip the barrel a little farther (≈23° instead of 17°)
var RECOIL_TIME = 200; // total recoil lasts 0.2s instead of 0.5s
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.3
}];
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 recoil only (no arm kick)
{
// capture the gun’s base rotation:
var baseRot = player.gun.rotation;
// tween up half-time:
tween(player.gun, {
rotation: baseRot - RECOIL_ANGLE
}, {
duration: RECOIL_TIME / 2,
easing: tween.cubicOut,
onFinish: function onFinish() {
// tween back down the other half:
tween(player.gun, {
rotation: baseRot
}, {
duration: RECOIL_TIME / 2,
easing: tween.cubicIn
});
}
});
}
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 recoil only (no arm kick)
{
var baseRotE = enemy.gun.rotation;
tween(enemy.gun, {
rotation: baseRotE + RECOIL_ANGLE
}, {
duration: RECOIL_TIME / 2,
easing: tween.cubicOut,
onFinish: function onFinish() {
tween(enemy.gun, {
rotation: baseRotE
}, {
duration: RECOIL_TIME / 2,
easing: tween.cubicIn
});
}
});
}
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)
// Clear all timers immediately
cancelHold(); // musicTimeout'u iptal eder
if (duelTimer) {
LK.clearTimeout(duelTimer);
duelTimer = null;
}
if (drawTimeout) {
LK.clearTimeout(drawTimeout);
drawTimeout = null;
}
if (resultTimeout) {
LK.clearTimeout(resultTimeout);
resultTimeout = null;
}
drawTimeout = null;
duelTimer = null;
resultTimeout = null;
duelState = STATE_RESULT;
// 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() {
// Hem bullet’lar var mı diye bak:
if (playerBullet && enemyBullet) {
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);
// 1) Önce oyuncuya gelen mermiyi kontrol et:
if (edist < 120) {
// Player vurulmuş, her durumda kaybeder
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);
return;
}
// 2) Sonra düşmana geleni:
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);
return;
}
}
// 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);
}
}
// 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 = 2000 + Math.random() * 4000; // 2-6 s
musicTimeout = LK.setTimeout(stopMusicAndDraw, extra);
}
function cancelHold() {
if (musicTimeout) {
LK.clearTimeout(musicTimeout);
}
musicTimeout = null;
}
function stopMusicAndDraw() {
if (duelState !== STATE_WAIT) return;
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) {
if (duelState === STATE_RESULT) return;
// 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) {
if (duelState === STATE_RESULT) return;
// 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) {
if (duelState === STATE_RESULT) return;
// 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) {
if (duelState === STATE_RESULT) return;
// 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 + 5; // +5px x-offset
player.gun.y = player.rarm.y + Math.cos(armRot) * L + 3; // +3px y-offset
// 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) {
if (duelState === STATE_RESULT) return;
// 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 + 4; // +4px x-offset
enemy.gun.y = enemy.rarm.y + Math.cos(armRot) * L + 2; // +2px y-offset
// 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) {
if (duelState === STATE_RESULT) {
// Optionally, statusTxt can still be shown
statusTxt.visible = true;
return;
}
// 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) {
// Sadece roundNum 1–3 arası ipuçlarını göster
if (roundNum <= 3) {
if (!playerReady) {
statusTxt.setText("Hold holster to start duel");
} else {
statusTxt.setText("Wait for music to end…\nthen aim & release");
}
statusTxt.visible = true;
} else {
// 4. raund ve sonrası için hiç göstermeme
statusTxt.visible = false;
}
}
// if (holsterHoldTime >= 4000 && !duelStarted) {
// duelStarted = true;
// holsterHoldTime = 0;
// resetDuel();
// }
} else {
if (duelState === STATE_DRAW) {
if (roundNum <= 3) {
statusTxt.setText("Aim and SHOOT!");
statusTxt.visible = true;
} else {
statusTxt.visible = false;
}
} else {
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();