385 lines
9.2 KiB
JavaScript
385 lines
9.2 KiB
JavaScript
// Name displayed for unassigned player slots
|
|
const NAME_UNASSIGNED = "[color=\"90 90 90 255\"][unassigned]";
|
|
|
|
// Is this is a networked game, or offline
|
|
var g_IsNetworked;
|
|
|
|
// Is this user in control of game settings (i.e. is a network server, or offline player)
|
|
var g_IsController;
|
|
|
|
// Are we currently updating the GUI in response to network messages instead of user input
|
|
// (and therefore shouldn't send further messages to the network)
|
|
var g_IsInGuiUpdate;
|
|
|
|
var g_PlayerAssignments = {};
|
|
|
|
// Default game setup attributes
|
|
var g_GameAttributes = { "map": "" };
|
|
|
|
// Number of players for currently selected map
|
|
var g_MaxPlayers = 8;
|
|
|
|
var g_ChatMessages = [];
|
|
|
|
// Cache of output from Engine.LoadMapData
|
|
var g_MapData = {};
|
|
|
|
function init(attribs)
|
|
{
|
|
switch (attribs.type)
|
|
{
|
|
case "offline":
|
|
g_IsNetworked = false;
|
|
g_IsController = true;
|
|
break;
|
|
case "server":
|
|
g_IsNetworked = true;
|
|
g_IsController = true;
|
|
break;
|
|
case "client":
|
|
g_IsNetworked = true;
|
|
g_IsController = false;
|
|
break;
|
|
default:
|
|
error("Unexpected 'type' in gamesetup init: "+attribs.type);
|
|
}
|
|
|
|
// Set a default map
|
|
if (attribs.type == "offline")
|
|
g_GameAttributes.map = "Arcadia";
|
|
else
|
|
g_GameAttributes.map = "Multiplayer_demo";
|
|
|
|
initMapNameList(getGUIObjectByName("mapSelection"));
|
|
|
|
// If we're a network client, disable all the map controls
|
|
// TODO: make them look visually disabled so it's obvious why they don't work
|
|
if (!g_IsController)
|
|
{
|
|
getGUIObjectByName("mapSelection").enabled = false;
|
|
for (var i = 0; i < g_MaxPlayers; ++i)
|
|
getGUIObjectByName("playerAssignment["+i+"]").enabled = false;
|
|
}
|
|
|
|
// Set up offline-only bits:
|
|
if (!g_IsNetworked)
|
|
{
|
|
getGUIObjectByName("chatPanel").hidden = true;
|
|
|
|
g_PlayerAssignments = { "local": { "name": "You", "player": 1 } };
|
|
}
|
|
|
|
updatePlayerList();
|
|
|
|
getGUIObjectByName("chatInput").focus();
|
|
}
|
|
|
|
function cancelSetup()
|
|
{
|
|
Engine.DisconnectNetworkGame();
|
|
}
|
|
|
|
function onTick()
|
|
{
|
|
while (true)
|
|
{
|
|
var message = Engine.PollNetworkClient();
|
|
if (!message)
|
|
break;
|
|
handleNetMessage(message);
|
|
}
|
|
}
|
|
|
|
function handleNetMessage(message)
|
|
{
|
|
log("Net message: "+uneval(message));
|
|
|
|
switch (message.type)
|
|
{
|
|
case "netstatus":
|
|
switch (message.status)
|
|
{
|
|
case "disconnected":
|
|
Engine.DisconnectNetworkGame();
|
|
Engine.PopGuiPage();
|
|
reportDisconnect(message.reason);
|
|
break;
|
|
|
|
default:
|
|
error("Unrecognised netstatus type "+message.status);
|
|
break;
|
|
}
|
|
break;
|
|
|
|
case "gamesetup":
|
|
if (message.data) // (the host gets undefined data on first connect, so skip that)
|
|
g_GameAttributes = message.data;
|
|
|
|
onGameAttributesChange();
|
|
break;
|
|
|
|
case "players":
|
|
// Find and report all joinings/leavings
|
|
for (var host in message.hosts)
|
|
if (! g_PlayerAssignments[host])
|
|
addChatMessage({ "type": "connect", "username": message.hosts[host].name });
|
|
for (var host in g_PlayerAssignments)
|
|
if (! message.hosts[host])
|
|
addChatMessage({ "type": "disconnect", "username": g_PlayerAssignments[host].name });
|
|
// Update the player list
|
|
g_PlayerAssignments = message.hosts;
|
|
updatePlayerList();
|
|
break;
|
|
|
|
case "start":
|
|
Engine.SwitchGuiPage("page_loading.xml", { "attribs": g_GameAttributes });
|
|
break;
|
|
|
|
case "chat":
|
|
addChatMessage({ "type": "message", "username": message.username, "text": message.text });
|
|
break;
|
|
|
|
default:
|
|
error("Unrecognised net message type "+message.type);
|
|
}
|
|
}
|
|
|
|
// Convert map .xml filename into displayed name
|
|
function getMapDisplayName(filename)
|
|
{
|
|
var name = filename;
|
|
|
|
// Replace "_" with " " (so we can avoid spaces in filenames)
|
|
name = name.replace(/_/g, " ");
|
|
|
|
return name;
|
|
}
|
|
|
|
// Initialise the list control containing all the available maps
|
|
function initMapNameList(object)
|
|
{
|
|
var mapPath = "maps/scenarios/"
|
|
|
|
// Get a list of map filenames
|
|
var mapFiles = buildDirEntList(mapPath, "*.xml", false);
|
|
|
|
// Remove the path and extension from each name, since we just want the filename
|
|
mapFiles = [ n.substring(mapPath.length, n.length-4) for each (n in mapFiles) ];
|
|
|
|
// Remove any files starting with "_" (these are for special maps used by the engine/editor)
|
|
mapFiles = [ n for each (n in mapFiles) if (n[0] != "_") ];
|
|
|
|
var mapList = [ { "name": getMapDisplayName(n), "file": n } for each (n in mapFiles) ];
|
|
|
|
// Alphabetically sort the list, ignoring case
|
|
mapList.sort(function (x, y) {
|
|
var lowerX = x.name.toLowerCase();
|
|
var lowerY = y.name.toLowerCase();
|
|
if (lowerX < lowerY) return -1;
|
|
else if (lowerX > lowerY) return 1;
|
|
else return 0;
|
|
});
|
|
|
|
var mapListNames = [ n.name for each (n in mapList) ];
|
|
var mapListFiles = [ n.file for each (n in mapList) ];
|
|
|
|
// Select the default map
|
|
var selected = mapListFiles.indexOf(g_GameAttributes.map);
|
|
// Default to the first element if we can't find the one we searched for
|
|
if (selected == -1)
|
|
selected = 0;
|
|
|
|
// Update the list control
|
|
object.list = mapListNames;
|
|
object.list_data = mapListFiles;
|
|
object.selected = selected;
|
|
}
|
|
|
|
function loadMapData(name)
|
|
{
|
|
if (!(name in g_MapData))
|
|
g_MapData[name] = Engine.LoadMapData(name);
|
|
|
|
return g_MapData[name];
|
|
}
|
|
|
|
// Called when the user selects a map from the list
|
|
function selectMap(name)
|
|
{
|
|
// Avoid recursion
|
|
if (g_IsInGuiUpdate)
|
|
return;
|
|
|
|
// Network clients can't change map
|
|
if (g_IsNetworked && !g_IsController)
|
|
return;
|
|
|
|
g_GameAttributes.map = name;
|
|
|
|
if (g_IsNetworked)
|
|
Engine.SetNetworkGameAttributes(g_GameAttributes);
|
|
else
|
|
onGameAttributesChange();
|
|
}
|
|
|
|
function onGameAttributesChange()
|
|
{
|
|
g_IsInGuiUpdate = true;
|
|
|
|
var mapName = g_GameAttributes.map;
|
|
|
|
var mapSelectionBox = getGUIObjectByName("mapSelection");
|
|
var mapIdx = mapSelectionBox.list_data.indexOf(mapName);
|
|
mapSelectionBox.selected = mapIdx;
|
|
|
|
getGUIObjectByName("mapInfoName").caption = getMapDisplayName(mapName);
|
|
|
|
var mapData = loadMapData(mapName);
|
|
var mapSettings = (mapData && mapData.settings ? mapData.settings : {});
|
|
|
|
// Load the description from the map file, if there is one
|
|
var description = mapSettings.Description || "Sorry, no description available.";
|
|
|
|
// Describe the number of players
|
|
var playerString = "";
|
|
if (mapSettings.NumPlayers)
|
|
playerString = mapSettings.NumPlayers + " " + (mapSettings.NumPlayers == 1 ? "player" : "players") + ". ";
|
|
|
|
getGUIObjectByName("mapInfoDescription").caption = playerString + description;
|
|
|
|
g_IsInGuiUpdate = false;
|
|
}
|
|
|
|
function launchGame()
|
|
{
|
|
if (g_IsNetworked && !g_IsController)
|
|
{
|
|
error("Only host can start game");
|
|
return;
|
|
}
|
|
|
|
if (g_IsNetworked)
|
|
{
|
|
Engine.SetNetworkGameAttributes(g_GameAttributes);
|
|
Engine.StartNetworkGame();
|
|
}
|
|
else
|
|
{
|
|
// Find the player ID which the user has been assigned to
|
|
var playerID = -1;
|
|
for (var i = 0; i < g_MaxPlayers; ++i)
|
|
{
|
|
var assignBox = getGUIObjectByName("playerAssignment["+i+"]");
|
|
if (assignBox.selected == 1)
|
|
playerID = i+1;
|
|
}
|
|
Engine.StartGame(g_GameAttributes, playerID);
|
|
Engine.PushGuiPage("page_loading.xml", { "attribs": g_GameAttributes });
|
|
}
|
|
}
|
|
|
|
function updatePlayerList()
|
|
{
|
|
g_IsInGuiUpdate = true;
|
|
|
|
var boxSpacing = 32;
|
|
|
|
var hostNameList = [NAME_UNASSIGNED];
|
|
var hostGuidList = [""];
|
|
var assignments = [];
|
|
|
|
for (var guid in g_PlayerAssignments)
|
|
{
|
|
var name = g_PlayerAssignments[guid].name;
|
|
var hostID = hostNameList.length;
|
|
hostNameList.push(name);
|
|
hostGuidList.push(guid);
|
|
assignments[g_PlayerAssignments[guid].player] = hostID;
|
|
}
|
|
|
|
for (var i = 0; i < g_MaxPlayers; ++i)
|
|
{
|
|
var box = getGUIObjectByName("playerBox["+i+"]");
|
|
var boxSize = box.size;
|
|
var h = boxSize.bottom - boxSize.top;
|
|
boxSize.top = i * boxSpacing;
|
|
boxSize.bottom = i * boxSpacing + h;
|
|
box.size = boxSize;
|
|
|
|
getGUIObjectByName("playerName["+i+"]").caption = "Player "+(i+1);
|
|
|
|
var assignBox = getGUIObjectByName("playerAssignment["+i+"]");
|
|
assignBox.list = hostNameList;
|
|
var selection = (assignments[i+1] || 0);
|
|
if (assignBox.selected != selection)
|
|
assignBox.selected = selection;
|
|
|
|
let playerID = i+1;
|
|
if (g_IsNetworked && g_IsController)
|
|
{
|
|
assignBox.onselectionchange = function ()
|
|
{
|
|
if (!g_IsInGuiUpdate)
|
|
Engine.AssignNetworkPlayer(playerID, hostGuidList[this.selected]);
|
|
};
|
|
}
|
|
else if (!g_IsNetworked)
|
|
{
|
|
assignBox.onselectionchange = function ()
|
|
{
|
|
if (!g_IsInGuiUpdate)
|
|
{
|
|
// If we didn't just select "unassigned", update the selected host's ID
|
|
if (this.selected > 0)
|
|
g_PlayerAssignments[hostGuidList[this.selected]].player = playerID;
|
|
|
|
updatePlayerList();
|
|
}
|
|
};
|
|
}
|
|
}
|
|
|
|
g_IsInGuiUpdate = false;
|
|
}
|
|
|
|
function submitChatInput()
|
|
{
|
|
var input = getGUIObjectByName("chatInput");
|
|
var text = input.caption;
|
|
if (text.length)
|
|
{
|
|
Engine.SendNetworkChat(text);
|
|
input.caption = "";
|
|
}
|
|
}
|
|
|
|
function addChatMessage(msg)
|
|
{
|
|
// TODO: we ought to escape all values before displaying them,
|
|
// to prevent people inserting colours and newlines etc
|
|
|
|
var formatted;
|
|
switch (msg.type)
|
|
{
|
|
case "connect":
|
|
formatted = '[font="serif-bold-13"][color="255 0 0"]' + msg.username + '[/color][/font] [color="64 64 64"]has joined[/color]';
|
|
break;
|
|
|
|
case "disconnect":
|
|
formatted = '[font="serif-bold-13"][color="255 0 0"]' + msg.username + '[/color][/font] [color="64 64 64"]has left[/color]';
|
|
break;
|
|
|
|
case "message":
|
|
formatted = '[font="serif-bold-13"]<[color="255 0 0"]' + msg.username + '[/color]>[/font] ' + msg.text;
|
|
break;
|
|
|
|
default:
|
|
error("Invalid chat message '" + uneval(msg) + "'");
|
|
return;
|
|
}
|
|
|
|
g_ChatMessages.push(formatted);
|
|
|
|
getGUIObjectByName("chatText").caption = g_ChatMessages.join("\n");
|
|
}
|