User prompt
Kill karakteri takip etsin
User prompt
Aynı renkten iki tane olmasın
User prompt
Oyuncunun ismindeki renk ne ise karakterinin rengi o olsun
User prompt
Temas ettiğim kişi ölsün
User prompt
Kill emergency butonumun altın da olsun
User prompt
Kill ekranın sağ alt köşesinde olsun
User prompt
Kill butonu ekranın sağ alt köşesinde olsun
User prompt
Görev barı ortada olsun
User prompt
Adı hangi renkse rengi o olsun
User prompt
Aşı hangi renkse rengi o olsun
User prompt
Oyuncunun adındaki 123456 sayıları olmasın
User prompt
Kill butonu ekranın sağ alt köşesinde olsun
User prompt
Görevler bitince kazanmayalım kaybedelim
User prompt
Kill butonu oyuncuyu takıp etsin
User prompt
Kill butonu kameranın sağ alt köşesinde olsun
User prompt
Görevler bitince kaybedelim
User prompt
Kill butonuna basmadan öldürmesin
User prompt
Kill butonu olsun ve ona basınca temas etiği kişiyi öldürsün
User prompt
Alan daha büyük olsun ve kamera oyuncuyu takip etsin ve oyuncu hızını düşür
User prompt
İmposter kazanırsa kazanmış olalım
User prompt
İmposter biz olalım
User prompt
İmposter oyuncu olsun
Code edit (1 edits merged)
Please save this source code
User prompt
Among Us: Micro Impostor
Initial prompt
Among us
/**** * Plugins ****/ var tween = LK.import("@upit/tween.v1"); /**** * Classes ****/ // Emergency meeting button var EmergencyBtn = Container.expand(function () { var self = Container.call(this); self.asset = self.attachAsset('emergencybtn', { anchorX: 0.5, anchorY: 0.5 }); self.txt = new Text2('EMERGENCY', { size: 48, fill: "#fff" }); self.txt.anchor.set(0.5, 0.5); self.txt.x = 0; self.txt.y = 0; self.addChild(self.txt); self.down = function (x, y, obj) { if (game.state === 'playing' && !game.meetingCalled) { game.callMeeting(); } }; return self; }); // Player class (Crewmate or Impostor) var Player = Container.expand(function () { var self = Container.call(this); // Properties self.isImpostor = false; self.isAlive = true; self.playerId = null; self.name = ''; self.avatar = null; self.isLocal = false; // Is this the local player? self.isVoted = false; self.voteTarget = null; self.showName = function () { if (!self.nameTxt) { // Determine color for name based on player role var nameColor = self.isImpostor ? "#d7263d" : "#83de44"; self.nameTxt = new Text2(self.name, { size: 48, fill: nameColor }); self.nameTxt.anchor.set(0.5, 0); self.addChild(self.nameTxt); self.nameTxt.y = self.avatar.height / 2 + 10; } }; // Set up avatar self.setRole = function (isImpostor) { self.isImpostor = isImpostor; if (self.avatar) { self.removeChild(self.avatar); } // Determine color from name var nameColorMap = { "Red": 0xd7263d, "Blue": 0x3ec1d3, "Green": 0x83de44, "Yellow": 0xf9d423, "Pink": 0xff69b4, "Orange": 0xf97c1b, "Cyan": 0x15ffff, "Lime": 0x7fff00, "Purple": 0x6c3483, "Brown": 0x8b5a2b }; var colorKey = (self.name || "").split(" ")[0]; var playerColor = nameColorMap[colorKey] !== undefined ? nameColorMap[colorKey] : isImpostor ? 0xd7263d : 0x83de44; if (isImpostor) { self.avatar = self.attachAsset('impostor', { anchorX: 0.5, anchorY: 0.5, tint: playerColor }); } else { self.avatar = self.attachAsset('crewmate', { anchorX: 0.5, anchorY: 0.5, tint: playerColor }); } }; // Show dead body self.showDead = function () { if (self.avatar) self.avatar.visible = false; if (!self.deadBody) { self.deadBody = self.attachAsset('deadbody', { anchorX: 0.5, anchorY: 0.5 }); } self.isAlive = false; }; // Hide dead body, show alive self.revive = function () { if (self.deadBody) self.deadBody.visible = false; if (self.avatar) self.avatar.visible = true; self.isAlive = true; }; // For touch/click events self.down = function (x, y, obj) { // Used for voting if (game.state === 'voting' && !game.localPlayer.isVoted && self.isAlive && self !== game.localPlayer) { game.voteFor(self); } else if (game.state === 'playing' && game.localPlayer && game.localPlayer.isImpostor && game.localPlayer.isAlive && self.isAlive && self !== game.localPlayer) { // Check distance for kill range (same as tryKill) var dx = self.x - game.localPlayer.x; var dy = self.y - game.localPlayer.y; var dist = Math.sqrt(dx * dx + dy * dy); if (dist < 200) { tryKill(self); // Deactivate kill ability and reset cooldown killAbilityActive = false; lastKillTime = Date.now(); } } }; return self; }); // Sabotage node class var SabotageNode = Container.expand(function () { var self = Container.call(this); self.active = false; self.asset = self.attachAsset('sabotagenode', { anchorX: 0.5, anchorY: 0.5 }); self.down = function (x, y, obj) { if (game.state === 'playing' && game.localPlayer.isImpostor && !self.active) { // Activate sabotage self.active = true; self.asset.alpha = 0.5; game.sabotageTriggered(self); } }; return self; }); // Task node class var TaskNode = Container.expand(function () { var self = Container.call(this); self.done = false; self.taskId = null; self.ownerId = null; // Which player this task belongs to (for local tasks) // Assign a color to each vaccine/task node. For example, cycle through a set of colors based on taskId. var vaccineColors = [0x83de44, 0x3ec1d3, 0xf9d423, 0xd7263d, 0x6c3483, 0x15ffff]; var color = vaccineColors[self.taskId % vaccineColors.length]; self.asset = self.attachAsset('tasknode', { anchorX: 0.5, anchorY: 0.5, color: color }); self.down = function (x, y, obj) { if (game.state === 'playing' && !self.done && self.ownerId === game.localPlayer.playerId) { // Complete task self.done = true; self.asset.alpha = 0.3; LK.getSound('taskdone').play(); game.completeTask(self); } }; return self; }); // Voting button (for skip or confirm) var VoteBtn = Container.expand(function () { var self = Container.call(this); self.asset = self.attachAsset('votebtn', { anchorX: 0.5, anchorY: 0.5 }); self.txt = new Text2('SKIP', { size: 48, fill: "#fff" }); self.txt.anchor.set(0.5, 0.5); self.txt.x = 0; self.txt.y = 0; self.addChild(self.txt); self.down = function (x, y, obj) { if (game.state === 'voting' && !game.localPlayer.isVoted) { game.voteFor(null); // skip } }; return self; }); /**** * Initialize Game ****/ var game = new LK.Game({ backgroundColor: 0x1a1a2e }); /**** * Game Code ****/ // Add background image to the game area if (!game.bg) { game.bg = LK.getAsset('background', { anchorX: 0, anchorY: 0, x: 0, y: 0, width: 4096, height: 4096 }); game.addChildAt(game.bg, 0); // Add as the bottom-most layer } // Place Emergency button at the bottom right corner using LK.gui.bottomRight if (emergencyBtn) { // Remove from previous parent if needed if (emergencyBtn.parent) emergencyBtn.parent.removeChild(emergencyBtn); emergencyBtn.x = 0; emergencyBtn.y = 0; LK.gui.bottomRight.addChild(emergencyBtn); emergencyBtn.visible = true; } // Place Kill button as a button just below the emergencyBtn in LK.gui.bottomRight if (!game.killBtn) { // Create Kill button as a Container with the Kill asset game.killBtn = new Container(); var killAsset = game.killBtn.attachAsset('Kill', { anchorX: 0.5, anchorY: 0.5 }); // Optionally, you can add a label or effect here if needed // Set up button event (no action for now, can be extended) game.killBtn.down = function (x, y, obj) { // Placeholder for kill action if needed }; } // Remove from previous parent if needed if (game.killBtn.parent) game.killBtn.parent.removeChild(game.killBtn); // Place killBtn just below emergencyBtn in LK.gui.bottomRight // Calculate y offset based on emergencyBtn's height var killBtnYOffset = 0; if (emergencyBtn && emergencyBtn.asset && emergencyBtn.asset.height) { killBtnYOffset = emergencyBtn.asset.height + 32; // 32px gap for touch } else { killBtnYOffset = 300; // fallback if asset not loaded } game.killBtn.x = 0; game.killBtn.y = killBtnYOffset; LK.gui.bottomRight.addChild(game.killBtn); game.killBtn.visible = true; // Place bilmem button just below emergencyBtn in LK.gui.bottomRight if (!game.bilmemBtn) { game.bilmemBtn = new Container(); var bilmemAsset = game.bilmemBtn.attachAsset('bilmem', { anchorX: 0.5, anchorY: 0.5 }); // Optionally, add a label or effect here if needed // Set up button event (no action for now, can be extended) game.bilmemBtn.down = function (x, y, obj) { // Placeholder for bilmem action if needed }; } // Remove from previous parent if needed if (game.bilmemBtn.parent) game.bilmemBtn.parent.removeChild(game.bilmemBtn); // Place bilmemBtn just below emergencyBtn in LK.gui.bottomRight var bilmemBtnYOffset = 0; if (emergencyBtn && emergencyBtn.asset && emergencyBtn.asset.height) { bilmemBtnYOffset = emergencyBtn.asset.height + 32; // 32px gap for touch } else { bilmemBtnYOffset = 300; // fallback if asset not loaded } game.bilmemBtn.x = 0; game.bilmemBtn.y = bilmemBtnYOffset; LK.gui.bottomRight.addChild(game.bilmemBtn); game.bilmemBtn.visible = true; // Place yeniBtn just below bilmemBtn in LK.gui.bottomRight if (!game.yeniBtn) { game.yeniBtn = new Container(); var yeniAsset = game.yeniBtn.attachAsset('yeni', { anchorX: 0.5, anchorY: 0.5, tint: 0x00ff00 // Set to green }); // Optionally, add a label or effect here if needed game.yeniBtn.down = function (x, y, obj) { // Placeholder for yeni action if needed }; } // Remove from previous parent if needed if (game.yeniBtn.parent) game.yeniBtn.parent.removeChild(game.yeniBtn); // Place yeniBtn just below bilmemBtn in LK.gui.bottomRight var yeniBtnYOffset = 0; if (bilmemAsset && bilmemAsset.height) { yeniBtnYOffset = bilmemAsset.height + 32; // 32px gap for touch } else { yeniBtnYOffset = 300; // fallback if asset not loaded } game.yeniBtn.x = 0; game.yeniBtn.y = game.bilmemBtn.y + yeniBtnYOffset; LK.gui.bottomRight.addChild(game.yeniBtn); game.yeniBtn.visible = true; // Track if kill ability is active for the local player var killAbilityActive = false; // Track when the last kill was made (or game started) var killCooldown = 10000; // 10 seconds in ms var lastKillTime = Date.now(); // Initialize to game start // When Kill button is pressed, activate kill ability for local impostor game.killBtn.down = function (x, y, obj) { if (game.state === 'playing' && game.localPlayer && game.localPlayer.isImpostor && game.localPlayer.isAlive) { killAbilityActive = true; // Optionally, you can add a visual effect to the button to show it's active // e.g. game.killBtn.alpha = 0.7; } }; // Removed updateKillBtn and setupKillBtn logic as killBtn now follows the player // Simple sound for meeting // Simple sound for kill // Simple sound for task complete // Voting button (red) // Voting button (blue) // Button for emergency meeting // Dead body (gray ellipse) // Sabotage node (yellow box) // Task node (small green box) // Crewmate and Impostor avatars (simple colored ellipses) // Game state: 'playing', 'meeting', 'voting', 'ended' game.state = 'playing'; // Game variables var playerCount = 6; // For MVP, fixed at 6 (1 impostor, 5 crewmates) var impostorCount = 1; var players = []; var tasks = []; var sabotageNodes = []; var deadBodies = []; var votingBtns = []; var votes = {}; var voteResults = {}; var meetingTimer = null; var votingTimer = null; var taskGoal = 4; // Number of tasks per crewmate var sabotageActive = false; var sabotageTimer = null; var sabotageDuration = 6000; // ms var emergencyBtn = null; var skipBtn = null; var infoTxt = null; var taskBar = null; var taskBarBg = null; var meetingCalled = false; var localPlayer = null; // Helper: Random name generator function randomName() { var names = ['Red', 'Blue', 'Green', 'Yellow', 'Pink', 'Orange', 'Cyan', 'Lime', 'Purple', 'Brown']; return names[Math.floor(Math.random() * names.length)]; } // Helper: Shuffle array function shuffle(arr) { for (var i = arr.length - 1; i > 0; i--) { var j = Math.floor(Math.random() * (i + 1)); var t = arr[i]; arr[i] = arr[j]; arr[j] = t; } return arr; } // Initialize players function setupPlayers() { players = []; var roles = []; for (var i = 0; i < impostorCount; i++) roles.push(true); for (var i = impostorCount; i < playerCount; i++) roles.push(false); // Always assign impostor to local player (player 0) roles = []; roles[0] = true; // local player is impostor for (var i = 1; i < playerCount; i++) roles[i] = false; shuffle(roles.slice(1)); // shuffle only the crewmates for variety // Prepare a unique name pool so each player gets a unique color var namePool = ['Red', 'Blue', 'Green', 'Yellow', 'Pink', 'Orange', 'Cyan', 'Lime', 'Purple', 'Brown']; for (var i = 0; i < playerCount; i++) { var p = new Player(); p.playerId = i; // Assign a unique name/color to each player var nameIdx = Math.floor(Math.random() * namePool.length); p.name = namePool[nameIdx]; namePool.splice(nameIdx, 1); // Remove used name so no duplicates p.setRole(i === 0); // local player is impostor, others are crewmates p.isAlive = true; // Spread players in larger world p.x = 600 + i % 3 * 1200; p.y = 800 + Math.floor(i / 3) * 1200; p.showName(); p.isLocal = i === 0; // For MVP, player 0 is local if (p.isLocal) { localPlayer = p; } // Mark impostor visually for local player (for debug, can be removed in production) if (p.isImpostor && p.isLocal) { // Optionally, you could show a message or highlight // infoTxt.setText('You are the Impostor!'); } players.push(p); game.addChild(p); } game.localPlayer = localPlayer; lastKillTime = Date.now(); // Reset kill cooldown on new game } // Initialize tasks function setupTasks() { tasks = []; var worldWidth = 4096; var spacing = worldWidth / (taskGoal + 1); for (var i = 0; i < taskGoal; i++) { var t = new TaskNode(); t.taskId = i; t.ownerId = localPlayer.playerId; t.x = spacing * (i + 1); t.y = 600; tasks.push(t); game.addChild(t); } } // Initialize sabotage nodes function setupSabotage() { sabotageNodes = []; for (var i = 0; i < 2; i++) { var s = new SabotageNode(); s.x = 800 + i * 2400; s.y = 3400; sabotageNodes.push(s); game.addChild(s); } } // Emergency meeting button function setupEmergencyBtn() { emergencyBtn = new EmergencyBtn(); emergencyBtn.x = 2048 / 2; emergencyBtn.y = 2732 - 200; game.addChild(emergencyBtn); } // Task bar function setupTaskBar() { if (!taskBarBg) { taskBarBg = LK.getAsset('tasknode', { anchorX: 0.5, anchorY: 0 }); taskBarBg.width = 800; taskBarBg.height = 40; taskBarBg.tint = 0x222222; taskBarBg.x = 2048 / 2; taskBarBg.y = 80; LK.gui.top.addChild(taskBarBg); } if (!taskBar) { taskBar = LK.getAsset('tasknode', { anchorX: 0.5, anchorY: 0 }); taskBar.width = 0; taskBar.height = 40; taskBar.tint = 0x83de44; taskBar.x = 2048 / 2; taskBar.y = 80; LK.gui.top.addChild(taskBar); } } // Info text function setupInfoTxt() { if (!infoTxt) { infoTxt = new Text2('', { size: 64, fill: "#fff" }); infoTxt.anchor.set(0.5, 0); LK.gui.top.addChild(infoTxt); infoTxt.x = 2048 / 2; infoTxt.y = 160; } } // Voting skip button function setupSkipBtn() { skipBtn = new VoteBtn(); skipBtn.x = 2048 / 2; skipBtn.y = 2732 - 300; skipBtn.visible = false; game.addChild(skipBtn); } // Update task bar function updateTaskBar() { var done = 0; for (var i = 0; i < tasks.length; i++) { if (tasks[i].done) done++; } var percent = done / taskGoal; taskBar.width = 800 * percent; } // Complete a task game.completeTask = function (task) { updateTaskBar(); // Check win var allDone = true; for (var i = 0; i < tasks.length; i++) { if (!tasks[i].done) allDone = false; } if (allDone) { // If all tasks are done, impostor wins (crewmates lose) gameEnd('impostors'); } }; // Sabotage triggered game.sabotageTriggered = function (node) { sabotageActive = true; sabotageTimer = LK.setTimeout(function () { sabotageActive = false; node.active = false; node.asset.alpha = 1; // If sabotage not fixed, impostors win gameEnd('impostors'); }, sabotageDuration); // Show info infoTxt.setText('Sabotage! Fix it!'); tween(infoTxt, { alpha: 1 }, { duration: 200 }); }; // Call emergency meeting game.callMeeting = function () { if (meetingCalled) return; meetingCalled = true; LK.getSound('meeting').play(); game.state = 'meeting'; infoTxt.setText('Emergency Meeting!'); // Remove all dead bodies for (var i = 0; i < players.length; i++) { if (!players[i].isAlive && players[i].deadBody) { players[i].deadBody.visible = false; } } // After 2 seconds, go to voting meetingTimer = LK.setTimeout(function () { game.startVoting(); }, 2000); }; // Start voting phase game.startVoting = function () { game.state = 'voting'; infoTxt.setText('Vote: Who is the Impostor?'); // Show voting buttons for each player for (var i = 0; i < votingBtns.length; i++) { votingBtns[i].destroy(); } votingBtns = []; var alivePlayers = []; for (var i = 0; i < players.length; i++) { if (players[i].isAlive) alivePlayers.push(players[i]); } var spacing = 2048 / (alivePlayers.length + 1); for (var i = 0; i < alivePlayers.length; i++) { var btn = new Container(); var asset = btn.attachAsset(alivePlayers[i].isImpostor ? 'votebtnred' : 'votebtn', { anchorX: 0.5, anchorY: 0.5 }); btn.x = spacing * (i + 1); btn.y = 1200; var txt = new Text2(alivePlayers[i].name, { size: 48, fill: "#fff" }); txt.anchor.set(0.5, 0.5); btn.addChild(txt); btn.down = function (targetPlayer) { return function (x, y, obj) { if (game.state === 'voting' && !game.localPlayer.isVoted && targetPlayer.isAlive && targetPlayer !== game.localPlayer) { game.voteFor(targetPlayer); } }; }(alivePlayers[i]); votingBtns.push(btn); game.addChild(btn); } // Show skip button skipBtn.visible = true; // Voting timer (auto skip after 10s) votingTimer = LK.setTimeout(function () { if (!game.localPlayer.isVoted) { game.voteFor(null); } }, 10000); }; // Vote for a player (or skip) game.voteFor = function (targetPlayer) { if (game.localPlayer.isVoted) return; game.localPlayer.isVoted = true; game.localPlayer.voteTarget = targetPlayer ? targetPlayer.playerId : null; LK.getSound('vote').play(); // Show confirmation infoTxt.setText('Voted!'); // Wait for all votes (MVP: AI votes randomly) var allVoted = true; for (var i = 0; i < players.length; i++) { if (players[i].isAlive && !players[i].isVoted) allVoted = false; } if (!allVoted) { // Simulate AI votes after 1s LK.setTimeout(function () { for (var i = 0; i < players.length; i++) { if (players[i].isAlive && !players[i].isVoted) { // AI votes randomly (not self) var choices = []; for (var j = 0; j < players.length; j++) { if (players[j].isAlive && players[j] !== players[i]) choices.push(players[j]); } if (Math.random() < 0.2) { // 20% skip players[i].voteTarget = null; } else { players[i].voteTarget = choices[Math.floor(Math.random() * choices.length)].playerId; } players[i].isVoted = true; } } game.tallyVotes(); }, 1000); } else { game.tallyVotes(); } }; // Tally votes and resolve game.tallyVotes = function () { // Clear voting timer if (votingTimer) { LK.clearTimeout(votingTimer); votingTimer = null; } // Hide voting buttons for (var i = 0; i < votingBtns.length; i++) { votingBtns[i].destroy(); } votingBtns = []; skipBtn.visible = false; // Count votes var counts = {}; var skipCount = 0; for (var i = 0; i < players.length; i++) { if (players[i].isAlive) { var v = players[i].voteTarget; if (v === null) { skipCount++; } else { if (!counts[v]) counts[v] = 0; counts[v]++; } } } // Find max var maxVotes = 0; var maxPlayerId = null; for (var pid in counts) { if (counts[pid] > maxVotes) { maxVotes = counts[pid]; maxPlayerId = parseInt(pid); } } // If tie or skip, no one ejected var tie = false; var totalVotes = 0; for (var pid in counts) { if (counts[pid] === maxVotes && parseInt(pid) !== maxPlayerId) tie = true; totalVotes += counts[pid]; } if (skipCount >= maxVotes || tie || maxPlayerId === null) { infoTxt.setText('No one was ejected.'); } else { // Eject player var ejected = null; for (var i = 0; i < players.length; i++) { if (players[i].playerId === maxPlayerId) { ejected = players[i]; break; } } if (ejected) { ejected.showDead(); infoTxt.setText(ejected.name + ' was ejected!'); // Check win var impostorsLeft = 0, crewmatesLeft = 0; for (var i = 0; i < players.length; i++) { if (players[i].isAlive) { if (players[i].isImpostor) impostorsLeft++;else crewmatesLeft++; } } if (impostorsLeft === 0) { gameEnd('crewmates'); return; } if (impostorsLeft >= crewmatesLeft) { gameEnd('impostors'); return; } } } // Reset votes for (var i = 0; i < players.length; i++) { players[i].isVoted = false; players[i].voteTarget = null; } // Resume play after 2s LK.setTimeout(function () { infoTxt.setText(''); game.state = 'playing'; meetingCalled = false; lastKillTime = Date.now(); // Reset kill cooldown after meeting }, 2000); }; // End game function gameEnd(winner) { game.state = 'ended'; if (winner === 'crewmates') { LK.showYouWin(); } else { // If local player is impostor, show win, else show game over if (game.localPlayer && game.localPlayer.isImpostor) { LK.showYouWin(); } else { LK.showGameOver(); } } } // Kill action (impostor only) function tryKill(target) { if (!game.localPlayer.isImpostor || !target.isAlive || !game.localPlayer.isAlive) return; // Must be close var dx = target.x - game.localPlayer.x; var dy = target.y - game.localPlayer.y; var dist = Math.sqrt(dx * dx + dy * dy); if (dist < 200) { target.showDead(); LK.getSound('kill').play(); // Show dead body if (target.deadBody) target.deadBody.visible = true; // Check win var impostorsLeft = 0, crewmatesLeft = 0; for (var i = 0; i < players.length; i++) { if (players[i].isAlive) { if (players[i].isImpostor) impostorsLeft++;else crewmatesLeft++; } } if (impostorsLeft === 0) { gameEnd('crewmates'); return; } if (impostorsLeft >= crewmatesLeft) { gameEnd('impostors'); return; } } } // Move handler (drag local player) var dragNode = null; function handleMove(x, y, obj) { if (game.state !== 'playing') return; // Make the local player always follow the mouse/touch position if (game.state === 'playing' && game.localPlayer && game.localPlayer.isAlive) { // Clamp to expanded game area var worldWidth = 4096; var worldHeight = 4096; var nx = Math.max(100, Math.min(worldWidth - 100, x)); var ny = Math.max(200, Math.min(worldHeight - 200, y)); // Move local player gradually toward the pointer/touch position (reduced speed) if (typeof game.localPlayer.targetX === "undefined") { game.localPlayer.targetX = game.localPlayer.x; game.localPlayer.targetY = game.localPlayer.y; } game.localPlayer.targetX = nx; game.localPlayer.targetY = ny; // Interpolate position (speed: 20 px per frame, capped to not overshoot) var dx = game.localPlayer.targetX - game.localPlayer.x; var dy = game.localPlayer.targetY - game.localPlayer.y; var dist = Math.sqrt(dx * dx + dy * dy); var maxSpeed = 20; if (dist > maxSpeed) { game.localPlayer.x += dx / dist * maxSpeed; game.localPlayer.y += dy / dist * maxSpeed; } else { game.localPlayer.x = game.localPlayer.targetX; game.localPlayer.y = game.localPlayer.targetY; } // Camera follow: center camera on local player, clamp to world bounds var camX = Math.max(0, Math.min(worldWidth - 2048, game.localPlayer.x - 1024)); var camY = Math.max(0, Math.min(worldHeight - 2732, game.localPlayer.y - 1366)); game.x = -camX; game.y = -camY; } // If impostor, check for kill if (game.state === 'playing' && game.localPlayer && game.localPlayer.isImpostor && game.localPlayer.isAlive && killAbilityActive) { // Only allow kill if 10s have passed since last kill var now = Date.now(); if (now - lastKillTime >= killCooldown) { for (var i = 0; i < players.length; i++) { var target = players[i]; if (target !== game.localPlayer && target.isAlive && !target.isImpostor && game.localPlayer.intersects(target)) { tryKill(target); // After a successful kill, deactivate kill ability until button is pressed again killAbilityActive = false; lastKillTime = now; // Reset cooldown // Optionally, reset killBtn visual effect // game.killBtn.alpha = 1; break; } } } } } game.move = handleMove; // Down handler (start drag or interact) game.down = function (x, y, obj) { if (game.state !== 'playing') return; // Only drag local player if touch is on them var lp = game.localPlayer; var dx = x - lp.x; var dy = y - lp.y; var dist = Math.sqrt(dx * dx + dy * dy); if (dist < 100 && lp.isAlive) { dragNode = lp; handleMove(x, y, obj); } }; game.up = function (x, y, obj) { dragNode = null; }; // Game update loop game.update = function () { // AI players move randomly if (game.state === 'playing') { for (var i = 0; i < players.length; i++) { var p = players[i]; if (!p.isLocal && p.isAlive) { // Random walk if (LK.ticks % (60 + i * 7) === 0) { var tx = 200 + Math.random() * (2048 - 400); var ty = 500 + Math.random() * (2732 - 1000); tween(p, { x: tx, y: ty }, { duration: 1200 + Math.random() * 800, easing: tween.easeInOut }); } // AI crewmates complete tasks if (!p.isImpostor) { for (var j = 0; j < tasks.length; j++) { var t = tasks[j]; if (!t.done && t.ownerId === p.playerId) { var dx = t.x - p.x; var dy = t.y - p.y; var dist = Math.sqrt(dx * dx + dy * dy); if (dist < 120) { t.done = true; t.asset.alpha = 0.3; updateTaskBar(); } } } } // AI impostor kills if (p.isImpostor) { for (var j = 0; j < players.length; j++) { var target = players[j]; if (target !== p && target.isAlive && !target.isImpostor) { var dx = target.x - p.x; var dy = target.y - p.y; var dist = Math.sqrt(dx * dx + dy * dy); if (dist < 180 && Math.random() < 0.01) { target.showDead(); if (target.deadBody) target.deadBody.visible = true; } } } } } } } // Make Kill button follow the local player (impostor) if (game.killBtn && game.localPlayer && game.localPlayer.isImpostor && game.localPlayer.isAlive) { // Position killBtn near the local player, offset to the right and below var offsetX = 120; var offsetY = 120; game.killBtn.x = game.localPlayer.x + offsetX; game.killBtn.y = game.localPlayer.y + offsetY; // Ensure killBtn is visible game.killBtn.visible = true; } }; // Initial setup setupPlayers(); setupTasks(); setupSabotage(); setupEmergencyBtn(); setupTaskBar(); setupInfoTxt(); setupSkipBtn(); updateTaskBar(); // If killBtn exists, it will follow the player in the update loop
/****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
/****
* Classes
****/
// Emergency meeting button
var EmergencyBtn = Container.expand(function () {
var self = Container.call(this);
self.asset = self.attachAsset('emergencybtn', {
anchorX: 0.5,
anchorY: 0.5
});
self.txt = new Text2('EMERGENCY', {
size: 48,
fill: "#fff"
});
self.txt.anchor.set(0.5, 0.5);
self.txt.x = 0;
self.txt.y = 0;
self.addChild(self.txt);
self.down = function (x, y, obj) {
if (game.state === 'playing' && !game.meetingCalled) {
game.callMeeting();
}
};
return self;
});
// Player class (Crewmate or Impostor)
var Player = Container.expand(function () {
var self = Container.call(this);
// Properties
self.isImpostor = false;
self.isAlive = true;
self.playerId = null;
self.name = '';
self.avatar = null;
self.isLocal = false; // Is this the local player?
self.isVoted = false;
self.voteTarget = null;
self.showName = function () {
if (!self.nameTxt) {
// Determine color for name based on player role
var nameColor = self.isImpostor ? "#d7263d" : "#83de44";
self.nameTxt = new Text2(self.name, {
size: 48,
fill: nameColor
});
self.nameTxt.anchor.set(0.5, 0);
self.addChild(self.nameTxt);
self.nameTxt.y = self.avatar.height / 2 + 10;
}
};
// Set up avatar
self.setRole = function (isImpostor) {
self.isImpostor = isImpostor;
if (self.avatar) {
self.removeChild(self.avatar);
}
// Determine color from name
var nameColorMap = {
"Red": 0xd7263d,
"Blue": 0x3ec1d3,
"Green": 0x83de44,
"Yellow": 0xf9d423,
"Pink": 0xff69b4,
"Orange": 0xf97c1b,
"Cyan": 0x15ffff,
"Lime": 0x7fff00,
"Purple": 0x6c3483,
"Brown": 0x8b5a2b
};
var colorKey = (self.name || "").split(" ")[0];
var playerColor = nameColorMap[colorKey] !== undefined ? nameColorMap[colorKey] : isImpostor ? 0xd7263d : 0x83de44;
if (isImpostor) {
self.avatar = self.attachAsset('impostor', {
anchorX: 0.5,
anchorY: 0.5,
tint: playerColor
});
} else {
self.avatar = self.attachAsset('crewmate', {
anchorX: 0.5,
anchorY: 0.5,
tint: playerColor
});
}
};
// Show dead body
self.showDead = function () {
if (self.avatar) self.avatar.visible = false;
if (!self.deadBody) {
self.deadBody = self.attachAsset('deadbody', {
anchorX: 0.5,
anchorY: 0.5
});
}
self.isAlive = false;
};
// Hide dead body, show alive
self.revive = function () {
if (self.deadBody) self.deadBody.visible = false;
if (self.avatar) self.avatar.visible = true;
self.isAlive = true;
};
// For touch/click events
self.down = function (x, y, obj) {
// Used for voting
if (game.state === 'voting' && !game.localPlayer.isVoted && self.isAlive && self !== game.localPlayer) {
game.voteFor(self);
} else if (game.state === 'playing' && game.localPlayer && game.localPlayer.isImpostor && game.localPlayer.isAlive && self.isAlive && self !== game.localPlayer) {
// Check distance for kill range (same as tryKill)
var dx = self.x - game.localPlayer.x;
var dy = self.y - game.localPlayer.y;
var dist = Math.sqrt(dx * dx + dy * dy);
if (dist < 200) {
tryKill(self);
// Deactivate kill ability and reset cooldown
killAbilityActive = false;
lastKillTime = Date.now();
}
}
};
return self;
});
// Sabotage node class
var SabotageNode = Container.expand(function () {
var self = Container.call(this);
self.active = false;
self.asset = self.attachAsset('sabotagenode', {
anchorX: 0.5,
anchorY: 0.5
});
self.down = function (x, y, obj) {
if (game.state === 'playing' && game.localPlayer.isImpostor && !self.active) {
// Activate sabotage
self.active = true;
self.asset.alpha = 0.5;
game.sabotageTriggered(self);
}
};
return self;
});
// Task node class
var TaskNode = Container.expand(function () {
var self = Container.call(this);
self.done = false;
self.taskId = null;
self.ownerId = null; // Which player this task belongs to (for local tasks)
// Assign a color to each vaccine/task node. For example, cycle through a set of colors based on taskId.
var vaccineColors = [0x83de44, 0x3ec1d3, 0xf9d423, 0xd7263d, 0x6c3483, 0x15ffff];
var color = vaccineColors[self.taskId % vaccineColors.length];
self.asset = self.attachAsset('tasknode', {
anchorX: 0.5,
anchorY: 0.5,
color: color
});
self.down = function (x, y, obj) {
if (game.state === 'playing' && !self.done && self.ownerId === game.localPlayer.playerId) {
// Complete task
self.done = true;
self.asset.alpha = 0.3;
LK.getSound('taskdone').play();
game.completeTask(self);
}
};
return self;
});
// Voting button (for skip or confirm)
var VoteBtn = Container.expand(function () {
var self = Container.call(this);
self.asset = self.attachAsset('votebtn', {
anchorX: 0.5,
anchorY: 0.5
});
self.txt = new Text2('SKIP', {
size: 48,
fill: "#fff"
});
self.txt.anchor.set(0.5, 0.5);
self.txt.x = 0;
self.txt.y = 0;
self.addChild(self.txt);
self.down = function (x, y, obj) {
if (game.state === 'voting' && !game.localPlayer.isVoted) {
game.voteFor(null); // skip
}
};
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0x1a1a2e
});
/****
* Game Code
****/
// Add background image to the game area
if (!game.bg) {
game.bg = LK.getAsset('background', {
anchorX: 0,
anchorY: 0,
x: 0,
y: 0,
width: 4096,
height: 4096
});
game.addChildAt(game.bg, 0); // Add as the bottom-most layer
}
// Place Emergency button at the bottom right corner using LK.gui.bottomRight
if (emergencyBtn) {
// Remove from previous parent if needed
if (emergencyBtn.parent) emergencyBtn.parent.removeChild(emergencyBtn);
emergencyBtn.x = 0;
emergencyBtn.y = 0;
LK.gui.bottomRight.addChild(emergencyBtn);
emergencyBtn.visible = true;
}
// Place Kill button as a button just below the emergencyBtn in LK.gui.bottomRight
if (!game.killBtn) {
// Create Kill button as a Container with the Kill asset
game.killBtn = new Container();
var killAsset = game.killBtn.attachAsset('Kill', {
anchorX: 0.5,
anchorY: 0.5
});
// Optionally, you can add a label or effect here if needed
// Set up button event (no action for now, can be extended)
game.killBtn.down = function (x, y, obj) {
// Placeholder for kill action if needed
};
}
// Remove from previous parent if needed
if (game.killBtn.parent) game.killBtn.parent.removeChild(game.killBtn);
// Place killBtn just below emergencyBtn in LK.gui.bottomRight
// Calculate y offset based on emergencyBtn's height
var killBtnYOffset = 0;
if (emergencyBtn && emergencyBtn.asset && emergencyBtn.asset.height) {
killBtnYOffset = emergencyBtn.asset.height + 32; // 32px gap for touch
} else {
killBtnYOffset = 300; // fallback if asset not loaded
}
game.killBtn.x = 0;
game.killBtn.y = killBtnYOffset;
LK.gui.bottomRight.addChild(game.killBtn);
game.killBtn.visible = true;
// Place bilmem button just below emergencyBtn in LK.gui.bottomRight
if (!game.bilmemBtn) {
game.bilmemBtn = new Container();
var bilmemAsset = game.bilmemBtn.attachAsset('bilmem', {
anchorX: 0.5,
anchorY: 0.5
});
// Optionally, add a label or effect here if needed
// Set up button event (no action for now, can be extended)
game.bilmemBtn.down = function (x, y, obj) {
// Placeholder for bilmem action if needed
};
}
// Remove from previous parent if needed
if (game.bilmemBtn.parent) game.bilmemBtn.parent.removeChild(game.bilmemBtn);
// Place bilmemBtn just below emergencyBtn in LK.gui.bottomRight
var bilmemBtnYOffset = 0;
if (emergencyBtn && emergencyBtn.asset && emergencyBtn.asset.height) {
bilmemBtnYOffset = emergencyBtn.asset.height + 32; // 32px gap for touch
} else {
bilmemBtnYOffset = 300; // fallback if asset not loaded
}
game.bilmemBtn.x = 0;
game.bilmemBtn.y = bilmemBtnYOffset;
LK.gui.bottomRight.addChild(game.bilmemBtn);
game.bilmemBtn.visible = true;
// Place yeniBtn just below bilmemBtn in LK.gui.bottomRight
if (!game.yeniBtn) {
game.yeniBtn = new Container();
var yeniAsset = game.yeniBtn.attachAsset('yeni', {
anchorX: 0.5,
anchorY: 0.5,
tint: 0x00ff00 // Set to green
});
// Optionally, add a label or effect here if needed
game.yeniBtn.down = function (x, y, obj) {
// Placeholder for yeni action if needed
};
}
// Remove from previous parent if needed
if (game.yeniBtn.parent) game.yeniBtn.parent.removeChild(game.yeniBtn);
// Place yeniBtn just below bilmemBtn in LK.gui.bottomRight
var yeniBtnYOffset = 0;
if (bilmemAsset && bilmemAsset.height) {
yeniBtnYOffset = bilmemAsset.height + 32; // 32px gap for touch
} else {
yeniBtnYOffset = 300; // fallback if asset not loaded
}
game.yeniBtn.x = 0;
game.yeniBtn.y = game.bilmemBtn.y + yeniBtnYOffset;
LK.gui.bottomRight.addChild(game.yeniBtn);
game.yeniBtn.visible = true;
// Track if kill ability is active for the local player
var killAbilityActive = false;
// Track when the last kill was made (or game started)
var killCooldown = 10000; // 10 seconds in ms
var lastKillTime = Date.now(); // Initialize to game start
// When Kill button is pressed, activate kill ability for local impostor
game.killBtn.down = function (x, y, obj) {
if (game.state === 'playing' && game.localPlayer && game.localPlayer.isImpostor && game.localPlayer.isAlive) {
killAbilityActive = true;
// Optionally, you can add a visual effect to the button to show it's active
// e.g. game.killBtn.alpha = 0.7;
}
};
// Removed updateKillBtn and setupKillBtn logic as killBtn now follows the player
// Simple sound for meeting
// Simple sound for kill
// Simple sound for task complete
// Voting button (red)
// Voting button (blue)
// Button for emergency meeting
// Dead body (gray ellipse)
// Sabotage node (yellow box)
// Task node (small green box)
// Crewmate and Impostor avatars (simple colored ellipses)
// Game state: 'playing', 'meeting', 'voting', 'ended'
game.state = 'playing';
// Game variables
var playerCount = 6; // For MVP, fixed at 6 (1 impostor, 5 crewmates)
var impostorCount = 1;
var players = [];
var tasks = [];
var sabotageNodes = [];
var deadBodies = [];
var votingBtns = [];
var votes = {};
var voteResults = {};
var meetingTimer = null;
var votingTimer = null;
var taskGoal = 4; // Number of tasks per crewmate
var sabotageActive = false;
var sabotageTimer = null;
var sabotageDuration = 6000; // ms
var emergencyBtn = null;
var skipBtn = null;
var infoTxt = null;
var taskBar = null;
var taskBarBg = null;
var meetingCalled = false;
var localPlayer = null;
// Helper: Random name generator
function randomName() {
var names = ['Red', 'Blue', 'Green', 'Yellow', 'Pink', 'Orange', 'Cyan', 'Lime', 'Purple', 'Brown'];
return names[Math.floor(Math.random() * names.length)];
}
// Helper: Shuffle array
function shuffle(arr) {
for (var i = arr.length - 1; i > 0; i--) {
var j = Math.floor(Math.random() * (i + 1));
var t = arr[i];
arr[i] = arr[j];
arr[j] = t;
}
return arr;
}
// Initialize players
function setupPlayers() {
players = [];
var roles = [];
for (var i = 0; i < impostorCount; i++) roles.push(true);
for (var i = impostorCount; i < playerCount; i++) roles.push(false);
// Always assign impostor to local player (player 0)
roles = [];
roles[0] = true; // local player is impostor
for (var i = 1; i < playerCount; i++) roles[i] = false;
shuffle(roles.slice(1)); // shuffle only the crewmates for variety
// Prepare a unique name pool so each player gets a unique color
var namePool = ['Red', 'Blue', 'Green', 'Yellow', 'Pink', 'Orange', 'Cyan', 'Lime', 'Purple', 'Brown'];
for (var i = 0; i < playerCount; i++) {
var p = new Player();
p.playerId = i;
// Assign a unique name/color to each player
var nameIdx = Math.floor(Math.random() * namePool.length);
p.name = namePool[nameIdx];
namePool.splice(nameIdx, 1); // Remove used name so no duplicates
p.setRole(i === 0); // local player is impostor, others are crewmates
p.isAlive = true;
// Spread players in larger world
p.x = 600 + i % 3 * 1200;
p.y = 800 + Math.floor(i / 3) * 1200;
p.showName();
p.isLocal = i === 0; // For MVP, player 0 is local
if (p.isLocal) {
localPlayer = p;
}
// Mark impostor visually for local player (for debug, can be removed in production)
if (p.isImpostor && p.isLocal) {
// Optionally, you could show a message or highlight
// infoTxt.setText('You are the Impostor!');
}
players.push(p);
game.addChild(p);
}
game.localPlayer = localPlayer;
lastKillTime = Date.now(); // Reset kill cooldown on new game
}
// Initialize tasks
function setupTasks() {
tasks = [];
var worldWidth = 4096;
var spacing = worldWidth / (taskGoal + 1);
for (var i = 0; i < taskGoal; i++) {
var t = new TaskNode();
t.taskId = i;
t.ownerId = localPlayer.playerId;
t.x = spacing * (i + 1);
t.y = 600;
tasks.push(t);
game.addChild(t);
}
}
// Initialize sabotage nodes
function setupSabotage() {
sabotageNodes = [];
for (var i = 0; i < 2; i++) {
var s = new SabotageNode();
s.x = 800 + i * 2400;
s.y = 3400;
sabotageNodes.push(s);
game.addChild(s);
}
}
// Emergency meeting button
function setupEmergencyBtn() {
emergencyBtn = new EmergencyBtn();
emergencyBtn.x = 2048 / 2;
emergencyBtn.y = 2732 - 200;
game.addChild(emergencyBtn);
}
// Task bar
function setupTaskBar() {
if (!taskBarBg) {
taskBarBg = LK.getAsset('tasknode', {
anchorX: 0.5,
anchorY: 0
});
taskBarBg.width = 800;
taskBarBg.height = 40;
taskBarBg.tint = 0x222222;
taskBarBg.x = 2048 / 2;
taskBarBg.y = 80;
LK.gui.top.addChild(taskBarBg);
}
if (!taskBar) {
taskBar = LK.getAsset('tasknode', {
anchorX: 0.5,
anchorY: 0
});
taskBar.width = 0;
taskBar.height = 40;
taskBar.tint = 0x83de44;
taskBar.x = 2048 / 2;
taskBar.y = 80;
LK.gui.top.addChild(taskBar);
}
}
// Info text
function setupInfoTxt() {
if (!infoTxt) {
infoTxt = new Text2('', {
size: 64,
fill: "#fff"
});
infoTxt.anchor.set(0.5, 0);
LK.gui.top.addChild(infoTxt);
infoTxt.x = 2048 / 2;
infoTxt.y = 160;
}
}
// Voting skip button
function setupSkipBtn() {
skipBtn = new VoteBtn();
skipBtn.x = 2048 / 2;
skipBtn.y = 2732 - 300;
skipBtn.visible = false;
game.addChild(skipBtn);
}
// Update task bar
function updateTaskBar() {
var done = 0;
for (var i = 0; i < tasks.length; i++) {
if (tasks[i].done) done++;
}
var percent = done / taskGoal;
taskBar.width = 800 * percent;
}
// Complete a task
game.completeTask = function (task) {
updateTaskBar();
// Check win
var allDone = true;
for (var i = 0; i < tasks.length; i++) {
if (!tasks[i].done) allDone = false;
}
if (allDone) {
// If all tasks are done, impostor wins (crewmates lose)
gameEnd('impostors');
}
};
// Sabotage triggered
game.sabotageTriggered = function (node) {
sabotageActive = true;
sabotageTimer = LK.setTimeout(function () {
sabotageActive = false;
node.active = false;
node.asset.alpha = 1;
// If sabotage not fixed, impostors win
gameEnd('impostors');
}, sabotageDuration);
// Show info
infoTxt.setText('Sabotage! Fix it!');
tween(infoTxt, {
alpha: 1
}, {
duration: 200
});
};
// Call emergency meeting
game.callMeeting = function () {
if (meetingCalled) return;
meetingCalled = true;
LK.getSound('meeting').play();
game.state = 'meeting';
infoTxt.setText('Emergency Meeting!');
// Remove all dead bodies
for (var i = 0; i < players.length; i++) {
if (!players[i].isAlive && players[i].deadBody) {
players[i].deadBody.visible = false;
}
}
// After 2 seconds, go to voting
meetingTimer = LK.setTimeout(function () {
game.startVoting();
}, 2000);
};
// Start voting phase
game.startVoting = function () {
game.state = 'voting';
infoTxt.setText('Vote: Who is the Impostor?');
// Show voting buttons for each player
for (var i = 0; i < votingBtns.length; i++) {
votingBtns[i].destroy();
}
votingBtns = [];
var alivePlayers = [];
for (var i = 0; i < players.length; i++) {
if (players[i].isAlive) alivePlayers.push(players[i]);
}
var spacing = 2048 / (alivePlayers.length + 1);
for (var i = 0; i < alivePlayers.length; i++) {
var btn = new Container();
var asset = btn.attachAsset(alivePlayers[i].isImpostor ? 'votebtnred' : 'votebtn', {
anchorX: 0.5,
anchorY: 0.5
});
btn.x = spacing * (i + 1);
btn.y = 1200;
var txt = new Text2(alivePlayers[i].name, {
size: 48,
fill: "#fff"
});
txt.anchor.set(0.5, 0.5);
btn.addChild(txt);
btn.down = function (targetPlayer) {
return function (x, y, obj) {
if (game.state === 'voting' && !game.localPlayer.isVoted && targetPlayer.isAlive && targetPlayer !== game.localPlayer) {
game.voteFor(targetPlayer);
}
};
}(alivePlayers[i]);
votingBtns.push(btn);
game.addChild(btn);
}
// Show skip button
skipBtn.visible = true;
// Voting timer (auto skip after 10s)
votingTimer = LK.setTimeout(function () {
if (!game.localPlayer.isVoted) {
game.voteFor(null);
}
}, 10000);
};
// Vote for a player (or skip)
game.voteFor = function (targetPlayer) {
if (game.localPlayer.isVoted) return;
game.localPlayer.isVoted = true;
game.localPlayer.voteTarget = targetPlayer ? targetPlayer.playerId : null;
LK.getSound('vote').play();
// Show confirmation
infoTxt.setText('Voted!');
// Wait for all votes (MVP: AI votes randomly)
var allVoted = true;
for (var i = 0; i < players.length; i++) {
if (players[i].isAlive && !players[i].isVoted) allVoted = false;
}
if (!allVoted) {
// Simulate AI votes after 1s
LK.setTimeout(function () {
for (var i = 0; i < players.length; i++) {
if (players[i].isAlive && !players[i].isVoted) {
// AI votes randomly (not self)
var choices = [];
for (var j = 0; j < players.length; j++) {
if (players[j].isAlive && players[j] !== players[i]) choices.push(players[j]);
}
if (Math.random() < 0.2) {
// 20% skip
players[i].voteTarget = null;
} else {
players[i].voteTarget = choices[Math.floor(Math.random() * choices.length)].playerId;
}
players[i].isVoted = true;
}
}
game.tallyVotes();
}, 1000);
} else {
game.tallyVotes();
}
};
// Tally votes and resolve
game.tallyVotes = function () {
// Clear voting timer
if (votingTimer) {
LK.clearTimeout(votingTimer);
votingTimer = null;
}
// Hide voting buttons
for (var i = 0; i < votingBtns.length; i++) {
votingBtns[i].destroy();
}
votingBtns = [];
skipBtn.visible = false;
// Count votes
var counts = {};
var skipCount = 0;
for (var i = 0; i < players.length; i++) {
if (players[i].isAlive) {
var v = players[i].voteTarget;
if (v === null) {
skipCount++;
} else {
if (!counts[v]) counts[v] = 0;
counts[v]++;
}
}
}
// Find max
var maxVotes = 0;
var maxPlayerId = null;
for (var pid in counts) {
if (counts[pid] > maxVotes) {
maxVotes = counts[pid];
maxPlayerId = parseInt(pid);
}
}
// If tie or skip, no one ejected
var tie = false;
var totalVotes = 0;
for (var pid in counts) {
if (counts[pid] === maxVotes && parseInt(pid) !== maxPlayerId) tie = true;
totalVotes += counts[pid];
}
if (skipCount >= maxVotes || tie || maxPlayerId === null) {
infoTxt.setText('No one was ejected.');
} else {
// Eject player
var ejected = null;
for (var i = 0; i < players.length; i++) {
if (players[i].playerId === maxPlayerId) {
ejected = players[i];
break;
}
}
if (ejected) {
ejected.showDead();
infoTxt.setText(ejected.name + ' was ejected!');
// Check win
var impostorsLeft = 0,
crewmatesLeft = 0;
for (var i = 0; i < players.length; i++) {
if (players[i].isAlive) {
if (players[i].isImpostor) impostorsLeft++;else crewmatesLeft++;
}
}
if (impostorsLeft === 0) {
gameEnd('crewmates');
return;
}
if (impostorsLeft >= crewmatesLeft) {
gameEnd('impostors');
return;
}
}
}
// Reset votes
for (var i = 0; i < players.length; i++) {
players[i].isVoted = false;
players[i].voteTarget = null;
}
// Resume play after 2s
LK.setTimeout(function () {
infoTxt.setText('');
game.state = 'playing';
meetingCalled = false;
lastKillTime = Date.now(); // Reset kill cooldown after meeting
}, 2000);
};
// End game
function gameEnd(winner) {
game.state = 'ended';
if (winner === 'crewmates') {
LK.showYouWin();
} else {
// If local player is impostor, show win, else show game over
if (game.localPlayer && game.localPlayer.isImpostor) {
LK.showYouWin();
} else {
LK.showGameOver();
}
}
}
// Kill action (impostor only)
function tryKill(target) {
if (!game.localPlayer.isImpostor || !target.isAlive || !game.localPlayer.isAlive) return;
// Must be close
var dx = target.x - game.localPlayer.x;
var dy = target.y - game.localPlayer.y;
var dist = Math.sqrt(dx * dx + dy * dy);
if (dist < 200) {
target.showDead();
LK.getSound('kill').play();
// Show dead body
if (target.deadBody) target.deadBody.visible = true;
// Check win
var impostorsLeft = 0,
crewmatesLeft = 0;
for (var i = 0; i < players.length; i++) {
if (players[i].isAlive) {
if (players[i].isImpostor) impostorsLeft++;else crewmatesLeft++;
}
}
if (impostorsLeft === 0) {
gameEnd('crewmates');
return;
}
if (impostorsLeft >= crewmatesLeft) {
gameEnd('impostors');
return;
}
}
}
// Move handler (drag local player)
var dragNode = null;
function handleMove(x, y, obj) {
if (game.state !== 'playing') return;
// Make the local player always follow the mouse/touch position
if (game.state === 'playing' && game.localPlayer && game.localPlayer.isAlive) {
// Clamp to expanded game area
var worldWidth = 4096;
var worldHeight = 4096;
var nx = Math.max(100, Math.min(worldWidth - 100, x));
var ny = Math.max(200, Math.min(worldHeight - 200, y));
// Move local player gradually toward the pointer/touch position (reduced speed)
if (typeof game.localPlayer.targetX === "undefined") {
game.localPlayer.targetX = game.localPlayer.x;
game.localPlayer.targetY = game.localPlayer.y;
}
game.localPlayer.targetX = nx;
game.localPlayer.targetY = ny;
// Interpolate position (speed: 20 px per frame, capped to not overshoot)
var dx = game.localPlayer.targetX - game.localPlayer.x;
var dy = game.localPlayer.targetY - game.localPlayer.y;
var dist = Math.sqrt(dx * dx + dy * dy);
var maxSpeed = 20;
if (dist > maxSpeed) {
game.localPlayer.x += dx / dist * maxSpeed;
game.localPlayer.y += dy / dist * maxSpeed;
} else {
game.localPlayer.x = game.localPlayer.targetX;
game.localPlayer.y = game.localPlayer.targetY;
}
// Camera follow: center camera on local player, clamp to world bounds
var camX = Math.max(0, Math.min(worldWidth - 2048, game.localPlayer.x - 1024));
var camY = Math.max(0, Math.min(worldHeight - 2732, game.localPlayer.y - 1366));
game.x = -camX;
game.y = -camY;
}
// If impostor, check for kill
if (game.state === 'playing' && game.localPlayer && game.localPlayer.isImpostor && game.localPlayer.isAlive && killAbilityActive) {
// Only allow kill if 10s have passed since last kill
var now = Date.now();
if (now - lastKillTime >= killCooldown) {
for (var i = 0; i < players.length; i++) {
var target = players[i];
if (target !== game.localPlayer && target.isAlive && !target.isImpostor && game.localPlayer.intersects(target)) {
tryKill(target);
// After a successful kill, deactivate kill ability until button is pressed again
killAbilityActive = false;
lastKillTime = now; // Reset cooldown
// Optionally, reset killBtn visual effect
// game.killBtn.alpha = 1;
break;
}
}
}
}
}
game.move = handleMove;
// Down handler (start drag or interact)
game.down = function (x, y, obj) {
if (game.state !== 'playing') return;
// Only drag local player if touch is on them
var lp = game.localPlayer;
var dx = x - lp.x;
var dy = y - lp.y;
var dist = Math.sqrt(dx * dx + dy * dy);
if (dist < 100 && lp.isAlive) {
dragNode = lp;
handleMove(x, y, obj);
}
};
game.up = function (x, y, obj) {
dragNode = null;
};
// Game update loop
game.update = function () {
// AI players move randomly
if (game.state === 'playing') {
for (var i = 0; i < players.length; i++) {
var p = players[i];
if (!p.isLocal && p.isAlive) {
// Random walk
if (LK.ticks % (60 + i * 7) === 0) {
var tx = 200 + Math.random() * (2048 - 400);
var ty = 500 + Math.random() * (2732 - 1000);
tween(p, {
x: tx,
y: ty
}, {
duration: 1200 + Math.random() * 800,
easing: tween.easeInOut
});
}
// AI crewmates complete tasks
if (!p.isImpostor) {
for (var j = 0; j < tasks.length; j++) {
var t = tasks[j];
if (!t.done && t.ownerId === p.playerId) {
var dx = t.x - p.x;
var dy = t.y - p.y;
var dist = Math.sqrt(dx * dx + dy * dy);
if (dist < 120) {
t.done = true;
t.asset.alpha = 0.3;
updateTaskBar();
}
}
}
}
// AI impostor kills
if (p.isImpostor) {
for (var j = 0; j < players.length; j++) {
var target = players[j];
if (target !== p && target.isAlive && !target.isImpostor) {
var dx = target.x - p.x;
var dy = target.y - p.y;
var dist = Math.sqrt(dx * dx + dy * dy);
if (dist < 180 && Math.random() < 0.01) {
target.showDead();
if (target.deadBody) target.deadBody.visible = true;
}
}
}
}
}
}
}
// Make Kill button follow the local player (impostor)
if (game.killBtn && game.localPlayer && game.localPlayer.isImpostor && game.localPlayer.isAlive) {
// Position killBtn near the local player, offset to the right and below
var offsetX = 120;
var offsetY = 120;
game.killBtn.x = game.localPlayer.x + offsetX;
game.killBtn.y = game.localPlayer.y + offsetY;
// Ensure killBtn is visible
game.killBtn.visible = true;
}
};
// Initial setup
setupPlayers();
setupTasks();
setupSabotage();
setupEmergencyBtn();
setupTaskBar();
setupInfoTxt();
setupSkipBtn();
updateTaskBar();
// If killBtn exists, it will follow the player in the update loop