/****
* 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