/**** * Plugins ****/ var tween = LK.import("@upit/tween.v1"); var storage = LK.import("@upit/storage.v1"); /**** * Classes ****/ var CablesTask = Container.expand(function (x, y, id, assignedPlayerId) { var self = Container.call(this); self.taskId = id || 0; self.isCompleted = false; self.x = x; self.y = y; self.taskType = 'cables'; self.taskName = 'Fix Wiring'; self.taskDescription = 'Connect the colored wires'; self.isActive = false; self.wiresConnected = 0; self.totalWires = 4; self.wires = []; self.connectors = []; self.isNearPlayer = false; self.lastNearPlayer = false; self.assignedPlayerId = assignedPlayerId || -1; // Which player this task is assigned to (-1 = any player) // Task panel background var taskPanel = self.attachAsset('cablePanel', { anchorX: 0.5, anchorY: 0.5 }); taskPanel.alpha = 0.8; // Add task name text var taskNameText = new Text2(self.taskName, { size: 24, fill: 0xFFFFFF }); taskNameText.anchor.set(0.5, 0); taskNameText.y = -180; self.addChild(taskNameText); // Add interaction hint text var interactionText = new Text2('Click to start wiring', { size: 18, fill: 0x00FFFF }); interactionText.anchor.set(0.5, 0); interactionText.y = 170; interactionText.alpha = 0; self.addChild(interactionText); // Wire colors var wireColors = [0xff0000, 0x00ff00, 0x0000ff, 0xffff00]; var leftConnectorPositions = []; var rightConnectorPositions = []; // Create left side connectors (sources) for (var i = 0; i < self.totalWires; i++) { var leftConnector = self.attachAsset('cableConnector', { anchorX: 0.5, anchorY: 0.5 }); leftConnector.x = -80; leftConnector.y = -60 + i * 40; leftConnector.tint = wireColors[i]; leftConnector.wireIndex = i; leftConnector.isSource = true; leftConnector.isConnected = false; self.connectors.push(leftConnector); leftConnectorPositions.push({ x: leftConnector.x, y: leftConnector.y }); } // Create right side connectors (destinations) - shuffled var shuffledColors = wireColors.slice(); for (var i = shuffledColors.length - 1; i > 0; i--) { var j = Math.floor(Math.random() * (i + 1)); var temp = shuffledColors[i]; shuffledColors[i] = shuffledColors[j]; shuffledColors[j] = temp; } for (var i = 0; i < self.totalWires; i++) { var rightConnector = self.attachAsset('cableConnector', { anchorX: 0.5, anchorY: 0.5 }); rightConnector.x = 80; rightConnector.y = -60 + i * 40; rightConnector.tint = shuffledColors[i]; rightConnector.wireIndex = wireColors.indexOf(shuffledColors[i]); rightConnector.isSource = false; rightConnector.isConnected = false; self.connectors.push(rightConnector); rightConnectorPositions.push({ x: rightConnector.x, y: rightConnector.y }); } // Create wires (initially disconnected) for (var i = 0; i < self.totalWires; i++) { var wire = self.attachAsset('cableWire', { anchorX: 0, anchorY: 0.5 }); wire.x = -70; wire.y = -60 + i * 40; wire.width = 60; wire.tint = wireColors[i]; wire.wireIndex = i; wire.isConnected = false; wire.alpha = 0.5; self.wires.push(wire); } self.startTask = function () { if (!self.isActive && !self.isCompleted) { self.isActive = true; // Show task panel with animation tween(taskPanel, { scaleX: 1.1, scaleY: 1.1, alpha: 1.0 }, { duration: 300, onFinish: function onFinish() { tween(taskPanel, { scaleX: 1.0, scaleY: 1.0 }, { duration: 200 }); } }); // Show instruction var instructionText = new Text2('Connect matching wire colors', { size: 20, fill: 0xFFFF00 }); instructionText.anchor.set(0.5, 0); instructionText.y = -130; self.addChild(instructionText); // Remove after delay LK.setTimeout(function () { if (instructionText) { instructionText.destroy(); } }, 2000); } }; self.connectWire = function (sourceIndex, targetIndex) { if (sourceIndex === targetIndex && !self.wires[sourceIndex].isConnected) { // Correct connection self.wires[sourceIndex].isConnected = true; self.wires[sourceIndex].alpha = 1.0; self.wires[sourceIndex].width = 160; self.wiresConnected++; // Mark connectors as connected self.connectors.forEach(function (connector) { if (connector.wireIndex === sourceIndex) { connector.isConnected = true; // Flash connected effect tween(connector, { scaleX: 1.3, scaleY: 1.3 }, { duration: 150, onFinish: function onFinish() { tween(connector, { scaleX: 1.0, scaleY: 1.0 }, { duration: 150 }); } }); } }); // Play success sound LK.getSound('taskComplete').play(); // Check if all wires are connected if (self.wiresConnected >= self.totalWires) { self.complete(); } return true; } else { // Wrong connection - flash red var wrongWire = self.wires[sourceIndex]; tween(wrongWire, { tint: 0xff0000, alpha: 0.8 }, { duration: 200, onFinish: function onFinish() { tween(wrongWire, { tint: wireColors[sourceIndex], alpha: 0.5 }, { duration: 200 }); } }); return false; } }; self.complete = function () { if (!self.isCompleted) { self.isCompleted = true; self.isActive = false; // Hide task panel tween(taskPanel, { alpha: 0, scaleX: 0.8, scaleY: 0.8 }, { duration: 500 }); // Show completion text var completedText = new Text2('WIRING COMPLETE', { size: 24, fill: 0x00FF00 }); completedText.anchor.set(0.5, 0.5); completedText.y = 0; self.addChild(completedText); // Flash completion effect tween(completedText, { scaleX: 1.2, scaleY: 1.2, alpha: 1.0 }, { duration: 300, onFinish: function onFinish() { tween(completedText, { scaleX: 1.0, scaleY: 1.0, alpha: 0.8 }, { duration: 300 }); } }); return true; } return false; }; self.update = function () { if (self.isCompleted) return; // Check if player is near self.lastNearPlayer = self.isNearPlayer; self.isNearPlayer = false; if (currentPlayer && currentPlayer.isAlive && !currentPlayer.isImpostor) { var distance = Math.sqrt(Math.pow(currentPlayer.x - self.x, 2) + Math.pow(currentPlayer.y - self.y, 2)); // Only show task if it's assigned to current player or unassigned var isAssignedToCurrentPlayer = self.assignedPlayerId === -1 || self.assignedPlayerId === currentPlayer.playerId; self.isNearPlayer = distance < 120 && isAssignedToCurrentPlayer; } // Show interaction hint when player gets close if (!self.lastNearPlayer && self.isNearPlayer) { interactionText.alpha = 1; tween(interactionText, { y: 180, alpha: 0.9 }, { duration: 300 }); } // Hide interaction hint when player moves away if (self.lastNearPlayer && !self.isNearPlayer) { tween(interactionText, { y: 170, alpha: 0 }, { duration: 300 }); self.isActive = false; } }; self.down = function (x, y, obj) { if (currentPlayer && currentPlayer.isAlive && !currentPlayer.isImpostor) { var distance = Math.sqrt(Math.pow(currentPlayer.x - self.x, 2) + Math.pow(currentPlayer.y - self.y, 2)); // Only allow interaction if task is assigned to current player or unassigned var isAssignedToCurrentPlayer = self.assignedPlayerId === -1 || self.assignedPlayerId === currentPlayer.playerId; if (distance < 120 && !self.isCompleted && isAssignedToCurrentPlayer) { if (!self.isActive) { self.startTask(); } else { // Handle wire connection clicks var localX = x - self.x; var localY = y - self.y; // Check if clicking on a connector for (var i = 0; i < self.connectors.length; i++) { var connector = self.connectors[i]; var connectorDistance = Math.sqrt(Math.pow(localX - connector.x, 2) + Math.pow(localY - connector.y, 2)); if (connectorDistance < 25 && connector.isSource && !connector.isConnected) { // Find matching target connector for (var j = 0; j < self.connectors.length; j++) { var targetConnector = self.connectors[j]; if (!targetConnector.isSource && targetConnector.wireIndex === connector.wireIndex) { self.connectWire(connector.wireIndex, targetConnector.wireIndex); break; } } break; } } } } } }; return self; }); var Camera = Container.expand(function (x, y, id, viewAngle) { var self = Container.call(this); self.cameraId = id || 0; self.x = x; self.y = y; self.viewAngle = viewAngle || 0; self.detectionRange = 200; self.isActive = true; self.playersInView = []; // Camera graphics var cameraGraphics = self.attachAsset('camera', { anchorX: 0.5, anchorY: 0.5 }); cameraGraphics.rotation = self.viewAngle; // Camera name text var cameraNameText = new Text2('CAM ' + (self.cameraId + 1), { size: 16, fill: 0xFFFFFF }); cameraNameText.anchor.set(0.5, 0); cameraNameText.y = -30; self.addChild(cameraNameText); // Detection light indicator var detectionLight = self.attachAsset('cableConnector', { anchorX: 0.5, anchorY: 0.5, scaleX: 0.5, scaleY: 0.5 }); detectionLight.x = 0; detectionLight.y = 25; detectionLight.tint = 0x00FF00; self.update = function () { if (!self.isActive) return; // Clear previous detections self.playersInView = []; // Check for players in camera view if (players && players.length > 0) { for (var i = 0; i < players.length; i++) { var player = players[i]; if (!player.isAlive) continue; var distance = Math.sqrt(Math.pow(player.x - self.x, 2) + Math.pow(player.y - self.y, 2)); if (distance <= self.detectionRange) { // Check if player is within camera's field of view var angleToPlayer = Math.atan2(player.y - self.y, player.x - self.x); var angleDiff = Math.abs(angleToPlayer - self.viewAngle); // Normalize angle difference if (angleDiff > Math.PI) { angleDiff = 2 * Math.PI - angleDiff; } // Field of view is 90 degrees (π/2 radians) if (angleDiff <= Math.PI / 4) { self.playersInView.push(player); } } } } // Update detection light based on players in view if (self.playersInView.length > 0) { detectionLight.tint = 0xFF0000; // Red when detecting players if (LK.ticks % 30 < 15) { detectionLight.alpha = 1.0; } else { detectionLight.alpha = 0.5; } } else { detectionLight.tint = 0x00FF00; // Green when no detection detectionLight.alpha = 1.0; } }; return self; }); var Door = Container.expand(function (x, y, id, orientation) { var self = Container.call(this); self.doorId = id || 0; self.x = x; self.y = y; self.orientation = orientation || 0; // 0 = horizontal, 1 = vertical self.isOpen = true; self.closeTimer = 0; self.closeDuration = 10000; // 10 seconds self.isNearPlayer = false; self.lastNearPlayer = false; // Door graphics var doorGraphics = self.attachAsset('door', { anchorX: 0.5, anchorY: 0.5 }); if (self.orientation === 1) { doorGraphics.rotation = Math.PI / 2; } // Door status indicator var statusText = new Text2('OPEN', { size: 14, fill: 0x00FF00 }); statusText.anchor.set(0.5, 0); statusText.y = -40; self.addChild(statusText); // Visual indicator for impostors var impostorIndicator = self.attachAsset('doorButton', { anchorX: 0.5, anchorY: 0.5, scaleX: 0.3, scaleY: 0.3 }); impostorIndicator.y = 20; impostorIndicator.alpha = 0; impostorIndicator.tint = 0xFF0000; self.close = function () { if (self.isOpen) { self.isOpen = false; self.closeTimer = self.closeDuration; // Change graphics doorGraphics.destroy(); doorGraphics = self.attachAsset('doorClosed', { anchorX: 0.5, anchorY: 0.5 }); if (self.orientation === 1) { doorGraphics.rotation = Math.PI / 2; } // Update status statusText.setText('CLOSED'); statusText.tint = 0xFF0000; // Play sound LK.getSound('doorClose').play(); // Flash effect tween(doorGraphics, { tint: 0xFF0000, scaleX: 1.2, scaleY: 1.2 }, { duration: 200, onFinish: function onFinish() { tween(doorGraphics, { tint: 0xFFFFFF, scaleX: 1.0, scaleY: 1.0 }, { duration: 200 }); } }); } }; self.open = function () { if (!self.isOpen) { self.isOpen = true; self.closeTimer = 0; // Change graphics doorGraphics.destroy(); doorGraphics = self.attachAsset('door', { anchorX: 0.5, anchorY: 0.5 }); if (self.orientation === 1) { doorGraphics.rotation = Math.PI / 2; } // Update status statusText.setText('OPEN'); statusText.tint = 0x00FF00; // Play sound LK.getSound('doorOpen').play(); // Flash effect tween(doorGraphics, { tint: 0x00FF00, scaleX: 1.2, scaleY: 1.2 }, { duration: 200, onFinish: function onFinish() { tween(doorGraphics, { tint: 0xFFFFFF, scaleX: 1.0, scaleY: 1.0 }, { duration: 200 }); } }); } }; self.update = function () { // Auto-open after timer expires if (!self.isOpen && self.closeTimer > 0) { self.closeTimer -= 16.67; // Roughly 1 frame at 60fps if (self.closeTimer <= 0) { self.open(); } } // Check if impostor player is near self.lastNearPlayer = self.isNearPlayer; self.isNearPlayer = false; if (currentPlayer && currentPlayer.isAlive && currentPlayer.isImpostor) { var distance = Math.sqrt(Math.pow(currentPlayer.x - self.x, 2) + Math.pow(currentPlayer.y - self.y, 2)); self.isNearPlayer = distance < 100; } // Show impostor indicator when impostor is near if (currentPlayer && currentPlayer.isImpostor && self.isNearPlayer) { impostorIndicator.alpha = 0.8; if (LK.ticks % 30 < 15) { impostorIndicator.alpha = 1.0; } else { impostorIndicator.alpha = 0.6; } } else { impostorIndicator.alpha = 0; } // Update timer display if (!self.isOpen && self.closeTimer > 0) { var secondsLeft = Math.ceil(self.closeTimer / 1000); statusText.setText('CLOSED (' + secondsLeft + 's)'); } }; self.down = function (x, y, obj) { if (currentPlayer && currentPlayer.isAlive && currentPlayer.isImpostor) { var distance = Math.sqrt(Math.pow(currentPlayer.x - self.x, 2) + Math.pow(currentPlayer.y - self.y, 2)); if (distance < 100) { if (self.isOpen) { self.close(); } else { self.open(); } } } }; // Check if door blocks movement self.blocksMovement = function (x, y, width, height) { // If door is open, it doesn't block movement if (self.isOpen) return false; var playerLeft = x - width / 2; var playerRight = x + width / 2; var playerTop = y - height / 2; var playerBottom = y + height / 2; var doorLeft = self.x - 20; var doorRight = self.x + 20; var doorTop = self.y - 60; var doorBottom = self.y + 60; if (self.orientation === 1) { // Vertical door doorLeft = self.x - 60; doorRight = self.x + 60; doorTop = self.y - 20; doorBottom = self.y + 20; } // Check if player rectangle intersects with door rectangle return playerLeft < doorRight && playerRight > doorLeft && playerTop < doorBottom && playerBottom > doorTop; }; return self; }); var Ghost = Container.expand(function (deadPlayer) { var self = Container.call(this); self.x = deadPlayer.x; self.y = deadPlayer.y; self.targetX = deadPlayer.x; self.targetY = deadPlayer.y; self.speed = 1.5; // Slower than living players self.deadPlayerId = deadPlayer.playerId; // Create ghost graphics based on the dead player var ghostGraphics = self.attachAsset(deadPlayer.isImpostor ? 'impostor' : 'crewmate', { anchorX: 0.5, anchorY: 0.5 }); ghostGraphics.tint = 0xAAAAFF; // Ghostly blue-white tint ghostGraphics.alpha = 0.6; // Semi-transparent // Add floating animation self.floatOffset = 0; self.floatSpeed = 0.05; // Ghost moves randomly around the ship self.wanderTimer = 0; self.wanderInterval = 3000; // Change direction every 3 seconds self.update = function () { // Floating animation self.floatOffset += self.floatSpeed; ghostGraphics.y = Math.sin(self.floatOffset) * 5; // Pulsing alpha effect if (LK.ticks % 120 < 60) { ghostGraphics.alpha = 0.6; } else { ghostGraphics.alpha = 0.3; } // Wandering behavior self.wanderTimer += 16.67; if (self.wanderTimer >= self.wanderInterval) { self.wanderTimer = 0; // Pick a random location to wander to self.targetX = 300 + Math.random() * 1400; self.targetY = 300 + Math.random() * 2000; } // Move towards target (ghosts can pass through walls) var dx = self.targetX - self.x; var dy = self.targetY - self.y; var distance = Math.sqrt(dx * dx + dy * dy); if (distance > 10) { self.x += dx / distance * self.speed; self.y += dy / distance * self.speed; } // Phase in and out randomly if (Math.random() < 0.01) { tween(ghostGraphics, { alpha: 0.1, scaleX: 1.2, scaleY: 1.2 }, { duration: 500, onFinish: function onFinish() { tween(ghostGraphics, { alpha: 0.6, scaleX: 1.0, scaleY: 1.0 }, { duration: 500 }); } }); } }; return self; }); var Player = Container.expand(function (isImpostor, color, id) { var self = Container.call(this); self.isImpostor = isImpostor || false; self.playerColor = color || 0x00ff00; self.playerId = id || 0; self.isAlive = true; self.speed = 3; self.targetX = 0; self.targetY = 0; self.tasksCompleted = 0; self.maxTasks = 5; self.canVote = true; self.lastKillTime = 0; self.killCooldown = 30000; // 30 seconds self.currentTask = null; // Track current task being worked on self.taskCompletionTimer = 0; // Timer for task completion self.assignedTasks = []; // Individual tasks assigned to this player var playerGraphics = self.attachAsset(self.isImpostor ? 'impostor' : 'crewmate', { anchorX: 0.5, anchorY: 0.5 }); if (!self.isImpostor) { playerGraphics.tint = self.playerColor; } self.moveTo = function (x, y) { var playerSize = 80; // Player width/height // Only set target if destination is not inside a wall if (!checkWallCollision(x, y, playerSize, playerSize)) { self.targetX = x; self.targetY = y; } else { // Keep current position as target if new position would collide self.targetX = self.x; self.targetY = self.y; } }; self.eliminate = function () { if (self.isAlive) { self.isAlive = false; self.alpha = 0.3; LK.getSound('eliminate').play(); // Create ghost effect with dramatic animation tween(self, { alpha: 0.1, scaleX: 1.5, scaleY: 1.5, tint: 0xFFFFFF }, { duration: 1000, onFinish: function onFinish() { // Create ghost entity var ghost = new Ghost(self); ghosts.push(ghost); game.addChild(ghost); // Ghost spawn effect ghost.alpha = 0; tween(ghost, { alpha: 0.6, scaleX: 1.2, scaleY: 1.2 }, { duration: 800, easing: tween.easeOut, onFinish: function onFinish() { tween(ghost, { scaleX: 1.0, scaleY: 1.0 }, { duration: 400 }); } }); } }); } }; self.completeTask = function () { if (!self.isImpostor && self.isAlive) { self.tasksCompleted++; LK.getSound('taskComplete').play(); return true; } return false; }; self.canKill = function () { return self.isImpostor && self.isAlive && LK.ticks - self.lastKillTime > self.killCooldown; }; self.kill = function (target) { if (self.canKill() && target.isAlive && !target.isImpostor) { target.eliminate(); self.lastKillTime = LK.ticks; return true; } return false; }; self.update = function () { if (!self.isAlive) return; // Move towards target var dx = self.targetX - self.x; var dy = self.targetY - self.y; var distance = Math.sqrt(dx * dx + dy * dy); if (distance > 5) { var newX = self.x + dx / distance * self.speed; var newY = self.y + dy / distance * self.speed; var playerSize = 80; // Player width/height // Check collision before moving if (!checkWallCollision(newX, newY, playerSize, playerSize)) { self.x = newX; self.y = newY; } else { // Try moving only on X axis if (!checkWallCollision(newX, self.y, playerSize, playerSize)) { self.x = newX; } else if (!checkWallCollision(self.x, newY, playerSize, playerSize)) { // Try moving only on Y axis self.y = newY; } } } }; return self; }); var SecurityRoom = Container.expand(function (x, y) { var self = Container.call(this); self.x = x; self.y = y; self.isActive = false; self.currentCameraIndex = 0; self.isNearPlayer = false; self.lastNearPlayer = false; // Main security console var console = self.attachAsset('cameraFrame', { anchorX: 0.5, anchorY: 0.5, scaleX: 2.5, scaleY: 2.0 }); console.tint = 0x222222; // Monitor screens (3x2 grid) self.monitors = []; var monitorPositions = [{ x: -150, y: -80 }, { x: 0, y: -80 }, { x: 150, y: -80 }, { x: -150, y: 80 }, { x: 0, y: 80 }, { x: 150, y: 80 }]; for (var i = 0; i < 6; i++) { var monitorFrame = self.attachAsset('cameraFrame', { anchorX: 0.5, anchorY: 0.5 }); monitorFrame.x = monitorPositions[i].x; monitorFrame.y = monitorPositions[i].y; monitorFrame.tint = 0x333333; var monitorScreen = self.attachAsset('cameraView', { anchorX: 0.5, anchorY: 0.5 }); monitorScreen.x = monitorPositions[i].x; monitorScreen.y = monitorPositions[i].y; // Camera label var cameraLabel = new Text2('CAM ' + (i + 1), { size: 18, fill: 0x00FFFF }); cameraLabel.anchor.set(0.5, 0); cameraLabel.x = monitorPositions[i].x; cameraLabel.y = monitorPositions[i].y - 75; self.addChild(cameraLabel); // Player count display var playerCountDisplay = new Text2('0 PLAYERS', { size: 14, fill: 0xFFFFFF }); playerCountDisplay.anchor.set(0.5, 0.5); playerCountDisplay.x = monitorPositions[i].x; playerCountDisplay.y = monitorPositions[i].y; self.addChild(playerCountDisplay); self.monitors.push({ frame: monitorFrame, screen: monitorScreen, label: cameraLabel, playerCount: playerCountDisplay }); } // Security room title var titleText = new Text2('SECURITY ROOM', { size: 32, fill: 0x00FFFF }); titleText.anchor.set(0.5, 0); titleText.y = -200; self.addChild(titleText); // Interaction hint var interactionText = new Text2('View Security Cameras', { size: 20, fill: 0x00FFFF }); interactionText.anchor.set(0.5, 0); interactionText.y = 200; interactionText.alpha = 0; self.addChild(interactionText); // Instructions when active var instructionText = new Text2('Click monitors to switch cameras', { size: 16, fill: 0xFFFF00 }); instructionText.anchor.set(0.5, 0); instructionText.y = 220; instructionText.alpha = 0; self.addChild(instructionText); self.activate = function () { if (!self.isActive) { self.isActive = true; // Show activation animation tween(console, { scaleX: 2.7, scaleY: 2.2, tint: 0x004400 }, { duration: 300, onFinish: function onFinish() { tween(console, { scaleX: 2.5, scaleY: 2.0, tint: 0x222222 }, { duration: 200 }); } }); // Show instructions instructionText.alpha = 1; } }; self.deactivate = function () { if (self.isActive) { self.isActive = false; instructionText.alpha = 0; // Reset all monitors for (var i = 0; i < self.monitors.length; i++) { self.monitors[i].screen.tint = 0x000000; self.monitors[i].frame.tint = 0x333333; } } }; self.update = function () { // Check if player is near self.lastNearPlayer = self.isNearPlayer; self.isNearPlayer = false; if (currentPlayer && currentPlayer.isAlive) { var distance = Math.sqrt(Math.pow(currentPlayer.x - self.x, 2) + Math.pow(currentPlayer.y - self.y, 2)); self.isNearPlayer = distance < 150; } // Show interaction hint when player gets close if (!self.lastNearPlayer && self.isNearPlayer) { interactionText.alpha = 1; tween(interactionText, { y: 210, alpha: 0.8 }, { duration: 300 }); } // Hide interaction hint when player moves away if (self.lastNearPlayer && !self.isNearPlayer) { tween(interactionText, { y: 200, alpha: 0 }, { duration: 300 }); self.deactivate(); } // Update camera feeds if active if (self.isActive && cameras && cameras.length > 0) { for (var i = 0; i < Math.min(self.monitors.length, cameras.length); i++) { var camera = cameras[i]; var monitor = self.monitors[i]; if (camera && monitor) { // Update player count display var playerCount = camera.playersInView.length; monitor.playerCount.setText(playerCount + ' PLAYERS'); // Update monitor colors based on activity if (playerCount > 0) { // Red tint when players detected monitor.screen.tint = 0x440000; monitor.frame.tint = 0xFF0000; // Flash effect for active cameras if (LK.ticks % 60 < 30) { monitor.frame.alpha = 1.0; } else { monitor.frame.alpha = 0.7; } } else { // Green tint when no players monitor.screen.tint = 0x004400; monitor.frame.tint = 0x333333; monitor.frame.alpha = 1.0; } } } } }; self.down = function (x, y, obj) { if (currentPlayer && currentPlayer.isAlive) { var distance = Math.sqrt(Math.pow(currentPlayer.x - self.x, 2) + Math.pow(currentPlayer.y - self.y, 2)); if (distance < 150) { if (!self.isActive) { self.activate(); } else { // Check if clicking on a specific monitor var localX = x - self.x; var localY = y - self.y; for (var i = 0; i < self.monitors.length; i++) { var monitor = self.monitors[i]; var monitorX = monitor.frame.x; var monitorY = monitor.frame.y; var monitorDistance = Math.sqrt(Math.pow(localX - monitorX, 2) + Math.pow(localY - monitorY, 2)); if (monitorDistance < 75) { // Highlight selected monitor monitor.frame.tint = 0x0000FF; // Reset other monitors for (var j = 0; j < self.monitors.length; j++) { if (j !== i) { var otherMonitor = self.monitors[j]; if (cameras[j] && cameras[j].playersInView.length > 0) { otherMonitor.frame.tint = 0xFF0000; } else { otherMonitor.frame.tint = 0x333333; } } } break; } } } } } }; return self; }); var Sewer = Container.expand(function (x, y, id, targetSewerId) { var self = Container.call(this); self.sewerId = id || 0; self.targetSewerId = targetSewerId || 0; self.x = x; self.y = y; self.isNearPlayer = false; self.lastNearPlayer = false; self.teleportCooldown = 0; self.teleportCooldownTime = 2000; // 2 seconds // Sewer entrance graphics var sewerGraphics = self.attachAsset('sewerEntrance', { anchorX: 0.5, anchorY: 0.5 }); sewerGraphics.tint = 0x654321; // Brown color for sewer // Sewer name text var sewerNameText = new Text2('SEWER ' + (self.sewerId + 1), { size: 18, fill: 0xFFFFFF }); sewerNameText.anchor.set(0.5, 0); sewerNameText.y = -50; self.addChild(sewerNameText); // Interaction hint text var interactionText = new Text2('Enter Sewer (Impostors Only)', { size: 16, fill: 0xFF0000 }); interactionText.anchor.set(0.5, 0); interactionText.y = 50; interactionText.alpha = 0; self.addChild(interactionText); // Teleport cooldown text var cooldownText = new Text2('', { size: 14, fill: 0xFF0000 }); cooldownText.anchor.set(0.5, 0); cooldownText.y = 70; cooldownText.alpha = 0; self.addChild(cooldownText); self.teleportPlayer = function (player) { if (self.teleportCooldown > 0) return false; // Find target sewer var targetSewer = null; for (var i = 0; i < sewers.length; i++) { if (sewers[i].sewerId === self.targetSewerId) { targetSewer = sewers[i]; break; } } if (targetSewer) { // Play teleport sound LK.getSound('teleport').play(); // Teleport player to target sewer player.x = targetSewer.x; player.y = targetSewer.y; player.targetX = targetSewer.x; player.targetY = targetSewer.y; // Set cooldown for both sewers self.teleportCooldown = self.teleportCooldownTime; targetSewer.teleportCooldown = targetSewer.teleportCooldownTime; // Flash effect on both sewers tween(sewerGraphics, { tint: 0x00FFFF, scaleX: 1.3, scaleY: 1.3 }, { duration: 300, onFinish: function onFinish() { tween(sewerGraphics, { tint: 0x654321, scaleX: 1.0, scaleY: 1.0 }, { duration: 300 }); } }); if (targetSewer.children && targetSewer.children.length > 0) { var targetGraphics = targetSewer.children[0]; tween(targetGraphics, { tint: 0x00FFFF, scaleX: 1.3, scaleY: 1.3 }, { duration: 300, onFinish: function onFinish() { tween(targetGraphics, { tint: 0x654321, scaleX: 1.0, scaleY: 1.0 }, { duration: 300 }); } }); } return true; } return false; }; self.update = function () { // Update teleport cooldown if (self.teleportCooldown > 0) { self.teleportCooldown -= 16.67; // Roughly 1 frame at 60fps var secondsLeft = Math.ceil(self.teleportCooldown / 1000); cooldownText.setText('Cooldown: ' + secondsLeft + 's'); cooldownText.alpha = 1; } else { cooldownText.alpha = 0; } // Check if player is near self.lastNearPlayer = self.isNearPlayer; self.isNearPlayer = false; if (currentPlayer && currentPlayer.isAlive && currentPlayer.isImpostor) { var distance = Math.sqrt(Math.pow(currentPlayer.x - self.x, 2) + Math.pow(currentPlayer.y - self.y, 2)); self.isNearPlayer = distance < 100; } // Show interaction hint when player gets close if (!self.lastNearPlayer && self.isNearPlayer) { interactionText.alpha = 1; tween(interactionText, { y: 60, alpha: 0.8 }, { duration: 300 }); } // Hide interaction hint when player moves away if (self.lastNearPlayer && !self.isNearPlayer) { tween(interactionText, { y: 50, alpha: 0 }, { duration: 300 }); } // Pulsing animation for sewer entrance if (LK.ticks % 120 < 60) { sewerGraphics.alpha = 1.0; } else { sewerGraphics.alpha = 0.8; } }; self.down = function (x, y, obj) { if (currentPlayer && currentPlayer.isAlive && currentPlayer.isImpostor && gamePhase === 'playing') { var distance = Math.sqrt(Math.pow(currentPlayer.x - self.x, 2) + Math.pow(currentPlayer.y - self.y, 2)); if (distance < 100 && self.teleportCooldown <= 0) { self.teleportPlayer(currentPlayer); } } }; return self; }); var Task = Container.expand(function (x, y, id, taskType, assignedPlayerId) { var self = Container.call(this); self.taskId = id || 0; self.isCompleted = false; self.x = x; self.y = y; self.taskType = taskType || 'basic'; self.completionProgress = 0; self.requiredClicks = 1; self.completionTime = 0; self.isBeingCompleted = false; self.interactionTimer = 0; self.taskStarted = false; self.isNearPlayer = false; self.lastNearPlayer = false; self.assignedPlayerId = assignedPlayerId || -1; // Which player this task is assigned to (-1 = any player) // Set task properties based on type switch (self.taskType) { case 'electrical': self.requiredClicks = 3; self.assetName = 'electricalTask'; self.taskName = 'Fix Wiring'; self.taskDescription = 'Connect the wires in the correct order'; break; case 'engine': self.requiredClicks = 5; self.assetName = 'engineTask'; self.taskName = 'Fuel Engines'; self.taskDescription = 'Fill the fuel tanks'; break; case 'reactor': self.requiredClicks = 7; self.assetName = 'reactorTask'; self.taskName = 'Stabilize Reactor'; self.taskDescription = 'Balance the reactor core'; break; case 'navigation': self.requiredClicks = 4; self.assetName = 'navigationTask'; self.taskName = 'Chart Course'; self.taskDescription = 'Set navigation coordinates'; break; case 'medical': self.requiredClicks = 6; self.assetName = 'medicalTask'; self.taskName = 'Submit Scan'; self.taskDescription = 'Complete medical scan'; break; case 'cables': self.requiredClicks = 4; self.assetName = 'electricalTask'; self.taskName = 'Fix Wiring'; self.taskDescription = 'Connect the colored wires'; break; default: self.requiredClicks = 1; self.assetName = 'task'; self.taskName = 'Basic Task'; self.taskDescription = 'Complete basic task'; break; } var taskGraphics = self.attachAsset(self.assetName, { anchorX: 0.5, anchorY: 0.5 }); // Add task name text var taskNameText = new Text2(self.taskName, { size: 24, fill: 0xFFFFFF }); taskNameText.anchor.set(0.5, 0); taskNameText.y = -50; self.addChild(taskNameText); // Add interaction hint text (initially hidden) var interactionText = new Text2('Hold to complete', { size: 20, fill: 0x00FFFF }); interactionText.anchor.set(0.5, 0); interactionText.y = 50; interactionText.alpha = 0; self.addChild(interactionText); self.complete = function () { if (!self.isCompleted) { self.isCompleted = true; taskGraphics.destroy(); taskNameText.destroy(); if (interactionText) { interactionText.destroy(); } if (self.lastProgressText) { self.lastProgressText.destroy(); } var completedGraphics = self.attachAsset('taskCompleted', { anchorX: 0.5, anchorY: 0.5 }); var completedText = new Text2('Complete', { size: 20, fill: 0x00FF00 }); completedText.anchor.set(0.5, 0); completedText.y = -40; self.addChild(completedText); return true; } return false; }; self.startTask = function () { if (!self.taskStarted && !self.isCompleted) { self.taskStarted = true; self.interactionTimer = 0; // Visual feedback for task start tween(taskGraphics, { scaleX: 1.2, scaleY: 1.2, tint: 0x00FFFF }, { duration: 200, onFinish: function onFinish() { tween(taskGraphics, { scaleX: 1.0, scaleY: 1.0, tint: 0xFFFFFF }, { duration: 200 }); } }); // Show progress for multi-step tasks if (self.requiredClicks > 1) { var progressText = new Text2(self.completionProgress + '/' + self.requiredClicks, { size: 32, fill: 0xFFFF00 }); progressText.anchor.set(0.5, 0); progressText.y = 70; self.addChild(progressText); // Remove old progress text if (self.lastProgressText) { self.lastProgressText.destroy(); } self.lastProgressText = progressText; } } }; self.update = function () { if (self.isCompleted) return; // Check if player is near self.lastNearPlayer = self.isNearPlayer; self.isNearPlayer = false; if (currentPlayer && currentPlayer.isAlive && !currentPlayer.isImpostor) { var distance = Math.sqrt(Math.pow(currentPlayer.x - self.x, 2) + Math.pow(currentPlayer.y - self.y, 2)); // Only show task if it's assigned to current player or unassigned var isAssignedToCurrentPlayer = self.assignedPlayerId === -1 || self.assignedPlayerId === currentPlayer.playerId; self.isNearPlayer = distance < 100 && isAssignedToCurrentPlayer; } // Show interaction hint when player gets close if (!self.lastNearPlayer && self.isNearPlayer) { interactionText.alpha = 1; tween(interactionText, { y: 60, alpha: 0.8 }, { duration: 300 }); } // Hide interaction hint when player moves away if (self.lastNearPlayer && !self.isNearPlayer) { tween(interactionText, { y: 50, alpha: 0 }, { duration: 300 }); self.taskStarted = false; self.interactionTimer = 0; } // Update interaction timer if task is being worked on if (self.taskStarted && self.isNearPlayer) { self.interactionTimer += 16.67; // Roughly 1 frame at 60fps // Visual progress indicator var progressPercent = Math.min(self.interactionTimer / 2000, 1.0); // 2 seconds per step if (progressPercent < 1.0) { taskGraphics.alpha = 0.5 + progressPercent * 0.5; } else { // Complete this step self.completionProgress++; self.interactionTimer = 0; taskGraphics.alpha = 1.0; // Flash effect for progress tween(taskGraphics, { alpha: 0.3, tint: 0x00FF00 }, { duration: 150, onFinish: function onFinish() { tween(taskGraphics, { alpha: 1.0, tint: 0xFFFFFF }, { duration: 150 }); } }); // Update progress text if (self.requiredClicks > 1 && self.lastProgressText) { self.lastProgressText.setText(self.completionProgress + '/' + self.requiredClicks); } // Check if task is complete if (self.completionProgress >= self.requiredClicks) { self.complete(); if (currentPlayer) { currentPlayer.completeTask(); } completedTasks++; updateTaskProgress(); } else { // Reset for next step self.taskStarted = false; } } } }; self.down = function (x, y, obj) { if (currentPlayer && currentPlayer.isAlive && !currentPlayer.isImpostor) { var distance = Math.sqrt(Math.pow(currentPlayer.x - self.x, 2) + Math.pow(currentPlayer.y - self.y, 2)); // Only allow interaction if task is assigned to current player or unassigned var isAssignedToCurrentPlayer = self.assignedPlayerId === -1 || self.assignedPlayerId === currentPlayer.playerId; if (distance < 100 && !self.isCompleted && isAssignedToCurrentPlayer) { self.startTask(); } } }; return self; }); var VoteButton = Container.expand(function (playerId, x, y) { var self = Container.call(this); self.playerId = playerId; self.x = x; self.y = y; self.votes = 0; var buttonGraphics = self.attachAsset('voteButton', { anchorX: 0.5, anchorY: 0.5 }); var voteText = new Text2('Vote Player ' + playerId, { size: 40, fill: 0xFFFFFF }); voteText.anchor.set(0.5, 0.5); self.addChild(voteText); self.down = function (x, y, obj) { if (votingPhase && currentPlayer && currentPlayer.canVote) { self.votes++; currentPlayer.canVote = false; LK.getSound('vote').play(); voteText.setText('Votes: ' + self.votes); // Check if voting is complete var alivePlayers = players.filter(function (p) { return p.isAlive; }); var totalVotes = voteButtons.reduce(function (sum, button) { return sum + button.votes; }, 0); if (totalVotes >= alivePlayers.length) { endVoting(); } } }; return self; }); /**** * Initialize Game ****/ var game = new LK.Game({ backgroundColor: 0x1a1a2e }); /**** * Game Code ****/ var players = []; var tasks = []; var taskCategories = { critical: [], // High priority tasks normal: [], // Standard tasks maintenance: [] // Lower priority tasks }; var taskDifficulty = { electrical: 3, cables: 4, reactor: 5, navigation: 2, medical: 3, engine: 4 }; var currentPlayer = null; var totalTasks = 0; // Will be calculated based on actual crew members var completedTasks = 0; var impostorCount = 2; var votingPhase = false; var voteButtons = []; var gamePhase = 'roleSelection'; // 'roleSelection', 'playing', 'voting', 'gameOver' var meetingCooldown = 0; var meetingCooldownTime = 15000; // 15 seconds var roleSelectionComplete = false; var ghosts = []; // Array to track ghost entities var killButton = null; var killTarget = null; var sabotageButton = null; var sabotageTarget = null; var sabotageElectricityButton = null; var sabotageOxygenButton = null; var sabotageDoorsButton = null; var sabotageReactorButton = null; var sabotageConnectionsButton = null; var reportButton = null; var reportTarget = null; var taskButton = null; var currentTaskTarget = null; var cameraButton = null; // UI Elements var taskProgressText = new Text2('Tasks: 0/' + totalTasks, { size: 60, fill: 0xFFFFFF }); taskProgressText.anchor.set(0.5, 0); LK.gui.top.addChild(taskProgressText); // Task completion UI var taskCompletionText = new Text2('', { size: 40, fill: 0x00FF00 }); taskCompletionText.anchor.set(0.5, 0.5); taskCompletionText.alpha = 0; LK.gui.center.addChild(taskCompletionText); // Current task indicator var currentTaskText = new Text2('', { size: 30, fill: 0xFFFF00 }); currentTaskText.anchor.set(0.5, 1); currentTaskText.y = -50; currentTaskText.alpha = 0; LK.gui.center.addChild(currentTaskText); // Mission panel UI var missionPanel = LK.getAsset('cameraFrame', { anchorX: 0, anchorY: 0, scaleX: 2.5, scaleY: 3.0, x: 50, y: 200 }); missionPanel.tint = 0x222222; missionPanel.alpha = 0.8; missionPanel.visible = false; LK.gui.topLeft.addChild(missionPanel); var missionPanelTitle = new Text2('MISSIONS', { size: 32, fill: 0x00FFFF }); missionPanelTitle.anchor.set(0.5, 0); missionPanelTitle.x = 250; missionPanelTitle.y = 220; missionPanelTitle.visible = false; LK.gui.topLeft.addChild(missionPanelTitle); var missionTaskTexts = []; var missionToggleButton = LK.getAsset('taskButton', { anchorX: 0, anchorY: 0, scaleX: 0.6, scaleY: 0.6, x: 120, y: 200 }); missionToggleButton.tint = 0x00FFFF; missionToggleButton.alpha = 0; missionToggleButton.visible = false; LK.gui.topLeft.addChild(missionToggleButton); var missionToggleText = new Text2('MISSIONS', { size: 20, fill: 0xFFFFFF }); missionToggleText.anchor.set(0.5, 0.5); missionToggleText.x = 36; missionToggleText.y = 36; missionToggleButton.addChild(missionToggleText); var missionPanelVisible = false; // Mission panel toggle handler missionToggleButton.down = function (x, y, obj) { if (currentPlayer && currentPlayer.isAlive && !currentPlayer.isImpostor && gamePhase === 'playing') { toggleMissionPanel(); } }; // Camera system variables var cameras = []; var securityRoom = null; // Door system variables var doors = []; var doorButton = null; var currentDoorTarget = null; // Sewer system variables var sewers = []; // Sabotage system variables var activeSabotage = null; var sabotageTimer = 0; var sabotageEffect = null; var sabotageIndicators = []; var isSabotageActive = false; var sabotageTypes = ['electricity', 'oxygen', 'reactor', 'doors', 'communications']; // Progress Bar Elements var progressBarBackground = LK.getAsset('wall', { anchorX: 0.5, anchorY: 0.5, scaleX: 8, scaleY: 0.5, x: 0, y: 80 }); progressBarBackground.tint = 0x333333; LK.gui.top.addChild(progressBarBackground); var progressBarFill = LK.getAsset('wall', { anchorX: 0, anchorY: 0.5, scaleX: 0, scaleY: 0.4, x: -800, y: 80 }); progressBarFill.tint = 0x00ff00; LK.gui.top.addChild(progressBarFill); // Sabotage effect overlay (initially hidden) sabotageEffect = LK.getAsset('sabotageEffect', { anchorX: 0.5, anchorY: 0.5, x: 1024, y: 1366 }); sabotageEffect.alpha = 0; sabotageEffect.visible = false; game.addChild(sabotageEffect); var phaseText = new Text2('Complete Tasks or Find Impostors!', { size: 50, fill: 0xFFFF00 }); phaseText.anchor.set(0.5, 0); phaseText.y = 100; LK.gui.top.addChild(phaseText); var playerCountText = new Text2('', { size: 40, fill: 0xFFFFFF }); playerCountText.anchor.set(0, 0); LK.gui.topRight.addChild(playerCountText); // Role selection UI var roleSelectionTitle = new Text2('Choose Your Role', { size: 80, fill: 0xFFFFFF }); roleSelectionTitle.anchor.set(0.5, 0.5); roleSelectionTitle.x = 1024; roleSelectionTitle.y = 800; game.addChild(roleSelectionTitle); var crewButton = LK.getAsset('voteButton', { anchorX: 0.5, anchorY: 0.5, x: 700, y: 1200, scaleX: 1.5, scaleY: 1.5 }); crewButton.tint = 0x00ff00; game.addChild(crewButton); var crewButtonText = new Text2('CREW MEMBER', { size: 50, fill: 0xFFFFFF }); crewButtonText.anchor.set(0.5, 0.5); crewButtonText.x = 700; crewButtonText.y = 1200; game.addChild(crewButtonText); var impostorButton = LK.getAsset('voteButton', { anchorX: 0.5, anchorY: 0.5, x: 1348, y: 1200, scaleX: 1.5, scaleY: 1.5 }); impostorButton.tint = 0xff0000; game.addChild(impostorButton); var impostorButtonText = new Text2('IMPOSTOR', { size: 50, fill: 0xFFFFFF }); impostorButtonText.anchor.set(0.5, 0.5); impostorButtonText.x = 1348; impostorButtonText.y = 1200; game.addChild(impostorButtonText); // Role selection instructions var instructionText = new Text2('Choose your role and start the game!', { size: 40, fill: 0xFFFF00 }); instructionText.anchor.set(0.5, 0.5); instructionText.x = 1024; instructionText.y = 1400; game.addChild(instructionText); // Create spaceship walls var walls = []; function createWalls() { // Outer perimeter walls - Extended ship dimensions var topWall = game.addChild(LK.getAsset('wall', { anchorX: 0.5, anchorY: 0.5, scaleX: 14, x: 1024, y: 150 })); walls.push(topWall); var bottomWall = game.addChild(LK.getAsset('wall', { anchorX: 0.5, anchorY: 0.5, scaleX: 14, x: 1024, y: 2600 })); walls.push(bottomWall); var leftWall = game.addChild(LK.getAsset('wall', { anchorX: 0.5, anchorY: 0.5, scaleY: 12, x: 150, y: 1366 })); walls.push(leftWall); var rightWall = game.addChild(LK.getAsset('wall', { anchorX: 0.5, anchorY: 0.5, scaleY: 12, x: 1900, y: 1366 })); walls.push(rightWall); // Upper corridor walls var upperLeftWall = game.addChild(LK.getAsset('wall', { anchorX: 0.5, anchorY: 0.5, scaleX: 2, x: 550, y: 600 })); walls.push(upperLeftWall); var upperRightWall = game.addChild(LK.getAsset('wall', { anchorX: 0.5, anchorY: 0.5, scaleX: 2, x: 1500, y: 600 })); walls.push(upperRightWall); // Central corridor walls var centralTopWall = game.addChild(LK.getAsset('wall', { anchorX: 0.5, anchorY: 0.5, scaleX: 4, x: 1024, y: 1000 })); walls.push(centralTopWall); var centralBottomWall = game.addChild(LK.getAsset('wall', { anchorX: 0.5, anchorY: 0.5, scaleX: 4, x: 1024, y: 1700 })); walls.push(centralBottomWall); // Room dividers var leftRoomWall = game.addChild(LK.getAsset('wall', { anchorX: 0.5, anchorY: 0.5, scaleY: 3, x: 700, y: 1000 })); walls.push(leftRoomWall); var rightRoomWall = game.addChild(LK.getAsset('wall', { anchorX: 0.5, anchorY: 0.5, scaleY: 3, x: 1350, y: 1000 })); walls.push(rightRoomWall); // Lower corridor walls var lowerLeftWall = game.addChild(LK.getAsset('wall', { anchorX: 0.5, anchorY: 0.5, scaleX: 2, x: 550, y: 2100 })); walls.push(lowerLeftWall); var lowerRightWall = game.addChild(LK.getAsset('wall', { anchorX: 0.5, anchorY: 0.5, scaleX: 2, x: 1500, y: 2100 })); walls.push(lowerRightWall); // Engine room walls var engineLeftWall = game.addChild(LK.getAsset('wall', { anchorX: 0.5, anchorY: 0.5, scaleY: 2, x: 400, y: 1800 })); walls.push(engineLeftWall); var engineRightWall = game.addChild(LK.getAsset('wall', { anchorX: 0.5, anchorY: 0.5, scaleY: 2, x: 1650, y: 1800 })); walls.push(engineRightWall); // Security room walls - expanded for camera monitoring var securityWallTop = game.addChild(LK.getAsset('wall', { anchorX: 0.5, anchorY: 0.5, scaleX: 3.5, x: 1024, y: 450 })); walls.push(securityWallTop); var securityWallLeft = game.addChild(LK.getAsset('wall', { anchorX: 0.5, anchorY: 0.5, scaleY: 2.5, x: 700, y: 600 })); walls.push(securityWallLeft); var securityWallRight = game.addChild(LK.getAsset('wall', { anchorX: 0.5, anchorY: 0.5, scaleY: 2.5, x: 1350, y: 600 })); walls.push(securityWallRight); // Medical bay walls var medicalWall = game.addChild(LK.getAsset('wall', { anchorX: 0.5, anchorY: 0.5, scaleY: 1.5, x: 1200, y: 1500 })); walls.push(medicalWall); // Reactor room walls var reactorWall = game.addChild(LK.getAsset('wall', { anchorX: 0.5, anchorY: 0.5, scaleY: 1.5, x: 850, y: 1500 })); walls.push(reactorWall); // Command Bridge - New room at top var bridgeTopWall = game.addChild(LK.getAsset('wall', { anchorX: 0.5, anchorY: 0.5, scaleX: 4, x: 1024, y: 250 })); walls.push(bridgeTopWall); var bridgeLeftWall = game.addChild(LK.getAsset('wall', { anchorX: 0.5, anchorY: 0.5, scaleY: 1.5, x: 700, y: 350 })); walls.push(bridgeLeftWall); var bridgeRightWall = game.addChild(LK.getAsset('wall', { anchorX: 0.5, anchorY: 0.5, scaleY: 1.5, x: 1350, y: 350 })); walls.push(bridgeRightWall); // Cargo Bay - New large room at bottom var cargoTopWall = game.addChild(LK.getAsset('wall', { anchorX: 0.5, anchorY: 0.5, scaleX: 6, x: 1024, y: 2200 })); walls.push(cargoTopWall); var cargoLeftWall = game.addChild(LK.getAsset('wall', { anchorX: 0.5, anchorY: 0.5, scaleY: 2, x: 500, y: 2350 })); walls.push(cargoLeftWall); var cargoRightWall = game.addChild(LK.getAsset('wall', { anchorX: 0.5, anchorY: 0.5, scaleY: 2, x: 1550, y: 2350 })); walls.push(cargoRightWall); // Extended Laboratory - Left side expansion var labTopWall = game.addChild(LK.getAsset('wall', { anchorX: 0.5, anchorY: 0.5, scaleX: 2, x: 300, y: 800 })); walls.push(labTopWall); var labBottomWall = game.addChild(LK.getAsset('wall', { anchorX: 0.5, anchorY: 0.5, scaleX: 2, x: 300, y: 1200 })); walls.push(labBottomWall); var labRightWall = game.addChild(LK.getAsset('wall', { anchorX: 0.5, anchorY: 0.5, scaleY: 2, x: 450, y: 1000 })); walls.push(labRightWall); // Extended Weapons Room - Right side expansion var weaponsTopWall = game.addChild(LK.getAsset('wall', { anchorX: 0.5, anchorY: 0.5, scaleX: 2, x: 1750, y: 800 })); walls.push(weaponsTopWall); var weaponsBottomWall = game.addChild(LK.getAsset('wall', { anchorX: 0.5, anchorY: 0.5, scaleX: 2, x: 1750, y: 1200 })); walls.push(weaponsBottomWall); var weaponsLeftWall = game.addChild(LK.getAsset('wall', { anchorX: 0.5, anchorY: 0.5, scaleY: 2, x: 1600, y: 1000 })); walls.push(weaponsLeftWall); // Communication Array - New room top right var commTopWall = game.addChild(LK.getAsset('wall', { anchorX: 0.5, anchorY: 0.5, scaleX: 2.5, x: 1600, y: 400 })); walls.push(commTopWall); var commBottomWall = game.addChild(LK.getAsset('wall', { anchorX: 0.5, anchorY: 0.5, scaleX: 2.5, x: 1600, y: 650 })); walls.push(commBottomWall); var commLeftWall = game.addChild(LK.getAsset('wall', { anchorX: 0.5, anchorY: 0.5, scaleY: 1.5, x: 1400, y: 525 })); walls.push(commLeftWall); // Maintenance Tunnels - New room bottom left var maintTopWall = game.addChild(LK.getAsset('wall', { anchorX: 0.5, anchorY: 0.5, scaleX: 2.5, x: 400, y: 1800 })); walls.push(maintTopWall); var maintBottomWall = game.addChild(LK.getAsset('wall', { anchorX: 0.5, anchorY: 0.5, scaleX: 2.5, x: 400, y: 2050 })); walls.push(maintBottomWall); var maintRightWall = game.addChild(LK.getAsset('wall', { anchorX: 0.5, anchorY: 0.5, scaleY: 1.5, x: 600, y: 1925 })); walls.push(maintRightWall); // Add spaceship floor tiles - Extended coverage var _loop = function _loop() { for (var floorY = 200; floorY < 2550; floorY += 100) { var floorTile = game.addChild(LK.getAsset('shipFloor', { anchorX: 0.5, anchorY: 0.5, x: floorX, y: floorY })); game.setChildIndex(floorTile, 0); } }; for (var floorX = 200; floorX < 1900; floorX += 100) { _loop(); } // Add main corridor highlights - central hub var mainCorridor = game.addChild(LK.getAsset('shipHall', { anchorX: 0.5, anchorY: 0.5, scaleX: 8, scaleY: 4, x: 1024, y: 1366 })); game.setChildIndex(mainCorridor, 1); // Add upper corridor - connects upper rooms var upperCorridor = game.addChild(LK.getAsset('shipHall', { anchorX: 0.5, anchorY: 0.5, scaleX: 6, scaleY: 2, x: 1024, y: 700 })); game.setChildIndex(upperCorridor, 1); // Add lower corridor - connects lower rooms var lowerCorridor = game.addChild(LK.getAsset('shipHall', { anchorX: 0.5, anchorY: 0.5, scaleX: 6, scaleY: 2, x: 1024, y: 2000 })); game.setChildIndex(lowerCorridor, 1); // Add left vertical corridor - connects left rooms var leftVerticalCorridor = game.addChild(LK.getAsset('shipHall', { anchorX: 0.5, anchorY: 0.5, scaleX: 2, scaleY: 8, x: 550, y: 1366 })); game.setChildIndex(leftVerticalCorridor, 1); // Add right vertical corridor - connects right rooms var rightVerticalCorridor = game.addChild(LK.getAsset('shipHall', { anchorX: 0.5, anchorY: 0.5, scaleX: 2, scaleY: 8, x: 1500, y: 1366 })); game.setChildIndex(rightVerticalCorridor, 1); // Add Command Bridge corridor var bridgeCorridor = game.addChild(LK.getAsset('shipHall', { anchorX: 0.5, anchorY: 0.5, scaleX: 4, scaleY: 1.5, x: 1024, y: 350 })); game.setChildIndex(bridgeCorridor, 1); // Add Cargo Bay corridor var cargoCorridor = game.addChild(LK.getAsset('shipHall', { anchorX: 0.5, anchorY: 0.5, scaleX: 6, scaleY: 2, x: 1024, y: 2350 })); game.setChildIndex(cargoCorridor, 1); // Add Laboratory corridor var labCorridor = game.addChild(LK.getAsset('shipHall', { anchorX: 0.5, anchorY: 0.5, scaleX: 1.5, scaleY: 2, x: 300, y: 1000 })); game.setChildIndex(labCorridor, 1); // Add Weapons Room corridor var weaponsCorridor = game.addChild(LK.getAsset('shipHall', { anchorX: 0.5, anchorY: 0.5, scaleX: 1.5, scaleY: 2, x: 1750, y: 1000 })); game.setChildIndex(weaponsCorridor, 1); // Add Communication Array corridor var commCorridor = game.addChild(LK.getAsset('shipHall', { anchorX: 0.5, anchorY: 0.5, scaleX: 2.5, scaleY: 1.5, x: 1600, y: 525 })); game.setChildIndex(commCorridor, 1); // Add Maintenance Tunnels corridor var maintCorridor = game.addChild(LK.getAsset('shipHall', { anchorX: 0.5, anchorY: 0.5, scaleX: 2.5, scaleY: 1.5, x: 400, y: 1925 })); game.setChildIndex(maintCorridor, 1); // Extended connecting corridors var bridgeConnector = game.addChild(LK.getAsset('shipHall', { anchorX: 0.5, anchorY: 0.5, scaleX: 2, scaleY: 3, x: 1024, y: 500 })); game.setChildIndex(bridgeConnector, 1); var cargoConnector = game.addChild(LK.getAsset('shipHall', { anchorX: 0.5, anchorY: 0.5, scaleX: 2, scaleY: 2, x: 1024, y: 2150 })); game.setChildIndex(cargoConnector, 1); // Side wing corridors var leftWingCorridor = game.addChild(LK.getAsset('shipHall', { anchorX: 0.5, anchorY: 0.5, scaleX: 1.5, scaleY: 10, x: 250, y: 1366 })); game.setChildIndex(leftWingCorridor, 1); var rightWingCorridor = game.addChild(LK.getAsset('shipHall', { anchorX: 0.5, anchorY: 0.5, scaleX: 1.5, scaleY: 10, x: 1800, y: 1366 })); game.setChildIndex(rightWingCorridor, 1); // Add connecting corridors between upper and main areas var upperLeftConnector = game.addChild(LK.getAsset('shipHall', { anchorX: 0.5, anchorY: 0.5, scaleX: 2, scaleY: 3, x: 700, y: 900 })); game.setChildIndex(upperLeftConnector, 1); var upperRightConnector = game.addChild(LK.getAsset('shipHall', { anchorX: 0.5, anchorY: 0.5, scaleX: 2, scaleY: 3, x: 1350, y: 900 })); game.setChildIndex(upperRightConnector, 1); // Add connecting corridors between lower and main areas var lowerLeftConnector = game.addChild(LK.getAsset('shipHall', { anchorX: 0.5, anchorY: 0.5, scaleX: 2, scaleY: 3, x: 700, y: 1800 })); game.setChildIndex(lowerLeftConnector, 1); var lowerRightConnector = game.addChild(LK.getAsset('shipHall', { anchorX: 0.5, anchorY: 0.5, scaleX: 2, scaleY: 3, x: 1350, y: 1800 })); game.setChildIndex(lowerRightConnector, 1); // Add horizontal connectors to main corridor var mainLeftConnector = game.addChild(LK.getAsset('shipHall', { anchorX: 0.5, anchorY: 0.5, scaleX: 3, scaleY: 1.5, x: 800, y: 1366 })); game.setChildIndex(mainLeftConnector, 1); var mainRightConnector = game.addChild(LK.getAsset('shipHall', { anchorX: 0.5, anchorY: 0.5, scaleX: 3, scaleY: 1.5, x: 1250, y: 1366 })); game.setChildIndex(mainRightConnector, 1); // Add cafeteria/meeting room corridor var cafeteriaCorridor = game.addChild(LK.getAsset('shipHall', { anchorX: 0.5, anchorY: 0.5, scaleX: 4, scaleY: 2, x: 1024, y: 1100 })); game.setChildIndex(cafeteriaCorridor, 1); // Add storage area corridors var storageLeftCorridor = game.addChild(LK.getAsset('shipHall', { anchorX: 0.5, anchorY: 0.5, scaleX: 1.5, scaleY: 2, x: 400, y: 1100 })); game.setChildIndex(storageLeftCorridor, 1); var storageRightCorridor = game.addChild(LK.getAsset('shipHall', { anchorX: 0.5, anchorY: 0.5, scaleX: 1.5, scaleY: 2, x: 1650, y: 1100 })); game.setChildIndex(storageRightCorridor, 1); // Add engine room access corridors var engineAccessLeft = game.addChild(LK.getAsset('shipHall', { anchorX: 0.5, anchorY: 0.5, scaleX: 1.5, scaleY: 2, x: 500, y: 2200 })); game.setChildIndex(engineAccessLeft, 1); var engineAccessRight = game.addChild(LK.getAsset('shipHall', { anchorX: 0.5, anchorY: 0.5, scaleX: 1.5, scaleY: 2, x: 1550, y: 2200 })); game.setChildIndex(engineAccessRight, 1); // Add navigation/helm corridor var navigationCorridor = game.addChild(LK.getAsset('shipHall', { anchorX: 0.5, anchorY: 0.5, scaleX: 3, scaleY: 1.5, x: 1024, y: 500 })); game.setChildIndex(navigationCorridor, 1); // Add T-junction corridors for better connectivity var upperTJunction = game.addChild(LK.getAsset('shipHall', { anchorX: 0.5, anchorY: 0.5, scaleX: 2, scaleY: 2, x: 1024, y: 800 })); game.setChildIndex(upperTJunction, 1); var lowerTJunction = game.addChild(LK.getAsset('shipHall', { anchorX: 0.5, anchorY: 0.5, scaleX: 2, scaleY: 2, x: 1024, y: 1900 })); game.setChildIndex(lowerTJunction, 1); // Add room separator walls to create distinct areas var labSeparatorWall = game.addChild(LK.getAsset('wall', { anchorX: 0.5, anchorY: 0.5, scaleY: 1.5, x: 375, y: 850 })); walls.push(labSeparatorWall); var weaponsSeparatorWall = game.addChild(LK.getAsset('wall', { anchorX: 0.5, anchorY: 0.5, scaleY: 1.5, x: 1675, y: 850 })); walls.push(weaponsSeparatorWall); var medicalSeparatorWall = game.addChild(LK.getAsset('wall', { anchorX: 0.5, anchorY: 0.5, scaleX: 1.5, x: 1125, y: 1300 })); walls.push(medicalSeparatorWall); var reactorSeparatorWall = game.addChild(LK.getAsset('wall', { anchorX: 0.5, anchorY: 0.5, scaleX: 1.5, x: 925, y: 1300 })); walls.push(reactorSeparatorWall); // Add side corridor extensions var leftSideCorridor = game.addChild(LK.getAsset('shipHall', { anchorX: 0.5, anchorY: 0.5, scaleX: 1.5, scaleY: 6, x: 300, y: 1366 })); game.setChildIndex(leftSideCorridor, 1); var rightSideCorridor = game.addChild(LK.getAsset('shipHall', { anchorX: 0.5, anchorY: 0.5, scaleX: 1.5, scaleY: 6, x: 1750, y: 1366 })); game.setChildIndex(rightSideCorridor, 1); } // Create emergency button var emergencyButton = game.addChild(LK.getAsset('emergencyButton', { anchorX: 0.5, anchorY: 0.5, x: 1024, y: 1366 })); emergencyButton.down = function (x, y, obj) { if (gamePhase === 'playing' && meetingCooldown <= 0) { startEmergencyMeeting(); } }; // Create kill button (initially hidden) killButton = LK.getAsset('killButton', { anchorX: 0.5, anchorY: 0.5, x: 0, y: 0 }); killButton.alpha = 0; killButton.visible = false; LK.gui.center.addChild(killButton); var killButtonText = new Text2('KILL', { size: 40, fill: 0xFFFFFF }); killButtonText.anchor.set(0.5, 0.5); killButton.addChild(killButtonText); // Create kill cooldown timer (initially hidden) var killCooldownTimer = new Text2('', { size: 32, fill: 0xFF0000 }); killCooldownTimer.anchor.set(0.5, 0.5); killCooldownTimer.x = 0; killCooldownTimer.y = 80; killCooldownTimer.alpha = 0; killCooldownTimer.visible = false; LK.gui.center.addChild(killCooldownTimer); killButton.down = function (x, y, obj) { if (killTarget && currentPlayer && currentPlayer.isImpostor) { if (currentPlayer.canKill()) { if (currentPlayer.kill(killTarget)) { updatePlayerCount(); hideKillButton(); // Hide cooldown timer when kill is successful if (killCooldownTimer) { killCooldownTimer.alpha = 0; killCooldownTimer.visible = false; } } } else { // Show cooldown timer with animation var cooldownLeft = Math.ceil((currentPlayer.killCooldown - (LK.ticks - currentPlayer.lastKillTime)) / 1000); if (killCooldownTimer) { killCooldownTimer.setText(cooldownLeft + 's'); killCooldownTimer.alpha = 1; killCooldownTimer.visible = true; // Pulsing animation for cooldown timer tween(killCooldownTimer, { scaleX: 1.3, scaleY: 1.3, tint: 0xFF4444 }, { duration: 300, onFinish: function onFinish() { tween(killCooldownTimer, { scaleX: 1.0, scaleY: 1.0, tint: 0xFF0000 }, { duration: 300 }); } }); } if (taskCompletionText) { taskCompletionText.setText('Kill cooldown: ' + cooldownLeft + 's'); taskCompletionText.alpha = 1; taskCompletionText.y = 0; tween(taskCompletionText, { y: -100, alpha: 0 }, { duration: 2000 }); } } } }; // Create sabotage button (initially hidden) sabotageButton = LK.getAsset('sabotageButton', { anchorX: 0.5, anchorY: 0.5, x: 200, y: 0 }); sabotageButton.alpha = 0; sabotageButton.visible = false; LK.gui.center.addChild(sabotageButton); // Create skip voting button (initially hidden) var skipVotingButton = LK.getAsset('voteButton', { anchorX: 0.5, anchorY: 0.5, x: 0, y: 100 }); skipVotingButton.alpha = 0; skipVotingButton.visible = false; skipVotingButton.tint = 0xffff00; LK.gui.center.addChild(skipVotingButton); var skipVotingButtonText = new Text2('SKIP VOTING', { size: 36, fill: 0x000000 }); skipVotingButtonText.anchor.set(0.5, 0.5); skipVotingButton.addChild(skipVotingButtonText); var sabotageButtonText = new Text2('SABOTAGE', { size: 32, fill: 0xFFFFFF }); sabotageButtonText.anchor.set(0.5, 0.5); sabotageButton.addChild(sabotageButtonText); // Create report button (initially hidden) reportButton = LK.getAsset('reportButton', { anchorX: 0.5, anchorY: 0.5, x: -200, y: 0 }); reportButton.alpha = 0; reportButton.visible = false; reportButton.tint = 0x00ff00; LK.gui.center.addChild(reportButton); var reportButtonText = new Text2('REPORT', { size: 32, fill: 0xFFFFFF }); reportButtonText.anchor.set(0.5, 0.5); reportButton.addChild(reportButtonText); // Create task button (initially hidden) taskButton = LK.getAsset('taskButton', { anchorX: 0.5, anchorY: 0.5, x: 0, y: -150 }); taskButton.alpha = 0; taskButton.visible = false; taskButton.tint = 0x00ffff; LK.gui.center.addChild(taskButton); var taskButtonText = new Text2('DO TASK', { size: 32, fill: 0xFFFFFF }); taskButtonText.anchor.set(0.5, 0.5); taskButton.addChild(taskButtonText); // Create camera button (initially hidden) cameraButton = LK.getAsset('cameraButton', { anchorX: 0.5, anchorY: 0.5, x: 300, y: 0 }); cameraButton.alpha = 0; cameraButton.visible = false; cameraButton.tint = 0x00ff00; LK.gui.center.addChild(cameraButton); var cameraButtonText = new Text2('CAMERAS', { size: 28, fill: 0xFFFFFF }); cameraButtonText.anchor.set(0.5, 0.5); cameraButton.addChild(cameraButtonText); // Create door button (initially hidden) doorButton = LK.getAsset('doorButton', { anchorX: 0.5, anchorY: 0.5, x: -300, y: 0 }); doorButton.alpha = 0; doorButton.visible = false; doorButton.tint = 0xff0000; LK.gui.center.addChild(doorButton); var doorButtonText = new Text2('DOORS', { size: 28, fill: 0xFFFFFF }); doorButtonText.anchor.set(0.5, 0.5); doorButton.addChild(doorButtonText); // Make doorButtonText globally accessible doorButton.doorButtonText = doorButtonText; doorButton.down = function (x, y, obj) { if (currentDoorTarget && currentPlayer && currentPlayer.isAlive && currentPlayer.isImpostor && gamePhase === 'playing') { if (currentDoorTarget.isOpen) { currentDoorTarget.close(); } else { currentDoorTarget.open(); } } }; // Create individual sabotage buttons (initially hidden) sabotageElectricityButton = LK.getAsset('sabotageElectricityButton', { anchorX: 0.5, anchorY: 0.5, x: -200, y: -100 }); sabotageElectricityButton.alpha = 0; sabotageElectricityButton.visible = false; sabotageElectricityButton.tint = 0xffff00; LK.gui.center.addChild(sabotageElectricityButton); var sabotageElectricityButtonText = new Text2('ELECTRICITY', { size: 20, fill: 0x000000 }); sabotageElectricityButtonText.anchor.set(0.5, 0.5); sabotageElectricityButton.addChild(sabotageElectricityButtonText); sabotageOxygenButton = LK.getAsset('sabotageOxygenButton', { anchorX: 0.5, anchorY: 0.5, x: 0, y: -100 }); sabotageOxygenButton.alpha = 0; sabotageOxygenButton.visible = false; sabotageOxygenButton.tint = 0x00ffff; LK.gui.center.addChild(sabotageOxygenButton); var sabotageOxygenButtonText = new Text2('OXYGEN', { size: 20, fill: 0x000000 }); sabotageOxygenButtonText.anchor.set(0.5, 0.5); sabotageOxygenButton.addChild(sabotageOxygenButtonText); sabotageDoorsButton = LK.getAsset('sabotageDoorsButton', { anchorX: 0.5, anchorY: 0.5, x: 200, y: -100 }); sabotageDoorsButton.alpha = 0; sabotageDoorsButton.visible = false; sabotageDoorsButton.tint = 0xff8800; LK.gui.center.addChild(sabotageDoorsButton); var sabotageDoorsButtonText = new Text2('DOORS', { size: 20, fill: 0x000000 }); sabotageDoorsButtonText.anchor.set(0.5, 0.5); sabotageDoorsButton.addChild(sabotageDoorsButtonText); sabotageReactorButton = LK.getAsset('sabotageReactorButton', { anchorX: 0.5, anchorY: 0.5, x: -100, y: -200 }); sabotageReactorButton.alpha = 0; sabotageReactorButton.visible = false; sabotageReactorButton.tint = 0xff0000; LK.gui.center.addChild(sabotageReactorButton); var sabotageReactorButtonText = new Text2('REACTOR', { size: 20, fill: 0x000000 }); sabotageReactorButtonText.anchor.set(0.5, 0.5); sabotageReactorButton.addChild(sabotageReactorButtonText); sabotageConnectionsButton = LK.getAsset('sabotageConnectionsButton', { anchorX: 0.5, anchorY: 0.5, x: 100, y: -200 }); sabotageConnectionsButton.alpha = 0; sabotageConnectionsButton.visible = false; sabotageConnectionsButton.tint = 0x8800ff; LK.gui.center.addChild(sabotageConnectionsButton); var sabotageConnectionsButtonText = new Text2('CONNECTIONS', { size: 18, fill: 0x000000 }); sabotageConnectionsButtonText.anchor.set(0.5, 0.5); sabotageConnectionsButton.addChild(sabotageConnectionsButtonText); sabotageButton.down = function (x, y, obj) { if (currentPlayer && currentPlayer.isImpostor && gamePhase === 'playing') { performSabotage(); hideSabotageButton(); } }; // Individual sabotage button handlers sabotageElectricityButton.down = function (x, y, obj) { if (currentPlayer && currentPlayer.isImpostor && gamePhase === 'playing' && !isSabotageActive) { performSpecificSabotage('electricity'); hideIndividualSabotageButtons(); } }; sabotageOxygenButton.down = function (x, y, obj) { if (currentPlayer && currentPlayer.isImpostor && gamePhase === 'playing' && !isSabotageActive) { performSpecificSabotage('oxygen'); hideIndividualSabotageButtons(); } }; sabotageDoorsButton.down = function (x, y, obj) { if (currentPlayer && currentPlayer.isImpostor && gamePhase === 'playing' && !isSabotageActive) { performSpecificSabotage('doors'); hideIndividualSabotageButtons(); } }; sabotageReactorButton.down = function (x, y, obj) { if (currentPlayer && currentPlayer.isImpostor && gamePhase === 'playing' && !isSabotageActive) { performSpecificSabotage('reactor'); hideIndividualSabotageButtons(); } }; sabotageConnectionsButton.down = function (x, y, obj) { if (currentPlayer && currentPlayer.isImpostor && gamePhase === 'playing' && !isSabotageActive) { performSpecificSabotage('communications'); hideIndividualSabotageButtons(); } }; reportButton.down = function (x, y, obj) { if (reportTarget && currentPlayer && currentPlayer.isAlive && gamePhase === 'playing') { reportDeadBody(); hideReportButton(); } }; skipVotingButton.down = function (x, y, obj) { if (votingPhase && gamePhase === 'voting') { endVoting(); } }; taskButton.down = function (x, y, obj) { if (currentTaskTarget && currentPlayer && currentPlayer.isAlive && !currentPlayer.isImpostor && gamePhase === 'playing') { currentTaskTarget.startTask(); } }; cameraButton.down = function (x, y, obj) { if (currentPlayer && currentPlayer.isAlive && gamePhase === 'playing' && securityRoom) { // Check if player is near security room var distance = Math.sqrt(Math.pow(currentPlayer.x - securityRoom.x, 2) + Math.pow(currentPlayer.y - securityRoom.y, 2)); if (distance < 150) { if (!securityRoom.isActive) { securityRoom.activate(); } } } }; // Mission panel helper functions function toggleMissionPanel() { missionPanelVisible = !missionPanelVisible; missionPanel.visible = missionPanelVisible; missionPanelTitle.visible = missionPanelVisible; if (missionPanelVisible) { updateMissionPanel(); // Show panel with animation tween(missionPanel, { alpha: 0.9, scaleX: 2.5, scaleY: 3.0 }, { duration: 300 }); } else { // Hide panel with animation tween(missionPanel, { alpha: 0.8, scaleX: 2.3, scaleY: 2.8 }, { duration: 300, onFinish: function onFinish() { missionTaskTexts.forEach(function (text) { if (text) text.visible = false; }); } }); } } function updateMissionPanel() { if (!currentPlayer || currentPlayer.isImpostor || !missionPanelVisible) return; // Clear existing task texts missionTaskTexts.forEach(function (text) { if (text) text.destroy(); }); missionTaskTexts = []; // Show assigned tasks var yOffset = 280; var taskIndex = 0; if (currentPlayer.assignedTasks && currentPlayer.assignedTasks.length > 0) { currentPlayer.assignedTasks.forEach(function (task) { if (taskIndex >= 8) return; // Limit to 8 tasks to fit in panel var statusIcon = task.isCompleted ? '✓' : '○'; var statusColor = task.isCompleted ? 0x00FF00 : 0xFFFF00; var taskText = new Text2(statusIcon + ' ' + task.taskName, { size: 24, fill: statusColor }); taskText.anchor.set(0, 0); taskText.x = 80; taskText.y = yOffset; taskText.visible = true; LK.gui.topLeft.addChild(taskText); missionTaskTexts.push(taskText); // Add task location hint var locationText = new Text2('Location: ' + getTaskLocationName(task), { size: 16, fill: 0xAAAAAAA }); locationText.anchor.set(0, 0); locationText.x = 100; locationText.y = yOffset + 30; locationText.visible = true; LK.gui.topLeft.addChild(locationText); missionTaskTexts.push(locationText); yOffset += 60; taskIndex++; }); } else { // Show message if no tasks assigned var noTasksText = new Text2('No tasks assigned', { size: 20, fill: 0xFFFFFF }); noTasksText.anchor.set(0, 0); noTasksText.x = 80; noTasksText.y = 280; noTasksText.visible = true; LK.gui.topLeft.addChild(noTasksText); missionTaskTexts.push(noTasksText); } // Show completion progress var completedCount = currentPlayer.assignedTasks ? currentPlayer.assignedTasks.filter(function (task) { return task.isCompleted; }).length : 0; var totalAssigned = currentPlayer.assignedTasks ? currentPlayer.assignedTasks.length : 0; var progressText = new Text2('Progress: ' + completedCount + '/' + totalAssigned, { size: 20, fill: 0x00FFFF }); progressText.anchor.set(0.5, 0); progressText.x = 250; progressText.y = 260; progressText.visible = true; LK.gui.topLeft.addChild(progressText); missionTaskTexts.push(progressText); } function getTaskLocationName(task) { // Use stored area name if available if (task.areaName) { return task.areaName; } // Fallback to position-based naming if (task.x < 500) { if (task.y < 800) return 'Upper Left Wing'; if (task.y > 1800) return 'Lower Left Wing'; return 'Left Wing'; } else if (task.x > 1500) { if (task.y < 800) return 'Upper Right Wing'; if (task.y > 1800) return 'Lower Right Wing'; return 'Right Wing'; } else { if (task.y < 800) return 'Command Bridge'; if (task.y > 1800) return 'Cargo Bay'; if (task.y < 1200) return 'Security Area'; return 'Central Corridor'; } } // Role selection button handlers crewButton.down = function (x, y, obj) { if (gamePhase === 'roleSelection') { selectRole(false); // false = crew member } }; impostorButton.down = function (x, y, obj) { if (gamePhase === 'roleSelection') { selectRole(true); // true = impostor } }; // Initialize players function selectRole(isImpostor) { // Hide role selection UI roleSelectionTitle.destroy(); crewButton.destroy(); crewButtonText.destroy(); impostorButton.destroy(); impostorButtonText.destroy(); instructionText.destroy(); // Initialize game with selected role initializePlayers(isImpostor); initializeTasks(); // Update total tasks based on actual crew members var crewCount = players.filter(function (p) { return !p.isImpostor; }).length; totalTasks = crewCount * 4; updatePlayerCount(); gamePhase = 'playing'; phaseText.setText('Complete Tasks or Find Impostors!'); } function initializePlayers(playerIsImpostor) { var colors = [0x00ff00, 0x0000ff, 0xff00ff, 0x00ffff, 0xffa500, 0xffff00, 0x8a2be2, 0xffc0cb]; // Create current player first with selected role currentPlayer = new Player(playerIsImpostor, colors[0], 0); // Generate safe spawn position for current player var validPosition = findValidSpawnPosition(); currentPlayer.x = validPosition.x; currentPlayer.y = validPosition.y; currentPlayer.targetX = currentPlayer.x; currentPlayer.targetY = currentPlayer.y; players.push(currentPlayer); game.addChild(currentPlayer); // Create other players var remainingImpostors = playerIsImpostor ? impostorCount - 1 : impostorCount; for (var i = 1; i < 8; i++) { var isImpostor = i <= remainingImpostors; var player = new Player(isImpostor, colors[i], i); // Generate safe spawn position var validPosition = findValidSpawnPosition(); player.x = validPosition.x; player.y = validPosition.y; player.targetX = player.x; player.targetY = player.y; players.push(player); game.addChild(player); } } // Initialize tasks function initializeTasks() { var taskTypes = ['electrical', 'engine', 'reactor', 'navigation', 'medical', 'cables']; var taskAreas = [ // Command Bridge - Navigation task { x: 900, y: 350, width: 250, height: 150, preferredType: 'navigation', name: 'Command Bridge' }, // Communication Array - Electrical task { x: 1500, y: 525, width: 200, height: 125, preferredType: 'electrical', name: 'Communications' }, // Laboratory - Medical task { x: 250, y: 1000, width: 200, height: 200, preferredType: 'medical', name: 'Laboratory' }, // Weapons Room - Reactor task { x: 1750, y: 1000, width: 200, height: 200, preferredType: 'reactor', name: 'Weapons' }, // Maintenance Tunnels - Cables task { x: 350, y: 1925, width: 200, height: 125, preferredType: 'cables', name: 'Maintenance' }, // Cargo Bay - Engine task { x: 900, y: 2350, width: 400, height: 200, preferredType: 'engine', name: 'Cargo Bay' }, // Upper Electrical room { x: 400, y: 500, width: 200, height: 150, preferredType: 'electrical', name: 'Upper Electrical' }, // Upper Engine room { x: 1200, y: 500, width: 200, height: 150, preferredType: 'engine', name: 'Upper Engines' }, // Security room { x: 800, y: 800, width: 300, height: 200, preferredType: 'navigation', name: 'Security' }, // Medical bay { x: 1200, y: 1200, width: 250, height: 180, preferredType: 'medical', name: 'Medbay' }, // Reactor room { x: 400, y: 1200, width: 250, height: 180, preferredType: 'reactor', name: 'Reactor' }, // Lower Engine room { x: 1200, y: 2000, width: 200, height: 150, preferredType: 'engine', name: 'Lower Engines' }, // Lower Electrical { x: 400, y: 2000, width: 200, height: 150, preferredType: 'electrical', name: 'Lower Electrical' }, // Central corridor { x: 800, y: 1600, width: 400, height: 200, preferredType: 'navigation', name: 'Central Hub' }, // Cables maintenance room { x: 600, y: 1000, width: 200, height: 150, preferredType: 'cables', name: 'Maintenance' }]; // Get crew members (non-impostors) var crewMembers = players.filter(function (player) { return !player.isImpostor; }); // Create tasks with individual assignments var taskTypeCount = {}; taskTypes.forEach(function (type) { taskTypeCount[type] = 0; }); var taskId = 0; var allAssignedTasks = []; // Track all tasks globally // Create exactly 5 tasks per crew member for balanced gameplay var tasksPerPlayer = 5; crewMembers.forEach(function (player, playerIndex) { var playerTaskCount = {}; taskTypes.forEach(function (type) { playerTaskCount[type] = 0; }); // Initialize assigned tasks array for this player if (!player.assignedTasks) { player.assignedTasks = []; } // Assign tasks with improved distribution for (var i = 0; i < tasksPerPlayer; i++) { // Ensure tasks are spread across different areas var areaIndex = (playerIndex * tasksPerPlayer + i) % taskAreas.length; var area = taskAreas[areaIndex]; // Generate position within area with some randomness var taskX = area.x + (Math.random() - 0.5) * area.width * 0.8; var taskY = area.y + (Math.random() - 0.5) * area.height * 0.8; // Ensure task is within valid game boundaries taskX = Math.max(200, Math.min(1800, taskX)); taskY = Math.max(300, Math.min(2500, taskY)); // Smart task type selection var taskType = area.preferredType; // Ensure variety - max 2 of same type per player if (playerTaskCount[taskType] >= 2) { var availableTypes = taskTypes.filter(function (type) { return playerTaskCount[type] < 2; }); if (availableTypes.length > 0) { taskType = availableTypes[Math.floor(Math.random() * availableTypes.length)]; } else { // If all types have 2, allow 1 more of the area's preferred type taskType = area.preferredType; } } playerTaskCount[taskType]++; taskTypeCount[taskType]++; var task; // Create appropriate task type if (taskType === 'cables') { task = new CablesTask(taskX, taskY, taskId, player.playerId); } else { task = new Task(taskX, taskY, taskId, taskType, player.playerId); } // Store area information for location hints task.areaName = area.name; task.areaType = area.preferredType; // Add task to player's assigned tasks player.assignedTasks.push(task); allAssignedTasks.push(task); tasks.push(task); game.addChild(task); taskId++; } }); // Update global task count totalTasks = allAssignedTasks.length; // Show task assignment summary for debugging console.log('Task Assignment Complete:'); console.log('- Total Players:', crewMembers.length); console.log('- Tasks per Player:', tasksPerPlayer); console.log('- Total Tasks:', totalTasks); console.log('- Task Distribution:', taskTypeCount); } function updateTaskProgress() { // Calculate actual total tasks based on crew members var crewMembers = players.filter(function (p) { return !p.isImpostor; }); var actualTotalTasks = totalTasks; // Use the globally tracked total taskProgressText.setText('Tasks: ' + completedTasks + '/' + actualTotalTasks); // Update progress bar with smooth animation var progressPercentage = Math.min(completedTasks / actualTotalTasks, 1.0); tween(progressBarFill, { scaleX: progressPercentage * 8 }, { duration: 300 }); // Show task completion animation with more detail if (taskCompletionText) { var remainingTasks = actualTotalTasks - completedTasks; taskCompletionText.setText('Task Completed! (' + completedTasks + '/' + actualTotalTasks + ') - ' + remainingTasks + ' remaining'); taskCompletionText.alpha = 1; taskCompletionText.y = 0; // Scale animation for emphasis tween(taskCompletionText, { scaleX: 1.2, scaleY: 1.2 }, { duration: 200, onFinish: function onFinish() { tween(taskCompletionText, { y: -100, alpha: 0, scaleX: 1.0, scaleY: 1.0 }, { duration: 2000 }); } }); } // Enhanced progress bar flash with completion level indication var flashColor = progressPercentage > 0.8 ? 0x00FF00 : progressPercentage > 0.5 ? 0xFFFF00 : 0xFF8800; tween(progressBarFill, { tint: flashColor }, { duration: 200, onFinish: function onFinish() { tween(progressBarFill, { tint: 0x00FF00 }, { duration: 200 }); } }); // Check win condition with percentage threshold if (completedTasks >= actualTotalTasks) { // Play victory sound effect LK.getSound('taskComplete').play(); // Add victory message if (taskCompletionText) { taskCompletionText.setText('ALL TASKS COMPLETED! CREW WINS!'); taskCompletionText.tint = 0x00FF00; taskCompletionText.alpha = 1; taskCompletionText.y = 0; taskCompletionText.scaleX = 1.5; taskCompletionText.scaleY = 1.5; } endGame('crew'); } else { // Show milestone celebrations var milestones = [0.25, 0.5, 0.75]; milestones.forEach(function (milestone) { if (Math.abs(progressPercentage - milestone) < 0.01) { var percentComplete = Math.round(milestone * 100); if (taskCompletionText) { taskCompletionText.setText(percentComplete + '% of tasks completed! Keep going!'); taskCompletionText.tint = 0xFFFF00; } } }); } } function updatePlayerCount() { var aliveCrew = players.filter(function (p) { return p.isAlive && !p.isImpostor; }).length; var aliveImpostors = players.filter(function (p) { return p.isAlive && p.isImpostor; }).length; playerCountText.setText('Crew: ' + aliveCrew + ' | Impostors: ' + aliveImpostors); // Check win conditions if (aliveImpostors >= aliveCrew) { endGame('impostors'); } else if (aliveImpostors === 0) { endGame('crew'); } } function startEmergencyMeeting() { gamePhase = 'voting'; votingPhase = true; meetingCooldown = meetingCooldownTime; LK.getSound('emergency').play(); phaseText.setText('Emergency Meeting - Vote!'); // Create vote buttons var alivePlayers = players.filter(function (p) { return p.isAlive; }); var buttonWidth = 200; var startX = (2048 - alivePlayers.length * buttonWidth) / 2; for (var i = 0; i < alivePlayers.length; i++) { var button = new VoteButton(alivePlayers[i].playerId, startX + i * buttonWidth, 2400); voteButtons.push(button); game.addChild(button); } // Reset player voting ability players.forEach(function (p) { if (p.isAlive) { p.canVote = true; } }); // Show skip voting button if (skipVotingButton) { skipVotingButton.alpha = 1; skipVotingButton.visible = true; } } function endVoting() { votingPhase = false; // Find player with most votes var maxVotes = 0; var ejectedPlayer = null; for (var i = 0; i < voteButtons.length; i++) { if (voteButtons[i].votes > maxVotes) { maxVotes = voteButtons[i].votes; ejectedPlayer = players.find(function (p) { return p.playerId === voteButtons[i].playerId; }); } } // Eject player with most votes if (ejectedPlayer && maxVotes > 0) { ejectedPlayer.eliminate(); if (ejectedPlayer.isImpostor) { phaseText.setText('Impostor Ejected!'); } else { phaseText.setText('Innocent Ejected!'); } } else { phaseText.setText('No One Ejected!'); } // Clean up vote buttons voteButtons.forEach(function (button) { button.destroy(); }); voteButtons = []; // Hide skip voting button if (skipVotingButton) { skipVotingButton.alpha = 0; skipVotingButton.visible = false; } // Return to playing phase after delay LK.setTimeout(function () { gamePhase = 'playing'; phaseText.setText('Complete Tasks or Find Impostors!'); updatePlayerCount(); }, 3000); } function showKillButton(target) { if (killButton && currentPlayer && currentPlayer.isImpostor) { killTarget = target; killButton.alpha = 1; killButton.visible = true; // Flash effect to draw attention tween(killButton, { scaleX: 1.2, scaleY: 1.2 }, { duration: 200, onFinish: function onFinish() { tween(killButton, { scaleX: 1.0, scaleY: 1.0 }, { duration: 200 }); } }); } } function hideKillButton() { if (killButton) { killTarget = null; killButton.alpha = 0; killButton.visible = false; } // Also hide cooldown timer if (killCooldownTimer) { killCooldownTimer.alpha = 0; killCooldownTimer.visible = false; } } function showSabotageButton() { if (sabotageButton && currentPlayer && currentPlayer.isImpostor) { sabotageButton.alpha = 1; sabotageButton.visible = true; // Flash effect to draw attention tween(sabotageButton, { scaleX: 1.2, scaleY: 1.2 }, { duration: 200, onFinish: function onFinish() { tween(sabotageButton, { scaleX: 1.0, scaleY: 1.0 }, { duration: 200 }); } }); } } function hideSabotageButton() { if (sabotageButton) { sabotageTarget = null; sabotageButton.alpha = 0; sabotageButton.visible = false; } } function showReportButton(target) { if (reportButton && currentPlayer && currentPlayer.isAlive) { reportTarget = target; reportButton.alpha = 1; reportButton.visible = true; // Flash effect to draw attention tween(reportButton, { scaleX: 1.2, scaleY: 1.2 }, { duration: 200, onFinish: function onFinish() { tween(reportButton, { scaleX: 1.0, scaleY: 1.0 }, { duration: 200 }); } }); } } function hideReportButton() { if (reportButton) { reportTarget = null; reportButton.alpha = 0; reportButton.visible = false; } } function showTaskButton(target) { if (taskButton && currentPlayer && currentPlayer.isAlive && !currentPlayer.isImpostor) { currentTaskTarget = target; taskButton.alpha = 1; taskButton.visible = true; // Flash effect to draw attention tween(taskButton, { scaleX: 1.2, scaleY: 1.2 }, { duration: 200, onFinish: function onFinish() { tween(taskButton, { scaleX: 1.0, scaleY: 1.0 }, { duration: 200 }); } }); } } function hideTaskButton() { if (taskButton) { currentTaskTarget = null; taskButton.alpha = 0; taskButton.visible = false; } } function showCameraButton() { if (cameraButton && currentPlayer && currentPlayer.isAlive) { cameraButton.alpha = 1; cameraButton.visible = true; // Flash effect to draw attention tween(cameraButton, { scaleX: 1.2, scaleY: 1.2 }, { duration: 200, onFinish: function onFinish() { tween(cameraButton, { scaleX: 1.0, scaleY: 1.0 }, { duration: 200 }); } }); } } function hideCameraButton() { if (cameraButton) { cameraButton.alpha = 0; cameraButton.visible = false; } } function reportDeadBody() { if (currentPlayer && currentPlayer.isAlive && gamePhase === 'playing') { // Play report sound LK.getSound('report').play(); // Start emergency meeting startEmergencyMeeting(); // Update phase text to indicate it was a report phaseText.setText('Dead Body Reported - Vote!'); } } function performSabotage() { if (currentPlayer && currentPlayer.isImpostor && gamePhase === 'playing' && !isSabotageActive) { // Show individual sabotage buttons instead of performing random sabotage showIndividualSabotageButtons(); } } function performSpecificSabotage(sabotageType) { if (currentPlayer && currentPlayer.isImpostor && gamePhase === 'playing' && !isSabotageActive) { // Play sabotage sound LK.getSound('sabotage').play(); // Activate specific sabotage type activateSabotage(sabotageType); } } function showIndividualSabotageButtons() { if (currentPlayer && currentPlayer.isImpostor && gamePhase === 'playing' && !isSabotageActive) { // Show all individual sabotage buttons sabotageElectricityButton.alpha = 1; sabotageElectricityButton.visible = true; sabotageOxygenButton.alpha = 1; sabotageOxygenButton.visible = true; sabotageDoorsButton.alpha = 1; sabotageDoorsButton.visible = true; sabotageReactorButton.alpha = 1; sabotageReactorButton.visible = true; sabotageConnectionsButton.alpha = 1; sabotageConnectionsButton.visible = true; // Hide main sabotage button hideSabotageButton(); // Add pulsing animation to draw attention tween(sabotageElectricityButton, { scaleX: 1.2, scaleY: 1.2 }, { duration: 300, onFinish: function onFinish() { tween(sabotageElectricityButton, { scaleX: 1.0, scaleY: 1.0 }, { duration: 300 }); } }); } } function hideIndividualSabotageButtons() { if (sabotageElectricityButton) { sabotageElectricityButton.alpha = 0; sabotageElectricityButton.visible = false; } if (sabotageOxygenButton) { sabotageOxygenButton.alpha = 0; sabotageOxygenButton.visible = false; } if (sabotageDoorsButton) { sabotageDoorsButton.alpha = 0; sabotageDoorsButton.visible = false; } if (sabotageReactorButton) { sabotageReactorButton.alpha = 0; sabotageReactorButton.visible = false; } if (sabotageConnectionsButton) { sabotageConnectionsButton.alpha = 0; sabotageConnectionsButton.visible = false; } } function activateSabotage(sabotageType) { if (isSabotageActive) return; isSabotageActive = true; activeSabotage = sabotageType; sabotageTimer = 30000; // 30 seconds to fix // Hide individual sabotage buttons hideIndividualSabotageButtons(); // Hide task progress bar during sabotage taskProgressText.alpha = 0; progressBarBackground.alpha = 0; progressBarFill.alpha = 0; // Disable cameras during sabotage if (securityRoom && securityRoom.isActive) { securityRoom.deactivate(); } cameras.forEach(function (camera) { camera.isActive = false; }); // Apply sabotage effects based on type switch (sabotageType) { case 'electricity': // Power outage - darken screen sabotageEffect.alpha = 0.7; sabotageEffect.visible = true; sabotageEffect.tint = 0x000000; phaseText.setText('POWER OUTAGE! Fix electrical systems!'); phaseText.tint = 0xff0000; break; case 'oxygen': // Oxygen depletion - blue tint sabotageEffect.alpha = 0.3; sabotageEffect.visible = true; sabotageEffect.tint = 0x0000ff; phaseText.setText('OXYGEN DEPLETED! Restore life support!'); phaseText.tint = 0x0000ff; break; case 'reactor': // Reactor meltdown - red tint and flash sabotageEffect.alpha = 0.4; sabotageEffect.visible = true; sabotageEffect.tint = 0xff0000; phaseText.setText('REACTOR CRITICAL! Fix reactor core!'); phaseText.tint = 0xff0000; // Flash effect tween(sabotageEffect, { alpha: 0.6 }, { duration: 500, onFinish: function onFinish() { tween(sabotageEffect, { alpha: 0.4 }, { duration: 500 }); } }); break; case 'doors': // Close all doors doors.forEach(function (door) { if (door.isOpen) { door.close(); } }); phaseText.setText('DOORS SABOTAGED! Systems compromised!'); phaseText.tint = 0xff8800; break; case 'communications': // Disable task visibility tasks.forEach(function (task) { task.alpha = 0.3; }); phaseText.setText('COMMUNICATIONS DOWN! Tasks hidden!'); phaseText.tint = 0xffff00; break; } // Create sabotage fix indicators createSabotageIndicators(sabotageType); } function createSabotageIndicators(sabotageType) { // Clear existing indicators sabotageIndicators.forEach(function (indicator) { indicator.destroy(); }); sabotageIndicators = []; // Create fix locations based on sabotage type var fixLocations = []; switch (sabotageType) { case 'electricity': fixLocations = [{ x: 400, y: 500 }, // Upper electrical { x: 400, y: 2000 } // Lower electrical ]; break; case 'oxygen': fixLocations = [{ x: 1200, y: 1200 }, // Medical bay { x: 850, y: 1500 } // Reactor area ]; break; case 'reactor': fixLocations = [{ x: 850, y: 1500 }, // Reactor room { x: 1200, y: 500 } // Engine room ]; break; case 'doors': fixLocations = [{ x: 1024, y: 600 }, // Security room { x: 1024, y: 1366 } // Central corridor ]; break; case 'communications': fixLocations = [{ x: 1024, y: 600 }, // Security room { x: 1024, y: 800 } // Admin area ]; break; } // Create visual indicators fixLocations.forEach(function (location) { var indicator = game.addChild(LK.getAsset('sabotageIndicator', { anchorX: 0.5, anchorY: 0.5, x: location.x, y: location.y })); indicator.fixLocation = location; sabotageIndicators.push(indicator); // Pulsing animation tween(indicator, { scaleX: 1.5, scaleY: 1.5, alpha: 0.7 }, { duration: 1000, onFinish: function onFinish() { tween(indicator, { scaleX: 1.0, scaleY: 1.0, alpha: 1.0 }, { duration: 1000 }); } }); }); } function fixSabotage() { if (!isSabotageActive) return; isSabotageActive = false; activeSabotage = null; sabotageTimer = 0; // Restore task progress bar taskProgressText.alpha = 1; progressBarBackground.alpha = 1; progressBarFill.alpha = 1; // Re-enable cameras cameras.forEach(function (camera) { camera.isActive = true; }); // Hide sabotage effect sabotageEffect.alpha = 0; sabotageEffect.visible = false; // Restore task visibility tasks.forEach(function (task) { task.alpha = 1.0; }); // Reset phase text phaseText.setText('Complete Tasks or Find Impostors!'); phaseText.tint = 0xFFFF00; // Clear sabotage indicators sabotageIndicators.forEach(function (indicator) { indicator.destroy(); }); sabotageIndicators = []; // Play success sound LK.getSound('taskComplete').play(); } // Find valid spawn position that doesn't collide with walls function findValidSpawnPosition() { var playerSize = 80; var maxAttempts = 50; var attempts = 0; while (attempts < maxAttempts) { var x = 300 + Math.random() * 1400; var y = 300 + Math.random() * 2000; // Check if this position is valid (not inside a wall) if (!checkWallCollision(x, y, playerSize, playerSize)) { return { x: x, y: y }; } attempts++; } // If we can't find a valid position after many attempts, use a known safe position // Return center of main corridor as fallback return { x: 1024, y: 1366 }; } // Collision detection function function checkWallCollision(x, y, playerWidth, playerHeight) { var playerLeft = x - playerWidth / 2; var playerRight = x + playerWidth / 2; var playerTop = y - playerHeight / 2; var playerBottom = y + playerHeight / 2; for (var i = 0; i < walls.length; i++) { var wall = walls[i]; var wallLeft = wall.x - wall.width * (wall.scaleX || 1) / 2; var wallRight = wall.x + wall.width * (wall.scaleX || 1) / 2; var wallTop = wall.y - wall.height * (wall.scaleY || 1) / 2; var wallBottom = wall.y + wall.height * (wall.scaleY || 1) / 2; // Check if player rectangle intersects with wall rectangle if (playerLeft < wallRight && playerRight > wallLeft && playerTop < wallBottom && playerBottom > wallTop) { return true; } } // Check door collisions - only check if door is closed for (var i = 0; i < doors.length; i++) { var door = doors[i]; // Only check collision if door is closed if (!door.isOpen && door.blocksMovement(x, y, playerWidth, playerHeight)) { return true; } } return false; } function endGame(winner) { gamePhase = 'gameOver'; if (winner === 'crew') { phaseText.setText('Crew Wins!'); LK.showYouWin(); } else { phaseText.setText('Impostors Win!'); LK.showGameOver(); } } // AI behavior for other players - optimized for performance function updateAI() { // Process only a subset of players each update to spread load var playersToUpdate = players.length; var startIndex = LK.ticks % playersToUpdate; for (var playerIndex = 0; playerIndex < Math.min(2, playersToUpdate); playerIndex++) { var index = (startIndex + playerIndex) % playersToUpdate; var player = players[index]; if (player === currentPlayer || !player.isAlive) continue; // Crew member AI - simplified for performance if (!player.isImpostor && gamePhase === 'playing') { // Priority 1: Fix sabotages (highest priority) if (isSabotageActive && sabotageIndicators.length > 0) { // Check if near any sabotage indicator (simplified) var nearIndicator = false; for (var i = 0; i < sabotageIndicators.length; i++) { var indicator = sabotageIndicators[i]; var dx = indicator.x - player.x; var dy = indicator.y - player.y; if (dx * dx + dy * dy < 10000) { // 100 * 100 nearIndicator = true; if (Math.random() < 0.1) { // Reduced chance fixSabotage(); return; } break; } } if (!nearIndicator && Math.random() < 0.05) { // Move towards first sabotage indicator var indicator = sabotageIndicators[0]; player.moveTo(indicator.x + (Math.random() - 0.5) * 30, indicator.y + (Math.random() - 0.5) * 30); } continue; } // Priority 2: Report dead bodies (simplified) var foundDeadBody = false; for (var i = 0; i < players.length; i++) { var p = players[i]; if (p === player || p.isAlive) continue; var dx = p.x - player.x; var dy = p.y - player.y; if (dx * dx + dy * dy < 22500) { // 150 * 150 foundDeadBody = true; if (Math.random() < 0.05) { // Reduced chance reportDeadBody(); return; } break; } } if (foundDeadBody) continue; // Priority 3: Complete assigned tasks (simplified) if (player.assignedTasks && player.assignedTasks.length > 0) { var nearTask = false; for (var i = 0; i < player.assignedTasks.length; i++) { var task = player.assignedTasks[i]; if (task.isCompleted) continue; var dx = task.x - player.x; var dy = task.y - player.y; if (dx * dx + dy * dy < 10000) { // 100 * 100 nearTask = true; if (Math.random() < 0.1) { task.completionProgress++; if (task.completionProgress >= task.requiredClicks) { task.complete(); player.completeTask(); completedTasks++; updateTaskProgress(); } } break; } } // Move towards task occasionally if (!nearTask && Math.random() < 0.02) { var incompleteTasks = player.assignedTasks.filter(function (task) { return !task.isCompleted; }); if (incompleteTasks.length > 0) { var task = incompleteTasks[0]; player.moveTo(task.x + (Math.random() - 0.5) * 50, task.y + (Math.random() - 0.5) * 50); } } } } // Impostor AI (simplified) if (player.isImpostor && gamePhase === 'playing') { // Try to kill nearby crew members var foundTarget = false; for (var i = 0; i < players.length; i++) { var p = players[i]; if (p === player || !p.isAlive || p.isImpostor) continue; var dx = p.x - player.x; var dy = p.y - player.y; if (dx * dx + dy * dy < 14400) { // 120 * 120 foundTarget = true; if (player.canKill() && Math.random() < 0.05) { // Reduced chance player.kill(p); updatePlayerCount(); return; } break; } } // Move occasionally if (!foundTarget && Math.random() < 0.01) { player.moveTo(300 + Math.random() * 1400, 300 + Math.random() * 2000); } } } } // Initialize cameras and security room function initializeCameras() { // Create security cameras in key locations var cameraPositions = [{ x: 400, y: 500, angle: 0 }, // Upper left electrical { x: 1600, y: 500, angle: Math.PI }, // Upper right engine { x: 1024, y: 800, angle: Math.PI / 2 }, // Security/Admin area { x: 400, y: 1500, angle: 0 }, // Lower left reactor { x: 1600, y: 1500, angle: Math.PI }, // Lower right medical { x: 1024, y: 2000, angle: Math.PI / 2 } // Lower corridor ]; for (var i = 0; i < cameraPositions.length; i++) { var camPos = cameraPositions[i]; var camera = new Camera(camPos.x, camPos.y, i, camPos.angle); cameras.push(camera); game.addChild(camera); } // Create security room in the upper central area securityRoom = new SecurityRoom(1024, 600); game.addChild(securityRoom); } // Initialize sewers function initializeSewers() { // Create sewer entrances throughout the ship for room-to-room teleportation var sewerPositions = [ // Command Bridge sewer { x: 950, y: 350, id: 0, targetId: 8 }, // Communication Array sewer { x: 1550, y: 450, id: 1, targetId: 9 }, // Security Room sewer { x: 1024, y: 550, id: 2, targetId: 10 }, // Laboratory sewer { x: 200, y: 1000, id: 3, targetId: 11 }, // Weapons Room sewer { x: 1850, y: 1000, id: 4, targetId: 3 }, // Medical Bay sewer { x: 1300, y: 1400, id: 5, targetId: 6 }, // Reactor Room sewer { x: 750, y: 1400, id: 6, targetId: 5 }, // Central Corridor sewer { x: 1024, y: 1366, id: 7, targetId: 12 }, // Maintenance Tunnels sewer { x: 300, y: 1925, id: 8, targetId: 0 }, // Upper Left Electrical sewer { x: 350, y: 650, id: 9, targetId: 1 }, // Lower Left Electrical sewer { x: 350, y: 2100, id: 10, targetId: 2 }, // Upper Right Engine sewer { x: 1700, y: 650, id: 11, targetId: 4 }, // Cargo Bay sewer { x: 1024, y: 2450, id: 12, targetId: 7 }, // Storage Left sewer { x: 250, y: 1366, id: 13, targetId: 14 }, // Storage Right sewer { x: 1800, y: 1366, id: 14, targetId: 13 }, // Lower Engine Left sewer { x: 400, y: 1800, id: 15, targetId: 16 }, // Lower Engine Right sewer { x: 1650, y: 1800, id: 16, targetId: 15 }]; for (var i = 0; i < sewerPositions.length; i++) { var pos = sewerPositions[i]; var sewer = new Sewer(pos.x, pos.y, pos.id, pos.targetId); sewers.push(sewer); game.addChild(sewer); } } // Initialize doors function initializeDoors() { // Create doors to define proper room entrances and corridors var doorPositions = [ // Command Bridge room doors { x: 850, y: 450, orientation: 0 }, // Bridge entrance left { x: 1200, y: 450, orientation: 0 }, // Bridge entrance right // Communication Array room doors { x: 1400, y: 525, orientation: 1 }, // Comm room entrance // Security room doors { x: 800, y: 600, orientation: 1 }, // Security entrance left { x: 1248, y: 600, orientation: 1 }, // Security entrance right // Laboratory room doors { x: 450, y: 900, orientation: 0 }, // Lab entrance top { x: 450, y: 1100, orientation: 0 }, // Lab entrance bottom // Weapons room doors { x: 1600, y: 900, orientation: 0 }, // Weapons entrance top { x: 1600, y: 1100, orientation: 0 }, // Weapons entrance bottom // Central corridor access doors { x: 700, y: 1000, orientation: 1 }, // Left corridor access { x: 1350, y: 1000, orientation: 1 }, // Right corridor access // Main corridor horizontal doors { x: 824, y: 1300, orientation: 0 }, // Central access left { x: 1224, y: 1300, orientation: 0 }, // Central access right // Medical bay and reactor room doors { x: 1200, y: 1400, orientation: 0 }, // Medical entrance { x: 850, y: 1400, orientation: 0 }, // Reactor entrance // Maintenance tunnels doors { x: 600, y: 1850, orientation: 0 }, // Maintenance entrance // Lower corridor doors { x: 700, y: 1900, orientation: 1 }, // Lower left access { x: 1350, y: 1900, orientation: 1 }, // Lower right access // Cargo bay doors { x: 800, y: 2200, orientation: 0 }, // Cargo entrance left { x: 1248, y: 2200, orientation: 0 }, // Cargo entrance right // Engine room doors { x: 500, y: 1700, orientation: 1 }, // Left engine entrance { x: 1550, y: 1700, orientation: 1 }, // Right engine entrance // Additional room separators { x: 1024, y: 750, orientation: 0 }, // Upper central passage { x: 1024, y: 1550, orientation: 0 }, // Lower central passage // Storage room doors { x: 350, y: 1200, orientation: 1 }, // Left storage { x: 1700, y: 1200, orientation: 1 }, // Right storage // Electrical room doors { x: 500, y: 600, orientation: 1 }, // Upper electrical { x: 500, y: 1950, orientation: 1 } // Lower electrical ]; for (var i = 0; i < doorPositions.length; i++) { var pos = doorPositions[i]; var door = new Door(pos.x, pos.y, i, pos.orientation); doors.push(door); game.addChild(door); } } // Common task areas that all players should have access to var commonTaskAreas = [{ x: 1024, y: 1366, // Central emergency button area taskType: 'emergency', name: 'Emergency Protocols' }, { x: 1024, y: 600, // Security area taskType: 'security', name: 'Security Systems' }]; // Initialize game createWalls(); initializeCameras(); initializeDoors(); initializeSewers(); // Don't initialize players yet - wait for role selection // Game controls game.down = function (x, y, obj) { if (gamePhase === 'playing' && currentPlayer && currentPlayer.isAlive) { // Check if clicking on sabotage fix location if (isSabotageActive && !currentPlayer.isImpostor) { var clickedIndicator = null; sabotageIndicators.forEach(function (indicator) { var distance = Math.sqrt(Math.pow(x - indicator.x, 2) + Math.pow(y - indicator.y, 2)); if (distance < 80) { clickedIndicator = indicator; } }); if (clickedIndicator) { // Fix sabotage fixSabotage(); return; } } currentPlayer.moveTo(x, y); } }; game.update = function () { if (gamePhase === 'roleSelection') { // Don't update game logic during role selection return; } // Hide sabotage button if not impostor or not playing if (!currentPlayer || !currentPlayer.isImpostor || gamePhase !== 'playing') { if (sabotageButton && sabotageButton.visible) { hideSabotageButton(); } // Also hide individual sabotage buttons hideIndividualSabotageButtons(); } // Hide report button if not playing if (!currentPlayer || !currentPlayer.isAlive || gamePhase !== 'playing') { if (reportButton && reportButton.visible) { hideReportButton(); } } // Hide task button if not playing or is impostor if (!currentPlayer || !currentPlayer.isAlive || currentPlayer.isImpostor || gamePhase !== 'playing') { if (taskButton && taskButton.visible) { hideTaskButton(); } } // Hide mission panel elements if not crew member or not playing if (!currentPlayer || !currentPlayer.isAlive || currentPlayer.isImpostor || gamePhase !== 'playing') { if (missionToggleButton && missionToggleButton.visible) { missionToggleButton.alpha = 0; missionToggleButton.visible = false; } if (missionPanelVisible) { missionPanelVisible = false; missionPanel.visible = false; missionPanelTitle.visible = false; missionTaskTexts.forEach(function (text) { if (text) text.visible = false; }); } } // Hide camera button if not playing if (!currentPlayer || !currentPlayer.isAlive || gamePhase !== 'playing') { if (cameraButton && cameraButton.visible) { hideCameraButton(); } } // Hide door button if not impostor or not playing if (!currentPlayer || !currentPlayer.isAlive || !currentPlayer.isImpostor || gamePhase !== 'playing') { if (doorButton && doorButton.visible) { hideDoorButton(); } } // Check line of sight between two points (returns true if clear, false if blocked by wall) function checkLineOfSight(x1, y1, x2, y2) { var dx = x2 - x1; var dy = y2 - y1; var distance = Math.sqrt(dx * dx + dy * dy); // If points are very close, assume line of sight is clear if (distance < 10) return true; // Check multiple points along the line var steps = Math.ceil(distance / 20); // Check every 20 pixels for (var i = 0; i <= steps; i++) { var t = i / steps; var checkX = x1 + dx * t; var checkY = y1 + dy * t; // Check if this point intersects with any wall for (var j = 0; j < walls.length; j++) { var wall = walls[j]; var wallLeft = wall.x - wall.width * (wall.scaleX || 1) / 2; var wallRight = wall.x + wall.width * (wall.scaleX || 1) / 2; var wallTop = wall.y - wall.height * (wall.scaleY || 1) / 2; var wallBottom = wall.y + wall.height * (wall.scaleY || 1) / 2; // Check if point is inside wall if (checkX >= wallLeft && checkX <= wallRight && checkY >= wallTop && checkY <= wallBottom) { return false; // Line of sight blocked } } } return true; // Line of sight clear } // Update visibility of all game objects based on line of sight - optimized for performance function updateVisibility() { if (!currentPlayer || !currentPlayer.isAlive) return; var playerX = currentPlayer.x; var playerY = currentPlayer.y; var visionRange = 300; // How far player can see var visionRangeSquared = visionRange * visionRange; // Avoid sqrt when possible // Update player visibility for (var i = 0; i < players.length; i++) { var player = players[i]; if (player === currentPlayer) { player.alpha = 1.0; // Always see yourself continue; } // Use squared distance to avoid expensive sqrt var dx = player.x - playerX; var dy = player.y - playerY; var distanceSquared = dx * dx + dy * dy; if (distanceSquared <= visionRangeSquared) { player.alpha = 1.0; // Fully visible (simplified - no line of sight for performance) } else { player.alpha = 0.0; // Hidden } } // Update task visibility (only for crew members) - simplified if (!currentPlayer.isImpostor) { for (var i = 0; i < tasks.length; i++) { var task = tasks[i]; var dx = task.x - playerX; var dy = task.y - playerY; var distanceSquared = dx * dx + dy * dy; if (distanceSquared <= visionRangeSquared) { task.alpha = task.isCompleted ? 0.5 : 1.0; // Visible } else { task.alpha = 0.0; // Hidden } } } // Update other objects with simple distance check (no line of sight for performance) // Update camera visibility for (var i = 0; i < cameras.length; i++) { var camera = cameras[i]; var dx = camera.x - playerX; var dy = camera.y - playerY; var distanceSquared = dx * dx + dy * dy; camera.alpha = distanceSquared <= visionRangeSquared ? 1.0 : 0.0; } // Update door visibility for (var i = 0; i < doors.length; i++) { var door = doors[i]; var dx = door.x - playerX; var dy = door.y - playerY; var distanceSquared = dx * dx + dy * dy; door.alpha = distanceSquared <= visionRangeSquared ? 1.0 : 0.0; } // Update sabotage indicator visibility for (var i = 0; i < sabotageIndicators.length; i++) { var indicator = sabotageIndicators[i]; var dx = indicator.x - playerX; var dy = indicator.y - playerY; var distanceSquared = dx * dx + dy * dy; if (distanceSquared <= visionRangeSquared) { // Preserve existing alpha for animations if visible } else { indicator.alpha = 0.0; // Hidden } } // Update sewer visibility for (var i = 0; i < sewers.length; i++) { var sewer = sewers[i]; var dx = sewer.x - playerX; var dy = sewer.y - playerY; var distanceSquared = dx * dx + dy * dy; sewer.alpha = distanceSquared <= visionRangeSquared ? 1.0 : 0.0; } // Update ghost visibility - ghosts are always somewhat visible but fade with distance for (var i = 0; i < ghosts.length; i++) { var ghost = ghosts[i]; var dx = ghost.x - playerX; var dy = ghost.y - playerY; var distanceSquared = dx * dx + dy * dy; if (distanceSquared <= visionRangeSquared) { // Ghosts maintain their alpha but are visible ghost.visible = true; } else { // Ghosts fade out with distance but remain slightly visible ghost.alpha = Math.max(ghost.alpha * 0.3, 0.1); } } } // Update camera to follow current player if (currentPlayer && currentPlayer.isAlive && gamePhase === 'playing') { // Calculate target camera position (center player on screen) var targetX = 1024 - currentPlayer.x; // Center horizontally var targetY = 1366 - currentPlayer.y; // Center vertically // Smooth camera movement with lerp var lerpFactor = 0.1; var currentX = game.x; var currentY = game.y; game.x = currentX + (targetX - currentX) * lerpFactor; game.y = currentY + (targetY - currentY) * lerpFactor; // Clamp camera to keep game world bounds in view var minX = 1024 - 1800; // Don't go past right edge var maxX = 1024 - 200; // Don't go past left edge var minY = 1366 - 2500; // Don't go past bottom edge var maxY = 1366 - 200; // Don't go past top edge game.x = Math.max(minX, Math.min(maxX, game.x)); game.y = Math.max(minY, Math.min(maxY, game.y)); // Update visibility less frequently for performance if (LK.ticks % 6 === 0) { // Update visibility 10 times per second instead of 60 updateVisibility(); } // Update ghosts for (var i = 0; i < ghosts.length; i++) { var ghost = ghosts[i]; if (ghost && ghost.update) { ghost.update(); } } } // Update cooldowns if (meetingCooldown > 0) { meetingCooldown -= 16.67; // Roughly 1 frame at 60fps } // Update AI less frequently for performance if (LK.ticks % 60 === 0) { // Update AI once per second instead of twice per second updateAI(); } // Update current player impostor kill opportunities - optimized if (currentPlayer && currentPlayer.isImpostor && gamePhase === 'playing') { var nearestTarget = null; var nearestDistanceSquared = 14400; // 120 * 120 // Find nearest target with optimized distance calculation for (var i = 0; i < players.length; i++) { var p = players[i]; if (p === currentPlayer || !p.isAlive || p.isImpostor) continue; var dx = p.x - currentPlayer.x; var dy = p.y - currentPlayer.y; var distanceSquared = dx * dx + dy * dy; if (distanceSquared < nearestDistanceSquared) { nearestTarget = p; nearestDistanceSquared = distanceSquared; } } // Show kill button if there are targets nearby (regardless of cooldown) if (nearestTarget) { // Add visual indicator for kill opportunity if (LK.ticks % 30 < 15) { nearestTarget.alpha = 0.7; } else { nearestTarget.alpha = 1.0; } // Show kill button for closest target if (!killButton.visible) { showKillButton(nearestTarget); } // Update kill button opacity based on cooldown if (currentPlayer.canKill()) { killButton.alpha = 1.0; // Hide cooldown timer when ready to kill if (killCooldownTimer) { killCooldownTimer.alpha = 0; killCooldownTimer.visible = false; } } else { killButton.alpha = 0.5; // Show button but dimmed during cooldown // Show and update cooldown timer if (killCooldownTimer) { var cooldownLeft = Math.ceil((currentPlayer.killCooldown - (LK.ticks - currentPlayer.lastKillTime)) / 1000); killCooldownTimer.setText(cooldownLeft + 's'); killCooldownTimer.alpha = 0.9; killCooldownTimer.visible = true; // Gentle pulsing effect during cooldown if (LK.ticks % 60 < 30) { killCooldownTimer.alpha = 0.9; } else { killCooldownTimer.alpha = 0.6; } } } } else { // Hide kill button if no targets nearby if (killButton.visible) { hideKillButton(); } // Reset target alphas for (var i = 0; i < players.length; i++) { var p = players[i]; if (!p.isImpostor) { p.alpha = 1.0; } } } // Show sabotage button for impostors if (!sabotageButton.visible) { showSabotageButton(); } } // Update report button for all players near dead bodies - optimized if (currentPlayer && currentPlayer.isAlive && gamePhase === 'playing') { // Door button helper functions var showDoorButton = function showDoorButton(door) { if (doorButton && currentPlayer && currentPlayer.isImpostor) { currentDoorTarget = door; doorButton.alpha = 1; doorButton.visible = true; // Update button text based on door state if (door.isOpen) { doorButton.doorButtonText.setText('CLOSE'); } else { doorButton.doorButtonText.setText('OPEN'); } // Flash effect to draw attention tween(doorButton, { scaleX: 1.2, scaleY: 1.2 }, { duration: 200, onFinish: function onFinish() { tween(doorButton, { scaleX: 1.0, scaleY: 1.0 }, { duration: 200 }); } }); } }; var _hideDoorButton = function _hideDoorButton() { if (doorButton) { currentDoorTarget = null; doorButton.alpha = 0; doorButton.visible = false; } }; // Update door button for impostors near doors (only if no sabotage active) var nearestDeadBody = null; var nearestDeadDistanceSquared = 22500; // 150 * 150 // Find nearest dead body with optimized distance calculation for (var i = 0; i < players.length; i++) { var p = players[i]; if (p === currentPlayer || p.isAlive) continue; var dx = p.x - currentPlayer.x; var dy = p.y - currentPlayer.y; var distanceSquared = dx * dx + dy * dy; if (distanceSquared < nearestDeadDistanceSquared) { nearestDeadBody = p; nearestDeadDistanceSquared = distanceSquared; } } // Show report button if there are dead bodies nearby if (nearestDeadBody) { // Add visual indicator for dead body if (LK.ticks % 60 < 30) { nearestDeadBody.alpha = 0.5; } else { nearestDeadBody.alpha = 0.3; } // Show report button for closest dead body if (!reportButton.visible) { showReportButton(nearestDeadBody); } } else { // Hide report button if no dead bodies nearby if (reportButton.visible) { hideReportButton(); } } // Update current task display for crew members and task button if (currentPlayer && !currentPlayer.isImpostor && currentPlayer.isAlive) { var nearbyTasks = currentPlayer.assignedTasks.filter(function (task) { if (task.isCompleted) return false; var distance = Math.sqrt(Math.pow(task.x - currentPlayer.x, 2) + Math.pow(task.y - currentPlayer.y, 2)); return distance < 100; }); if (nearbyTasks.length > 0) { var closestTask = nearbyTasks[0]; if (currentTaskText) { currentTaskText.setText(closestTask.taskName); currentTaskText.alpha = 0.8; } // Show task button for closest task if (!taskButton.visible) { showTaskButton(closestTask); } } else { if (currentTaskText) { currentTaskText.alpha = 0; } // Hide task button if no tasks nearby if (taskButton.visible) { hideTaskButton(); } } // Show mission toggle button for crew members if (missionToggleButton) { missionToggleButton.alpha = 0.8; missionToggleButton.visible = true; } // Update mission panel if visible if (missionPanelVisible && LK.ticks % 60 === 0) { updateMissionPanel(); } } // Update camera button for all players near security room if (currentPlayer && currentPlayer.isAlive && gamePhase === 'playing' && securityRoom) { var distanceToSecurity = Math.sqrt(Math.pow(currentPlayer.x - securityRoom.x, 2) + Math.pow(currentPlayer.y - securityRoom.y, 2)); // Show camera button if near security room if (distanceToSecurity < 150) { // Show camera button for security room access if (!cameraButton.visible) { showCameraButton(); } } else { // Hide camera button if not near security room if (cameraButton.visible) { hideCameraButton(); } } } // Update sabotage system if (isSabotageActive) { // Update sabotage timer sabotageTimer -= 16.67; // Roughly 1 frame at 60fps // Check if sabotage timer expired (crew loses) if (sabotageTimer <= 0) { endGame('impostors'); return; } // Update sabotage timer display var secondsLeft = Math.ceil(sabotageTimer / 1000); var currentText = phaseText.text || ''; phaseText.setText(currentText.split('(')[0] + '(' + secondsLeft + 's)'); // Check if player is near sabotage fix locations if (currentPlayer && currentPlayer.isAlive && !currentPlayer.isImpostor) { sabotageIndicators.forEach(function (indicator) { var distance = Math.sqrt(Math.pow(currentPlayer.x - indicator.x, 2) + Math.pow(currentPlayer.y - indicator.y, 2)); if (distance < 80) { // Player is close to fix location if (LK.ticks % 30 < 15) { indicator.alpha = 0.3; } else { indicator.alpha = 1.0; } // Allow fixing with click/tap indicator.canFix = true; } else { indicator.canFix = false; } }); } // Flash sabotage effect for critical sabotages if (activeSabotage === 'reactor' || activeSabotage === 'oxygen') { if (LK.ticks % 60 < 30) { sabotageEffect.alpha = Math.min(sabotageEffect.alpha * 1.2, 0.8); } else { sabotageEffect.alpha = Math.max(sabotageEffect.alpha * 0.8, 0.3); } } } if (currentPlayer && currentPlayer.isAlive && currentPlayer.isImpostor && gamePhase === 'playing' && !isSabotageActive) { var nearbyDoors = doors.filter(function (door) { var distance = Math.sqrt(Math.pow(currentPlayer.x - door.x, 2) + Math.pow(currentPlayer.y - door.y, 2)); return distance < 100; }); // Show door button if near doors if (nearbyDoors.length > 0) { var closestDoor = nearbyDoors[0]; // Show door button for closest door if (!doorButton.visible) { showDoorButton(closestDoor); } else if (currentDoorTarget !== closestDoor) { // Update target if different door is closer showDoorButton(closestDoor); } } else { // Hide door button if not near any doors if (doorButton.visible) { _hideDoorButton(); } } } } };
/****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
var storage = LK.import("@upit/storage.v1");
/****
* Classes
****/
var CablesTask = Container.expand(function (x, y, id, assignedPlayerId) {
var self = Container.call(this);
self.taskId = id || 0;
self.isCompleted = false;
self.x = x;
self.y = y;
self.taskType = 'cables';
self.taskName = 'Fix Wiring';
self.taskDescription = 'Connect the colored wires';
self.isActive = false;
self.wiresConnected = 0;
self.totalWires = 4;
self.wires = [];
self.connectors = [];
self.isNearPlayer = false;
self.lastNearPlayer = false;
self.assignedPlayerId = assignedPlayerId || -1; // Which player this task is assigned to (-1 = any player)
// Task panel background
var taskPanel = self.attachAsset('cablePanel', {
anchorX: 0.5,
anchorY: 0.5
});
taskPanel.alpha = 0.8;
// Add task name text
var taskNameText = new Text2(self.taskName, {
size: 24,
fill: 0xFFFFFF
});
taskNameText.anchor.set(0.5, 0);
taskNameText.y = -180;
self.addChild(taskNameText);
// Add interaction hint text
var interactionText = new Text2('Click to start wiring', {
size: 18,
fill: 0x00FFFF
});
interactionText.anchor.set(0.5, 0);
interactionText.y = 170;
interactionText.alpha = 0;
self.addChild(interactionText);
// Wire colors
var wireColors = [0xff0000, 0x00ff00, 0x0000ff, 0xffff00];
var leftConnectorPositions = [];
var rightConnectorPositions = [];
// Create left side connectors (sources)
for (var i = 0; i < self.totalWires; i++) {
var leftConnector = self.attachAsset('cableConnector', {
anchorX: 0.5,
anchorY: 0.5
});
leftConnector.x = -80;
leftConnector.y = -60 + i * 40;
leftConnector.tint = wireColors[i];
leftConnector.wireIndex = i;
leftConnector.isSource = true;
leftConnector.isConnected = false;
self.connectors.push(leftConnector);
leftConnectorPositions.push({
x: leftConnector.x,
y: leftConnector.y
});
}
// Create right side connectors (destinations) - shuffled
var shuffledColors = wireColors.slice();
for (var i = shuffledColors.length - 1; i > 0; i--) {
var j = Math.floor(Math.random() * (i + 1));
var temp = shuffledColors[i];
shuffledColors[i] = shuffledColors[j];
shuffledColors[j] = temp;
}
for (var i = 0; i < self.totalWires; i++) {
var rightConnector = self.attachAsset('cableConnector', {
anchorX: 0.5,
anchorY: 0.5
});
rightConnector.x = 80;
rightConnector.y = -60 + i * 40;
rightConnector.tint = shuffledColors[i];
rightConnector.wireIndex = wireColors.indexOf(shuffledColors[i]);
rightConnector.isSource = false;
rightConnector.isConnected = false;
self.connectors.push(rightConnector);
rightConnectorPositions.push({
x: rightConnector.x,
y: rightConnector.y
});
}
// Create wires (initially disconnected)
for (var i = 0; i < self.totalWires; i++) {
var wire = self.attachAsset('cableWire', {
anchorX: 0,
anchorY: 0.5
});
wire.x = -70;
wire.y = -60 + i * 40;
wire.width = 60;
wire.tint = wireColors[i];
wire.wireIndex = i;
wire.isConnected = false;
wire.alpha = 0.5;
self.wires.push(wire);
}
self.startTask = function () {
if (!self.isActive && !self.isCompleted) {
self.isActive = true;
// Show task panel with animation
tween(taskPanel, {
scaleX: 1.1,
scaleY: 1.1,
alpha: 1.0
}, {
duration: 300,
onFinish: function onFinish() {
tween(taskPanel, {
scaleX: 1.0,
scaleY: 1.0
}, {
duration: 200
});
}
});
// Show instruction
var instructionText = new Text2('Connect matching wire colors', {
size: 20,
fill: 0xFFFF00
});
instructionText.anchor.set(0.5, 0);
instructionText.y = -130;
self.addChild(instructionText);
// Remove after delay
LK.setTimeout(function () {
if (instructionText) {
instructionText.destroy();
}
}, 2000);
}
};
self.connectWire = function (sourceIndex, targetIndex) {
if (sourceIndex === targetIndex && !self.wires[sourceIndex].isConnected) {
// Correct connection
self.wires[sourceIndex].isConnected = true;
self.wires[sourceIndex].alpha = 1.0;
self.wires[sourceIndex].width = 160;
self.wiresConnected++;
// Mark connectors as connected
self.connectors.forEach(function (connector) {
if (connector.wireIndex === sourceIndex) {
connector.isConnected = true;
// Flash connected effect
tween(connector, {
scaleX: 1.3,
scaleY: 1.3
}, {
duration: 150,
onFinish: function onFinish() {
tween(connector, {
scaleX: 1.0,
scaleY: 1.0
}, {
duration: 150
});
}
});
}
});
// Play success sound
LK.getSound('taskComplete').play();
// Check if all wires are connected
if (self.wiresConnected >= self.totalWires) {
self.complete();
}
return true;
} else {
// Wrong connection - flash red
var wrongWire = self.wires[sourceIndex];
tween(wrongWire, {
tint: 0xff0000,
alpha: 0.8
}, {
duration: 200,
onFinish: function onFinish() {
tween(wrongWire, {
tint: wireColors[sourceIndex],
alpha: 0.5
}, {
duration: 200
});
}
});
return false;
}
};
self.complete = function () {
if (!self.isCompleted) {
self.isCompleted = true;
self.isActive = false;
// Hide task panel
tween(taskPanel, {
alpha: 0,
scaleX: 0.8,
scaleY: 0.8
}, {
duration: 500
});
// Show completion text
var completedText = new Text2('WIRING COMPLETE', {
size: 24,
fill: 0x00FF00
});
completedText.anchor.set(0.5, 0.5);
completedText.y = 0;
self.addChild(completedText);
// Flash completion effect
tween(completedText, {
scaleX: 1.2,
scaleY: 1.2,
alpha: 1.0
}, {
duration: 300,
onFinish: function onFinish() {
tween(completedText, {
scaleX: 1.0,
scaleY: 1.0,
alpha: 0.8
}, {
duration: 300
});
}
});
return true;
}
return false;
};
self.update = function () {
if (self.isCompleted) return;
// Check if player is near
self.lastNearPlayer = self.isNearPlayer;
self.isNearPlayer = false;
if (currentPlayer && currentPlayer.isAlive && !currentPlayer.isImpostor) {
var distance = Math.sqrt(Math.pow(currentPlayer.x - self.x, 2) + Math.pow(currentPlayer.y - self.y, 2));
// Only show task if it's assigned to current player or unassigned
var isAssignedToCurrentPlayer = self.assignedPlayerId === -1 || self.assignedPlayerId === currentPlayer.playerId;
self.isNearPlayer = distance < 120 && isAssignedToCurrentPlayer;
}
// Show interaction hint when player gets close
if (!self.lastNearPlayer && self.isNearPlayer) {
interactionText.alpha = 1;
tween(interactionText, {
y: 180,
alpha: 0.9
}, {
duration: 300
});
}
// Hide interaction hint when player moves away
if (self.lastNearPlayer && !self.isNearPlayer) {
tween(interactionText, {
y: 170,
alpha: 0
}, {
duration: 300
});
self.isActive = false;
}
};
self.down = function (x, y, obj) {
if (currentPlayer && currentPlayer.isAlive && !currentPlayer.isImpostor) {
var distance = Math.sqrt(Math.pow(currentPlayer.x - self.x, 2) + Math.pow(currentPlayer.y - self.y, 2));
// Only allow interaction if task is assigned to current player or unassigned
var isAssignedToCurrentPlayer = self.assignedPlayerId === -1 || self.assignedPlayerId === currentPlayer.playerId;
if (distance < 120 && !self.isCompleted && isAssignedToCurrentPlayer) {
if (!self.isActive) {
self.startTask();
} else {
// Handle wire connection clicks
var localX = x - self.x;
var localY = y - self.y;
// Check if clicking on a connector
for (var i = 0; i < self.connectors.length; i++) {
var connector = self.connectors[i];
var connectorDistance = Math.sqrt(Math.pow(localX - connector.x, 2) + Math.pow(localY - connector.y, 2));
if (connectorDistance < 25 && connector.isSource && !connector.isConnected) {
// Find matching target connector
for (var j = 0; j < self.connectors.length; j++) {
var targetConnector = self.connectors[j];
if (!targetConnector.isSource && targetConnector.wireIndex === connector.wireIndex) {
self.connectWire(connector.wireIndex, targetConnector.wireIndex);
break;
}
}
break;
}
}
}
}
}
};
return self;
});
var Camera = Container.expand(function (x, y, id, viewAngle) {
var self = Container.call(this);
self.cameraId = id || 0;
self.x = x;
self.y = y;
self.viewAngle = viewAngle || 0;
self.detectionRange = 200;
self.isActive = true;
self.playersInView = [];
// Camera graphics
var cameraGraphics = self.attachAsset('camera', {
anchorX: 0.5,
anchorY: 0.5
});
cameraGraphics.rotation = self.viewAngle;
// Camera name text
var cameraNameText = new Text2('CAM ' + (self.cameraId + 1), {
size: 16,
fill: 0xFFFFFF
});
cameraNameText.anchor.set(0.5, 0);
cameraNameText.y = -30;
self.addChild(cameraNameText);
// Detection light indicator
var detectionLight = self.attachAsset('cableConnector', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 0.5,
scaleY: 0.5
});
detectionLight.x = 0;
detectionLight.y = 25;
detectionLight.tint = 0x00FF00;
self.update = function () {
if (!self.isActive) return;
// Clear previous detections
self.playersInView = [];
// Check for players in camera view
if (players && players.length > 0) {
for (var i = 0; i < players.length; i++) {
var player = players[i];
if (!player.isAlive) continue;
var distance = Math.sqrt(Math.pow(player.x - self.x, 2) + Math.pow(player.y - self.y, 2));
if (distance <= self.detectionRange) {
// Check if player is within camera's field of view
var angleToPlayer = Math.atan2(player.y - self.y, player.x - self.x);
var angleDiff = Math.abs(angleToPlayer - self.viewAngle);
// Normalize angle difference
if (angleDiff > Math.PI) {
angleDiff = 2 * Math.PI - angleDiff;
}
// Field of view is 90 degrees (π/2 radians)
if (angleDiff <= Math.PI / 4) {
self.playersInView.push(player);
}
}
}
}
// Update detection light based on players in view
if (self.playersInView.length > 0) {
detectionLight.tint = 0xFF0000; // Red when detecting players
if (LK.ticks % 30 < 15) {
detectionLight.alpha = 1.0;
} else {
detectionLight.alpha = 0.5;
}
} else {
detectionLight.tint = 0x00FF00; // Green when no detection
detectionLight.alpha = 1.0;
}
};
return self;
});
var Door = Container.expand(function (x, y, id, orientation) {
var self = Container.call(this);
self.doorId = id || 0;
self.x = x;
self.y = y;
self.orientation = orientation || 0; // 0 = horizontal, 1 = vertical
self.isOpen = true;
self.closeTimer = 0;
self.closeDuration = 10000; // 10 seconds
self.isNearPlayer = false;
self.lastNearPlayer = false;
// Door graphics
var doorGraphics = self.attachAsset('door', {
anchorX: 0.5,
anchorY: 0.5
});
if (self.orientation === 1) {
doorGraphics.rotation = Math.PI / 2;
}
// Door status indicator
var statusText = new Text2('OPEN', {
size: 14,
fill: 0x00FF00
});
statusText.anchor.set(0.5, 0);
statusText.y = -40;
self.addChild(statusText);
// Visual indicator for impostors
var impostorIndicator = self.attachAsset('doorButton', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 0.3,
scaleY: 0.3
});
impostorIndicator.y = 20;
impostorIndicator.alpha = 0;
impostorIndicator.tint = 0xFF0000;
self.close = function () {
if (self.isOpen) {
self.isOpen = false;
self.closeTimer = self.closeDuration;
// Change graphics
doorGraphics.destroy();
doorGraphics = self.attachAsset('doorClosed', {
anchorX: 0.5,
anchorY: 0.5
});
if (self.orientation === 1) {
doorGraphics.rotation = Math.PI / 2;
}
// Update status
statusText.setText('CLOSED');
statusText.tint = 0xFF0000;
// Play sound
LK.getSound('doorClose').play();
// Flash effect
tween(doorGraphics, {
tint: 0xFF0000,
scaleX: 1.2,
scaleY: 1.2
}, {
duration: 200,
onFinish: function onFinish() {
tween(doorGraphics, {
tint: 0xFFFFFF,
scaleX: 1.0,
scaleY: 1.0
}, {
duration: 200
});
}
});
}
};
self.open = function () {
if (!self.isOpen) {
self.isOpen = true;
self.closeTimer = 0;
// Change graphics
doorGraphics.destroy();
doorGraphics = self.attachAsset('door', {
anchorX: 0.5,
anchorY: 0.5
});
if (self.orientation === 1) {
doorGraphics.rotation = Math.PI / 2;
}
// Update status
statusText.setText('OPEN');
statusText.tint = 0x00FF00;
// Play sound
LK.getSound('doorOpen').play();
// Flash effect
tween(doorGraphics, {
tint: 0x00FF00,
scaleX: 1.2,
scaleY: 1.2
}, {
duration: 200,
onFinish: function onFinish() {
tween(doorGraphics, {
tint: 0xFFFFFF,
scaleX: 1.0,
scaleY: 1.0
}, {
duration: 200
});
}
});
}
};
self.update = function () {
// Auto-open after timer expires
if (!self.isOpen && self.closeTimer > 0) {
self.closeTimer -= 16.67; // Roughly 1 frame at 60fps
if (self.closeTimer <= 0) {
self.open();
}
}
// Check if impostor player is near
self.lastNearPlayer = self.isNearPlayer;
self.isNearPlayer = false;
if (currentPlayer && currentPlayer.isAlive && currentPlayer.isImpostor) {
var distance = Math.sqrt(Math.pow(currentPlayer.x - self.x, 2) + Math.pow(currentPlayer.y - self.y, 2));
self.isNearPlayer = distance < 100;
}
// Show impostor indicator when impostor is near
if (currentPlayer && currentPlayer.isImpostor && self.isNearPlayer) {
impostorIndicator.alpha = 0.8;
if (LK.ticks % 30 < 15) {
impostorIndicator.alpha = 1.0;
} else {
impostorIndicator.alpha = 0.6;
}
} else {
impostorIndicator.alpha = 0;
}
// Update timer display
if (!self.isOpen && self.closeTimer > 0) {
var secondsLeft = Math.ceil(self.closeTimer / 1000);
statusText.setText('CLOSED (' + secondsLeft + 's)');
}
};
self.down = function (x, y, obj) {
if (currentPlayer && currentPlayer.isAlive && currentPlayer.isImpostor) {
var distance = Math.sqrt(Math.pow(currentPlayer.x - self.x, 2) + Math.pow(currentPlayer.y - self.y, 2));
if (distance < 100) {
if (self.isOpen) {
self.close();
} else {
self.open();
}
}
}
};
// Check if door blocks movement
self.blocksMovement = function (x, y, width, height) {
// If door is open, it doesn't block movement
if (self.isOpen) return false;
var playerLeft = x - width / 2;
var playerRight = x + width / 2;
var playerTop = y - height / 2;
var playerBottom = y + height / 2;
var doorLeft = self.x - 20;
var doorRight = self.x + 20;
var doorTop = self.y - 60;
var doorBottom = self.y + 60;
if (self.orientation === 1) {
// Vertical door
doorLeft = self.x - 60;
doorRight = self.x + 60;
doorTop = self.y - 20;
doorBottom = self.y + 20;
}
// Check if player rectangle intersects with door rectangle
return playerLeft < doorRight && playerRight > doorLeft && playerTop < doorBottom && playerBottom > doorTop;
};
return self;
});
var Ghost = Container.expand(function (deadPlayer) {
var self = Container.call(this);
self.x = deadPlayer.x;
self.y = deadPlayer.y;
self.targetX = deadPlayer.x;
self.targetY = deadPlayer.y;
self.speed = 1.5; // Slower than living players
self.deadPlayerId = deadPlayer.playerId;
// Create ghost graphics based on the dead player
var ghostGraphics = self.attachAsset(deadPlayer.isImpostor ? 'impostor' : 'crewmate', {
anchorX: 0.5,
anchorY: 0.5
});
ghostGraphics.tint = 0xAAAAFF; // Ghostly blue-white tint
ghostGraphics.alpha = 0.6; // Semi-transparent
// Add floating animation
self.floatOffset = 0;
self.floatSpeed = 0.05;
// Ghost moves randomly around the ship
self.wanderTimer = 0;
self.wanderInterval = 3000; // Change direction every 3 seconds
self.update = function () {
// Floating animation
self.floatOffset += self.floatSpeed;
ghostGraphics.y = Math.sin(self.floatOffset) * 5;
// Pulsing alpha effect
if (LK.ticks % 120 < 60) {
ghostGraphics.alpha = 0.6;
} else {
ghostGraphics.alpha = 0.3;
}
// Wandering behavior
self.wanderTimer += 16.67;
if (self.wanderTimer >= self.wanderInterval) {
self.wanderTimer = 0;
// Pick a random location to wander to
self.targetX = 300 + Math.random() * 1400;
self.targetY = 300 + Math.random() * 2000;
}
// Move towards target (ghosts can pass through walls)
var dx = self.targetX - self.x;
var dy = self.targetY - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance > 10) {
self.x += dx / distance * self.speed;
self.y += dy / distance * self.speed;
}
// Phase in and out randomly
if (Math.random() < 0.01) {
tween(ghostGraphics, {
alpha: 0.1,
scaleX: 1.2,
scaleY: 1.2
}, {
duration: 500,
onFinish: function onFinish() {
tween(ghostGraphics, {
alpha: 0.6,
scaleX: 1.0,
scaleY: 1.0
}, {
duration: 500
});
}
});
}
};
return self;
});
var Player = Container.expand(function (isImpostor, color, id) {
var self = Container.call(this);
self.isImpostor = isImpostor || false;
self.playerColor = color || 0x00ff00;
self.playerId = id || 0;
self.isAlive = true;
self.speed = 3;
self.targetX = 0;
self.targetY = 0;
self.tasksCompleted = 0;
self.maxTasks = 5;
self.canVote = true;
self.lastKillTime = 0;
self.killCooldown = 30000; // 30 seconds
self.currentTask = null; // Track current task being worked on
self.taskCompletionTimer = 0; // Timer for task completion
self.assignedTasks = []; // Individual tasks assigned to this player
var playerGraphics = self.attachAsset(self.isImpostor ? 'impostor' : 'crewmate', {
anchorX: 0.5,
anchorY: 0.5
});
if (!self.isImpostor) {
playerGraphics.tint = self.playerColor;
}
self.moveTo = function (x, y) {
var playerSize = 80; // Player width/height
// Only set target if destination is not inside a wall
if (!checkWallCollision(x, y, playerSize, playerSize)) {
self.targetX = x;
self.targetY = y;
} else {
// Keep current position as target if new position would collide
self.targetX = self.x;
self.targetY = self.y;
}
};
self.eliminate = function () {
if (self.isAlive) {
self.isAlive = false;
self.alpha = 0.3;
LK.getSound('eliminate').play();
// Create ghost effect with dramatic animation
tween(self, {
alpha: 0.1,
scaleX: 1.5,
scaleY: 1.5,
tint: 0xFFFFFF
}, {
duration: 1000,
onFinish: function onFinish() {
// Create ghost entity
var ghost = new Ghost(self);
ghosts.push(ghost);
game.addChild(ghost);
// Ghost spawn effect
ghost.alpha = 0;
tween(ghost, {
alpha: 0.6,
scaleX: 1.2,
scaleY: 1.2
}, {
duration: 800,
easing: tween.easeOut,
onFinish: function onFinish() {
tween(ghost, {
scaleX: 1.0,
scaleY: 1.0
}, {
duration: 400
});
}
});
}
});
}
};
self.completeTask = function () {
if (!self.isImpostor && self.isAlive) {
self.tasksCompleted++;
LK.getSound('taskComplete').play();
return true;
}
return false;
};
self.canKill = function () {
return self.isImpostor && self.isAlive && LK.ticks - self.lastKillTime > self.killCooldown;
};
self.kill = function (target) {
if (self.canKill() && target.isAlive && !target.isImpostor) {
target.eliminate();
self.lastKillTime = LK.ticks;
return true;
}
return false;
};
self.update = function () {
if (!self.isAlive) return;
// Move towards target
var dx = self.targetX - self.x;
var dy = self.targetY - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance > 5) {
var newX = self.x + dx / distance * self.speed;
var newY = self.y + dy / distance * self.speed;
var playerSize = 80; // Player width/height
// Check collision before moving
if (!checkWallCollision(newX, newY, playerSize, playerSize)) {
self.x = newX;
self.y = newY;
} else {
// Try moving only on X axis
if (!checkWallCollision(newX, self.y, playerSize, playerSize)) {
self.x = newX;
} else if (!checkWallCollision(self.x, newY, playerSize, playerSize)) {
// Try moving only on Y axis
self.y = newY;
}
}
}
};
return self;
});
var SecurityRoom = Container.expand(function (x, y) {
var self = Container.call(this);
self.x = x;
self.y = y;
self.isActive = false;
self.currentCameraIndex = 0;
self.isNearPlayer = false;
self.lastNearPlayer = false;
// Main security console
var console = self.attachAsset('cameraFrame', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 2.5,
scaleY: 2.0
});
console.tint = 0x222222;
// Monitor screens (3x2 grid)
self.monitors = [];
var monitorPositions = [{
x: -150,
y: -80
}, {
x: 0,
y: -80
}, {
x: 150,
y: -80
}, {
x: -150,
y: 80
}, {
x: 0,
y: 80
}, {
x: 150,
y: 80
}];
for (var i = 0; i < 6; i++) {
var monitorFrame = self.attachAsset('cameraFrame', {
anchorX: 0.5,
anchorY: 0.5
});
monitorFrame.x = monitorPositions[i].x;
monitorFrame.y = monitorPositions[i].y;
monitorFrame.tint = 0x333333;
var monitorScreen = self.attachAsset('cameraView', {
anchorX: 0.5,
anchorY: 0.5
});
monitorScreen.x = monitorPositions[i].x;
monitorScreen.y = monitorPositions[i].y;
// Camera label
var cameraLabel = new Text2('CAM ' + (i + 1), {
size: 18,
fill: 0x00FFFF
});
cameraLabel.anchor.set(0.5, 0);
cameraLabel.x = monitorPositions[i].x;
cameraLabel.y = monitorPositions[i].y - 75;
self.addChild(cameraLabel);
// Player count display
var playerCountDisplay = new Text2('0 PLAYERS', {
size: 14,
fill: 0xFFFFFF
});
playerCountDisplay.anchor.set(0.5, 0.5);
playerCountDisplay.x = monitorPositions[i].x;
playerCountDisplay.y = monitorPositions[i].y;
self.addChild(playerCountDisplay);
self.monitors.push({
frame: monitorFrame,
screen: monitorScreen,
label: cameraLabel,
playerCount: playerCountDisplay
});
}
// Security room title
var titleText = new Text2('SECURITY ROOM', {
size: 32,
fill: 0x00FFFF
});
titleText.anchor.set(0.5, 0);
titleText.y = -200;
self.addChild(titleText);
// Interaction hint
var interactionText = new Text2('View Security Cameras', {
size: 20,
fill: 0x00FFFF
});
interactionText.anchor.set(0.5, 0);
interactionText.y = 200;
interactionText.alpha = 0;
self.addChild(interactionText);
// Instructions when active
var instructionText = new Text2('Click monitors to switch cameras', {
size: 16,
fill: 0xFFFF00
});
instructionText.anchor.set(0.5, 0);
instructionText.y = 220;
instructionText.alpha = 0;
self.addChild(instructionText);
self.activate = function () {
if (!self.isActive) {
self.isActive = true;
// Show activation animation
tween(console, {
scaleX: 2.7,
scaleY: 2.2,
tint: 0x004400
}, {
duration: 300,
onFinish: function onFinish() {
tween(console, {
scaleX: 2.5,
scaleY: 2.0,
tint: 0x222222
}, {
duration: 200
});
}
});
// Show instructions
instructionText.alpha = 1;
}
};
self.deactivate = function () {
if (self.isActive) {
self.isActive = false;
instructionText.alpha = 0;
// Reset all monitors
for (var i = 0; i < self.monitors.length; i++) {
self.monitors[i].screen.tint = 0x000000;
self.monitors[i].frame.tint = 0x333333;
}
}
};
self.update = function () {
// Check if player is near
self.lastNearPlayer = self.isNearPlayer;
self.isNearPlayer = false;
if (currentPlayer && currentPlayer.isAlive) {
var distance = Math.sqrt(Math.pow(currentPlayer.x - self.x, 2) + Math.pow(currentPlayer.y - self.y, 2));
self.isNearPlayer = distance < 150;
}
// Show interaction hint when player gets close
if (!self.lastNearPlayer && self.isNearPlayer) {
interactionText.alpha = 1;
tween(interactionText, {
y: 210,
alpha: 0.8
}, {
duration: 300
});
}
// Hide interaction hint when player moves away
if (self.lastNearPlayer && !self.isNearPlayer) {
tween(interactionText, {
y: 200,
alpha: 0
}, {
duration: 300
});
self.deactivate();
}
// Update camera feeds if active
if (self.isActive && cameras && cameras.length > 0) {
for (var i = 0; i < Math.min(self.monitors.length, cameras.length); i++) {
var camera = cameras[i];
var monitor = self.monitors[i];
if (camera && monitor) {
// Update player count display
var playerCount = camera.playersInView.length;
monitor.playerCount.setText(playerCount + ' PLAYERS');
// Update monitor colors based on activity
if (playerCount > 0) {
// Red tint when players detected
monitor.screen.tint = 0x440000;
monitor.frame.tint = 0xFF0000;
// Flash effect for active cameras
if (LK.ticks % 60 < 30) {
monitor.frame.alpha = 1.0;
} else {
monitor.frame.alpha = 0.7;
}
} else {
// Green tint when no players
monitor.screen.tint = 0x004400;
monitor.frame.tint = 0x333333;
monitor.frame.alpha = 1.0;
}
}
}
}
};
self.down = function (x, y, obj) {
if (currentPlayer && currentPlayer.isAlive) {
var distance = Math.sqrt(Math.pow(currentPlayer.x - self.x, 2) + Math.pow(currentPlayer.y - self.y, 2));
if (distance < 150) {
if (!self.isActive) {
self.activate();
} else {
// Check if clicking on a specific monitor
var localX = x - self.x;
var localY = y - self.y;
for (var i = 0; i < self.monitors.length; i++) {
var monitor = self.monitors[i];
var monitorX = monitor.frame.x;
var monitorY = monitor.frame.y;
var monitorDistance = Math.sqrt(Math.pow(localX - monitorX, 2) + Math.pow(localY - monitorY, 2));
if (monitorDistance < 75) {
// Highlight selected monitor
monitor.frame.tint = 0x0000FF;
// Reset other monitors
for (var j = 0; j < self.monitors.length; j++) {
if (j !== i) {
var otherMonitor = self.monitors[j];
if (cameras[j] && cameras[j].playersInView.length > 0) {
otherMonitor.frame.tint = 0xFF0000;
} else {
otherMonitor.frame.tint = 0x333333;
}
}
}
break;
}
}
}
}
}
};
return self;
});
var Sewer = Container.expand(function (x, y, id, targetSewerId) {
var self = Container.call(this);
self.sewerId = id || 0;
self.targetSewerId = targetSewerId || 0;
self.x = x;
self.y = y;
self.isNearPlayer = false;
self.lastNearPlayer = false;
self.teleportCooldown = 0;
self.teleportCooldownTime = 2000; // 2 seconds
// Sewer entrance graphics
var sewerGraphics = self.attachAsset('sewerEntrance', {
anchorX: 0.5,
anchorY: 0.5
});
sewerGraphics.tint = 0x654321; // Brown color for sewer
// Sewer name text
var sewerNameText = new Text2('SEWER ' + (self.sewerId + 1), {
size: 18,
fill: 0xFFFFFF
});
sewerNameText.anchor.set(0.5, 0);
sewerNameText.y = -50;
self.addChild(sewerNameText);
// Interaction hint text
var interactionText = new Text2('Enter Sewer (Impostors Only)', {
size: 16,
fill: 0xFF0000
});
interactionText.anchor.set(0.5, 0);
interactionText.y = 50;
interactionText.alpha = 0;
self.addChild(interactionText);
// Teleport cooldown text
var cooldownText = new Text2('', {
size: 14,
fill: 0xFF0000
});
cooldownText.anchor.set(0.5, 0);
cooldownText.y = 70;
cooldownText.alpha = 0;
self.addChild(cooldownText);
self.teleportPlayer = function (player) {
if (self.teleportCooldown > 0) return false;
// Find target sewer
var targetSewer = null;
for (var i = 0; i < sewers.length; i++) {
if (sewers[i].sewerId === self.targetSewerId) {
targetSewer = sewers[i];
break;
}
}
if (targetSewer) {
// Play teleport sound
LK.getSound('teleport').play();
// Teleport player to target sewer
player.x = targetSewer.x;
player.y = targetSewer.y;
player.targetX = targetSewer.x;
player.targetY = targetSewer.y;
// Set cooldown for both sewers
self.teleportCooldown = self.teleportCooldownTime;
targetSewer.teleportCooldown = targetSewer.teleportCooldownTime;
// Flash effect on both sewers
tween(sewerGraphics, {
tint: 0x00FFFF,
scaleX: 1.3,
scaleY: 1.3
}, {
duration: 300,
onFinish: function onFinish() {
tween(sewerGraphics, {
tint: 0x654321,
scaleX: 1.0,
scaleY: 1.0
}, {
duration: 300
});
}
});
if (targetSewer.children && targetSewer.children.length > 0) {
var targetGraphics = targetSewer.children[0];
tween(targetGraphics, {
tint: 0x00FFFF,
scaleX: 1.3,
scaleY: 1.3
}, {
duration: 300,
onFinish: function onFinish() {
tween(targetGraphics, {
tint: 0x654321,
scaleX: 1.0,
scaleY: 1.0
}, {
duration: 300
});
}
});
}
return true;
}
return false;
};
self.update = function () {
// Update teleport cooldown
if (self.teleportCooldown > 0) {
self.teleportCooldown -= 16.67; // Roughly 1 frame at 60fps
var secondsLeft = Math.ceil(self.teleportCooldown / 1000);
cooldownText.setText('Cooldown: ' + secondsLeft + 's');
cooldownText.alpha = 1;
} else {
cooldownText.alpha = 0;
}
// Check if player is near
self.lastNearPlayer = self.isNearPlayer;
self.isNearPlayer = false;
if (currentPlayer && currentPlayer.isAlive && currentPlayer.isImpostor) {
var distance = Math.sqrt(Math.pow(currentPlayer.x - self.x, 2) + Math.pow(currentPlayer.y - self.y, 2));
self.isNearPlayer = distance < 100;
}
// Show interaction hint when player gets close
if (!self.lastNearPlayer && self.isNearPlayer) {
interactionText.alpha = 1;
tween(interactionText, {
y: 60,
alpha: 0.8
}, {
duration: 300
});
}
// Hide interaction hint when player moves away
if (self.lastNearPlayer && !self.isNearPlayer) {
tween(interactionText, {
y: 50,
alpha: 0
}, {
duration: 300
});
}
// Pulsing animation for sewer entrance
if (LK.ticks % 120 < 60) {
sewerGraphics.alpha = 1.0;
} else {
sewerGraphics.alpha = 0.8;
}
};
self.down = function (x, y, obj) {
if (currentPlayer && currentPlayer.isAlive && currentPlayer.isImpostor && gamePhase === 'playing') {
var distance = Math.sqrt(Math.pow(currentPlayer.x - self.x, 2) + Math.pow(currentPlayer.y - self.y, 2));
if (distance < 100 && self.teleportCooldown <= 0) {
self.teleportPlayer(currentPlayer);
}
}
};
return self;
});
var Task = Container.expand(function (x, y, id, taskType, assignedPlayerId) {
var self = Container.call(this);
self.taskId = id || 0;
self.isCompleted = false;
self.x = x;
self.y = y;
self.taskType = taskType || 'basic';
self.completionProgress = 0;
self.requiredClicks = 1;
self.completionTime = 0;
self.isBeingCompleted = false;
self.interactionTimer = 0;
self.taskStarted = false;
self.isNearPlayer = false;
self.lastNearPlayer = false;
self.assignedPlayerId = assignedPlayerId || -1; // Which player this task is assigned to (-1 = any player)
// Set task properties based on type
switch (self.taskType) {
case 'electrical':
self.requiredClicks = 3;
self.assetName = 'electricalTask';
self.taskName = 'Fix Wiring';
self.taskDescription = 'Connect the wires in the correct order';
break;
case 'engine':
self.requiredClicks = 5;
self.assetName = 'engineTask';
self.taskName = 'Fuel Engines';
self.taskDescription = 'Fill the fuel tanks';
break;
case 'reactor':
self.requiredClicks = 7;
self.assetName = 'reactorTask';
self.taskName = 'Stabilize Reactor';
self.taskDescription = 'Balance the reactor core';
break;
case 'navigation':
self.requiredClicks = 4;
self.assetName = 'navigationTask';
self.taskName = 'Chart Course';
self.taskDescription = 'Set navigation coordinates';
break;
case 'medical':
self.requiredClicks = 6;
self.assetName = 'medicalTask';
self.taskName = 'Submit Scan';
self.taskDescription = 'Complete medical scan';
break;
case 'cables':
self.requiredClicks = 4;
self.assetName = 'electricalTask';
self.taskName = 'Fix Wiring';
self.taskDescription = 'Connect the colored wires';
break;
default:
self.requiredClicks = 1;
self.assetName = 'task';
self.taskName = 'Basic Task';
self.taskDescription = 'Complete basic task';
break;
}
var taskGraphics = self.attachAsset(self.assetName, {
anchorX: 0.5,
anchorY: 0.5
});
// Add task name text
var taskNameText = new Text2(self.taskName, {
size: 24,
fill: 0xFFFFFF
});
taskNameText.anchor.set(0.5, 0);
taskNameText.y = -50;
self.addChild(taskNameText);
// Add interaction hint text (initially hidden)
var interactionText = new Text2('Hold to complete', {
size: 20,
fill: 0x00FFFF
});
interactionText.anchor.set(0.5, 0);
interactionText.y = 50;
interactionText.alpha = 0;
self.addChild(interactionText);
self.complete = function () {
if (!self.isCompleted) {
self.isCompleted = true;
taskGraphics.destroy();
taskNameText.destroy();
if (interactionText) {
interactionText.destroy();
}
if (self.lastProgressText) {
self.lastProgressText.destroy();
}
var completedGraphics = self.attachAsset('taskCompleted', {
anchorX: 0.5,
anchorY: 0.5
});
var completedText = new Text2('Complete', {
size: 20,
fill: 0x00FF00
});
completedText.anchor.set(0.5, 0);
completedText.y = -40;
self.addChild(completedText);
return true;
}
return false;
};
self.startTask = function () {
if (!self.taskStarted && !self.isCompleted) {
self.taskStarted = true;
self.interactionTimer = 0;
// Visual feedback for task start
tween(taskGraphics, {
scaleX: 1.2,
scaleY: 1.2,
tint: 0x00FFFF
}, {
duration: 200,
onFinish: function onFinish() {
tween(taskGraphics, {
scaleX: 1.0,
scaleY: 1.0,
tint: 0xFFFFFF
}, {
duration: 200
});
}
});
// Show progress for multi-step tasks
if (self.requiredClicks > 1) {
var progressText = new Text2(self.completionProgress + '/' + self.requiredClicks, {
size: 32,
fill: 0xFFFF00
});
progressText.anchor.set(0.5, 0);
progressText.y = 70;
self.addChild(progressText);
// Remove old progress text
if (self.lastProgressText) {
self.lastProgressText.destroy();
}
self.lastProgressText = progressText;
}
}
};
self.update = function () {
if (self.isCompleted) return;
// Check if player is near
self.lastNearPlayer = self.isNearPlayer;
self.isNearPlayer = false;
if (currentPlayer && currentPlayer.isAlive && !currentPlayer.isImpostor) {
var distance = Math.sqrt(Math.pow(currentPlayer.x - self.x, 2) + Math.pow(currentPlayer.y - self.y, 2));
// Only show task if it's assigned to current player or unassigned
var isAssignedToCurrentPlayer = self.assignedPlayerId === -1 || self.assignedPlayerId === currentPlayer.playerId;
self.isNearPlayer = distance < 100 && isAssignedToCurrentPlayer;
}
// Show interaction hint when player gets close
if (!self.lastNearPlayer && self.isNearPlayer) {
interactionText.alpha = 1;
tween(interactionText, {
y: 60,
alpha: 0.8
}, {
duration: 300
});
}
// Hide interaction hint when player moves away
if (self.lastNearPlayer && !self.isNearPlayer) {
tween(interactionText, {
y: 50,
alpha: 0
}, {
duration: 300
});
self.taskStarted = false;
self.interactionTimer = 0;
}
// Update interaction timer if task is being worked on
if (self.taskStarted && self.isNearPlayer) {
self.interactionTimer += 16.67; // Roughly 1 frame at 60fps
// Visual progress indicator
var progressPercent = Math.min(self.interactionTimer / 2000, 1.0); // 2 seconds per step
if (progressPercent < 1.0) {
taskGraphics.alpha = 0.5 + progressPercent * 0.5;
} else {
// Complete this step
self.completionProgress++;
self.interactionTimer = 0;
taskGraphics.alpha = 1.0;
// Flash effect for progress
tween(taskGraphics, {
alpha: 0.3,
tint: 0x00FF00
}, {
duration: 150,
onFinish: function onFinish() {
tween(taskGraphics, {
alpha: 1.0,
tint: 0xFFFFFF
}, {
duration: 150
});
}
});
// Update progress text
if (self.requiredClicks > 1 && self.lastProgressText) {
self.lastProgressText.setText(self.completionProgress + '/' + self.requiredClicks);
}
// Check if task is complete
if (self.completionProgress >= self.requiredClicks) {
self.complete();
if (currentPlayer) {
currentPlayer.completeTask();
}
completedTasks++;
updateTaskProgress();
} else {
// Reset for next step
self.taskStarted = false;
}
}
}
};
self.down = function (x, y, obj) {
if (currentPlayer && currentPlayer.isAlive && !currentPlayer.isImpostor) {
var distance = Math.sqrt(Math.pow(currentPlayer.x - self.x, 2) + Math.pow(currentPlayer.y - self.y, 2));
// Only allow interaction if task is assigned to current player or unassigned
var isAssignedToCurrentPlayer = self.assignedPlayerId === -1 || self.assignedPlayerId === currentPlayer.playerId;
if (distance < 100 && !self.isCompleted && isAssignedToCurrentPlayer) {
self.startTask();
}
}
};
return self;
});
var VoteButton = Container.expand(function (playerId, x, y) {
var self = Container.call(this);
self.playerId = playerId;
self.x = x;
self.y = y;
self.votes = 0;
var buttonGraphics = self.attachAsset('voteButton', {
anchorX: 0.5,
anchorY: 0.5
});
var voteText = new Text2('Vote Player ' + playerId, {
size: 40,
fill: 0xFFFFFF
});
voteText.anchor.set(0.5, 0.5);
self.addChild(voteText);
self.down = function (x, y, obj) {
if (votingPhase && currentPlayer && currentPlayer.canVote) {
self.votes++;
currentPlayer.canVote = false;
LK.getSound('vote').play();
voteText.setText('Votes: ' + self.votes);
// Check if voting is complete
var alivePlayers = players.filter(function (p) {
return p.isAlive;
});
var totalVotes = voteButtons.reduce(function (sum, button) {
return sum + button.votes;
}, 0);
if (totalVotes >= alivePlayers.length) {
endVoting();
}
}
};
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0x1a1a2e
});
/****
* Game Code
****/
var players = [];
var tasks = [];
var taskCategories = {
critical: [],
// High priority tasks
normal: [],
// Standard tasks
maintenance: [] // Lower priority tasks
};
var taskDifficulty = {
electrical: 3,
cables: 4,
reactor: 5,
navigation: 2,
medical: 3,
engine: 4
};
var currentPlayer = null;
var totalTasks = 0; // Will be calculated based on actual crew members
var completedTasks = 0;
var impostorCount = 2;
var votingPhase = false;
var voteButtons = [];
var gamePhase = 'roleSelection'; // 'roleSelection', 'playing', 'voting', 'gameOver'
var meetingCooldown = 0;
var meetingCooldownTime = 15000; // 15 seconds
var roleSelectionComplete = false;
var ghosts = []; // Array to track ghost entities
var killButton = null;
var killTarget = null;
var sabotageButton = null;
var sabotageTarget = null;
var sabotageElectricityButton = null;
var sabotageOxygenButton = null;
var sabotageDoorsButton = null;
var sabotageReactorButton = null;
var sabotageConnectionsButton = null;
var reportButton = null;
var reportTarget = null;
var taskButton = null;
var currentTaskTarget = null;
var cameraButton = null;
// UI Elements
var taskProgressText = new Text2('Tasks: 0/' + totalTasks, {
size: 60,
fill: 0xFFFFFF
});
taskProgressText.anchor.set(0.5, 0);
LK.gui.top.addChild(taskProgressText);
// Task completion UI
var taskCompletionText = new Text2('', {
size: 40,
fill: 0x00FF00
});
taskCompletionText.anchor.set(0.5, 0.5);
taskCompletionText.alpha = 0;
LK.gui.center.addChild(taskCompletionText);
// Current task indicator
var currentTaskText = new Text2('', {
size: 30,
fill: 0xFFFF00
});
currentTaskText.anchor.set(0.5, 1);
currentTaskText.y = -50;
currentTaskText.alpha = 0;
LK.gui.center.addChild(currentTaskText);
// Mission panel UI
var missionPanel = LK.getAsset('cameraFrame', {
anchorX: 0,
anchorY: 0,
scaleX: 2.5,
scaleY: 3.0,
x: 50,
y: 200
});
missionPanel.tint = 0x222222;
missionPanel.alpha = 0.8;
missionPanel.visible = false;
LK.gui.topLeft.addChild(missionPanel);
var missionPanelTitle = new Text2('MISSIONS', {
size: 32,
fill: 0x00FFFF
});
missionPanelTitle.anchor.set(0.5, 0);
missionPanelTitle.x = 250;
missionPanelTitle.y = 220;
missionPanelTitle.visible = false;
LK.gui.topLeft.addChild(missionPanelTitle);
var missionTaskTexts = [];
var missionToggleButton = LK.getAsset('taskButton', {
anchorX: 0,
anchorY: 0,
scaleX: 0.6,
scaleY: 0.6,
x: 120,
y: 200
});
missionToggleButton.tint = 0x00FFFF;
missionToggleButton.alpha = 0;
missionToggleButton.visible = false;
LK.gui.topLeft.addChild(missionToggleButton);
var missionToggleText = new Text2('MISSIONS', {
size: 20,
fill: 0xFFFFFF
});
missionToggleText.anchor.set(0.5, 0.5);
missionToggleText.x = 36;
missionToggleText.y = 36;
missionToggleButton.addChild(missionToggleText);
var missionPanelVisible = false;
// Mission panel toggle handler
missionToggleButton.down = function (x, y, obj) {
if (currentPlayer && currentPlayer.isAlive && !currentPlayer.isImpostor && gamePhase === 'playing') {
toggleMissionPanel();
}
};
// Camera system variables
var cameras = [];
var securityRoom = null;
// Door system variables
var doors = [];
var doorButton = null;
var currentDoorTarget = null;
// Sewer system variables
var sewers = [];
// Sabotage system variables
var activeSabotage = null;
var sabotageTimer = 0;
var sabotageEffect = null;
var sabotageIndicators = [];
var isSabotageActive = false;
var sabotageTypes = ['electricity', 'oxygen', 'reactor', 'doors', 'communications'];
// Progress Bar Elements
var progressBarBackground = LK.getAsset('wall', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 8,
scaleY: 0.5,
x: 0,
y: 80
});
progressBarBackground.tint = 0x333333;
LK.gui.top.addChild(progressBarBackground);
var progressBarFill = LK.getAsset('wall', {
anchorX: 0,
anchorY: 0.5,
scaleX: 0,
scaleY: 0.4,
x: -800,
y: 80
});
progressBarFill.tint = 0x00ff00;
LK.gui.top.addChild(progressBarFill);
// Sabotage effect overlay (initially hidden)
sabotageEffect = LK.getAsset('sabotageEffect', {
anchorX: 0.5,
anchorY: 0.5,
x: 1024,
y: 1366
});
sabotageEffect.alpha = 0;
sabotageEffect.visible = false;
game.addChild(sabotageEffect);
var phaseText = new Text2('Complete Tasks or Find Impostors!', {
size: 50,
fill: 0xFFFF00
});
phaseText.anchor.set(0.5, 0);
phaseText.y = 100;
LK.gui.top.addChild(phaseText);
var playerCountText = new Text2('', {
size: 40,
fill: 0xFFFFFF
});
playerCountText.anchor.set(0, 0);
LK.gui.topRight.addChild(playerCountText);
// Role selection UI
var roleSelectionTitle = new Text2('Choose Your Role', {
size: 80,
fill: 0xFFFFFF
});
roleSelectionTitle.anchor.set(0.5, 0.5);
roleSelectionTitle.x = 1024;
roleSelectionTitle.y = 800;
game.addChild(roleSelectionTitle);
var crewButton = LK.getAsset('voteButton', {
anchorX: 0.5,
anchorY: 0.5,
x: 700,
y: 1200,
scaleX: 1.5,
scaleY: 1.5
});
crewButton.tint = 0x00ff00;
game.addChild(crewButton);
var crewButtonText = new Text2('CREW MEMBER', {
size: 50,
fill: 0xFFFFFF
});
crewButtonText.anchor.set(0.5, 0.5);
crewButtonText.x = 700;
crewButtonText.y = 1200;
game.addChild(crewButtonText);
var impostorButton = LK.getAsset('voteButton', {
anchorX: 0.5,
anchorY: 0.5,
x: 1348,
y: 1200,
scaleX: 1.5,
scaleY: 1.5
});
impostorButton.tint = 0xff0000;
game.addChild(impostorButton);
var impostorButtonText = new Text2('IMPOSTOR', {
size: 50,
fill: 0xFFFFFF
});
impostorButtonText.anchor.set(0.5, 0.5);
impostorButtonText.x = 1348;
impostorButtonText.y = 1200;
game.addChild(impostorButtonText);
// Role selection instructions
var instructionText = new Text2('Choose your role and start the game!', {
size: 40,
fill: 0xFFFF00
});
instructionText.anchor.set(0.5, 0.5);
instructionText.x = 1024;
instructionText.y = 1400;
game.addChild(instructionText);
// Create spaceship walls
var walls = [];
function createWalls() {
// Outer perimeter walls - Extended ship dimensions
var topWall = game.addChild(LK.getAsset('wall', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 14,
x: 1024,
y: 150
}));
walls.push(topWall);
var bottomWall = game.addChild(LK.getAsset('wall', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 14,
x: 1024,
y: 2600
}));
walls.push(bottomWall);
var leftWall = game.addChild(LK.getAsset('wall', {
anchorX: 0.5,
anchorY: 0.5,
scaleY: 12,
x: 150,
y: 1366
}));
walls.push(leftWall);
var rightWall = game.addChild(LK.getAsset('wall', {
anchorX: 0.5,
anchorY: 0.5,
scaleY: 12,
x: 1900,
y: 1366
}));
walls.push(rightWall);
// Upper corridor walls
var upperLeftWall = game.addChild(LK.getAsset('wall', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 2,
x: 550,
y: 600
}));
walls.push(upperLeftWall);
var upperRightWall = game.addChild(LK.getAsset('wall', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 2,
x: 1500,
y: 600
}));
walls.push(upperRightWall);
// Central corridor walls
var centralTopWall = game.addChild(LK.getAsset('wall', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 4,
x: 1024,
y: 1000
}));
walls.push(centralTopWall);
var centralBottomWall = game.addChild(LK.getAsset('wall', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 4,
x: 1024,
y: 1700
}));
walls.push(centralBottomWall);
// Room dividers
var leftRoomWall = game.addChild(LK.getAsset('wall', {
anchorX: 0.5,
anchorY: 0.5,
scaleY: 3,
x: 700,
y: 1000
}));
walls.push(leftRoomWall);
var rightRoomWall = game.addChild(LK.getAsset('wall', {
anchorX: 0.5,
anchorY: 0.5,
scaleY: 3,
x: 1350,
y: 1000
}));
walls.push(rightRoomWall);
// Lower corridor walls
var lowerLeftWall = game.addChild(LK.getAsset('wall', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 2,
x: 550,
y: 2100
}));
walls.push(lowerLeftWall);
var lowerRightWall = game.addChild(LK.getAsset('wall', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 2,
x: 1500,
y: 2100
}));
walls.push(lowerRightWall);
// Engine room walls
var engineLeftWall = game.addChild(LK.getAsset('wall', {
anchorX: 0.5,
anchorY: 0.5,
scaleY: 2,
x: 400,
y: 1800
}));
walls.push(engineLeftWall);
var engineRightWall = game.addChild(LK.getAsset('wall', {
anchorX: 0.5,
anchorY: 0.5,
scaleY: 2,
x: 1650,
y: 1800
}));
walls.push(engineRightWall);
// Security room walls - expanded for camera monitoring
var securityWallTop = game.addChild(LK.getAsset('wall', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 3.5,
x: 1024,
y: 450
}));
walls.push(securityWallTop);
var securityWallLeft = game.addChild(LK.getAsset('wall', {
anchorX: 0.5,
anchorY: 0.5,
scaleY: 2.5,
x: 700,
y: 600
}));
walls.push(securityWallLeft);
var securityWallRight = game.addChild(LK.getAsset('wall', {
anchorX: 0.5,
anchorY: 0.5,
scaleY: 2.5,
x: 1350,
y: 600
}));
walls.push(securityWallRight);
// Medical bay walls
var medicalWall = game.addChild(LK.getAsset('wall', {
anchorX: 0.5,
anchorY: 0.5,
scaleY: 1.5,
x: 1200,
y: 1500
}));
walls.push(medicalWall);
// Reactor room walls
var reactorWall = game.addChild(LK.getAsset('wall', {
anchorX: 0.5,
anchorY: 0.5,
scaleY: 1.5,
x: 850,
y: 1500
}));
walls.push(reactorWall);
// Command Bridge - New room at top
var bridgeTopWall = game.addChild(LK.getAsset('wall', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 4,
x: 1024,
y: 250
}));
walls.push(bridgeTopWall);
var bridgeLeftWall = game.addChild(LK.getAsset('wall', {
anchorX: 0.5,
anchorY: 0.5,
scaleY: 1.5,
x: 700,
y: 350
}));
walls.push(bridgeLeftWall);
var bridgeRightWall = game.addChild(LK.getAsset('wall', {
anchorX: 0.5,
anchorY: 0.5,
scaleY: 1.5,
x: 1350,
y: 350
}));
walls.push(bridgeRightWall);
// Cargo Bay - New large room at bottom
var cargoTopWall = game.addChild(LK.getAsset('wall', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 6,
x: 1024,
y: 2200
}));
walls.push(cargoTopWall);
var cargoLeftWall = game.addChild(LK.getAsset('wall', {
anchorX: 0.5,
anchorY: 0.5,
scaleY: 2,
x: 500,
y: 2350
}));
walls.push(cargoLeftWall);
var cargoRightWall = game.addChild(LK.getAsset('wall', {
anchorX: 0.5,
anchorY: 0.5,
scaleY: 2,
x: 1550,
y: 2350
}));
walls.push(cargoRightWall);
// Extended Laboratory - Left side expansion
var labTopWall = game.addChild(LK.getAsset('wall', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 2,
x: 300,
y: 800
}));
walls.push(labTopWall);
var labBottomWall = game.addChild(LK.getAsset('wall', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 2,
x: 300,
y: 1200
}));
walls.push(labBottomWall);
var labRightWall = game.addChild(LK.getAsset('wall', {
anchorX: 0.5,
anchorY: 0.5,
scaleY: 2,
x: 450,
y: 1000
}));
walls.push(labRightWall);
// Extended Weapons Room - Right side expansion
var weaponsTopWall = game.addChild(LK.getAsset('wall', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 2,
x: 1750,
y: 800
}));
walls.push(weaponsTopWall);
var weaponsBottomWall = game.addChild(LK.getAsset('wall', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 2,
x: 1750,
y: 1200
}));
walls.push(weaponsBottomWall);
var weaponsLeftWall = game.addChild(LK.getAsset('wall', {
anchorX: 0.5,
anchorY: 0.5,
scaleY: 2,
x: 1600,
y: 1000
}));
walls.push(weaponsLeftWall);
// Communication Array - New room top right
var commTopWall = game.addChild(LK.getAsset('wall', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 2.5,
x: 1600,
y: 400
}));
walls.push(commTopWall);
var commBottomWall = game.addChild(LK.getAsset('wall', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 2.5,
x: 1600,
y: 650
}));
walls.push(commBottomWall);
var commLeftWall = game.addChild(LK.getAsset('wall', {
anchorX: 0.5,
anchorY: 0.5,
scaleY: 1.5,
x: 1400,
y: 525
}));
walls.push(commLeftWall);
// Maintenance Tunnels - New room bottom left
var maintTopWall = game.addChild(LK.getAsset('wall', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 2.5,
x: 400,
y: 1800
}));
walls.push(maintTopWall);
var maintBottomWall = game.addChild(LK.getAsset('wall', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 2.5,
x: 400,
y: 2050
}));
walls.push(maintBottomWall);
var maintRightWall = game.addChild(LK.getAsset('wall', {
anchorX: 0.5,
anchorY: 0.5,
scaleY: 1.5,
x: 600,
y: 1925
}));
walls.push(maintRightWall);
// Add spaceship floor tiles - Extended coverage
var _loop = function _loop() {
for (var floorY = 200; floorY < 2550; floorY += 100) {
var floorTile = game.addChild(LK.getAsset('shipFloor', {
anchorX: 0.5,
anchorY: 0.5,
x: floorX,
y: floorY
}));
game.setChildIndex(floorTile, 0);
}
};
for (var floorX = 200; floorX < 1900; floorX += 100) {
_loop();
}
// Add main corridor highlights - central hub
var mainCorridor = game.addChild(LK.getAsset('shipHall', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 8,
scaleY: 4,
x: 1024,
y: 1366
}));
game.setChildIndex(mainCorridor, 1);
// Add upper corridor - connects upper rooms
var upperCorridor = game.addChild(LK.getAsset('shipHall', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 6,
scaleY: 2,
x: 1024,
y: 700
}));
game.setChildIndex(upperCorridor, 1);
// Add lower corridor - connects lower rooms
var lowerCorridor = game.addChild(LK.getAsset('shipHall', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 6,
scaleY: 2,
x: 1024,
y: 2000
}));
game.setChildIndex(lowerCorridor, 1);
// Add left vertical corridor - connects left rooms
var leftVerticalCorridor = game.addChild(LK.getAsset('shipHall', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 2,
scaleY: 8,
x: 550,
y: 1366
}));
game.setChildIndex(leftVerticalCorridor, 1);
// Add right vertical corridor - connects right rooms
var rightVerticalCorridor = game.addChild(LK.getAsset('shipHall', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 2,
scaleY: 8,
x: 1500,
y: 1366
}));
game.setChildIndex(rightVerticalCorridor, 1);
// Add Command Bridge corridor
var bridgeCorridor = game.addChild(LK.getAsset('shipHall', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 4,
scaleY: 1.5,
x: 1024,
y: 350
}));
game.setChildIndex(bridgeCorridor, 1);
// Add Cargo Bay corridor
var cargoCorridor = game.addChild(LK.getAsset('shipHall', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 6,
scaleY: 2,
x: 1024,
y: 2350
}));
game.setChildIndex(cargoCorridor, 1);
// Add Laboratory corridor
var labCorridor = game.addChild(LK.getAsset('shipHall', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 1.5,
scaleY: 2,
x: 300,
y: 1000
}));
game.setChildIndex(labCorridor, 1);
// Add Weapons Room corridor
var weaponsCorridor = game.addChild(LK.getAsset('shipHall', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 1.5,
scaleY: 2,
x: 1750,
y: 1000
}));
game.setChildIndex(weaponsCorridor, 1);
// Add Communication Array corridor
var commCorridor = game.addChild(LK.getAsset('shipHall', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 2.5,
scaleY: 1.5,
x: 1600,
y: 525
}));
game.setChildIndex(commCorridor, 1);
// Add Maintenance Tunnels corridor
var maintCorridor = game.addChild(LK.getAsset('shipHall', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 2.5,
scaleY: 1.5,
x: 400,
y: 1925
}));
game.setChildIndex(maintCorridor, 1);
// Extended connecting corridors
var bridgeConnector = game.addChild(LK.getAsset('shipHall', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 2,
scaleY: 3,
x: 1024,
y: 500
}));
game.setChildIndex(bridgeConnector, 1);
var cargoConnector = game.addChild(LK.getAsset('shipHall', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 2,
scaleY: 2,
x: 1024,
y: 2150
}));
game.setChildIndex(cargoConnector, 1);
// Side wing corridors
var leftWingCorridor = game.addChild(LK.getAsset('shipHall', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 1.5,
scaleY: 10,
x: 250,
y: 1366
}));
game.setChildIndex(leftWingCorridor, 1);
var rightWingCorridor = game.addChild(LK.getAsset('shipHall', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 1.5,
scaleY: 10,
x: 1800,
y: 1366
}));
game.setChildIndex(rightWingCorridor, 1);
// Add connecting corridors between upper and main areas
var upperLeftConnector = game.addChild(LK.getAsset('shipHall', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 2,
scaleY: 3,
x: 700,
y: 900
}));
game.setChildIndex(upperLeftConnector, 1);
var upperRightConnector = game.addChild(LK.getAsset('shipHall', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 2,
scaleY: 3,
x: 1350,
y: 900
}));
game.setChildIndex(upperRightConnector, 1);
// Add connecting corridors between lower and main areas
var lowerLeftConnector = game.addChild(LK.getAsset('shipHall', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 2,
scaleY: 3,
x: 700,
y: 1800
}));
game.setChildIndex(lowerLeftConnector, 1);
var lowerRightConnector = game.addChild(LK.getAsset('shipHall', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 2,
scaleY: 3,
x: 1350,
y: 1800
}));
game.setChildIndex(lowerRightConnector, 1);
// Add horizontal connectors to main corridor
var mainLeftConnector = game.addChild(LK.getAsset('shipHall', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 3,
scaleY: 1.5,
x: 800,
y: 1366
}));
game.setChildIndex(mainLeftConnector, 1);
var mainRightConnector = game.addChild(LK.getAsset('shipHall', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 3,
scaleY: 1.5,
x: 1250,
y: 1366
}));
game.setChildIndex(mainRightConnector, 1);
// Add cafeteria/meeting room corridor
var cafeteriaCorridor = game.addChild(LK.getAsset('shipHall', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 4,
scaleY: 2,
x: 1024,
y: 1100
}));
game.setChildIndex(cafeteriaCorridor, 1);
// Add storage area corridors
var storageLeftCorridor = game.addChild(LK.getAsset('shipHall', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 1.5,
scaleY: 2,
x: 400,
y: 1100
}));
game.setChildIndex(storageLeftCorridor, 1);
var storageRightCorridor = game.addChild(LK.getAsset('shipHall', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 1.5,
scaleY: 2,
x: 1650,
y: 1100
}));
game.setChildIndex(storageRightCorridor, 1);
// Add engine room access corridors
var engineAccessLeft = game.addChild(LK.getAsset('shipHall', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 1.5,
scaleY: 2,
x: 500,
y: 2200
}));
game.setChildIndex(engineAccessLeft, 1);
var engineAccessRight = game.addChild(LK.getAsset('shipHall', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 1.5,
scaleY: 2,
x: 1550,
y: 2200
}));
game.setChildIndex(engineAccessRight, 1);
// Add navigation/helm corridor
var navigationCorridor = game.addChild(LK.getAsset('shipHall', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 3,
scaleY: 1.5,
x: 1024,
y: 500
}));
game.setChildIndex(navigationCorridor, 1);
// Add T-junction corridors for better connectivity
var upperTJunction = game.addChild(LK.getAsset('shipHall', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 2,
scaleY: 2,
x: 1024,
y: 800
}));
game.setChildIndex(upperTJunction, 1);
var lowerTJunction = game.addChild(LK.getAsset('shipHall', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 2,
scaleY: 2,
x: 1024,
y: 1900
}));
game.setChildIndex(lowerTJunction, 1);
// Add room separator walls to create distinct areas
var labSeparatorWall = game.addChild(LK.getAsset('wall', {
anchorX: 0.5,
anchorY: 0.5,
scaleY: 1.5,
x: 375,
y: 850
}));
walls.push(labSeparatorWall);
var weaponsSeparatorWall = game.addChild(LK.getAsset('wall', {
anchorX: 0.5,
anchorY: 0.5,
scaleY: 1.5,
x: 1675,
y: 850
}));
walls.push(weaponsSeparatorWall);
var medicalSeparatorWall = game.addChild(LK.getAsset('wall', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 1.5,
x: 1125,
y: 1300
}));
walls.push(medicalSeparatorWall);
var reactorSeparatorWall = game.addChild(LK.getAsset('wall', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 1.5,
x: 925,
y: 1300
}));
walls.push(reactorSeparatorWall);
// Add side corridor extensions
var leftSideCorridor = game.addChild(LK.getAsset('shipHall', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 1.5,
scaleY: 6,
x: 300,
y: 1366
}));
game.setChildIndex(leftSideCorridor, 1);
var rightSideCorridor = game.addChild(LK.getAsset('shipHall', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 1.5,
scaleY: 6,
x: 1750,
y: 1366
}));
game.setChildIndex(rightSideCorridor, 1);
}
// Create emergency button
var emergencyButton = game.addChild(LK.getAsset('emergencyButton', {
anchorX: 0.5,
anchorY: 0.5,
x: 1024,
y: 1366
}));
emergencyButton.down = function (x, y, obj) {
if (gamePhase === 'playing' && meetingCooldown <= 0) {
startEmergencyMeeting();
}
};
// Create kill button (initially hidden)
killButton = LK.getAsset('killButton', {
anchorX: 0.5,
anchorY: 0.5,
x: 0,
y: 0
});
killButton.alpha = 0;
killButton.visible = false;
LK.gui.center.addChild(killButton);
var killButtonText = new Text2('KILL', {
size: 40,
fill: 0xFFFFFF
});
killButtonText.anchor.set(0.5, 0.5);
killButton.addChild(killButtonText);
// Create kill cooldown timer (initially hidden)
var killCooldownTimer = new Text2('', {
size: 32,
fill: 0xFF0000
});
killCooldownTimer.anchor.set(0.5, 0.5);
killCooldownTimer.x = 0;
killCooldownTimer.y = 80;
killCooldownTimer.alpha = 0;
killCooldownTimer.visible = false;
LK.gui.center.addChild(killCooldownTimer);
killButton.down = function (x, y, obj) {
if (killTarget && currentPlayer && currentPlayer.isImpostor) {
if (currentPlayer.canKill()) {
if (currentPlayer.kill(killTarget)) {
updatePlayerCount();
hideKillButton();
// Hide cooldown timer when kill is successful
if (killCooldownTimer) {
killCooldownTimer.alpha = 0;
killCooldownTimer.visible = false;
}
}
} else {
// Show cooldown timer with animation
var cooldownLeft = Math.ceil((currentPlayer.killCooldown - (LK.ticks - currentPlayer.lastKillTime)) / 1000);
if (killCooldownTimer) {
killCooldownTimer.setText(cooldownLeft + 's');
killCooldownTimer.alpha = 1;
killCooldownTimer.visible = true;
// Pulsing animation for cooldown timer
tween(killCooldownTimer, {
scaleX: 1.3,
scaleY: 1.3,
tint: 0xFF4444
}, {
duration: 300,
onFinish: function onFinish() {
tween(killCooldownTimer, {
scaleX: 1.0,
scaleY: 1.0,
tint: 0xFF0000
}, {
duration: 300
});
}
});
}
if (taskCompletionText) {
taskCompletionText.setText('Kill cooldown: ' + cooldownLeft + 's');
taskCompletionText.alpha = 1;
taskCompletionText.y = 0;
tween(taskCompletionText, {
y: -100,
alpha: 0
}, {
duration: 2000
});
}
}
}
};
// Create sabotage button (initially hidden)
sabotageButton = LK.getAsset('sabotageButton', {
anchorX: 0.5,
anchorY: 0.5,
x: 200,
y: 0
});
sabotageButton.alpha = 0;
sabotageButton.visible = false;
LK.gui.center.addChild(sabotageButton);
// Create skip voting button (initially hidden)
var skipVotingButton = LK.getAsset('voteButton', {
anchorX: 0.5,
anchorY: 0.5,
x: 0,
y: 100
});
skipVotingButton.alpha = 0;
skipVotingButton.visible = false;
skipVotingButton.tint = 0xffff00;
LK.gui.center.addChild(skipVotingButton);
var skipVotingButtonText = new Text2('SKIP VOTING', {
size: 36,
fill: 0x000000
});
skipVotingButtonText.anchor.set(0.5, 0.5);
skipVotingButton.addChild(skipVotingButtonText);
var sabotageButtonText = new Text2('SABOTAGE', {
size: 32,
fill: 0xFFFFFF
});
sabotageButtonText.anchor.set(0.5, 0.5);
sabotageButton.addChild(sabotageButtonText);
// Create report button (initially hidden)
reportButton = LK.getAsset('reportButton', {
anchorX: 0.5,
anchorY: 0.5,
x: -200,
y: 0
});
reportButton.alpha = 0;
reportButton.visible = false;
reportButton.tint = 0x00ff00;
LK.gui.center.addChild(reportButton);
var reportButtonText = new Text2('REPORT', {
size: 32,
fill: 0xFFFFFF
});
reportButtonText.anchor.set(0.5, 0.5);
reportButton.addChild(reportButtonText);
// Create task button (initially hidden)
taskButton = LK.getAsset('taskButton', {
anchorX: 0.5,
anchorY: 0.5,
x: 0,
y: -150
});
taskButton.alpha = 0;
taskButton.visible = false;
taskButton.tint = 0x00ffff;
LK.gui.center.addChild(taskButton);
var taskButtonText = new Text2('DO TASK', {
size: 32,
fill: 0xFFFFFF
});
taskButtonText.anchor.set(0.5, 0.5);
taskButton.addChild(taskButtonText);
// Create camera button (initially hidden)
cameraButton = LK.getAsset('cameraButton', {
anchorX: 0.5,
anchorY: 0.5,
x: 300,
y: 0
});
cameraButton.alpha = 0;
cameraButton.visible = false;
cameraButton.tint = 0x00ff00;
LK.gui.center.addChild(cameraButton);
var cameraButtonText = new Text2('CAMERAS', {
size: 28,
fill: 0xFFFFFF
});
cameraButtonText.anchor.set(0.5, 0.5);
cameraButton.addChild(cameraButtonText);
// Create door button (initially hidden)
doorButton = LK.getAsset('doorButton', {
anchorX: 0.5,
anchorY: 0.5,
x: -300,
y: 0
});
doorButton.alpha = 0;
doorButton.visible = false;
doorButton.tint = 0xff0000;
LK.gui.center.addChild(doorButton);
var doorButtonText = new Text2('DOORS', {
size: 28,
fill: 0xFFFFFF
});
doorButtonText.anchor.set(0.5, 0.5);
doorButton.addChild(doorButtonText);
// Make doorButtonText globally accessible
doorButton.doorButtonText = doorButtonText;
doorButton.down = function (x, y, obj) {
if (currentDoorTarget && currentPlayer && currentPlayer.isAlive && currentPlayer.isImpostor && gamePhase === 'playing') {
if (currentDoorTarget.isOpen) {
currentDoorTarget.close();
} else {
currentDoorTarget.open();
}
}
};
// Create individual sabotage buttons (initially hidden)
sabotageElectricityButton = LK.getAsset('sabotageElectricityButton', {
anchorX: 0.5,
anchorY: 0.5,
x: -200,
y: -100
});
sabotageElectricityButton.alpha = 0;
sabotageElectricityButton.visible = false;
sabotageElectricityButton.tint = 0xffff00;
LK.gui.center.addChild(sabotageElectricityButton);
var sabotageElectricityButtonText = new Text2('ELECTRICITY', {
size: 20,
fill: 0x000000
});
sabotageElectricityButtonText.anchor.set(0.5, 0.5);
sabotageElectricityButton.addChild(sabotageElectricityButtonText);
sabotageOxygenButton = LK.getAsset('sabotageOxygenButton', {
anchorX: 0.5,
anchorY: 0.5,
x: 0,
y: -100
});
sabotageOxygenButton.alpha = 0;
sabotageOxygenButton.visible = false;
sabotageOxygenButton.tint = 0x00ffff;
LK.gui.center.addChild(sabotageOxygenButton);
var sabotageOxygenButtonText = new Text2('OXYGEN', {
size: 20,
fill: 0x000000
});
sabotageOxygenButtonText.anchor.set(0.5, 0.5);
sabotageOxygenButton.addChild(sabotageOxygenButtonText);
sabotageDoorsButton = LK.getAsset('sabotageDoorsButton', {
anchorX: 0.5,
anchorY: 0.5,
x: 200,
y: -100
});
sabotageDoorsButton.alpha = 0;
sabotageDoorsButton.visible = false;
sabotageDoorsButton.tint = 0xff8800;
LK.gui.center.addChild(sabotageDoorsButton);
var sabotageDoorsButtonText = new Text2('DOORS', {
size: 20,
fill: 0x000000
});
sabotageDoorsButtonText.anchor.set(0.5, 0.5);
sabotageDoorsButton.addChild(sabotageDoorsButtonText);
sabotageReactorButton = LK.getAsset('sabotageReactorButton', {
anchorX: 0.5,
anchorY: 0.5,
x: -100,
y: -200
});
sabotageReactorButton.alpha = 0;
sabotageReactorButton.visible = false;
sabotageReactorButton.tint = 0xff0000;
LK.gui.center.addChild(sabotageReactorButton);
var sabotageReactorButtonText = new Text2('REACTOR', {
size: 20,
fill: 0x000000
});
sabotageReactorButtonText.anchor.set(0.5, 0.5);
sabotageReactorButton.addChild(sabotageReactorButtonText);
sabotageConnectionsButton = LK.getAsset('sabotageConnectionsButton', {
anchorX: 0.5,
anchorY: 0.5,
x: 100,
y: -200
});
sabotageConnectionsButton.alpha = 0;
sabotageConnectionsButton.visible = false;
sabotageConnectionsButton.tint = 0x8800ff;
LK.gui.center.addChild(sabotageConnectionsButton);
var sabotageConnectionsButtonText = new Text2('CONNECTIONS', {
size: 18,
fill: 0x000000
});
sabotageConnectionsButtonText.anchor.set(0.5, 0.5);
sabotageConnectionsButton.addChild(sabotageConnectionsButtonText);
sabotageButton.down = function (x, y, obj) {
if (currentPlayer && currentPlayer.isImpostor && gamePhase === 'playing') {
performSabotage();
hideSabotageButton();
}
};
// Individual sabotage button handlers
sabotageElectricityButton.down = function (x, y, obj) {
if (currentPlayer && currentPlayer.isImpostor && gamePhase === 'playing' && !isSabotageActive) {
performSpecificSabotage('electricity');
hideIndividualSabotageButtons();
}
};
sabotageOxygenButton.down = function (x, y, obj) {
if (currentPlayer && currentPlayer.isImpostor && gamePhase === 'playing' && !isSabotageActive) {
performSpecificSabotage('oxygen');
hideIndividualSabotageButtons();
}
};
sabotageDoorsButton.down = function (x, y, obj) {
if (currentPlayer && currentPlayer.isImpostor && gamePhase === 'playing' && !isSabotageActive) {
performSpecificSabotage('doors');
hideIndividualSabotageButtons();
}
};
sabotageReactorButton.down = function (x, y, obj) {
if (currentPlayer && currentPlayer.isImpostor && gamePhase === 'playing' && !isSabotageActive) {
performSpecificSabotage('reactor');
hideIndividualSabotageButtons();
}
};
sabotageConnectionsButton.down = function (x, y, obj) {
if (currentPlayer && currentPlayer.isImpostor && gamePhase === 'playing' && !isSabotageActive) {
performSpecificSabotage('communications');
hideIndividualSabotageButtons();
}
};
reportButton.down = function (x, y, obj) {
if (reportTarget && currentPlayer && currentPlayer.isAlive && gamePhase === 'playing') {
reportDeadBody();
hideReportButton();
}
};
skipVotingButton.down = function (x, y, obj) {
if (votingPhase && gamePhase === 'voting') {
endVoting();
}
};
taskButton.down = function (x, y, obj) {
if (currentTaskTarget && currentPlayer && currentPlayer.isAlive && !currentPlayer.isImpostor && gamePhase === 'playing') {
currentTaskTarget.startTask();
}
};
cameraButton.down = function (x, y, obj) {
if (currentPlayer && currentPlayer.isAlive && gamePhase === 'playing' && securityRoom) {
// Check if player is near security room
var distance = Math.sqrt(Math.pow(currentPlayer.x - securityRoom.x, 2) + Math.pow(currentPlayer.y - securityRoom.y, 2));
if (distance < 150) {
if (!securityRoom.isActive) {
securityRoom.activate();
}
}
}
};
// Mission panel helper functions
function toggleMissionPanel() {
missionPanelVisible = !missionPanelVisible;
missionPanel.visible = missionPanelVisible;
missionPanelTitle.visible = missionPanelVisible;
if (missionPanelVisible) {
updateMissionPanel();
// Show panel with animation
tween(missionPanel, {
alpha: 0.9,
scaleX: 2.5,
scaleY: 3.0
}, {
duration: 300
});
} else {
// Hide panel with animation
tween(missionPanel, {
alpha: 0.8,
scaleX: 2.3,
scaleY: 2.8
}, {
duration: 300,
onFinish: function onFinish() {
missionTaskTexts.forEach(function (text) {
if (text) text.visible = false;
});
}
});
}
}
function updateMissionPanel() {
if (!currentPlayer || currentPlayer.isImpostor || !missionPanelVisible) return;
// Clear existing task texts
missionTaskTexts.forEach(function (text) {
if (text) text.destroy();
});
missionTaskTexts = [];
// Show assigned tasks
var yOffset = 280;
var taskIndex = 0;
if (currentPlayer.assignedTasks && currentPlayer.assignedTasks.length > 0) {
currentPlayer.assignedTasks.forEach(function (task) {
if (taskIndex >= 8) return; // Limit to 8 tasks to fit in panel
var statusIcon = task.isCompleted ? '✓' : '○';
var statusColor = task.isCompleted ? 0x00FF00 : 0xFFFF00;
var taskText = new Text2(statusIcon + ' ' + task.taskName, {
size: 24,
fill: statusColor
});
taskText.anchor.set(0, 0);
taskText.x = 80;
taskText.y = yOffset;
taskText.visible = true;
LK.gui.topLeft.addChild(taskText);
missionTaskTexts.push(taskText);
// Add task location hint
var locationText = new Text2('Location: ' + getTaskLocationName(task), {
size: 16,
fill: 0xAAAAAAA
});
locationText.anchor.set(0, 0);
locationText.x = 100;
locationText.y = yOffset + 30;
locationText.visible = true;
LK.gui.topLeft.addChild(locationText);
missionTaskTexts.push(locationText);
yOffset += 60;
taskIndex++;
});
} else {
// Show message if no tasks assigned
var noTasksText = new Text2('No tasks assigned', {
size: 20,
fill: 0xFFFFFF
});
noTasksText.anchor.set(0, 0);
noTasksText.x = 80;
noTasksText.y = 280;
noTasksText.visible = true;
LK.gui.topLeft.addChild(noTasksText);
missionTaskTexts.push(noTasksText);
}
// Show completion progress
var completedCount = currentPlayer.assignedTasks ? currentPlayer.assignedTasks.filter(function (task) {
return task.isCompleted;
}).length : 0;
var totalAssigned = currentPlayer.assignedTasks ? currentPlayer.assignedTasks.length : 0;
var progressText = new Text2('Progress: ' + completedCount + '/' + totalAssigned, {
size: 20,
fill: 0x00FFFF
});
progressText.anchor.set(0.5, 0);
progressText.x = 250;
progressText.y = 260;
progressText.visible = true;
LK.gui.topLeft.addChild(progressText);
missionTaskTexts.push(progressText);
}
function getTaskLocationName(task) {
// Use stored area name if available
if (task.areaName) {
return task.areaName;
}
// Fallback to position-based naming
if (task.x < 500) {
if (task.y < 800) return 'Upper Left Wing';
if (task.y > 1800) return 'Lower Left Wing';
return 'Left Wing';
} else if (task.x > 1500) {
if (task.y < 800) return 'Upper Right Wing';
if (task.y > 1800) return 'Lower Right Wing';
return 'Right Wing';
} else {
if (task.y < 800) return 'Command Bridge';
if (task.y > 1800) return 'Cargo Bay';
if (task.y < 1200) return 'Security Area';
return 'Central Corridor';
}
}
// Role selection button handlers
crewButton.down = function (x, y, obj) {
if (gamePhase === 'roleSelection') {
selectRole(false); // false = crew member
}
};
impostorButton.down = function (x, y, obj) {
if (gamePhase === 'roleSelection') {
selectRole(true); // true = impostor
}
};
// Initialize players
function selectRole(isImpostor) {
// Hide role selection UI
roleSelectionTitle.destroy();
crewButton.destroy();
crewButtonText.destroy();
impostorButton.destroy();
impostorButtonText.destroy();
instructionText.destroy();
// Initialize game with selected role
initializePlayers(isImpostor);
initializeTasks();
// Update total tasks based on actual crew members
var crewCount = players.filter(function (p) {
return !p.isImpostor;
}).length;
totalTasks = crewCount * 4;
updatePlayerCount();
gamePhase = 'playing';
phaseText.setText('Complete Tasks or Find Impostors!');
}
function initializePlayers(playerIsImpostor) {
var colors = [0x00ff00, 0x0000ff, 0xff00ff, 0x00ffff, 0xffa500, 0xffff00, 0x8a2be2, 0xffc0cb];
// Create current player first with selected role
currentPlayer = new Player(playerIsImpostor, colors[0], 0);
// Generate safe spawn position for current player
var validPosition = findValidSpawnPosition();
currentPlayer.x = validPosition.x;
currentPlayer.y = validPosition.y;
currentPlayer.targetX = currentPlayer.x;
currentPlayer.targetY = currentPlayer.y;
players.push(currentPlayer);
game.addChild(currentPlayer);
// Create other players
var remainingImpostors = playerIsImpostor ? impostorCount - 1 : impostorCount;
for (var i = 1; i < 8; i++) {
var isImpostor = i <= remainingImpostors;
var player = new Player(isImpostor, colors[i], i);
// Generate safe spawn position
var validPosition = findValidSpawnPosition();
player.x = validPosition.x;
player.y = validPosition.y;
player.targetX = player.x;
player.targetY = player.y;
players.push(player);
game.addChild(player);
}
}
// Initialize tasks
function initializeTasks() {
var taskTypes = ['electrical', 'engine', 'reactor', 'navigation', 'medical', 'cables'];
var taskAreas = [
// Command Bridge - Navigation task
{
x: 900,
y: 350,
width: 250,
height: 150,
preferredType: 'navigation',
name: 'Command Bridge'
},
// Communication Array - Electrical task
{
x: 1500,
y: 525,
width: 200,
height: 125,
preferredType: 'electrical',
name: 'Communications'
},
// Laboratory - Medical task
{
x: 250,
y: 1000,
width: 200,
height: 200,
preferredType: 'medical',
name: 'Laboratory'
},
// Weapons Room - Reactor task
{
x: 1750,
y: 1000,
width: 200,
height: 200,
preferredType: 'reactor',
name: 'Weapons'
},
// Maintenance Tunnels - Cables task
{
x: 350,
y: 1925,
width: 200,
height: 125,
preferredType: 'cables',
name: 'Maintenance'
},
// Cargo Bay - Engine task
{
x: 900,
y: 2350,
width: 400,
height: 200,
preferredType: 'engine',
name: 'Cargo Bay'
},
// Upper Electrical room
{
x: 400,
y: 500,
width: 200,
height: 150,
preferredType: 'electrical',
name: 'Upper Electrical'
},
// Upper Engine room
{
x: 1200,
y: 500,
width: 200,
height: 150,
preferredType: 'engine',
name: 'Upper Engines'
},
// Security room
{
x: 800,
y: 800,
width: 300,
height: 200,
preferredType: 'navigation',
name: 'Security'
},
// Medical bay
{
x: 1200,
y: 1200,
width: 250,
height: 180,
preferredType: 'medical',
name: 'Medbay'
},
// Reactor room
{
x: 400,
y: 1200,
width: 250,
height: 180,
preferredType: 'reactor',
name: 'Reactor'
},
// Lower Engine room
{
x: 1200,
y: 2000,
width: 200,
height: 150,
preferredType: 'engine',
name: 'Lower Engines'
},
// Lower Electrical
{
x: 400,
y: 2000,
width: 200,
height: 150,
preferredType: 'electrical',
name: 'Lower Electrical'
},
// Central corridor
{
x: 800,
y: 1600,
width: 400,
height: 200,
preferredType: 'navigation',
name: 'Central Hub'
},
// Cables maintenance room
{
x: 600,
y: 1000,
width: 200,
height: 150,
preferredType: 'cables',
name: 'Maintenance'
}];
// Get crew members (non-impostors)
var crewMembers = players.filter(function (player) {
return !player.isImpostor;
});
// Create tasks with individual assignments
var taskTypeCount = {};
taskTypes.forEach(function (type) {
taskTypeCount[type] = 0;
});
var taskId = 0;
var allAssignedTasks = []; // Track all tasks globally
// Create exactly 5 tasks per crew member for balanced gameplay
var tasksPerPlayer = 5;
crewMembers.forEach(function (player, playerIndex) {
var playerTaskCount = {};
taskTypes.forEach(function (type) {
playerTaskCount[type] = 0;
});
// Initialize assigned tasks array for this player
if (!player.assignedTasks) {
player.assignedTasks = [];
}
// Assign tasks with improved distribution
for (var i = 0; i < tasksPerPlayer; i++) {
// Ensure tasks are spread across different areas
var areaIndex = (playerIndex * tasksPerPlayer + i) % taskAreas.length;
var area = taskAreas[areaIndex];
// Generate position within area with some randomness
var taskX = area.x + (Math.random() - 0.5) * area.width * 0.8;
var taskY = area.y + (Math.random() - 0.5) * area.height * 0.8;
// Ensure task is within valid game boundaries
taskX = Math.max(200, Math.min(1800, taskX));
taskY = Math.max(300, Math.min(2500, taskY));
// Smart task type selection
var taskType = area.preferredType;
// Ensure variety - max 2 of same type per player
if (playerTaskCount[taskType] >= 2) {
var availableTypes = taskTypes.filter(function (type) {
return playerTaskCount[type] < 2;
});
if (availableTypes.length > 0) {
taskType = availableTypes[Math.floor(Math.random() * availableTypes.length)];
} else {
// If all types have 2, allow 1 more of the area's preferred type
taskType = area.preferredType;
}
}
playerTaskCount[taskType]++;
taskTypeCount[taskType]++;
var task;
// Create appropriate task type
if (taskType === 'cables') {
task = new CablesTask(taskX, taskY, taskId, player.playerId);
} else {
task = new Task(taskX, taskY, taskId, taskType, player.playerId);
}
// Store area information for location hints
task.areaName = area.name;
task.areaType = area.preferredType;
// Add task to player's assigned tasks
player.assignedTasks.push(task);
allAssignedTasks.push(task);
tasks.push(task);
game.addChild(task);
taskId++;
}
});
// Update global task count
totalTasks = allAssignedTasks.length;
// Show task assignment summary for debugging
console.log('Task Assignment Complete:');
console.log('- Total Players:', crewMembers.length);
console.log('- Tasks per Player:', tasksPerPlayer);
console.log('- Total Tasks:', totalTasks);
console.log('- Task Distribution:', taskTypeCount);
}
function updateTaskProgress() {
// Calculate actual total tasks based on crew members
var crewMembers = players.filter(function (p) {
return !p.isImpostor;
});
var actualTotalTasks = totalTasks; // Use the globally tracked total
taskProgressText.setText('Tasks: ' + completedTasks + '/' + actualTotalTasks);
// Update progress bar with smooth animation
var progressPercentage = Math.min(completedTasks / actualTotalTasks, 1.0);
tween(progressBarFill, {
scaleX: progressPercentage * 8
}, {
duration: 300
});
// Show task completion animation with more detail
if (taskCompletionText) {
var remainingTasks = actualTotalTasks - completedTasks;
taskCompletionText.setText('Task Completed! (' + completedTasks + '/' + actualTotalTasks + ') - ' + remainingTasks + ' remaining');
taskCompletionText.alpha = 1;
taskCompletionText.y = 0;
// Scale animation for emphasis
tween(taskCompletionText, {
scaleX: 1.2,
scaleY: 1.2
}, {
duration: 200,
onFinish: function onFinish() {
tween(taskCompletionText, {
y: -100,
alpha: 0,
scaleX: 1.0,
scaleY: 1.0
}, {
duration: 2000
});
}
});
}
// Enhanced progress bar flash with completion level indication
var flashColor = progressPercentage > 0.8 ? 0x00FF00 : progressPercentage > 0.5 ? 0xFFFF00 : 0xFF8800;
tween(progressBarFill, {
tint: flashColor
}, {
duration: 200,
onFinish: function onFinish() {
tween(progressBarFill, {
tint: 0x00FF00
}, {
duration: 200
});
}
});
// Check win condition with percentage threshold
if (completedTasks >= actualTotalTasks) {
// Play victory sound effect
LK.getSound('taskComplete').play();
// Add victory message
if (taskCompletionText) {
taskCompletionText.setText('ALL TASKS COMPLETED! CREW WINS!');
taskCompletionText.tint = 0x00FF00;
taskCompletionText.alpha = 1;
taskCompletionText.y = 0;
taskCompletionText.scaleX = 1.5;
taskCompletionText.scaleY = 1.5;
}
endGame('crew');
} else {
// Show milestone celebrations
var milestones = [0.25, 0.5, 0.75];
milestones.forEach(function (milestone) {
if (Math.abs(progressPercentage - milestone) < 0.01) {
var percentComplete = Math.round(milestone * 100);
if (taskCompletionText) {
taskCompletionText.setText(percentComplete + '% of tasks completed! Keep going!');
taskCompletionText.tint = 0xFFFF00;
}
}
});
}
}
function updatePlayerCount() {
var aliveCrew = players.filter(function (p) {
return p.isAlive && !p.isImpostor;
}).length;
var aliveImpostors = players.filter(function (p) {
return p.isAlive && p.isImpostor;
}).length;
playerCountText.setText('Crew: ' + aliveCrew + ' | Impostors: ' + aliveImpostors);
// Check win conditions
if (aliveImpostors >= aliveCrew) {
endGame('impostors');
} else if (aliveImpostors === 0) {
endGame('crew');
}
}
function startEmergencyMeeting() {
gamePhase = 'voting';
votingPhase = true;
meetingCooldown = meetingCooldownTime;
LK.getSound('emergency').play();
phaseText.setText('Emergency Meeting - Vote!');
// Create vote buttons
var alivePlayers = players.filter(function (p) {
return p.isAlive;
});
var buttonWidth = 200;
var startX = (2048 - alivePlayers.length * buttonWidth) / 2;
for (var i = 0; i < alivePlayers.length; i++) {
var button = new VoteButton(alivePlayers[i].playerId, startX + i * buttonWidth, 2400);
voteButtons.push(button);
game.addChild(button);
}
// Reset player voting ability
players.forEach(function (p) {
if (p.isAlive) {
p.canVote = true;
}
});
// Show skip voting button
if (skipVotingButton) {
skipVotingButton.alpha = 1;
skipVotingButton.visible = true;
}
}
function endVoting() {
votingPhase = false;
// Find player with most votes
var maxVotes = 0;
var ejectedPlayer = null;
for (var i = 0; i < voteButtons.length; i++) {
if (voteButtons[i].votes > maxVotes) {
maxVotes = voteButtons[i].votes;
ejectedPlayer = players.find(function (p) {
return p.playerId === voteButtons[i].playerId;
});
}
}
// Eject player with most votes
if (ejectedPlayer && maxVotes > 0) {
ejectedPlayer.eliminate();
if (ejectedPlayer.isImpostor) {
phaseText.setText('Impostor Ejected!');
} else {
phaseText.setText('Innocent Ejected!');
}
} else {
phaseText.setText('No One Ejected!');
}
// Clean up vote buttons
voteButtons.forEach(function (button) {
button.destroy();
});
voteButtons = [];
// Hide skip voting button
if (skipVotingButton) {
skipVotingButton.alpha = 0;
skipVotingButton.visible = false;
}
// Return to playing phase after delay
LK.setTimeout(function () {
gamePhase = 'playing';
phaseText.setText('Complete Tasks or Find Impostors!');
updatePlayerCount();
}, 3000);
}
function showKillButton(target) {
if (killButton && currentPlayer && currentPlayer.isImpostor) {
killTarget = target;
killButton.alpha = 1;
killButton.visible = true;
// Flash effect to draw attention
tween(killButton, {
scaleX: 1.2,
scaleY: 1.2
}, {
duration: 200,
onFinish: function onFinish() {
tween(killButton, {
scaleX: 1.0,
scaleY: 1.0
}, {
duration: 200
});
}
});
}
}
function hideKillButton() {
if (killButton) {
killTarget = null;
killButton.alpha = 0;
killButton.visible = false;
}
// Also hide cooldown timer
if (killCooldownTimer) {
killCooldownTimer.alpha = 0;
killCooldownTimer.visible = false;
}
}
function showSabotageButton() {
if (sabotageButton && currentPlayer && currentPlayer.isImpostor) {
sabotageButton.alpha = 1;
sabotageButton.visible = true;
// Flash effect to draw attention
tween(sabotageButton, {
scaleX: 1.2,
scaleY: 1.2
}, {
duration: 200,
onFinish: function onFinish() {
tween(sabotageButton, {
scaleX: 1.0,
scaleY: 1.0
}, {
duration: 200
});
}
});
}
}
function hideSabotageButton() {
if (sabotageButton) {
sabotageTarget = null;
sabotageButton.alpha = 0;
sabotageButton.visible = false;
}
}
function showReportButton(target) {
if (reportButton && currentPlayer && currentPlayer.isAlive) {
reportTarget = target;
reportButton.alpha = 1;
reportButton.visible = true;
// Flash effect to draw attention
tween(reportButton, {
scaleX: 1.2,
scaleY: 1.2
}, {
duration: 200,
onFinish: function onFinish() {
tween(reportButton, {
scaleX: 1.0,
scaleY: 1.0
}, {
duration: 200
});
}
});
}
}
function hideReportButton() {
if (reportButton) {
reportTarget = null;
reportButton.alpha = 0;
reportButton.visible = false;
}
}
function showTaskButton(target) {
if (taskButton && currentPlayer && currentPlayer.isAlive && !currentPlayer.isImpostor) {
currentTaskTarget = target;
taskButton.alpha = 1;
taskButton.visible = true;
// Flash effect to draw attention
tween(taskButton, {
scaleX: 1.2,
scaleY: 1.2
}, {
duration: 200,
onFinish: function onFinish() {
tween(taskButton, {
scaleX: 1.0,
scaleY: 1.0
}, {
duration: 200
});
}
});
}
}
function hideTaskButton() {
if (taskButton) {
currentTaskTarget = null;
taskButton.alpha = 0;
taskButton.visible = false;
}
}
function showCameraButton() {
if (cameraButton && currentPlayer && currentPlayer.isAlive) {
cameraButton.alpha = 1;
cameraButton.visible = true;
// Flash effect to draw attention
tween(cameraButton, {
scaleX: 1.2,
scaleY: 1.2
}, {
duration: 200,
onFinish: function onFinish() {
tween(cameraButton, {
scaleX: 1.0,
scaleY: 1.0
}, {
duration: 200
});
}
});
}
}
function hideCameraButton() {
if (cameraButton) {
cameraButton.alpha = 0;
cameraButton.visible = false;
}
}
function reportDeadBody() {
if (currentPlayer && currentPlayer.isAlive && gamePhase === 'playing') {
// Play report sound
LK.getSound('report').play();
// Start emergency meeting
startEmergencyMeeting();
// Update phase text to indicate it was a report
phaseText.setText('Dead Body Reported - Vote!');
}
}
function performSabotage() {
if (currentPlayer && currentPlayer.isImpostor && gamePhase === 'playing' && !isSabotageActive) {
// Show individual sabotage buttons instead of performing random sabotage
showIndividualSabotageButtons();
}
}
function performSpecificSabotage(sabotageType) {
if (currentPlayer && currentPlayer.isImpostor && gamePhase === 'playing' && !isSabotageActive) {
// Play sabotage sound
LK.getSound('sabotage').play();
// Activate specific sabotage type
activateSabotage(sabotageType);
}
}
function showIndividualSabotageButtons() {
if (currentPlayer && currentPlayer.isImpostor && gamePhase === 'playing' && !isSabotageActive) {
// Show all individual sabotage buttons
sabotageElectricityButton.alpha = 1;
sabotageElectricityButton.visible = true;
sabotageOxygenButton.alpha = 1;
sabotageOxygenButton.visible = true;
sabotageDoorsButton.alpha = 1;
sabotageDoorsButton.visible = true;
sabotageReactorButton.alpha = 1;
sabotageReactorButton.visible = true;
sabotageConnectionsButton.alpha = 1;
sabotageConnectionsButton.visible = true;
// Hide main sabotage button
hideSabotageButton();
// Add pulsing animation to draw attention
tween(sabotageElectricityButton, {
scaleX: 1.2,
scaleY: 1.2
}, {
duration: 300,
onFinish: function onFinish() {
tween(sabotageElectricityButton, {
scaleX: 1.0,
scaleY: 1.0
}, {
duration: 300
});
}
});
}
}
function hideIndividualSabotageButtons() {
if (sabotageElectricityButton) {
sabotageElectricityButton.alpha = 0;
sabotageElectricityButton.visible = false;
}
if (sabotageOxygenButton) {
sabotageOxygenButton.alpha = 0;
sabotageOxygenButton.visible = false;
}
if (sabotageDoorsButton) {
sabotageDoorsButton.alpha = 0;
sabotageDoorsButton.visible = false;
}
if (sabotageReactorButton) {
sabotageReactorButton.alpha = 0;
sabotageReactorButton.visible = false;
}
if (sabotageConnectionsButton) {
sabotageConnectionsButton.alpha = 0;
sabotageConnectionsButton.visible = false;
}
}
function activateSabotage(sabotageType) {
if (isSabotageActive) return;
isSabotageActive = true;
activeSabotage = sabotageType;
sabotageTimer = 30000; // 30 seconds to fix
// Hide individual sabotage buttons
hideIndividualSabotageButtons();
// Hide task progress bar during sabotage
taskProgressText.alpha = 0;
progressBarBackground.alpha = 0;
progressBarFill.alpha = 0;
// Disable cameras during sabotage
if (securityRoom && securityRoom.isActive) {
securityRoom.deactivate();
}
cameras.forEach(function (camera) {
camera.isActive = false;
});
// Apply sabotage effects based on type
switch (sabotageType) {
case 'electricity':
// Power outage - darken screen
sabotageEffect.alpha = 0.7;
sabotageEffect.visible = true;
sabotageEffect.tint = 0x000000;
phaseText.setText('POWER OUTAGE! Fix electrical systems!');
phaseText.tint = 0xff0000;
break;
case 'oxygen':
// Oxygen depletion - blue tint
sabotageEffect.alpha = 0.3;
sabotageEffect.visible = true;
sabotageEffect.tint = 0x0000ff;
phaseText.setText('OXYGEN DEPLETED! Restore life support!');
phaseText.tint = 0x0000ff;
break;
case 'reactor':
// Reactor meltdown - red tint and flash
sabotageEffect.alpha = 0.4;
sabotageEffect.visible = true;
sabotageEffect.tint = 0xff0000;
phaseText.setText('REACTOR CRITICAL! Fix reactor core!');
phaseText.tint = 0xff0000;
// Flash effect
tween(sabotageEffect, {
alpha: 0.6
}, {
duration: 500,
onFinish: function onFinish() {
tween(sabotageEffect, {
alpha: 0.4
}, {
duration: 500
});
}
});
break;
case 'doors':
// Close all doors
doors.forEach(function (door) {
if (door.isOpen) {
door.close();
}
});
phaseText.setText('DOORS SABOTAGED! Systems compromised!');
phaseText.tint = 0xff8800;
break;
case 'communications':
// Disable task visibility
tasks.forEach(function (task) {
task.alpha = 0.3;
});
phaseText.setText('COMMUNICATIONS DOWN! Tasks hidden!');
phaseText.tint = 0xffff00;
break;
}
// Create sabotage fix indicators
createSabotageIndicators(sabotageType);
}
function createSabotageIndicators(sabotageType) {
// Clear existing indicators
sabotageIndicators.forEach(function (indicator) {
indicator.destroy();
});
sabotageIndicators = [];
// Create fix locations based on sabotage type
var fixLocations = [];
switch (sabotageType) {
case 'electricity':
fixLocations = [{
x: 400,
y: 500
},
// Upper electrical
{
x: 400,
y: 2000
} // Lower electrical
];
break;
case 'oxygen':
fixLocations = [{
x: 1200,
y: 1200
},
// Medical bay
{
x: 850,
y: 1500
} // Reactor area
];
break;
case 'reactor':
fixLocations = [{
x: 850,
y: 1500
},
// Reactor room
{
x: 1200,
y: 500
} // Engine room
];
break;
case 'doors':
fixLocations = [{
x: 1024,
y: 600
},
// Security room
{
x: 1024,
y: 1366
} // Central corridor
];
break;
case 'communications':
fixLocations = [{
x: 1024,
y: 600
},
// Security room
{
x: 1024,
y: 800
} // Admin area
];
break;
}
// Create visual indicators
fixLocations.forEach(function (location) {
var indicator = game.addChild(LK.getAsset('sabotageIndicator', {
anchorX: 0.5,
anchorY: 0.5,
x: location.x,
y: location.y
}));
indicator.fixLocation = location;
sabotageIndicators.push(indicator);
// Pulsing animation
tween(indicator, {
scaleX: 1.5,
scaleY: 1.5,
alpha: 0.7
}, {
duration: 1000,
onFinish: function onFinish() {
tween(indicator, {
scaleX: 1.0,
scaleY: 1.0,
alpha: 1.0
}, {
duration: 1000
});
}
});
});
}
function fixSabotage() {
if (!isSabotageActive) return;
isSabotageActive = false;
activeSabotage = null;
sabotageTimer = 0;
// Restore task progress bar
taskProgressText.alpha = 1;
progressBarBackground.alpha = 1;
progressBarFill.alpha = 1;
// Re-enable cameras
cameras.forEach(function (camera) {
camera.isActive = true;
});
// Hide sabotage effect
sabotageEffect.alpha = 0;
sabotageEffect.visible = false;
// Restore task visibility
tasks.forEach(function (task) {
task.alpha = 1.0;
});
// Reset phase text
phaseText.setText('Complete Tasks or Find Impostors!');
phaseText.tint = 0xFFFF00;
// Clear sabotage indicators
sabotageIndicators.forEach(function (indicator) {
indicator.destroy();
});
sabotageIndicators = [];
// Play success sound
LK.getSound('taskComplete').play();
}
// Find valid spawn position that doesn't collide with walls
function findValidSpawnPosition() {
var playerSize = 80;
var maxAttempts = 50;
var attempts = 0;
while (attempts < maxAttempts) {
var x = 300 + Math.random() * 1400;
var y = 300 + Math.random() * 2000;
// Check if this position is valid (not inside a wall)
if (!checkWallCollision(x, y, playerSize, playerSize)) {
return {
x: x,
y: y
};
}
attempts++;
}
// If we can't find a valid position after many attempts, use a known safe position
// Return center of main corridor as fallback
return {
x: 1024,
y: 1366
};
}
// Collision detection function
function checkWallCollision(x, y, playerWidth, playerHeight) {
var playerLeft = x - playerWidth / 2;
var playerRight = x + playerWidth / 2;
var playerTop = y - playerHeight / 2;
var playerBottom = y + playerHeight / 2;
for (var i = 0; i < walls.length; i++) {
var wall = walls[i];
var wallLeft = wall.x - wall.width * (wall.scaleX || 1) / 2;
var wallRight = wall.x + wall.width * (wall.scaleX || 1) / 2;
var wallTop = wall.y - wall.height * (wall.scaleY || 1) / 2;
var wallBottom = wall.y + wall.height * (wall.scaleY || 1) / 2;
// Check if player rectangle intersects with wall rectangle
if (playerLeft < wallRight && playerRight > wallLeft && playerTop < wallBottom && playerBottom > wallTop) {
return true;
}
}
// Check door collisions - only check if door is closed
for (var i = 0; i < doors.length; i++) {
var door = doors[i];
// Only check collision if door is closed
if (!door.isOpen && door.blocksMovement(x, y, playerWidth, playerHeight)) {
return true;
}
}
return false;
}
function endGame(winner) {
gamePhase = 'gameOver';
if (winner === 'crew') {
phaseText.setText('Crew Wins!');
LK.showYouWin();
} else {
phaseText.setText('Impostors Win!');
LK.showGameOver();
}
}
// AI behavior for other players - optimized for performance
function updateAI() {
// Process only a subset of players each update to spread load
var playersToUpdate = players.length;
var startIndex = LK.ticks % playersToUpdate;
for (var playerIndex = 0; playerIndex < Math.min(2, playersToUpdate); playerIndex++) {
var index = (startIndex + playerIndex) % playersToUpdate;
var player = players[index];
if (player === currentPlayer || !player.isAlive) continue;
// Crew member AI - simplified for performance
if (!player.isImpostor && gamePhase === 'playing') {
// Priority 1: Fix sabotages (highest priority)
if (isSabotageActive && sabotageIndicators.length > 0) {
// Check if near any sabotage indicator (simplified)
var nearIndicator = false;
for (var i = 0; i < sabotageIndicators.length; i++) {
var indicator = sabotageIndicators[i];
var dx = indicator.x - player.x;
var dy = indicator.y - player.y;
if (dx * dx + dy * dy < 10000) {
// 100 * 100
nearIndicator = true;
if (Math.random() < 0.1) {
// Reduced chance
fixSabotage();
return;
}
break;
}
}
if (!nearIndicator && Math.random() < 0.05) {
// Move towards first sabotage indicator
var indicator = sabotageIndicators[0];
player.moveTo(indicator.x + (Math.random() - 0.5) * 30, indicator.y + (Math.random() - 0.5) * 30);
}
continue;
}
// Priority 2: Report dead bodies (simplified)
var foundDeadBody = false;
for (var i = 0; i < players.length; i++) {
var p = players[i];
if (p === player || p.isAlive) continue;
var dx = p.x - player.x;
var dy = p.y - player.y;
if (dx * dx + dy * dy < 22500) {
// 150 * 150
foundDeadBody = true;
if (Math.random() < 0.05) {
// Reduced chance
reportDeadBody();
return;
}
break;
}
}
if (foundDeadBody) continue;
// Priority 3: Complete assigned tasks (simplified)
if (player.assignedTasks && player.assignedTasks.length > 0) {
var nearTask = false;
for (var i = 0; i < player.assignedTasks.length; i++) {
var task = player.assignedTasks[i];
if (task.isCompleted) continue;
var dx = task.x - player.x;
var dy = task.y - player.y;
if (dx * dx + dy * dy < 10000) {
// 100 * 100
nearTask = true;
if (Math.random() < 0.1) {
task.completionProgress++;
if (task.completionProgress >= task.requiredClicks) {
task.complete();
player.completeTask();
completedTasks++;
updateTaskProgress();
}
}
break;
}
}
// Move towards task occasionally
if (!nearTask && Math.random() < 0.02) {
var incompleteTasks = player.assignedTasks.filter(function (task) {
return !task.isCompleted;
});
if (incompleteTasks.length > 0) {
var task = incompleteTasks[0];
player.moveTo(task.x + (Math.random() - 0.5) * 50, task.y + (Math.random() - 0.5) * 50);
}
}
}
}
// Impostor AI (simplified)
if (player.isImpostor && gamePhase === 'playing') {
// Try to kill nearby crew members
var foundTarget = false;
for (var i = 0; i < players.length; i++) {
var p = players[i];
if (p === player || !p.isAlive || p.isImpostor) continue;
var dx = p.x - player.x;
var dy = p.y - player.y;
if (dx * dx + dy * dy < 14400) {
// 120 * 120
foundTarget = true;
if (player.canKill() && Math.random() < 0.05) {
// Reduced chance
player.kill(p);
updatePlayerCount();
return;
}
break;
}
}
// Move occasionally
if (!foundTarget && Math.random() < 0.01) {
player.moveTo(300 + Math.random() * 1400, 300 + Math.random() * 2000);
}
}
}
}
// Initialize cameras and security room
function initializeCameras() {
// Create security cameras in key locations
var cameraPositions = [{
x: 400,
y: 500,
angle: 0
},
// Upper left electrical
{
x: 1600,
y: 500,
angle: Math.PI
},
// Upper right engine
{
x: 1024,
y: 800,
angle: Math.PI / 2
},
// Security/Admin area
{
x: 400,
y: 1500,
angle: 0
},
// Lower left reactor
{
x: 1600,
y: 1500,
angle: Math.PI
},
// Lower right medical
{
x: 1024,
y: 2000,
angle: Math.PI / 2
} // Lower corridor
];
for (var i = 0; i < cameraPositions.length; i++) {
var camPos = cameraPositions[i];
var camera = new Camera(camPos.x, camPos.y, i, camPos.angle);
cameras.push(camera);
game.addChild(camera);
}
// Create security room in the upper central area
securityRoom = new SecurityRoom(1024, 600);
game.addChild(securityRoom);
}
// Initialize sewers
function initializeSewers() {
// Create sewer entrances throughout the ship for room-to-room teleportation
var sewerPositions = [
// Command Bridge sewer
{
x: 950,
y: 350,
id: 0,
targetId: 8
},
// Communication Array sewer
{
x: 1550,
y: 450,
id: 1,
targetId: 9
},
// Security Room sewer
{
x: 1024,
y: 550,
id: 2,
targetId: 10
},
// Laboratory sewer
{
x: 200,
y: 1000,
id: 3,
targetId: 11
},
// Weapons Room sewer
{
x: 1850,
y: 1000,
id: 4,
targetId: 3
},
// Medical Bay sewer
{
x: 1300,
y: 1400,
id: 5,
targetId: 6
},
// Reactor Room sewer
{
x: 750,
y: 1400,
id: 6,
targetId: 5
},
// Central Corridor sewer
{
x: 1024,
y: 1366,
id: 7,
targetId: 12
},
// Maintenance Tunnels sewer
{
x: 300,
y: 1925,
id: 8,
targetId: 0
},
// Upper Left Electrical sewer
{
x: 350,
y: 650,
id: 9,
targetId: 1
},
// Lower Left Electrical sewer
{
x: 350,
y: 2100,
id: 10,
targetId: 2
},
// Upper Right Engine sewer
{
x: 1700,
y: 650,
id: 11,
targetId: 4
},
// Cargo Bay sewer
{
x: 1024,
y: 2450,
id: 12,
targetId: 7
},
// Storage Left sewer
{
x: 250,
y: 1366,
id: 13,
targetId: 14
},
// Storage Right sewer
{
x: 1800,
y: 1366,
id: 14,
targetId: 13
},
// Lower Engine Left sewer
{
x: 400,
y: 1800,
id: 15,
targetId: 16
},
// Lower Engine Right sewer
{
x: 1650,
y: 1800,
id: 16,
targetId: 15
}];
for (var i = 0; i < sewerPositions.length; i++) {
var pos = sewerPositions[i];
var sewer = new Sewer(pos.x, pos.y, pos.id, pos.targetId);
sewers.push(sewer);
game.addChild(sewer);
}
}
// Initialize doors
function initializeDoors() {
// Create doors to define proper room entrances and corridors
var doorPositions = [
// Command Bridge room doors
{
x: 850,
y: 450,
orientation: 0
},
// Bridge entrance left
{
x: 1200,
y: 450,
orientation: 0
},
// Bridge entrance right
// Communication Array room doors
{
x: 1400,
y: 525,
orientation: 1
},
// Comm room entrance
// Security room doors
{
x: 800,
y: 600,
orientation: 1
},
// Security entrance left
{
x: 1248,
y: 600,
orientation: 1
},
// Security entrance right
// Laboratory room doors
{
x: 450,
y: 900,
orientation: 0
},
// Lab entrance top
{
x: 450,
y: 1100,
orientation: 0
},
// Lab entrance bottom
// Weapons room doors
{
x: 1600,
y: 900,
orientation: 0
},
// Weapons entrance top
{
x: 1600,
y: 1100,
orientation: 0
},
// Weapons entrance bottom
// Central corridor access doors
{
x: 700,
y: 1000,
orientation: 1
},
// Left corridor access
{
x: 1350,
y: 1000,
orientation: 1
},
// Right corridor access
// Main corridor horizontal doors
{
x: 824,
y: 1300,
orientation: 0
},
// Central access left
{
x: 1224,
y: 1300,
orientation: 0
},
// Central access right
// Medical bay and reactor room doors
{
x: 1200,
y: 1400,
orientation: 0
},
// Medical entrance
{
x: 850,
y: 1400,
orientation: 0
},
// Reactor entrance
// Maintenance tunnels doors
{
x: 600,
y: 1850,
orientation: 0
},
// Maintenance entrance
// Lower corridor doors
{
x: 700,
y: 1900,
orientation: 1
},
// Lower left access
{
x: 1350,
y: 1900,
orientation: 1
},
// Lower right access
// Cargo bay doors
{
x: 800,
y: 2200,
orientation: 0
},
// Cargo entrance left
{
x: 1248,
y: 2200,
orientation: 0
},
// Cargo entrance right
// Engine room doors
{
x: 500,
y: 1700,
orientation: 1
},
// Left engine entrance
{
x: 1550,
y: 1700,
orientation: 1
},
// Right engine entrance
// Additional room separators
{
x: 1024,
y: 750,
orientation: 0
},
// Upper central passage
{
x: 1024,
y: 1550,
orientation: 0
},
// Lower central passage
// Storage room doors
{
x: 350,
y: 1200,
orientation: 1
},
// Left storage
{
x: 1700,
y: 1200,
orientation: 1
},
// Right storage
// Electrical room doors
{
x: 500,
y: 600,
orientation: 1
},
// Upper electrical
{
x: 500,
y: 1950,
orientation: 1
} // Lower electrical
];
for (var i = 0; i < doorPositions.length; i++) {
var pos = doorPositions[i];
var door = new Door(pos.x, pos.y, i, pos.orientation);
doors.push(door);
game.addChild(door);
}
}
// Common task areas that all players should have access to
var commonTaskAreas = [{
x: 1024,
y: 1366,
// Central emergency button area
taskType: 'emergency',
name: 'Emergency Protocols'
}, {
x: 1024,
y: 600,
// Security area
taskType: 'security',
name: 'Security Systems'
}];
// Initialize game
createWalls();
initializeCameras();
initializeDoors();
initializeSewers();
// Don't initialize players yet - wait for role selection
// Game controls
game.down = function (x, y, obj) {
if (gamePhase === 'playing' && currentPlayer && currentPlayer.isAlive) {
// Check if clicking on sabotage fix location
if (isSabotageActive && !currentPlayer.isImpostor) {
var clickedIndicator = null;
sabotageIndicators.forEach(function (indicator) {
var distance = Math.sqrt(Math.pow(x - indicator.x, 2) + Math.pow(y - indicator.y, 2));
if (distance < 80) {
clickedIndicator = indicator;
}
});
if (clickedIndicator) {
// Fix sabotage
fixSabotage();
return;
}
}
currentPlayer.moveTo(x, y);
}
};
game.update = function () {
if (gamePhase === 'roleSelection') {
// Don't update game logic during role selection
return;
}
// Hide sabotage button if not impostor or not playing
if (!currentPlayer || !currentPlayer.isImpostor || gamePhase !== 'playing') {
if (sabotageButton && sabotageButton.visible) {
hideSabotageButton();
}
// Also hide individual sabotage buttons
hideIndividualSabotageButtons();
}
// Hide report button if not playing
if (!currentPlayer || !currentPlayer.isAlive || gamePhase !== 'playing') {
if (reportButton && reportButton.visible) {
hideReportButton();
}
}
// Hide task button if not playing or is impostor
if (!currentPlayer || !currentPlayer.isAlive || currentPlayer.isImpostor || gamePhase !== 'playing') {
if (taskButton && taskButton.visible) {
hideTaskButton();
}
}
// Hide mission panel elements if not crew member or not playing
if (!currentPlayer || !currentPlayer.isAlive || currentPlayer.isImpostor || gamePhase !== 'playing') {
if (missionToggleButton && missionToggleButton.visible) {
missionToggleButton.alpha = 0;
missionToggleButton.visible = false;
}
if (missionPanelVisible) {
missionPanelVisible = false;
missionPanel.visible = false;
missionPanelTitle.visible = false;
missionTaskTexts.forEach(function (text) {
if (text) text.visible = false;
});
}
}
// Hide camera button if not playing
if (!currentPlayer || !currentPlayer.isAlive || gamePhase !== 'playing') {
if (cameraButton && cameraButton.visible) {
hideCameraButton();
}
}
// Hide door button if not impostor or not playing
if (!currentPlayer || !currentPlayer.isAlive || !currentPlayer.isImpostor || gamePhase !== 'playing') {
if (doorButton && doorButton.visible) {
hideDoorButton();
}
}
// Check line of sight between two points (returns true if clear, false if blocked by wall)
function checkLineOfSight(x1, y1, x2, y2) {
var dx = x2 - x1;
var dy = y2 - y1;
var distance = Math.sqrt(dx * dx + dy * dy);
// If points are very close, assume line of sight is clear
if (distance < 10) return true;
// Check multiple points along the line
var steps = Math.ceil(distance / 20); // Check every 20 pixels
for (var i = 0; i <= steps; i++) {
var t = i / steps;
var checkX = x1 + dx * t;
var checkY = y1 + dy * t;
// Check if this point intersects with any wall
for (var j = 0; j < walls.length; j++) {
var wall = walls[j];
var wallLeft = wall.x - wall.width * (wall.scaleX || 1) / 2;
var wallRight = wall.x + wall.width * (wall.scaleX || 1) / 2;
var wallTop = wall.y - wall.height * (wall.scaleY || 1) / 2;
var wallBottom = wall.y + wall.height * (wall.scaleY || 1) / 2;
// Check if point is inside wall
if (checkX >= wallLeft && checkX <= wallRight && checkY >= wallTop && checkY <= wallBottom) {
return false; // Line of sight blocked
}
}
}
return true; // Line of sight clear
}
// Update visibility of all game objects based on line of sight - optimized for performance
function updateVisibility() {
if (!currentPlayer || !currentPlayer.isAlive) return;
var playerX = currentPlayer.x;
var playerY = currentPlayer.y;
var visionRange = 300; // How far player can see
var visionRangeSquared = visionRange * visionRange; // Avoid sqrt when possible
// Update player visibility
for (var i = 0; i < players.length; i++) {
var player = players[i];
if (player === currentPlayer) {
player.alpha = 1.0; // Always see yourself
continue;
}
// Use squared distance to avoid expensive sqrt
var dx = player.x - playerX;
var dy = player.y - playerY;
var distanceSquared = dx * dx + dy * dy;
if (distanceSquared <= visionRangeSquared) {
player.alpha = 1.0; // Fully visible (simplified - no line of sight for performance)
} else {
player.alpha = 0.0; // Hidden
}
}
// Update task visibility (only for crew members) - simplified
if (!currentPlayer.isImpostor) {
for (var i = 0; i < tasks.length; i++) {
var task = tasks[i];
var dx = task.x - playerX;
var dy = task.y - playerY;
var distanceSquared = dx * dx + dy * dy;
if (distanceSquared <= visionRangeSquared) {
task.alpha = task.isCompleted ? 0.5 : 1.0; // Visible
} else {
task.alpha = 0.0; // Hidden
}
}
}
// Update other objects with simple distance check (no line of sight for performance)
// Update camera visibility
for (var i = 0; i < cameras.length; i++) {
var camera = cameras[i];
var dx = camera.x - playerX;
var dy = camera.y - playerY;
var distanceSquared = dx * dx + dy * dy;
camera.alpha = distanceSquared <= visionRangeSquared ? 1.0 : 0.0;
}
// Update door visibility
for (var i = 0; i < doors.length; i++) {
var door = doors[i];
var dx = door.x - playerX;
var dy = door.y - playerY;
var distanceSquared = dx * dx + dy * dy;
door.alpha = distanceSquared <= visionRangeSquared ? 1.0 : 0.0;
}
// Update sabotage indicator visibility
for (var i = 0; i < sabotageIndicators.length; i++) {
var indicator = sabotageIndicators[i];
var dx = indicator.x - playerX;
var dy = indicator.y - playerY;
var distanceSquared = dx * dx + dy * dy;
if (distanceSquared <= visionRangeSquared) {
// Preserve existing alpha for animations if visible
} else {
indicator.alpha = 0.0; // Hidden
}
}
// Update sewer visibility
for (var i = 0; i < sewers.length; i++) {
var sewer = sewers[i];
var dx = sewer.x - playerX;
var dy = sewer.y - playerY;
var distanceSquared = dx * dx + dy * dy;
sewer.alpha = distanceSquared <= visionRangeSquared ? 1.0 : 0.0;
}
// Update ghost visibility - ghosts are always somewhat visible but fade with distance
for (var i = 0; i < ghosts.length; i++) {
var ghost = ghosts[i];
var dx = ghost.x - playerX;
var dy = ghost.y - playerY;
var distanceSquared = dx * dx + dy * dy;
if (distanceSquared <= visionRangeSquared) {
// Ghosts maintain their alpha but are visible
ghost.visible = true;
} else {
// Ghosts fade out with distance but remain slightly visible
ghost.alpha = Math.max(ghost.alpha * 0.3, 0.1);
}
}
}
// Update camera to follow current player
if (currentPlayer && currentPlayer.isAlive && gamePhase === 'playing') {
// Calculate target camera position (center player on screen)
var targetX = 1024 - currentPlayer.x; // Center horizontally
var targetY = 1366 - currentPlayer.y; // Center vertically
// Smooth camera movement with lerp
var lerpFactor = 0.1;
var currentX = game.x;
var currentY = game.y;
game.x = currentX + (targetX - currentX) * lerpFactor;
game.y = currentY + (targetY - currentY) * lerpFactor;
// Clamp camera to keep game world bounds in view
var minX = 1024 - 1800; // Don't go past right edge
var maxX = 1024 - 200; // Don't go past left edge
var minY = 1366 - 2500; // Don't go past bottom edge
var maxY = 1366 - 200; // Don't go past top edge
game.x = Math.max(minX, Math.min(maxX, game.x));
game.y = Math.max(minY, Math.min(maxY, game.y));
// Update visibility less frequently for performance
if (LK.ticks % 6 === 0) {
// Update visibility 10 times per second instead of 60
updateVisibility();
}
// Update ghosts
for (var i = 0; i < ghosts.length; i++) {
var ghost = ghosts[i];
if (ghost && ghost.update) {
ghost.update();
}
}
}
// Update cooldowns
if (meetingCooldown > 0) {
meetingCooldown -= 16.67; // Roughly 1 frame at 60fps
}
// Update AI less frequently for performance
if (LK.ticks % 60 === 0) {
// Update AI once per second instead of twice per second
updateAI();
}
// Update current player impostor kill opportunities - optimized
if (currentPlayer && currentPlayer.isImpostor && gamePhase === 'playing') {
var nearestTarget = null;
var nearestDistanceSquared = 14400; // 120 * 120
// Find nearest target with optimized distance calculation
for (var i = 0; i < players.length; i++) {
var p = players[i];
if (p === currentPlayer || !p.isAlive || p.isImpostor) continue;
var dx = p.x - currentPlayer.x;
var dy = p.y - currentPlayer.y;
var distanceSquared = dx * dx + dy * dy;
if (distanceSquared < nearestDistanceSquared) {
nearestTarget = p;
nearestDistanceSquared = distanceSquared;
}
}
// Show kill button if there are targets nearby (regardless of cooldown)
if (nearestTarget) {
// Add visual indicator for kill opportunity
if (LK.ticks % 30 < 15) {
nearestTarget.alpha = 0.7;
} else {
nearestTarget.alpha = 1.0;
}
// Show kill button for closest target
if (!killButton.visible) {
showKillButton(nearestTarget);
}
// Update kill button opacity based on cooldown
if (currentPlayer.canKill()) {
killButton.alpha = 1.0;
// Hide cooldown timer when ready to kill
if (killCooldownTimer) {
killCooldownTimer.alpha = 0;
killCooldownTimer.visible = false;
}
} else {
killButton.alpha = 0.5; // Show button but dimmed during cooldown
// Show and update cooldown timer
if (killCooldownTimer) {
var cooldownLeft = Math.ceil((currentPlayer.killCooldown - (LK.ticks - currentPlayer.lastKillTime)) / 1000);
killCooldownTimer.setText(cooldownLeft + 's');
killCooldownTimer.alpha = 0.9;
killCooldownTimer.visible = true;
// Gentle pulsing effect during cooldown
if (LK.ticks % 60 < 30) {
killCooldownTimer.alpha = 0.9;
} else {
killCooldownTimer.alpha = 0.6;
}
}
}
} else {
// Hide kill button if no targets nearby
if (killButton.visible) {
hideKillButton();
}
// Reset target alphas
for (var i = 0; i < players.length; i++) {
var p = players[i];
if (!p.isImpostor) {
p.alpha = 1.0;
}
}
}
// Show sabotage button for impostors
if (!sabotageButton.visible) {
showSabotageButton();
}
}
// Update report button for all players near dead bodies - optimized
if (currentPlayer && currentPlayer.isAlive && gamePhase === 'playing') {
// Door button helper functions
var showDoorButton = function showDoorButton(door) {
if (doorButton && currentPlayer && currentPlayer.isImpostor) {
currentDoorTarget = door;
doorButton.alpha = 1;
doorButton.visible = true;
// Update button text based on door state
if (door.isOpen) {
doorButton.doorButtonText.setText('CLOSE');
} else {
doorButton.doorButtonText.setText('OPEN');
}
// Flash effect to draw attention
tween(doorButton, {
scaleX: 1.2,
scaleY: 1.2
}, {
duration: 200,
onFinish: function onFinish() {
tween(doorButton, {
scaleX: 1.0,
scaleY: 1.0
}, {
duration: 200
});
}
});
}
};
var _hideDoorButton = function _hideDoorButton() {
if (doorButton) {
currentDoorTarget = null;
doorButton.alpha = 0;
doorButton.visible = false;
}
}; // Update door button for impostors near doors (only if no sabotage active)
var nearestDeadBody = null;
var nearestDeadDistanceSquared = 22500; // 150 * 150
// Find nearest dead body with optimized distance calculation
for (var i = 0; i < players.length; i++) {
var p = players[i];
if (p === currentPlayer || p.isAlive) continue;
var dx = p.x - currentPlayer.x;
var dy = p.y - currentPlayer.y;
var distanceSquared = dx * dx + dy * dy;
if (distanceSquared < nearestDeadDistanceSquared) {
nearestDeadBody = p;
nearestDeadDistanceSquared = distanceSquared;
}
}
// Show report button if there are dead bodies nearby
if (nearestDeadBody) {
// Add visual indicator for dead body
if (LK.ticks % 60 < 30) {
nearestDeadBody.alpha = 0.5;
} else {
nearestDeadBody.alpha = 0.3;
}
// Show report button for closest dead body
if (!reportButton.visible) {
showReportButton(nearestDeadBody);
}
} else {
// Hide report button if no dead bodies nearby
if (reportButton.visible) {
hideReportButton();
}
}
// Update current task display for crew members and task button
if (currentPlayer && !currentPlayer.isImpostor && currentPlayer.isAlive) {
var nearbyTasks = currentPlayer.assignedTasks.filter(function (task) {
if (task.isCompleted) return false;
var distance = Math.sqrt(Math.pow(task.x - currentPlayer.x, 2) + Math.pow(task.y - currentPlayer.y, 2));
return distance < 100;
});
if (nearbyTasks.length > 0) {
var closestTask = nearbyTasks[0];
if (currentTaskText) {
currentTaskText.setText(closestTask.taskName);
currentTaskText.alpha = 0.8;
}
// Show task button for closest task
if (!taskButton.visible) {
showTaskButton(closestTask);
}
} else {
if (currentTaskText) {
currentTaskText.alpha = 0;
}
// Hide task button if no tasks nearby
if (taskButton.visible) {
hideTaskButton();
}
}
// Show mission toggle button for crew members
if (missionToggleButton) {
missionToggleButton.alpha = 0.8;
missionToggleButton.visible = true;
}
// Update mission panel if visible
if (missionPanelVisible && LK.ticks % 60 === 0) {
updateMissionPanel();
}
}
// Update camera button for all players near security room
if (currentPlayer && currentPlayer.isAlive && gamePhase === 'playing' && securityRoom) {
var distanceToSecurity = Math.sqrt(Math.pow(currentPlayer.x - securityRoom.x, 2) + Math.pow(currentPlayer.y - securityRoom.y, 2));
// Show camera button if near security room
if (distanceToSecurity < 150) {
// Show camera button for security room access
if (!cameraButton.visible) {
showCameraButton();
}
} else {
// Hide camera button if not near security room
if (cameraButton.visible) {
hideCameraButton();
}
}
}
// Update sabotage system
if (isSabotageActive) {
// Update sabotage timer
sabotageTimer -= 16.67; // Roughly 1 frame at 60fps
// Check if sabotage timer expired (crew loses)
if (sabotageTimer <= 0) {
endGame('impostors');
return;
}
// Update sabotage timer display
var secondsLeft = Math.ceil(sabotageTimer / 1000);
var currentText = phaseText.text || '';
phaseText.setText(currentText.split('(')[0] + '(' + secondsLeft + 's)');
// Check if player is near sabotage fix locations
if (currentPlayer && currentPlayer.isAlive && !currentPlayer.isImpostor) {
sabotageIndicators.forEach(function (indicator) {
var distance = Math.sqrt(Math.pow(currentPlayer.x - indicator.x, 2) + Math.pow(currentPlayer.y - indicator.y, 2));
if (distance < 80) {
// Player is close to fix location
if (LK.ticks % 30 < 15) {
indicator.alpha = 0.3;
} else {
indicator.alpha = 1.0;
}
// Allow fixing with click/tap
indicator.canFix = true;
} else {
indicator.canFix = false;
}
});
}
// Flash sabotage effect for critical sabotages
if (activeSabotage === 'reactor' || activeSabotage === 'oxygen') {
if (LK.ticks % 60 < 30) {
sabotageEffect.alpha = Math.min(sabotageEffect.alpha * 1.2, 0.8);
} else {
sabotageEffect.alpha = Math.max(sabotageEffect.alpha * 0.8, 0.3);
}
}
}
if (currentPlayer && currentPlayer.isAlive && currentPlayer.isImpostor && gamePhase === 'playing' && !isSabotageActive) {
var nearbyDoors = doors.filter(function (door) {
var distance = Math.sqrt(Math.pow(currentPlayer.x - door.x, 2) + Math.pow(currentPlayer.y - door.y, 2));
return distance < 100;
});
// Show door button if near doors
if (nearbyDoors.length > 0) {
var closestDoor = nearbyDoors[0];
// Show door button for closest door
if (!doorButton.visible) {
showDoorButton(closestDoor);
} else if (currentDoorTarget !== closestDoor) {
// Update target if different door is closer
showDoorButton(closestDoor);
}
} else {
// Hide door button if not near any doors
if (doorButton.visible) {
_hideDoorButton();
}
}
}
}
};
crewmate among us. In-Game asset. 2d. High contrast. No shadows
electricalTask. In-Game asset. 2d. High contrast. No shadows
emergencyButton. In-Game asset. 2d. High contrast. No shadows
voteButton among us. In-Game asset. 2d. High contrast. No shadows
killButton among us. In-Game asset. 2d. High contrast. No shadows
medicalTask. In-Game asset. 2d. High contrast. No shadows
engineTask among us. In-Game asset. 2d. High contrast. No shadows
navigationTask among us. In-Game asset. 2d. High contrast. No shadows
shipFloor casella. In-Game asset. 2d. High contrast. No shadows
shiphall among us. In-Game asset. 2d. High contrast. No shadows
among us task. In-Game asset. 2d. High contrast. No shadows
wall among us. In-Game asset. 2d. High contrast. No shadows
taskCompleted among us. In-Game asset. 2d. High contrast. No shadows
reactorTask among us. In-Game asset. 2d. High contrast. No shadows
taskButton among us. In-Game asset. 2d. High contrast. No shadows
sabotageButton among us. In-Game asset. 2d. High contrast. No shadows
reportbutton among us. In-Game asset. 2d. High contrast. No shadows
cameraView among us. In-Game asset. 2d. High contrast. No shadows
cameraFrame among us. In-Game asset. 2d. High contrast. No shadows
cameraButton among us. In-Game asset. 2d. High contrast. No shadows
cablePanel among us. In-Game asset. 2d. High contrast. No shadows
cableWire among us. In-Game asset. 2d. High contrast. No shadows
among us door. In-Game asset. 2d. High contrast. No shadows
among us doorButton. In-Game asset. 2d. High contrast. No shadows
among us doorClosed. In-Game asset. 2d. High contrast. No shadows
among us sabotageEffect. In-Game asset. 2d. High contrast. No shadows
among us sabotageIndicator. In-Game asset. 2d. High contrast. No shadows
among us cableConnector. In-Game asset. 2d. High contrast. No shadows
among us sabotageReactorButton. In-Game asset. 2d. High contrast. No shadows
among us sabotageOxygenButton. In-Game asset. 2d. High contrast. No shadows
among us sabotageElectricityButton. In-Game asset. 2d. High contrast. No shadows
among us sabotageDoorsButton. In-Game asset. 2d. High contrast. No shadows
among us sabotageConnectionsButton. In-Game asset. 2d. High contrast. No shadows
among us sewerEntrance. In-Game asset. 2d. High contrast. No shadows