1
0
forked from 0ad/0ad

Multiplayer lobby based on the XmPP protocol. Special thanks to Philip, alpha123, scythetwirler, and anyone else who has helped build, debug or test the lobby.

This was SVN commit r14098.
This commit is contained in:
JoshuaJB 2013-11-07 20:07:24 +00:00
parent d7121f4f55
commit bffe917914
79 changed files with 5906 additions and 369 deletions

View File

@ -303,6 +303,7 @@ hotkey.session.showstatusbars = Tab ; Toggle display of status bars
; > HOTKEYS ONLY
hotkey.chat = Return ; Toggle chat window
hotkey.teamchat = "T" ; Toggle chat window in team chat mode
hotkey.complete.playername = "Alt+C" ; Complete a player's name
; > GUI TEXTBOX HOTKEYS
hotkey.text.delete.left = "Ctrl+Backspace" ; Delete word to the left of cursor
@ -347,3 +348,8 @@ gui.session.minimap.blinkduration = 1.7 ; The blink duration while pingi
; GENERAL GUI SETTINGS
gui.cursorblinkrate = 0.5 ; Cursor blink rate in seconds (0.0 to disable blinking)
; Multiplayer lobby preferences
lobby.server = "lobby.wildfiregames.com" ; Address of lobby server
lobby.xpartamupp = "xpartamupp" ; Name of the server-side xmpp client that manage games
lobby.chattimestamp = false ; Show time chat message was posted.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -243,6 +243,15 @@
textcolor_selected="gold"
/>
<style name="StonePasswordInput"
sprite="BackgroundIndentFillDark"
sprite_selectarea="BackgroundRed"
textcolor="white"
textcolor_selected="gold"
mask="true"
mask_char="*"
/>
<style name="StoneDropDown"
dropdown_buffer="1"
font="serif-bold-stroke-14"

View File

@ -1,6 +1,6 @@
/*
DESCRIPTION : Generic utility functions.
NOTES :
NOTES :
*/
// ====================================================================
@ -11,7 +11,7 @@ function getRandom(randomMin, randomMax)
// NOTE: There should probably be an engine function for this,
// since we'd need to keep track of random seeds for replays.
var randomNum = randomMin + (randomMax-randomMin)*Math.random(); // num is random, from A to B
var randomNum = randomMin + (randomMax-randomMin)*Math.random(); // num is random, from A to B
return Math.round(randomNum);
}
@ -23,7 +23,7 @@ function getXMLFileList(pathname)
var files = buildDirEntList(pathname, "*.xml", true);
var result = [];
// Get only subpath from filename and discard extension
for (var i = 0; i < files.length; ++i)
{
@ -32,7 +32,7 @@ function getXMLFileList(pathname)
// Split path into directories so we can check for beginning _ character
var tokens = file.split("/");
if (tokens[tokens.length-1][0] != "_")
result.push(file);
}
@ -47,7 +47,7 @@ function getJSONFileList(pathname)
{
var files = buildDirEntList(pathname, "*.json", false);
// Remove the path and extension from each name, since we just want the filename
// Remove the path and extension from each name, since we just want the filename
files = [ n.substring(pathname.length, n.length-5) for each (n in files) ];
return files;
@ -60,7 +60,7 @@ function getJSONFileList(pathname)
function parseJSONData(pathname)
{
var data = {};
var rawData = readFile(pathname);
if (!rawData)
{
@ -74,15 +74,15 @@ function parseJSONData(pathname)
data = JSON.parse(rawData);
if (!data)
error("Failed to parse JSON data in: "+pathname+" (check for valid JSON data)");
}
catch(err)
{
error(err.toString()+": parsing JSON data in "+pathname);
}
}
return data;
}
@ -93,7 +93,7 @@ function sortNameIgnoreCase(x, y)
{
var lowerX = x.name.toLowerCase();
var lowerY = y.name.toLowerCase();
if (lowerX < lowerY)
return -1;
else if (lowerX > lowerY)
@ -110,12 +110,12 @@ function escapeText(text)
{
if (!text)
return text;
var out = text.replace(/[\[\]]+/g,"");
out = out.replace(/\s+/g, " ");
return out.substr(0, 255);
}
// ====================================================================
@ -128,7 +128,7 @@ function toTitleCase (string)
// Returns the title-case version of a given string.
string = string.toString();
string = string[0].toUpperCase() + string.substring(1).toLowerCase();
return string;
}
@ -180,6 +180,7 @@ function initPlayerDefaults()
function initMapSizes()
{
var sizes = {
"shortNames":[],
"names":[],
"tiles": [],
"default": 0
@ -192,6 +193,7 @@ function initMapSizes()
{
for (var i = 0; i < data.Sizes.length; ++i)
{
sizes.shortNames.push(data.Sizes[i].Name);
sizes.names.push(data.Sizes[i].LongName);
sizes.tiles.push(data.Sizes[i].Tiles);
@ -241,7 +243,7 @@ function iColorToString(color)
var string = "0 0 0";
if (color && ("r" in color) && ("g" in color) && ("b" in color))
string = color.r + " " + color.g + " " + color.b;
return string;
}
@ -289,4 +291,23 @@ function shuffleArray(source)
result[j] = source[i];
}
return result;
}
}
// ====================================================================
// Filter out conflicting characters and limit the length of a given name.
// @param name Name to be filtered.
// @param stripUnicode Whether or not to remove unicode characters.
// @param stripSpaces Whether or not to remove whitespace.
function sanitizePlayerName(name, stripUnicode, stripSpaces)
{
// We delete the '[', ']' characters (GUI tags) and delete the ',' characters (player name separators) by default.
var sanitizedName = name.replace(/[\[\],]/g, "");
// Optionally strip unicode
if (stripUnicode)
sanitizedName = sanitizedName.replace(/[^\x20-\x7f]/g, "");
// Optionally strip whitespace
if (stripSpaces)
sanitizedName = sanitizedName.replace(/\s/g, "");
// Limit the length to 20 characters
return sanitizedName.substr(0,20);
}

View File

@ -0,0 +1,16 @@
<?xml version="1.0" encoding="iso-8859-1" standalone="no" ?>
<!DOCTYPE setup SYSTEM "..\..\gui.dtd">
<setup>
==========================================
- SCROLLBARS -
==========================================
<scrollbar name="ModernScrollBar"
width = "15"
minimum_bar_size = "30"
maximum_bar_size = "30"
show_edge_buttons = "false"
sprite_back_vertical = "ModernScrollBack"
sprite_bar_vertical = "ModernScrollBar"
/>
</setup>

View File

@ -0,0 +1,255 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
- This file contains the sprites for the 'modern' style GUI.
-
-->
<sprites>
<!--
==========================================
- Scrollbar -
==========================================
-->
<sprite name = "ModernScrollBack">
<image texture = "global/modern/scrollback.png"
real_texture_placement = "0 0 15 128"
size = "0 0 100% 100%"
/>
</sprite>
<sprite name = "ModernScrollBar">
<image texture = "global/modern/scrollbar.png"
real_texture_placement = "0 0 15 15"
size = "0 0 100% 100%"
/>
</sprite>
<!--
==========================================
- Window -
==========================================
-->
<sprite name = "ModernWindow">
<!-- background -->
<image backcolor = "12 12 12"/>
<image texture = "global/modern/background.png"
texture_size = "0 0 1024 1024"
size = "12 22 100%-12 100%-12"
/>
<!-- shading -->
<!-- we just mirror the same texture on the top and bottom -->
<image texture = "global/modern/shadow-high.png"
texture_size = "0 0 1024 256"
size = "12 100%-268 100%-12 100%-12"
/>
<image texture = "global/modern/shadow-high.png"
texture_size = "1024 256 0 0"
size = "12 22 100%-12 268"
/>
<!-- top and bottom edge -->
<image texture = "global/modern/border.png"
real_texture_placement = "0 0 2048 8"
size = "10 18 100%-10 26"
/>
<image texture = "global/modern/border.png"
real_texture_placement = "0 0 2048 8"
size = "10 100%-16 100%-10 100%-8"
/>
<!-- title frame -->
<image texture = "global/modern/titlebar-middle.png"
real_texture_placement = "0 0 128 32"
size = "50%-72 4 50%+72 37"
/>
<image texture = "global/modern/titlebar-left.png"
real_texture_placement = "0 0 32 32"
size = "50%-98 4 50%-66 37"
/>
<image texture = "global/modern/titlebar-left.png"
real_texture_placement = "32 32 0 0"
size = "50%+66 4 50%+98 37"
/>
</sprite>
<!--
==========================================
- Dialog -
==========================================
-->
<sprite name="ModernDialog">
<!-- background -->
<image texture = "global/modern/background.png"
texture_size = "0 0 512 512"
size = "4 0 100%-4 100%-4"
/>
<!-- shading -->
<!-- we just mirror the same texture on the top and bottom -->
<image texture = "global/modern/shadow-low.png"
texture_size = "0 0 1024 128"
size = "4 100%-132 100%-4 100%-4"
/>
<image texture = "global/modern/shadow-low.png"
texture_size = "1024 128 0 0"
size = "4 0 100%-4 128"
/>
<!-- top and bottom edge -->
<image texture = "global/modern/border.png"
real_texture_placement = "0 0 2048 8"
size = "4 0%-4 100%-4 4"
/>
<image texture = "global/modern/border.png"
real_texture_placement = "0 0 2048 8"
size = "4 100%-8 100%-4 100%"
/>
<!-- corners -->
<image texture = "global/modern/dialog-deco-top.png"
real_texture_placement = "0 0 64 32"
texture_size = "64 0 0 32"
size = "-14 -21 50 11"
/>
<image texture = "global/modern/dialog-deco-top.png"
real_texture_placement = "0 0 64 32"
texture_size = "0 0 64 32"
size = "100%-50 -21 100%+14 11"
/>
<image texture = "global/modern/dialog-deco-bottom.png"
real_texture_placement = "0 0 64 32"
texture_size = "64 0 0 32"
size = "-31 100%-23 33 100%+9"
/>
<image texture = "global/modern/dialog-deco-bottom.png"
real_texture_placement = "0 0 64 32"
texture_size = "0 0 64 32"
size = "100%-31 100%-23 100%+33 100%+9"
/>
<!-- title frame -->
<image texture = "global/modern/titlebar-middle.png"
real_texture_placement = "0 0 128 32"
size = "50%-72 0%-18 50%+72 15"
/>
<image texture = "global/modern/titlebar-left.png"
real_texture_placement = "0 0 32 32"
size = "50%-98 0%-18 50%-66 15"
/>
<image texture = "global/modern/titlebar-left.png"
real_texture_placement = "32 32 0 0"
size = "50%+66 0%-18 50%+98 15"
/>
</sprite>
<!--
==========================================
- Box -
==========================================
-->
<sprite name="ModernDarkBoxGold">
<!-- borders -->
<image texture = "global/modern/gold-separator.png"
real_texture_placement = "0 0 806 1"
size = "0 0 100% 1"
/>
<image texture = "global/modern/gold-separator.png"
real_texture_placement = "0 0 806 1"
size = "0 100%-1 100% 100%"
/>
<!-- background -->
<image backcolor = "12 12 12 100"
size = "0 1 100% 100%-1"
/>
</sprite>
<sprite name="ModernDarkBoxWhite">
<!-- borders -->
<image texture = "global/modern/white-separator.png"
real_texture_placement = "0 0 806 1"
size = "0 0 100% 1"
/>
<image texture = "global/modern/white-separator.png"
real_texture_placement = "0 0 806 1"
size = "0 100%-1 100% 100%"
/>
<!-- background -->
<image backcolor = "12 12 12 100"
size = "0 1 100% 100%-1"
/>
</sprite>
<sprite name = "ModernDarkBoxGoldNoBottom">
<image texture = "global/modern/gold-separator.png"
real_texture_placement = "0 0 806 1"
size = "0 0 100% 1"
/>
<image backcolor = "12 12 12 100"
size = "0 1 100% 100%"
/>
</sprite>
<sprite name = "ModernDarkBoxGoldNoTop">
<image texture = "global/modern/gold-separator.png"
real_texture_placement = "0 0 806 1"
size = "0 100%-1 100% 100%"
/>
<image backcolor = "12 12 12 100"
size = "0 0 100% 100%-1"
/>
</sprite>
<!--
==========================================
- Shading -
==========================================
-->
<sprite name = "ModernItemBackShadeLeft">
<image texture = "global/modern/item-shading-left.png"
real_texture_placement = "108 0 256 32"
/>
</sprite>
<sprite name = "ModernItemBackShadeRight">
<image texture = "global/modern/item-shading-right.png"
real_texture_placement = "0 0 148 32"
/>
</sprite>
<!-- A translucent black background for behind dialogs. -->
<sprite name = "modernFade">
<image backcolor="0 0 0 85"
size="0 0 100% 100%"
/>
</sprite>
<!--
==========================================
- Lines -
==========================================
-->
<sprite name = "ModernGoldLine">
<image texture = "global/modern/gold-separator.png"
real_texture_placement = "0 0 806 1"
/>
</sprite>
<sprite name="ModernWhiteLine">
<image texture = "global/modern/white-separator.png"
real_texture_placement = "0 0 806 1"
/>
</sprite>
<!--
==========================================
- Misc. -
==========================================
-->
<sprite name = "ModernDropDownArrow">
<image texture = "global/modern/dropdown-arrow.png"
real_texture_placement = "0 0 16 16"
/>
</sprite>
<sprite name = "ModernDropDownArrowHighlight">
<image texture = "global/modern/dropdown-arrow.png"
real_texture_placement = "0 0 16 16"
/>
</sprite>
</sprites>

View File

@ -0,0 +1,81 @@
<?xml version = "1.0" encoding = "utf-8"?>
<styles>
<style name = "ModernWindow"
sprite = "ModernWindow"
buffer_zone = "12"
text_align = "left"
text_valign = "top"
/>
<style name = "ModernDialog"
sprite = "ModernDialog"
buffer_zone = "12"
text_align = "left"
text_valign = "top"
/>
<style name = "ModernList"
buffer_zone = "5"
font = "serif-bold-stroke-14"
scrollbar = "true"
scrollbar_style = "ModernScrollBar"
sprite = "ModernDarkBoxGoldNoTop"
sprite_selectarea = "ModernDarkBoxWhite"
sprite_heading = "ModernDarkBoxGoldNoBottom"
textcolor = "white"
textcolor_selected = "white"
text_align = "left"
text_valign = "center"
sound_selected = "audio/interface/ui/ui_button_click.ogg"
/>
<style name="ModernDropDown"
dropdown_buffer="1"
font="serif-14"
textcolor="white"
text_align="left"
text_valign="center"
sprite="colour:0 0 0"
button_width="16"
sprite2="ModernDropDownArrow"
sprite2_pressed="ModernDropDownArrowHighlight"
buffer_zone="5"
dropdown_size="200"
sprite_list="colour:12 12 12"
sprite_selectarea="ModernDarkBoxWhite"
textcolor_selected="white"
scrollbar="true"
scrollbar_style="ModernScrollBar"
sound_opened="audio/interface/ui/ui_button_click.ogg"
sound_closed="audio/interface/ui/ui_button_click.ogg"
sound_selected="audio/interface/ui/ui_button_click.ogg"
/>
<style name="ModernLeftLabelText"
font="serif-bold-stroke-14"
textcolor="white"
text_align="left"
text_valign="center"
/>
<style name="ModernRightLabelText"
font="serif-bold-stroke-14"
textcolor="white"
text_align="right"
text_valign="center"
/>
<style name="ModernCenteredLabelText"
font="serif-bold-stroke-14"
textcolor="white"
text_align="center"
text_valign="center"
/>
<style name="ModernInput"
sprite="ModernDarkBoxWhite"
sprite_selectarea="colour:150 0 0"
textcolor="white"
textcolor_selected="white"
/>
</styles>

View File

@ -12,7 +12,7 @@
<scrollbar name="wheatScrollBar"
width="12"
minimum_bar_size="4"
alwaysshown="false"
show_edge_buttons="true"
sprite_button_top="wheatArrowUp"
sprite_button_top_over="wheatArrowUpOver"
sprite_button_top_pressed="wheatArrowUpOver"

View File

@ -24,6 +24,9 @@ var g_IsNetworked;
// Is this user in control of game settings (i.e. is a network server, or offline player)
var g_IsController;
//Server name, if user is a server, connected to the multiplayer lobby
var g_ServerName;
// 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;
@ -80,6 +83,23 @@ function init(attribs)
default:
error("Unexpected 'type' in gamesetup init: "+attribs.type);
}
if (attribs.serverName)
g_ServerName = attribs.serverName;
// Init the Cancel Button caption and tooltip
var cancelButton = getGUIObjectByName("cancelGame");
if(!Engine.HasXmppClient())
{
cancelButton.caption = "Main menu";
cancelButton.tooltip = "Return to the main menu."
}
else
{
cancelButton.caption = "Quit";
cancelButton.tooltip = "Return to the lobby."
}
}
// Called after the map data is loaded and cached
@ -127,6 +147,10 @@ function initMain()
mapTypes.selected = 0;
mapFilters.selected = 0;
// Create a unique ID for this match, to be used for identifying the same game reports
// for the lobby.
g_GameAttributes.matchID = Engine.GetMatchID();
initMapNameList();
var numPlayersSelection = getGUIObjectByName("numPlayersSelection");
@ -203,6 +227,7 @@ function initMain()
if (!g_IsInGuiUpdate)
updateGameAttributes();
};
mapSize.selected = 0;
getGUIObjectByName("revealMap").onPress = function()
{ // Update attributes so other players can see change
@ -336,7 +361,7 @@ function handleNetMessage(message)
switch (message.status)
{
case "disconnected":
Engine.DisconnectNetworkGame();
cancelSetup();
Engine.SwitchGuiPage("page_pregame.xml");
reportDisconnect(message.reason);
break;
@ -367,13 +392,22 @@ function handleNetMessage(message)
// Update the player list
g_PlayerAssignments = message.hosts;
updatePlayerList();
if (g_IsController)
sendRegisterGameStanza();
break;
case "start":
if (g_IsController)
{
var players = [ assignment.name for each (assignment in g_PlayerAssignments) ]
Engine.SendChangeStateGame(Object.keys(g_PlayerAssignments).length, players.join(", "));
}
Engine.SwitchGuiPage("page_loading.xml", {
"attribs": g_GameAttributes,
"isNetworked" : g_IsNetworked,
"playerAssignments": g_PlayerAssignments
"playerAssignments": g_PlayerAssignments,
"isController": g_IsController
});
break;
@ -434,11 +468,11 @@ function initCivNameList()
// Extract name/code, and skip civs that are explicitly disabled
// (intended for unusable incomplete civs)
var civList = [
{ "name": civ.Name, "code": civ.Code }
for each (civ in g_CivData)
if (civ.SelectableInGameSetup !== false)
];
var civList = [
{ "name": civ.Name, "code": civ.Code }
for each (civ in g_CivData)
if (civ.SelectableInGameSetup !== false)
];
// Alphabetically sort the list, ignoring case
civList.sort(sortNameIgnoreCase);
@ -446,9 +480,9 @@ function initCivNameList()
var civListNames = [ civ.name for each (civ in civList) ];
var civListCodes = [ civ.code for each (civ in civList) ];
// Add random civ to beginning of list
civListNames.unshift("[color=\"orange\"]Random");
civListCodes.unshift("random");
// Add random civ to beginning of list
civListNames.unshift("[color=\"orange\"]Random");
civListCodes.unshift("random");
// Update the dropdowns
for (var i = 0; i < MAX_PLAYERS; ++i)
@ -553,6 +587,15 @@ function loadMapData(name)
function cancelSetup()
{
Engine.DisconnectNetworkGame();
// Set player presence
Engine.LobbySetPlayerPresence("available");
if(g_IsController)
{
// Unregister the game
Engine.SendUnregisterGame();
}
}
function onTick()
@ -772,9 +815,9 @@ function launchGame()
(getGUIObjectByName("mapSelection").list.length - 1)) + 1]);
g_GameAttributes.settings.mapType = g_GameAttributes.mapType;
var numPlayers = g_GameAttributes.settings.PlayerData.length;
// Assign random civilizations to players with that choice
// (this is synchronized because we're the host)
var numPlayers = g_GameAttributes.settings.PlayerData.length;
// Assign random civilizations to players with that choice
// (this is synchronized because we're the host)
var cultures = [];
for each (var civ in g_CivData)
if (civ.Culture !== undefined && cultures.indexOf(civ.Culture) < 0 && (civ.SelectableInGameSetup === undefined || civ.SelectableInGameSetup))
@ -787,12 +830,12 @@ function launchGame()
allcivs[cultures.indexOf(civ.Culture)].push(civ.Code);
const romanNumbers = [undefined, "I", "II", "III", "IV", "V", "VI", "VII", "VIII"];
for (var i = 0; i < numPlayers; ++i)
{
for (var i = 0; i < numPlayers; ++i)
{
civs = allcivs[Math.floor(Math.random()*allcivs.length)];
if (!g_GameAttributes.settings.PlayerData[i].Civ || g_GameAttributes.settings.PlayerData[i].Civ == "random")
g_GameAttributes.settings.PlayerData[i].Civ = civs[Math.floor(Math.random()*civs.length)];
if (!g_GameAttributes.settings.PlayerData[i].Civ || g_GameAttributes.settings.PlayerData[i].Civ == "random")
g_GameAttributes.settings.PlayerData[i].Civ = civs[Math.floor(Math.random()*civs.length)];
// Setting names for AI players. Check if the player is AI and the match is not a scenario
if (g_GameAttributes.mapType !== "scenario" && g_GameAttributes.settings.PlayerData[i].AI)
{
@ -805,20 +848,20 @@ function launchGame()
var usedName = 0;
if (i < civAINames.length)
var chosenName = civAINames[i];
else
else
var chosenName = civAINames[Math.floor(Math.random() * civAINames.length)];
for (var j = 0; j < numPlayers; ++j)
if (g_GameAttributes.settings.PlayerData[j].Name && g_GameAttributes.settings.PlayerData[j].Name.indexOf(chosenName) !== -1)
usedName++;
// Assign civ specific names to AI players
if (usedName)
g_GameAttributes.settings.PlayerData[i].Name = chosenName + " " + romanNumbers[usedName+1];
else
g_GameAttributes.settings.PlayerData[i].Name = chosenName;
}
}
}
if (g_IsNetworked)
{
Engine.SetNetworkGameAttributes(g_GameAttributes);
@ -885,7 +928,7 @@ function onGameAttributesChange()
var enableCheats = getGUIObjectByName("enableCheats");
var populationCap = getGUIObjectByName("populationCap");
var startingResources = getGUIObjectByName("startingResources");
var numPlayersText= getGUIObjectByName("numPlayersText");
var mapSizeText = getGUIObjectByName("mapSizeText");
var revealMapText = getGUIObjectByName("revealMapText");
@ -925,7 +968,7 @@ function onGameAttributesChange()
lockTeams.hidden = false;
populationCap.hidden = false;
startingResources.hidden = false;
numPlayersText.hidden = true;
mapSizeText.hidden = true;
revealMapText.hidden = true;
@ -933,7 +976,7 @@ function onGameAttributesChange()
lockTeamsText.hidden = true;
populationCapText.hidden = true;
startingResourcesText.hidden = true;
mapSizeText.caption = "Map size:";
mapSize.selected = sizeIdx;
revealMapText.caption = "Reveal map:";
@ -1050,7 +1093,7 @@ function onGameAttributesChange()
// Display map name
getGUIObjectByName("mapInfoName").caption = getMapDisplayName(mapName);
// Load the description from the map file, if there is one
var description = mapSettings.Description || "Sorry, no description available.";
@ -1125,9 +1168,15 @@ function onGameAttributesChange()
function updateGameAttributes()
{
if (g_IsNetworked)
{
Engine.SetNetworkGameAttributes(g_GameAttributes);
if (g_IsController && g_LoadingState >= 2)
sendRegisterGameStanza();
}
else
{
onGameAttributesChange();
}
}
function updatePlayerList()
@ -1161,7 +1210,7 @@ function updatePlayerList()
for each (var ai in g_AIs)
{
if (ai.data.hidden)
if (ai.data.hidden)
{
// If the map uses a hidden AI then don't hide it
var usedByMap = false;
@ -1239,16 +1288,13 @@ function updatePlayerList()
};
}
}
else
// There was a human, so make sure we don't have any AI left
// over in their slot, if we're in charge of the attributes
else if (g_IsController && g_GameAttributes.settings.PlayerData[playerSlot].AI && g_GameAttributes.settings.PlayerData[playerSlot].AI != "")
{
// There was a human, so make sure we don't have any AI left
// over in their slot, if we're in charge of the attributes
if (g_IsController && g_GameAttributes.settings.PlayerData[playerSlot].AI && g_GameAttributes.settings.PlayerData[playerSlot].AI != "")
{
g_GameAttributes.settings.PlayerData[playerSlot].AI = "";
if (g_IsNetworked)
Engine.SetNetworkGameAttributes(g_GameAttributes);
}
g_GameAttributes.settings.PlayerData[playerSlot].AI = "";
if (g_IsNetworked)
Engine.SetNetworkGameAttributes(g_GameAttributes);
}
var assignBox = getGUIObjectByName("playerAssignment["+i+"]");
@ -1477,3 +1523,35 @@ function keywordTestOR(keywords, matches)
return false;
}
function sendRegisterGameStanza()
{
var selectedMapSize = getGUIObjectByName("mapSize").selected;
var selectedVictoryCondition = getGUIObjectByName("victoryCondition").selected;
// Map sizes only apply to random maps.
if (g_GameAttributes.mapType == "random")
var mapSize = getGUIObjectByName("mapSize").list[selectedMapSize];
else
var mapSize = "Default";
var victoryCondition = getGUIObjectByName("victoryCondition").list[selectedVictoryCondition];
var numberOfPlayers = Object.keys(g_PlayerAssignments).length;
var players = [ assignment.name for each (assignment in g_PlayerAssignments) ].join(", ");
var nbp = numberOfPlayers ? numberOfPlayers : 1;
var tnbp = g_GameAttributes.settings.PlayerData.length;
gameData = {
"name":g_ServerName,
"mapName":g_GameAttributes.map,
"niceMapName":getMapDisplayName(g_GameAttributes.map),
"mapSize":mapSize,
"mapType":g_GameAttributes.mapType,
"victoryCondition":victoryCondition,
"nbp":nbp,
"tnbp":tnbp,
"players":players
};
Engine.SendRegisterGame(gameData);
}

View File

@ -141,16 +141,15 @@
tooltip="Select a map to play on.">
<action on="SelectionChange">selectMap(this.list_data[this.selected]);</action>
</object>
</object>
<object name="mapSize" size="100%-325 459 100%-25 487" type="dropdown" style="StoneDropDown" hidden="true" tooltip_style="onscreenToolTip" tooltip="Select map size. (Larger sizes may reduce performance.)"/>
<object name="mapSize" size="100%-325 459 100%-25 487" type="dropdown" style="StoneDropDown" hidden="true" tooltip_style="onscreenToolTip" tooltip="Select map size. (Larger sizes may reduce performance.)"/>
<!-- Map Preview -->
<object type="image" sprite="BackgroundIndentFillDark" name="gamePreviewBox" size="100%-426 57 100%-24 359">
<object type="image" sprite="snMapPreview" size="1 1 401 301" name="mapPreview"/>
</object>
<!-- Map Description -->
<object size="100%-425 497 100%-25 100%-60">
<object name="mapInfoName" type="text" style="LeftLabelText" size="0 0 100%-120 30"/>
@ -158,7 +157,7 @@
<object name="mapInfoDescription" type="text" style="MapDescription" size="0 0 100% 100%"/>
</object>
</object>
<!-- Options -->
<object name="gameOptionsBox" size="100%-425 497 100%-25 525">
<!-- More Options Button -->
@ -173,7 +172,7 @@
More Options
<action on="Press">toggleMoreOptions();</action>
</object>
<!-- End Options -->
</object>
@ -182,7 +181,7 @@
<object style="TitleText" type="text" size="50%-128 11 50%+128 27">
More Options
</object>
<object size="14 38 94% 66">
<object size="0 0 40% 28">
<object size="0 0 100% 100%" type="text" style="RightLabelText">Game Speed:</object>
@ -198,7 +197,7 @@
<object name="victoryConditionText" size="40% 0 100% 100%" type="text" style="LeftLabelText"/>
<object name="victoryCondition" size="40% 0 100% 28" type="dropdown" style="StoneDropDown" hidden="true" tooltip_style="onscreenToolTip" tooltip="Select victory condition."/>
</object>
<object size="14 98 94% 126">
<object size="0 0 40% 28">
<object size="0 0 100% 100%" type="text" style="RightLabelText">Population Cap:</object>
@ -206,7 +205,7 @@
<object name="populationCapText" size="40% 0 100% 100%" type="text" style="LeftLabelText"/>
<object name="populationCap" size="40% 0 100% 28" type="dropdown" style="StoneDropDown" hidden="true" tooltip_style="onscreenToolTip" tooltip="Select population cap."/>
</object>
<object size="14 128 94% 156">
<object size="0 0 40% 28">
<object size="0 0 100% 100%" type="text" style="RightLabelText">Starting Resources:</object>
@ -214,7 +213,7 @@
<object name="startingResourcesText" size="40% 0 100% 100%" type="text" style="LeftLabelText"/>
<object name="startingResources" size="40% 0 100% 28" type="dropdown" style="StoneDropDown" hidden="true" tooltip_style="onscreenToolTip" tooltip="Select the game's starting resources."/>
</object>
<object size="14 158 94% 246">
<object size="0 0 40% 28">
<object size="0 0 100% 100%" type="text" style="RightLabelText">Reveal Map:</object>
@ -225,7 +224,7 @@
<object size="0 60 40% 88" name="enableCheatsDesc" hidden="true">
<object size="0 0 100% 100%" type="text" style="RightLabelText">Cheats:</object>
</object>
<object size="40% 0 100% 28">
<object name="revealMapText" size="0 0 100% 100%" type="text" style="LeftLabelText"/>
<object name="revealMap" size="4 50%-8 20 50%+8" type="checkbox" style="StoneCrossBox" hidden="true" tooltip_style="onscreenToolTip" tooltip="Toggle reveal map."/>
@ -254,7 +253,6 @@
</object>
<!-- End More Options -->
</object>
<!-- Chat window -->
@ -280,7 +278,7 @@
hidden="true"
size="100%-700 100%-56 100%-312 100%-24"
>[Tooltip text]</object>
<!-- Start Button -->
<object
name="startGame"
@ -302,19 +300,17 @@
style="StoneButton"
size="100%-164 100%-52 100%-24 100%-24"
tooltip_style="onscreenToolTip"
tooltip="Return to the main menu."
>
Main menu
<action on="Press">
<![CDATA[
cancelSetup();
Engine.SwitchGuiPage("page_pregame.xml");
if(!Engine.HasXmppClient())
Engine.SwitchGuiPage("page_pregame.xml");
else
Engine.SwitchGuiPage("page_lobby.xml");
]]>
</action>
</object>
</object>
</object>
</objects>

View File

@ -1,24 +1,41 @@
var g_IsConnecting = false;
var g_GameType; // "server" or "client"
var g_ServerName = "";
var g_IsRejoining = false;
var g_GameAttributes; // used when rejoining
var g_PlayerAssignments; // used when rejoining
function init(multiplayerGameType)
function init(attribs)
{
switch (multiplayerGameType)
switch (attribs.multiplayerGameType)
{
case "join":
getGUIObjectByName("pageJoin").hidden = false;
getGUIObjectByName("pageHost").hidden = true;
if(Engine.HasXmppClient())
{
if (startJoin(attribs.name, attribs.ip))
switchSetupPage("pageJoin", "pageConnecting");
}
else
{
getGUIObjectByName("pageJoin").hidden = false;
getGUIObjectByName("pageHost").hidden = true;
}
break;
case "host":
getGUIObjectByName("pageJoin").hidden = true;
getGUIObjectByName("pageHost").hidden = false;
if(Engine.HasXmppClient())
{
getGUIObjectByName("hostServerNameWrapper").hidden = false;
getGUIObjectByName("hostPlayerName").caption = attribs.name;
getGUIObjectByName("hostServerName").caption = attribs.name + "'s game";
}
else
getGUIObjectByName("hostPlayerNameWrapper").hidden = false;
break;
default:
error("Unrecognised multiplayer game type : " + multiplayerGameType);
error("Unrecognised multiplayer game type : " + attribs.multiplayerGameType);
break;
}
}
@ -27,7 +44,9 @@ function cancelSetup()
{
if (g_IsConnecting)
Engine.DisconnectNetworkGame();
Engine.PopGuiPage();
// Set player lobby presence
Engine.LobbySetPlayerPresence("available");
Engine.PopGuiPage();
}
function startConnectionStatus(type)
@ -43,6 +62,11 @@ function onTick()
if (!g_IsConnecting)
return;
pollAndHandleNetworkClient();
}
function pollAndHandleNetworkClient()
{
while (true)
{
var message = Engine.PollNetworkClient();
@ -118,7 +142,7 @@ function onTick()
}
else
{
Engine.SwitchGuiPage("page_gamesetup.xml", { "type": g_GameType });
Engine.SwitchGuiPage("page_gamesetup.xml", { "type": g_GameType, "serverName": g_ServerName });
return; // don't process any more messages - leave them for the game GUI loop
}
@ -148,6 +172,18 @@ function switchSetupPage(oldpage, newpage)
function startHost(playername, servername)
{
// Disallow identically named games in the multiplayer lobby
if (Engine.HasXmppClient())
{
for each (g in Engine.GetGameList())
{
if (g.name === servername)
{
getGUIObjectByName("hostFeedback").caption = "Game name already in use.";
return false;
}
}
}
try
{
Engine.StartNetworkHost(playername);
@ -162,8 +198,9 @@ function startHost(playername, servername)
}
startConnectionStatus("server");
// TODO: ought to do something(?) with servername
g_ServerName = servername;
// Set player lobby presence
Engine.LobbySetPlayerPresence("playing");
return true;
}
@ -183,5 +220,7 @@ function startJoin(playername, ip)
}
startConnectionStatus("client");
// Set player lobby presence
Engine.LobbySetPlayerPresence("playing");
return true;
}

View File

@ -31,7 +31,7 @@
<object name="joinPlayerName" type="input" size="210 40 100%-32 64" style="StoneInput">
<action on="Load">
this.caption = Engine.GetDefaultPlayerName();
this.caption = Engine.ConfigDB_GetValue("user", "playername");
</action>
</object>
@ -41,7 +41,7 @@
<object name="joinServer" type="input" size="210 80 100%-32 104" style="StoneInput">
<action on="Load">
this.caption = Engine.GetDefaultMPServer();
this.caption = Engine.ConfigDB_GetValue("user", "multiplayerserver")
</action>
</object>3 100%-33 103 100%-3
@ -50,7 +50,9 @@
<action on="Press">
var joinPlayerName = getGUIObjectByName("joinPlayerName").caption;
var joinServer = getGUIObjectByName("joinServer").caption;
Engine.SaveMPConfig(joinPlayerName, joinServer);
Engine.ConfigDB_CreateValue("user", "playername", joinPlayerName);
Engine.ConfigDB_CreateValue("user", "multiplayerserver", joinServer);
Engine.ConfigDB_WriteFile("user", "config/user.cfg");
if (startJoin(joinPlayerName, joinServer))
{
switchSetupPage("pageJoin", "pageConnecting");
@ -65,24 +67,26 @@
Set up your server to host.
</object>
<object name="hostPlayerNameWrapper" hidden="true">
<object type="text" size="0 40 200 70" style="RightLabelText">
Player name:
</object>
<object name="hostPlayerName" type="input" size="210 40 100%-32 64" style="StoneInput">
<action on="Load">
this.caption = Engine.GetDefaultPlayerName();
this.caption = Engine.ConfigDB_GetValue("user", "playername");
</action>
</object>
</object>
<object hidden="true"> <!-- TODO: restore this when the server name is actually used -->
<object name="hostServerNameWrapper" hidden="true">
<object type="text" size="0 80 200 110" style="RightLabelText">
Server name:
</object>
<object name="hostServerName" type="input" size="210 80 100%-32 104" style="StoneInput">
<action on="Load">
this.caption = Engine.GetDefaultPlayerName() + "'s game";
this.caption = Engine.ConfigDB_GetValue("user", "playername") + "'s game";
</action>
</object>
</object>
@ -91,17 +95,16 @@
Continue
<action on="Press">
var hostPlayerName = getGUIObjectByName("hostPlayerName").caption;
Engine.SaveMPConfig(hostPlayerName, Engine.GetDefaultMPServer());
if (startHost(
hostPlayerName,
getGUIObjectByName("hostServerName").caption))
{
Engine.ConfigDB_CreateValue("user", "playername", hostPlayerName);
Engine.ConfigDB_WriteFile("user", "config/user.cfg");
if (startHost(hostPlayerName, getGUIObjectByName("hostServerName").caption))
switchSetupPage("pageHost", "pageConnecting");
}
</action>
</object>
</object>
<object name="hostFeedback" type="text" style="CenteredLabelText" size="32 150 100%-32 180" textcolor="red" />
<object type="button" style="StoneButton" size="50%+16 100%-60 50%+144 100%-32">
Cancel
<action on="Press">cancelSetup();</action>

View File

@ -20,7 +20,7 @@
ghost %bool; #IMPLIED
hidden %bool; #IMPLIED
size CDATA #IMPLIED
z CDATA #IMPLIED"
z CDATA #IMPLIED"
>
@ -29,21 +29,21 @@
<!ENTITY % ex_settings
"buffer_zone CDATA #IMPLIED
button_width CDATA #IMPLIED
checked %bool; #IMPLIED
checked %bool; #IMPLIED
dropdown_size CDATA #IMPLIED
dropdown_buffer CDATA #IMPLIED
font CDATA #IMPLIED
font CDATA #IMPLIED
fov_wedge_color CDATA #IMPLIED
hotkey CDATA #IMPLIED
cell_id CDATA #IMPLIED
hotkey CDATA #IMPLIED
cell_id CDATA #IMPLIED
input_initvalue_destroyed_at_focus %bool; #IMPLIED
max_length CDATA #IMPLIED
multiline %bool; #IMPLIED
rectcolor_selected CDATA #IMPLIED
scrollbar %bool; #IMPLIED
multiline %bool; #IMPLIED
rectcolor_selected CDATA #IMPLIED
scrollbar %bool; #IMPLIED
scrollbar_style CDATA #IMPLIED
sprite CDATA #IMPLIED
sprite2 CDATA #IMPLIED
sprite CDATA #IMPLIED
sprite2 CDATA #IMPLIED
sprite_bar CDATA #IMPLIED
sprite_background CDATA #IMPLIED
sprite_disabled CDATA #IMPLIED
@ -54,15 +54,16 @@
sprite_pressed CDATA #IMPLIED
sprite2_pressed CDATA #IMPLIED
sprite_selectarea CDATA #IMPLIED
sprite_heading CDATA #IMPLIED
square_side CDATA #IMPLIED
textcolor CDATA #IMPLIED
textcolor_disabled CDATA #IMPLIED
textcolor CDATA #IMPLIED
textcolor_disabled CDATA #IMPLIED
textcolor_over CDATA #IMPLIED
textcolor_pressed CDATA #IMPLIED
textcolor_selected CDATA #IMPLIED
textcolor_selected CDATA #IMPLIED
text_align %align; #IMPLIED
text_valign %valign; #IMPLIED
tooltip CDATA #IMPLIED
tooltip CDATA #IMPLIED
tooltip_style CDATA #IMPLIED"
>
@ -73,7 +74,7 @@
<!ELEMENT script (#PCDATA)>
<!ATTLIST script
file CDATA #IMPLIED
file CDATA #IMPLIED
>
<!ELEMENT object (#PCDATA|object|action|item)*> <!-- 'item' is used by "list" and "dropdown" -->
@ -84,12 +85,12 @@
>
<!ELEMENT action (#PCDATA)>
<!ATTLIST action
on CDATA #REQUIRED
file CDATA #IMPLIED
on CDATA #REQUIRED
file CDATA #IMPLIED
>
<!ELEMENT item (#PCDATA)>
<!ATTLIST item
enabled %bool; #IMPLIED
enabled %bool; #IMPLIED
>
@ -99,7 +100,7 @@
<!ELEMENT styles (style*)>
<!ELEMENT style EMPTY>
<!ATTLIST style
name CDATA #REQUIRED
name CDATA #REQUIRED
%base_settings;
%ex_settings;
>
@ -117,25 +118,25 @@
<scrollbar>
-->
<!ATTLIST scrollbar
name CDATA #REQUIRED
width CDATA #IMPLIED
scroll_wheel %bool; #IMPLIED
alwaysshown %bool; #IMPLIED
scroll_speed CDATA #IMPLIED
sprite_button_top CDATA #IMPLIED
name CDATA #REQUIRED
width CDATA #IMPLIED
scroll_wheel %bool; #IMPLIED
show_edge_buttons %bool; #REQUIRED
scroll_speed CDATA #IMPLIED
sprite_button_top CDATA #IMPLIED
sprite_button_top_pressed CDATA #IMPLIED
sprite_button_top_disabled CDATA #IMPLIED
sprite_button_top_disabled CDATA #IMPLIED
sprite_button_top_over CDATA #IMPLIED
sprite_button_bottom CDATA #IMPLIED
sprite_button_bottom_pressed CDATA #IMPLIED
sprite_button_bottom_disabled CDATA #IMPLIED
sprite_button_bottom_pressed CDATA #IMPLIED
sprite_button_bottom_disabled CDATA #IMPLIED
sprite_button_bottom_over CDATA #IMPLIED
sprite_bar_vertical CDATA #IMPLIED
sprite_bar_vertical_over CDATA #IMPLIED
sprite_bar_vertical_pressed CDATA #IMPLIED
sprite_bar_vertical_pressed CDATA #IMPLIED
sprite_back_vertical CDATA #IMPLIED
minimum_bar_size CDATA #IMPLIED
maximum_bar_size CDATA #IMPLIED
minimum_bar_size CDATA #IMPLIED
maximum_bar_size CDATA #IMPLIED
>
<!--
@ -155,14 +156,14 @@
name CDATA #REQUIRED
sprite CDATA #IMPLIED
anchor CDATA #IMPLIED
buffer_zone CDATA #IMPLIED
buffer_zone CDATA #IMPLIED
font CDATA #IMPLIED
maxwidth CDATA #IMPLIED
offset CDATA #IMPLIED
textcolor CDATA #IMPLIED
delay CDATA #IMPLIED
use_object CDATA #IMPLIED
hide_object CDATA #IMPLIED
use_object CDATA #IMPLIED
hide_object CDATA #IMPLIED
>
<!--
@ -186,19 +187,19 @@
>
<!ATTLIST image
texture CDATA #IMPLIED
size CDATA #IMPLIED
texture CDATA #IMPLIED
size CDATA #IMPLIED
texture_size CDATA #IMPLIED
real_texture_placement CDATA #IMPLIED
cell_size CDATA #IMPLIED
backcolor CDATA #IMPLIED
real_texture_placement CDATA #IMPLIED
cell_size CDATA #IMPLIED
backcolor CDATA #IMPLIED
bordercolor CDATA #IMPLIED
border %bool; #IMPLIED
z_level CDATA #IMPLIED
border %bool; #IMPLIED
z_level CDATA #IMPLIED
>
<!ATTLIST effect
add_color CDATA #IMPLIED
multiply_color CDATA #IMPLIED
multiply_color CDATA #IMPLIED
grayscale CDATA #IMPLIED
>

View File

@ -0,0 +1,797 @@
var g_ChatMessages = [];
var g_Name = "unknown";
var g_GameList = {};
var g_specialKey = Math.random();
var g_spamMonitor = {};
var g_spammers = {};
var g_timestamp = Engine.ConfigDB_GetValue("user", "lobby.chattimestamp") == "true";
var g_mapSizes = {};
////////////////////////////////////////////////////////////////////////////////////////////////
function init(attribs)
{
// Play menu music
global.music.setState(global.music.states.MENU);
g_Name = Engine.LobbyGetNick();
g_mapSizes = initMapSizes();
g_mapSizes.shortNames.push("Any");
g_mapSizes.tiles.push("");
var mapSizeFilter = getGUIObjectByName("mapSizeFilter");
mapSizeFilter.list = g_mapSizes.shortNames;
mapSizeFilter.list_data = g_mapSizes.tiles;
var playersNumberFilter = getGUIObjectByName("playersNumberFilter");
playersNumberFilter.list = [2,3,4,5,6,7,8,"Any"];
playersNumberFilter.list_data = [2,3,4,5,6,7,8,""];
var mapTypeFilter = getGUIObjectByName("mapTypeFilter");
mapTypeFilter.list = ["Random", "Scenario", "Any"];
mapTypeFilter.list_data = ["conquest", "scenario", ""];
Engine.LobbySetPlayerPresence("available");
Engine.SendGetGameList();
Engine.SendGetBoardList();
updatePlayerList();
resetFilters();
var spamMonitorTimer = setTimeout(clearSpamMonitor, 5000);
var spammerTimer = setTimeout(clearSpammers, 30000);
}
////////////////////////////////////////////////////////////////////////////////////////////////
// Xmpp client connection management
////////////////////////////////////////////////////////////////////////////////////////////////
function lobbyStop()
{
Engine.StopXmppClient();
}
function lobbyConnect()
{
Engine.ConnectXmppClient();
}
function lobbyDisconnect()
{
Engine.DisconnectXmppClient();
}
////////////////////////////////////////////////////////////////////////////////////////////////
// Server requests functions
////////////////////////////////////////////////////////////////////////////////////////////////
function lobbyRefreshGameList()
{
Engine.SendGetGameList();
}
////////////////////////////////////////////////////////////////////////////////////////////////
// Update functions
////////////////////////////////////////////////////////////////////////////////////////////////
function resetFilters()
{
// Reset states of gui objects
getGUIObjectByName("mapSizeFilter").selected = getGUIObjectByName("mapSizeFilter").list.length - 1;
getGUIObjectByName("playersNumberFilter").selected = getGUIObjectByName("playersNumberFilter").list.length - 1;
getGUIObjectByName("mapTypeFilter").selected = getGUIObjectByName("mapTypeFilter").list.length - 1;
getGUIObjectByName("showFullFilter").checked = false;
// Update the list of games
updateGameList();
// Update info box about the game currently selected
selectGame(getGUIObjectByName("gamesBox").selected);
}
function applyFilters()
{
// Update the list of games
updateGameList();
// Update info box about the game currently selected
selectGame(getGUIObjectByName("gamesBox").selected);
}
function displayGame(g, mapSizeFilter, playersNumberFilter, mapTypeFilter, showFullFilter)
{
if(mapSizeFilter != "" && g.mapSize != mapSizeFilter) return false;
if(playersNumberFilter != "" && g.tnbp != playersNumberFilter) return false;
if(mapTypeFilter != "" && g.mapType != mapTypeFilter) return false;
if(!showFullFilter && g.tnbp == g.nbp) return false;
return true;
}
// Do a full update of the player listing **Only call on init**
function updatePlayerList()
{
var playersBox = getGUIObjectByName("playersBox");
[playerList, presenceList, nickList] = [[],[],[]];
for each (var p in Engine.GetPlayerList())
{
var [name, status] = formatPlayerListEntry(p.name, p.presence);
playerList.push(name);
presenceList.push(status);
nickList.push(p.name);
}
playersBox.list_name = playerList;
playersBox.list_status = presenceList;
playersBox.list = nickList;
if (playersBox.selected >= playersBox.list.length)
playersBox.selected = -1;
return [playerList, presenceList, nickList];
}
// Update leaderboard listing
function updateBoardList()
{
// Get list from C++
var boardList = Engine.GetBoardList();
// Get GUI leaderboard object
var leaderboard = getGUIObjectByName("leaderboardBox");
// Sort list in acending order by rating
boardList.sort(function(a, b) b.rating - a.rating);
var list = [];
var list_name = [];
var list_rank = [];
var list_rating = [];
// Push changes
for (var i = 0; i < boardList.length; i++)
{
list_name.push(boardList[i].name);
list_rating.push(boardList[i].rating);
list_rank.push(i+1);
list.push(boardList[i].name);
}
leaderboard.list_name = list_name;
leaderboard.list_rating = list_rating;
leaderboard.list_rank = list_rank;
leaderboard.list = list;
if (leaderboard.selected >= leaderboard.list.length)
leaderboard.selected = -1;
}
// Update game listing
function updateGameList()
{
var gamesBox = getGUIObjectByName("gamesBox");
var gameList = Engine.GetGameList();
// Store the game whole game list data so that we can access it later
// to update the game info panel.
g_GameList = gameList;
// Sort the list of games to that games 'waiting' are displayed at the top
g_GameList.sort(function (a,b) {
return a.state == 'waiting' ? -1 : b.state == 'waiting' ? +1 : 0;
});
var list_name = [];
var list_ip = [];
var list_mapName = [];
var list_mapSize = [];
var list_mapType = [];
var list_nPlayers = [];
var list = [];
var list_data = [];
var mapSizeFilterDD = getGUIObjectByName("mapSizeFilter");
var playersNumberFilterDD = getGUIObjectByName("playersNumberFilter");
var mapTypeFilterDD = getGUIObjectByName("mapTypeFilter");
var showFullFilterCB = getGUIObjectByName("showFullFilter");
// Get filter values
var mapSizeFilter = mapSizeFilterDD.selected >= 0 ? mapSizeFilterDD.list_data[mapSizeFilterDD.selected] : "";
var playersNumberFilter = playersNumberFilterDD.selected >=0 ? playersNumberFilterDD.list_data[playersNumberFilterDD.selected] : "";
var mapTypeFilter = mapTypeFilterDD.selected >= 0 ? mapTypeFilterDD.list_data[mapTypeFilterDD.selected] : "";
var showFullFilter = showFullFilterCB.checked;
var c = 0;
for each (g in gameList)
{
if(displayGame(g, mapSizeFilter, playersNumberFilter, mapTypeFilter, showFullFilter))
{
// Highlight games 'waiting' for this player, otherwise display as green
var name = (g.state != 'waiting') ? '[color="0 125 0"]' + g.name + '[/color]' : '[color="orange"]' + g.name + '[/color]';
list_name.push(name);
list_ip.push(g.ip);
list_mapName.push(g.niceMapName);
list_mapSize.push(g.mapSize.split("(")[0]);
list_mapType.push(toTitleCase(g.mapType));
list_nPlayers.push(g.nbp + "/" +g.tnbp);
list.push(g.name);
list_data.push(c);
}
c++;
}
gamesBox.list_name = list_name;
// gamesBox.list_ip = list_ip;
gamesBox.list_mapName = list_mapName;
gamesBox.list_mapSize = list_mapSize;
gamesBox.list_mapType = list_mapType;
gamesBox.list_nPlayers = list_nPlayers;
gamesBox.list = list;
gamesBox.list_data = list_data;
if (gamesBox.selected >= gamesBox.list_name.length)
gamesBox.selected = -1;
// If game selected, update info box about the game.
if(getGUIObjectByName("gamesBox").selected != -1)
selectGame(getGUIObjectByName("gamesBox").selected)
}
// The following function colorizes and formats the entries in the player list.
function formatPlayerListEntry(nickname, presence)
{
// Set colors based on player status
var color_close = '[/color]';
switch (presence)
{
case "playing":
var color = '[color="125 0 0"]';
var status = color + "Busy" + color_close;
break;
case "away":
var color = '[color="0 0 125"]';
var status = color + "Away" + color_close;
break;
case "available":
var color = '[color="0 125 0"]';
var status = color + "Online" + color_close;
break;
case "offline":
var color = '[color="0 0 0"]';
var status = color + "Offline" + color_close;
break;
default:
warn("Unknown presence '"+presence+"'");
break;
}
var name = colorPlayerName(nickname);
// Push this player's name and status onto the list
return [name, status];
}
function selectGame(selected)
{
if (selected == -1)
{
// Hide the game info panel if a game is not selected
getGUIObjectByName("gameInfo").hidden = true;
getGUIObjectByName("gameInfoEmpty").hidden = false;
getGUIObjectByName("joinGameButton").hidden = true;
return;
}
var mapData;
var g = getGUIObjectByName("gamesBox").list_data[selected];
// Load map data
if (g_GameList[g].mapType == "random" && fileExists(g_GameList[g].mapName + ".json"))
mapData = parseJSONData(g_GameList[g].mapName + ".json");
else if (fileExists(g_GameList[g].mapName + ".xml"))
mapData = Engine.LoadMapSettings(g_GameList[g].mapName + ".xml");
else
// Warn the player if we can't find the map.
warn("Map '"+ g_GameList[g].mapName +"' not found");
// Show the game info paneland join button.
getGUIObjectByName("gameInfo").hidden = false;
getGUIObjectByName("gameInfoEmpty").hidden = true;
getGUIObjectByName("joinGameButton").hidden = false;
// Display the map name, number of players, the names of the players, the map size and the map type.
getGUIObjectByName("sgMapName").caption = g_GameList[g].niceMapName;
getGUIObjectByName("sgNbPlayers").caption = g_GameList[g].nbp + "/" + g_GameList[g].tnbp;
getGUIObjectByName("sgPlayersNames").caption = g_GameList[g].players;
getGUIObjectByName("sgMapSize").caption = g_GameList[g].mapSize.split("(")[0];
getGUIObjectByName("sgMapType").caption = toTitleCase(g_GameList[g].mapType);
// Display map description if it exists, otherwise display a placeholder.
if (mapData && mapData.settings.Description)
var mapDescription = mapData.settings.Description;
else
var mapDescription = "Sorry, no description available.";
// Display map preview if it exists, otherwise display a placeholder.
if (mapData && mapData.settings.Preview)
var mapPreview = mapData.settings.Preview;
else
var mapPreview = "nopreview.png";
getGUIObjectByName("sgMapDescription").caption = mapDescription;
getGUIObjectByName("sgMapPreview").sprite = "cropped:(0.7812,0.5859)session/icons/mappreview/" + mapPreview;
}
function joinSelectedGame()
{
var gamesBox = getGUIObjectByName("gamesBox");
if (gamesBox.selected >= 0)
{
var g = gamesBox.list_data[gamesBox.selected];
var sname = g_Name;
var sip = g_GameList[g].ip;
// TODO: What about valid host names?
// Check if it looks like an ip address
if (sip.split('.').length != 4)
{
addChatMessage({ "from": "system", "text": "This game does not have a valid address" });
return;
}
// Open Multiplayer connection window with join option.
Engine.PushGuiPage("page_gamesetup_mp.xml", { multiplayerGameType: "join", name: sname, ip: sip });
}
}
////////////////////////////////////////////////////////////////////////////////////////////////
// Utils
////////////////////////////////////////////////////////////////////////////////////////////////
function twoDigits(n)
{
return n < 10 ? "0" + n : n;
}
////////////////////////////////////////////////////////////////////////////////////////////////
// GUI event handlers
////////////////////////////////////////////////////////////////////////////////////////////////
function onTick()
{
// Wake up XmppClient
Engine.RecvXmppClient();
updateTimers();
// Receive messages
while (true)
{
var message = Engine.LobbyGuiPollMessage();
// Clean Message
if (!message)
break;
message.from = escapeText(message.from);
message.text = escapeText(message.text);
switch (message.type)
{
case "mucmessage": // For room messages
addChatMessage({ "from": message.from, "text": message.text });
break;
case "message": // For private messages
addChatMessage({ "from": message.from, "text": message.text });
break;
case "muc":
var nick = message.text;
var presence = Engine.LobbyGetPlayerPresence(nick);
var playersBox = getGUIObjectByName("playersBox");
var playerList = playersBox.list_name;
var presenceList = playersBox.list_status;
var nickList = playersBox.list;
var nickIndex = nickList.indexOf(nick);
switch(message.level)
{
case "join":
if (nick == g_Name)
{
// We just joined, we need to get the full player list
[playerList, presenceList, nickList] = updatePlayerList();
break;
}
var [name, status] = formatPlayerListEntry(nick, presence);
playerList.push(name);
presenceList.push(status);
nickList.push(nick);
addChatMessage({ "text": "/special " + nick + " has joined.", "key": g_specialKey });
break;
case "leave":
if (nickIndex == -1) // Left, but not present (TODO: warn about this?)
break;
playerList.splice(nickIndex, 1);
presenceList.splice(nickIndex, 1);
nickList.splice(nickIndex, 1);
addChatMessage({ "text": "/special " + nick + " has left.", "key": g_specialKey });
break;
case "nick":
if (nickIndex == -1) // This shouldn't ever happen
break;
var [name, status] = formatPlayerListEntry(message.data, presence); // TODO: actually we don't want to change the presence here, so use what was used before
playerList[nickIndex] = name;
// presence stays the same
nickList[nickIndex] = message.data;
addChatMessage({ "text": "/special " + nick + " is now known as " + message.data + ".", "key": g_specialKey });
break;
case "presence":
if (nickIndex == -1) // This shouldn't ever happen
break;
var [name, status] = formatPlayerListEntry(nick, presence);
presenceList[nickIndex] = status;
playerList[nickIndex] = name;
break;
default:
warn("Unknown message.level '" + message.level + "'");
break;
}
// Push new data to GUI
playersBox.list_name = playerList;
playersBox.list_status = presenceList;
playersBox.list = nickList;
if (playersBox.selected >= playersBox.list.length)
playersBox.selected = -1;
break;
case "system":
switch (message.level)
{
case "standard":
addChatMessage({ "from": "system", "text": message.text, "color": "0 150 0" });
if (message.text == "disconnected")
{
// Clear the list of games and the list of players
updateGameList();
updateBoardList();
updatePlayerList();
// Disable the 'host' button
getGUIObjectByName("hostButton").enabled = false;
}
else if (message.text == "connected")
{
getGUIObjectByName("hostButton").enabled = true;
}
break;
case "error":
addChatMessage({ "from": "system", "text": message.text, "color": "150 0 0" });
break;
case "internal":
switch (message.text)
{
case "gamelist updated":
updateGameList();
break;
case "boardlist updated":
updateBoardList();
break;
}
break
}
break;
default:
error("Unrecognised message type "+message.type);
}
}
}
/* Messages */
function submitChatInput()
{
var input = getGUIObjectByName("chatInput");
var text = escapeText(input.caption);
if (text.length)
{
if (!handleSpecialCommand(text))
Engine.LobbySendMessage(text);
input.caption = "";
}
}
function completeNick()
{
var input = getGUIObjectByName("chatInput");
var text = escapeText(input.caption);
if (text.length)
{
var matched = false;
for each (var playerObj in Engine.GetPlayerList()) {
var player = playerObj.name;
var breaks = text.match(/(\s+)/g) || [];
text.split(/\s+/g).reduceRight(function (wordsSoFar, word, index) {
if (matched)
return null;
var matchCandidate = word + (breaks[index - 1] || "") + wordsSoFar;
if (player.toUpperCase().indexOf(matchCandidate.toUpperCase().trim()) == 0) {
input.caption = text.replace(matchCandidate.trim(), player);
matched = true;
}
return matchCandidate;
}, "");
if (matched)
break;
}
}
}
function handleSpecialCommand(text)
{
if (text[0] != '/')
return false;
var [cmd, nick] = ircSplit(text);
switch (cmd)
{
case "away":
// TODO: Should we handle away messages?
Engine.LobbySetPlayerPresence("away");
break;
case "back":
Engine.LobbySetPlayerPresence("available");
break;
case "nick":
if (g_spammers[g_Name] != undefined)
break;
// Strip invalid characters.
nick = sanitizePlayerName(nick, true, true);
Engine.LobbySetNick(nick);
g_Name = nick;
break;
case "kick": // TODO: Split reason from nick and pass it too, for now just support "/kick nick"
// also allow quoting nicks (and/or prevent users from changing it here, but that doesn't help if the spammer uses a different client)
Engine.LobbyKick(nick, "");
break;
case "ban": // TODO: Split reason from nick and pass it too, for now just support "/ban nick"
Engine.LobbyBan(nick, "");
break;
case "quit":
lobbyStop();
Engine.SwitchGuiPage("page_pregame.xml");
break;
default:
return false;
}
return true;
}
function addChatMessage(msg)
{
// Set sender color
if (msg.color)
msg.from = '[color="' + msg.color + '"]' + msg.from + '[/color]';
else if (msg.from)
msg.from = colorPlayerName(msg.from);
// Highlight local user's nick
if (msg.text.indexOf(g_Name) != -1 && g_Name != msg.from)
msg.text = msg.text.replace(new RegExp('\\b' + '\\' + g_Name + '\\b', "g"), colorPlayerName(g_Name));
// Run spam test
if (updateSpamandDetect(msg.text, msg.from))
return;
// Format Text
var formatted = ircFormat(msg.text, msg.from, msg.key);
// If there is text, add it to the chat box.
if (formatted)
{
g_ChatMessages.push(formatted);
getGUIObjectByName("chatText").caption = g_ChatMessages.join("\n");
}
}
function ircSplit(string)
{
var idx = string.indexOf(' ');
if (idx != -1)
return [string.substr(1,idx-1), string.substr(idx+1)];
return [string.substr(1), ""];
}
// The following formats text in an IRC-like way
function ircFormat(text, from, key)
{
time = new Date(Date.now());
function warnUnsupportedCommand(command, from) // Function to warn only local player
{
if (from === g_Name)
addChatMessage({ "from": "system", "text": "We're sorry, the '" + command + "' command is not supported." });
return;
}
// Build time header if enabled
if (g_timestamp)
formatted = '[font="serif-bold-13"]\x5B' + twoDigits(time.getHours() % 12) + ":" + twoDigits(time.getMinutes()) + '\x5D[/font] '
else
formatted = "";
// Handle commands
if (text[0] == '/')
{
var [command, message] = ircSplit(text);
switch (command)
{
case "me":
return formatted + '[font="serif-bold-13"]* ' + from + '[/font] ' + message;
case "say":
return formatted + '[font="serif-bold-13"]<' + from + '>[/font] ' + message;
case "special":
if (key === g_specialKey)
return formatted + '[font="serif-bold-13"] == ' + message + '[/font]';
break;
default:
return warnUnsupportedCommand(command, from)
}
}
return formatted + '[font="serif-bold-13"]<' + from + '>[/font] ' + text;
}
// The following function tracks message stats and returns true if the input text is spam.
function updateSpamandDetect(text, from)
{
// Check for blank lines.
if (text == " ")
return true;
// Update the spam monitor.
if (g_spamMonitor[from])
g_spamMonitor[from]++;
else
g_spamMonitor[from] = 1;
if (g_spamMonitor[from] > 5)
g_spammers[from] = true
// Block spammers and notify the player if they are blocked.
if(from in g_spammers)
{
if (from == g_Name)
{
addChatMessage({ "from": "system", "text": "Please do not spam. You have been blocked for thirty seconds." });
}
return true;
}
// Return false if everything is clear.
return false;
}
// Timers to clear spammers after some time.
function clearSpamMonitor()
{
g_spamMonitor = {};
spamTimer = setTimeout(clearSpamMonitor, 5000);
}
function clearSpammers()
{
g_spammers = {};
spammerTimer = setTimeout(clearSpammers, 30000);
}
/* Utilities */
// Generate a (mostly) unique color for this player based on their name.
// See http://stackoverflow.com/questions/3426404/create-a-hexadecimal-colour-based-on-a-string-with-jquery-javascript
function getPlayerColor(playername)
{
// Generate a probably-unique hash for the player name and use that to create a color.
var hash = 0;
for (var i = 0; i < playername.length; i++)
hash = playername.charCodeAt(i) + ((hash << 5) - hash);
// First create the color in RGB then HSL, clamp the lightness so it's not too dark to read, and then convert back to RGB to display.
// The reason for this roundabout method is this algorithm can generate values from 0 to 255 for RGB but only 0 to 100 for HSL; this gives
// us much more variety if we generate in RGB. Unfortunately, enforcing that RGB values are a certain lightness is very difficult, so
// we convert to HSL to do the computation. Since our GUI code only displays RGB colors, we have to convert back.
var [h, s, l] = rgbToHsl(hash >> 24 & 0xFF, hash >> 16 & 0xFF, hash >> 8 & 0xFF);
return hslToRgb(h, s, Math.max(0.4, l)).join(" ");
}
function repeatString(times, string) {
return Array(times + 1).join(string);
}
// Some names are special and should always appear in certain colors.
var fixedColors = { "system": repeatString(7, "255.0.0."), "WFGbot": repeatString(6, "255.24.24."),
"pyrogenesis": repeatString(2, "97.0.0.") + repeatString(2, "124.0.0.") + "138.0.0." +
repeatString(2, "174.0.0.") + repeatString(2, "229.40.0.") + repeatString(2, "243.125.15.") };
function colorPlayerName(playername)
{
var color = fixedColors[playername];
if (color) {
color = color.split(".");
return ('[color="' + playername.split("").map(function (c, i) color.slice(i * 3, i * 3 + 3).join(" ") + '"]' + c + '[/color][color="')
.join("") + '"]').slice(0, -10);
}
return '[color="' + getPlayerColor(playername) + '"]' + playername + '[/color]';
}
// Ensure `value` is between 0 and 1.
function clampColorValue(value)
{
return Math.abs(1 - Math.abs(value - 1));
}
// See http://stackoverflow.com/questions/2353211/hsl-to-rgb-color-conversion
function rgbToHsl(r, g, b)
{
r /= 255;
g /= 255;
b /= 255;
var max = Math.max(r, g, b), min = Math.min(r, g, b);
var h, s, l = (max + min) / 2;
if (max == min)
h = s = 0; // achromatic
else
{
var d = max - min;
s = l > 0.5 ? d / (2 - max - min) : d / (max + min);
switch (max)
{
case r: h = (g - b) / d + (g < b ? 6 : 0); break;
case g: h = (b - r) / d + 2; break;
case b: h = (r - g) / d + 4; break;
}
h /= 6;
}
return [h, s, l];
}
function hslToRgb(h, s, l)
{
[h, s, l] = [h, s, l].map(clampColorValue);
var r, g, b;
if (s == 0)
r = g = b = l; // achromatic
else {
function hue2rgb(p, q, t)
{
if (t < 0) t += 1;
if (t > 1) t -= 1;
if (t < 1/6) return p + (q - p) * 6 * t;
if (t < 1/2) return q;
if (t < 2/3) return p + (q - p) * (2/3 - t) * 6;
return p;
}
var q = l < 0.5 ? l * (1 + s) : l + s - l * s;
var p = 2 * l - q;
r = hue2rgb(p, q, h + 1/3);
g = hue2rgb(p, q, h);
b = hue2rgb(p, q, h - 1/3);
}
return [r, g, b].map(function (n) Math.round(n * 255));
}
(function () {
function hexToRgb(hex) {
return parseInt(hex.slice(0, 2), 16) + "." + parseInt(hex.slice(2, 4), 16) + "." + parseInt(hex.slice(4, 6), 16) + ".";
}
function r(times, hex) {
return repeatString(times, hexToRgb(hex));
}
fixedColors["Twilight Sparkle"] = r(2, "d19fe3") + r(2, "b689c8") + r(2, "a76bc2") +
r(4, "263773") + r(2, "131f46") + r(2, "662d8a") + r(2, "ed438a");
fixedColors["Applejack"] = r(3, "ffc261") + r(3, "efb05d") + r(3, "f26f31");
fixedColors["Rarity"] = r(1, "ebeff1") + r(1, "dee3e4") + r(1, "bec2c3") +
r(1, "83509f") + r(1, "4b2568") + r(1, "4917d6");
fixedColors["Rainbow Dash"] = r(2, "ee4144") + r(1, "f37033") + r(1, "fdf6af") +
r(1, "62bc4d") + r(1, "1e98d3") + r(2, "672f89") + r(1, "9edbf9") +
r(1, "88c4eb") + r(1, "77b0e0") + r(1, "1e98d3");
fixedColors["Pinkie Pie"] = r(2, "f3b6cf") + r(2, "ec9dc4") + r(4, "eb81b4") +
r(1, "ed458b") + r(1, "be1d77");
fixedColors["Fluttershy"] = r(2, "fdf6af") + r(2, "fee78f") + r(2, "ead463") +
r(2, "f3b6cf") + r(2, "eb81b4");
fixedColors["Sweetie Belle"] = r(2, "efedee") + r(3, "e2dee3") + r(3, "cfc8d1") +
r(2, "b28dc0") + r(2, "f6b8d2") + r(1, "795b8a");
fixedColors["Apple Bloom"] = r(2, "f4f49b") + r(2, "e7e793") + r(2, "dac582") +
r(2, "f46091") + r(2, "f8415f") + r(1, "c52451");
fixedColors["Scootaloo"] = r(2, "fbba64") + r(2, "f2ab56") + r(2, "f37003") +
r(2, "bf5d95") + r(1, "bf1f79");
fixedColors["Luna"] = r(1, "7ca7fa") + r(1, "5d6fc1") + r(1, "656cb9") + r(1, "393993");
fixedColors["Celestia"] = r(1, "fdfafc") + r(1, "f7eaf2") + r(1, "d99ec5") +
r(1, "00aec5") + r(1, "f7c6dc") + r(1, "98d9ef") + r(1, "ced7ed") + r(1, "fed17b");
})();

View File

@ -0,0 +1,189 @@
<?xml version="1.0" encoding="utf-8"?>
<objects>
<script file="gui/common/functions_global_object.js"/>
<script file="gui/common/functions_utility.js"/>
<script file="gui/common/timer.js"/>
<script file="gui/lobby/lobby.js"/>
<object name="sn">
<object hotkey="complete.playername">
<action on="Press">completeNick();</action>
</object>
</object>
<object type="image" style="ModernWindow" size="0 0 100% 100%" name="lobbyWindow">
<object style="TitleText" type="text" size="50%-128 0%+4 50%+128 36">
Multiplayer Lobby
</object>
<action on="Tick">
onTick();
</action>
<!-- Left panel: Player list. -->
<object name="leftPanel" size="20 30 15% 100%-50">
<object name="playersBox" style="ModernList" type="olist" size="0 0 100% 100%">
<def id="status" heading="Status" width="40%"/>
<def id="name" heading="Name" width="60%"/>
</object>
</object>
<object name="leftButtonPanel" size="20 100%-45 15% 100%-20">
<object type="button" style="StoneButton" size="0 0 100% 100%">
Leaderboard
<action on="Press">getGUIObjectByName("leaderboard").hidden = false;getGUIObjectByName("leaderboardFade").hidden = false;</action>
</object>
</object>
<!-- Right panel: Game details. -->
<object name="rightPanel" size="100%-300 30 100%-20 100%-20">
<object name="gameInfoEmpty" size="0 0 100% 100%-60" type="image" sprite="ModernDarkBoxGold" hidden="false">
<object size="50%-110 50%-50 50%+110 50%+50" type="image" sprite="productLogo"/>
</object>
<object name="gameInfo" size="0 0 100% 100%-90" type="image" sprite="ModernDarkBoxGold" hidden="true">
<!-- Map Name -->
<object name="sgMapName" size="0 5 100% 20" type="text" style="ModernCenteredLabelText"/>
<!-- Map Preview -->
<object name="sgMapPreview" size="5 25 100%-5 235" type="image" sprite=""/>
<object size="5 239 100%-5 240" type="image" sprite="ModernWhiteLine" z="25"/>
<!-- Map Type -->
<object size="5 240 50% 265" type="image" sprite="ModernItemBackShadeLeft">
<object size="0 0 100%-10 100%" type="text" style="ModernRightLabelText">Map Type:</object>
</object>
<object size="50% 240 100%-5 265" type="image" sprite="ModernItemBackShadeRight">
<object name="sgMapType" size="0 0 100% 100%" type="text" style="ModernLeftLabelText"/>
</object>
<object size="5 264 100%-5 265" type="image" sprite="ModernWhiteLine" z="25"/>
<!-- Map Size -->
<object size="5 265 50% 290" type="image" sprite="ModernItemBackShadeLeft">
<object size="0 0 100%-10 100%" type="text" style="ModernRightLabelText">Map Size:</object>
</object>
<object size="50% 265 100%-5 290" type="image" sprite="ModernItemBackShadeRight">
<object name="sgMapSize" size="0 0 100% 100%" type="text" style="ModernLeftLabelText"/>
</object>
<object size="5 289 100%-5 290" type="image" sprite="ModernWhiteLine" z="25"/>
<!-- Map Description -->
<object type="image" sprite="ModernDarkBoxWhite" size="3% 295 97% 75%">
<object name="sgMapDescription" size="0 0 100% 100%" type="text" style="MapDescription"/>
</object>
<object type="image" sprite="ModernDarkBoxWhite" size="3% 76% 97% 99%">
<!-- Number of Players -->
<object size="32% 3% 57% 12%" type="text" style="ModernLeftLabelText">Players:</object>
<object name="sgNbPlayers" size="52% 3% 62% 12%" type="text" style="ModernLeftLabelText"/>
<!-- Player Names -->
<object name="sgPlayersNames" size="0 15% 100% 100%" type="text" style="MapPlayerList"/>
</object>
</object>
<object name="joinGameButton" type="button" style="StoneButton" size="0 100%-85 100% 100%-60" hidden="true">
Join Game
<action on="Press">
joinSelectedGame();
</action>
</object>
<object name="hostButton" type="button" style="StoneButton" size="0 100%-55 100% 100%-30">
Host Game
<action on="Press">
// Open Multiplayer connection window with host option.
Engine.PushGuiPage("page_gamesetup_mp.xml", { multiplayerGameType: "host", name: g_Name });
</action>
</object>
<object type="button" style="StoneButton" size="0 100%-25 100% 100%">
Main Menu
<action on="Press">
lobbyStop();
Engine.SwitchGuiPage("page_pregame.xml");
</action>
</object>
</object>
<!-- Middle panel: Filters, game list, chat box. -->
<object name="middlePanel" size="15%+5 5% 100%-305 97.2%">
<object name="gamesBox" style="ModernList" type="olist" size="0 25 100% 48%">
<action on="SelectionChange">selectGame(this.selected);</action>
<def id="name" heading="Name" color="0 60 0" width="25%"/>
<!--<def id="ip" heading="IP" color="0 128 128" width="170"/>-->
<def id="mapName" heading="Map Name" color="128 128 128" width="25%"/>
<def id="mapSize" heading="Map Size" color="128 128 128" width="20%"/>
<def id="mapType" heading="Map Type" color="0 128 128" width="20%"/>
<def id="nPlayers" heading="Players" color="0 128 128" width="10%"/>
</object>
<object name="filterPanel" size="0 0 100% 20">
<object name="mapSizeFilter"
type="dropdown"
style="ModernDropDown"
size="49.7% 0 62% 100%">
<action on="SelectionChange">applyFilters();</action>
</object>
<object name="mapTypeFilter"
type="dropdown"
style="ModernDropDown"
size="69.3% 0 82% 100%">
<action on="SelectionChange">applyFilters();</action>
</object>
<object name="playersNumberFilter"
type="dropdown"
style="ModernDropDown"
size="89% 0 100% 100%">
<action on="SelectionChange">applyFilters();</action>
</object>
<object type="text" size="15 0 35% 100%" text_align="left" textcolor="white">Show full servers</object>
<object name="showFullFilter"
type="checkbox"
checked="false"
style="StoneCrossBox"
size="0% 3 10 100%-3">
<action on="Press">applyFilters();</action>
</object>
</object>
<object name="chatPanel" size="0 49% 100% 100%" type="image" sprite="ModernDarkBoxGold">
<object name="chatText" size="0 0 100% 94%" type="text" style="ChatPanel"/>
<object name="chatInput" size="0 94% 100% 100%" type="input" style="ModernInput">
<action on="Press">submitChatInput();</action>
</object>
</object>
</object>
<!-- START Window for leaderboard stats -->
<!-- Add a translucent black background to fade out the menu page -->
<object hidden="true" name="leaderboardFade" type="image" z="100" sprite="modernFade"/>
<object hidden="true" name="leaderboard" type="image" style="ModernDialog" size="50%-224 50%-160 50%+224 50%+160" z="101">
<object style="TitleText" type="text" size="50%-128 0%-16 50%+128 16">Leaderboard</object>
<object name="leaderboardBox"
style="ModernList"
type="olist"
size="19 19 100%-19 100%-58">
<def id="rank" heading="Rank" color="255 255 255" width="15%"/>
<def id="rating" heading="Rating" color="255 255 255" width="20%"/>
<def id="name" heading="Name" color="255 255 255" width="65%"/>
</object>
<object type="button" style="StoneButton" size="50%+5 100%-45 50%+133 100%-17">
Back
<action on="Press">getGUIObjectByName("leaderboard").hidden = true;getGUIObjectByName("leaderboardFade").hidden = true;</action>
</object>
<object type="button" style="StoneButton" size="50%-133 100%-45 50%-5 100%-17">
Update
<action on="Press">Engine.SendGetBoardList();</action>
</object>
</object>
<!-- END Window for leaderboard stats -->
</object>
</objects>

View File

@ -0,0 +1,148 @@
var g_LobbyIsConnecting = false;
var g_EncrytedPassword = "";
function init()
{
g_EncrytedPassword = Engine.ConfigDB_GetValue("user", "lobby.password");
}
function lobbyStop()
{
getGUIObjectByName("connectFeedback").caption = "";
getGUIObjectByName("registerFeedback").caption = "";
if (g_LobbyIsConnecting == false)
return;
g_LobbyIsConnecting = false;
Engine.StopXmppClient();
}
function lobbyStart()
{
if (g_LobbyIsConnecting != false)
return;
var username = getGUIObjectByName("connectUsername").caption;
var password = getGUIObjectByName("connectPassword").caption;
var feedback = getGUIObjectByName("connectFeedback");
// Use username as nick unless overridden.
if (getGUIObjectByName("nickPanel").hidden == true)
var nick = sanitizePlayerName(username, true, true);
else
var nick = sanitizePlayerName(getGUIObjectByName("joinPlayerName").caption, true, true);
if (!username || !password)
{
feedback.caption = "Username or password empty";
return;
}
feedback.caption = "Connecting..";
// If they enter a different password, re-encrypt.
if (password != g_EncrytedPassword)
g_EncrytedPassword = Engine.EncryptPassword(password, username);
Engine.StartXmppClient(username, g_EncrytedPassword, "arena", nick);
g_LobbyIsConnecting = true;
Engine.ConnectXmppClient();
}
function lobbyStartRegister()
{
if (g_LobbyIsConnecting != false)
return;
var account = getGUIObjectByName("connectUsername").caption;
var password = getGUIObjectByName("connectPassword").caption;
var passwordAgain = getGUIObjectByName("registerPasswordAgain").caption;
var feedback = getGUIObjectByName("registerFeedback");
if (!account || !password || !passwordAgain)
{
feedback.caption = "Login or password empty";
return;
}
if (password != passwordAgain)
{
feedback.caption = "Password mismatch";
getGUIObjectByName("connectPassword").caption = "";
getGUIObjectByName("registerPasswordAgain").caption = "";
return;
}
// Check they are using a valid account name.
sanitizedName = sanitizePlayerName(account, true, true)
if (sanitizedName != account)
{
feedback.caption = "Sorry, you can't use [, ], unicode, whitespace, or commas.";
return;
}
feedback.caption = "Registering...";
if (password != g_EncrytedPassword)
g_EncrytedPassword = Engine.EncryptPassword(password, username);
Engine.StartRegisterXmppClient(account, g_EncrytedPassword);
g_LobbyIsConnecting = true;
Engine.ConnectXmppClient();
}
function onTick()
{
if (!g_LobbyIsConnecting)
// The Xmpp Client has not been created
return;
// The XmppClient has been created, we are waiting
// to be connected or to receive an error.
//Wake up XmppClient
Engine.RecvXmppClient();
//Receive messages
while (true)
{
var message = Engine.LobbyGuiPollMessage();
if (!message)
break;
if (message.type == "muc" && message.level == "join")
{
// We are connected, switch to the lobby page
Engine.PopGuiPage();
var username = getGUIObjectByName("connectUsername").caption;
var password = getGUIObjectByName("connectPassword").caption;
// Use username as nick unless overridden.
if (getGUIObjectByName("nickPanel").hidden == true)
var nick = sanitizePlayerName(username, true, true);
else
var nick = sanitizePlayerName(getGUIObjectByName("joinPlayerName").caption, true, true);
// Switch to lobby
Engine.SwitchGuiPage("page_lobby.xml");
// Store nick, login, and password
Engine.ConfigDB_CreateValue("user", "playername", nick);
Engine.ConfigDB_CreateValue("user", "lobby.login", username);
// We only store the encrypted password, so make sure to re-encrypt it if changed before saving.
if (password != g_EncrytedPassword)
g_EncrytedPassword = Engine.EncryptPassword(password, username);
Engine.ConfigDB_CreateValue("user", "lobby.password", g_EncrytedPassword);
Engine.ConfigDB_WriteFile("user", "config/user.cfg");
return;
}
else if (message.type == "system" && message.text == "registered")
{
// Great, we are registered. Switch to the connection window.
getGUIObjectByName("registerFeedback").caption = toTitleCase(message.text);
getGUIObjectByName("connectFeedback").caption = toTitleCase(message.text);
Engine.StopXmppClient();
g_LobbyIsConnecting = false;
getGUIObjectByName("pageRegister").hidden = true;
getGUIObjectByName("pageConnect").hidden = false;
}
else if(message.type == "system" && (message.level == "error" || message.text == "disconnected"))
{
getGUIObjectByName("connectFeedback").caption = toTitleCase(message.text);
getGUIObjectByName("registerFeedback").caption = toTitleCase(message.text);
Engine.StopXmppClient();
g_LobbyIsConnecting = false;
}
}
}

View File

@ -0,0 +1,120 @@
<?xml version="1.0" encoding="utf-8"?>
<objects>
<script file="gui/lobby/prelobby.js"/>
<script file="gui/common/functions_global_object.js"/>
<script file="gui/common/functions_utility.js"/>
<object name="dialog" type="image" style="StoneDialog" size="50%-190 50%-140 50%+190 50%+140">
<action on="Tick">
onTick();
</action>
<object name="pageConnecting" hidden="true">
<object name="connectionStatus" type="text" text_align="center" size="0 100 100% 120">
[Connection status]
</object>
</object>
<object style="TitleText" type="text" size="50%-128 0%-16 50%+128 16">
Multiplayer Lobby
</object>
<object name="pageConnect" size="0 32 100% 100%">
<object type="text" style="CenteredLabelText" size="0 0 400 30">
Connect to the game lobby.
</object>
<object type="text" size="0 40 200 70" style="RightLabelText">
Login:
</object>
<object name="connectUsername" type="input" size="210 40 100%-32 64" style="StoneInput">
<action on="Load">
this.caption = Engine.ConfigDB_GetValue("user", "lobby.login");
</action>
</object>
<object type="text" size="0 80 200 110" style="RightLabelText">
Password:
</object>
<object name="connectPassword" type="input" size="210 80 100%-32 104" style="StonePasswordInput">
<action on="Load">
this.caption = Engine.ConfigDB_GetValue("user", "lobby.password");
</action>
<action on="Press">
lobbyStart();
</action>
</object>
<object name="nickPanel" size="64 120 100%-32 144" hidden="true">
<object type="text" size="0 0 136 100%" style="RightLabelText">
Nickname:
</object>
<object name="joinPlayerName" type="input" size="146 0 100% 100%" style="StoneInput">
<action on="Load">
this.caption = Engine.ConfigDB_GetValue("user", "playername");
</action>
</object>
</object>
<object name="nickToggle" type="button" style="StoneButton" size="100%-64 120 100%-32 144">
<![CDATA[<<]]>
<action on="Press"><![CDATA[
this.hidden = true;
getGUIObjectByName("nickPanel").hidden = false;
]]></action>
</object>
<object name="connectFeedback" type="text" style="CenteredLabelText" size="32 150 100%-32 180" textcolor="red"/>
<object type="button" size="32 100%-60 122 100%-32" style="StoneButton">
Cancel
<action on="Press">
lobbyStop();
Engine.PopGuiPage();
</action>
</object>
<object type="button" size="145 100%-60 235 100%-32" style="StoneButton">
Register
<action on="Press">
lobbyStop();
getGUIObjectByName("pageConnect").hidden = true;
getGUIObjectByName("pageRegister").hidden = false;
</action>
</object>
<object type="button" size="258 100%-60 100%-32 100%-32" style="StoneButton">
Connect
<action on="Press">
lobbyStart();
</action>
</object>
</object>
<object name="pageRegister" size="0 32 100% 100%" hidden="true">
<object type="text" style="CenteredLabelText" size="0 0 400 30">
Registration.
</object>
<object type="text" size="0 40 200 70" style="RightLabelText">
Password again:
</object>
<object name="registerPasswordAgain" type="input" size="210 40 100%-32 64" style="StonePasswordInput">
<action on="Press">
lobbyStartRegister()
</action>
</object>
<object name="registerFeedback" type="text" style="CenteredLabelText" size="32 150 100%-32 180" textcolor="red"/>
<object type="button" size="32 100%-60 122 100%-32" style="StoneButton">
Back
<action on="Press">
lobbyStop();
getGUIObjectByName("pageRegister").hidden = true;
getGUIObjectByName("pageConnect").hidden = false;
</action>
</object>
<object type="button" size="258 100%-60 100%-32 100%-32" style="StoneButton">
Register
<action on="Press">
lobbyStartRegister()
</action>
</object>
</object>
</object>
</objects>

View File

@ -0,0 +1,37 @@
<?xml version="1.0" encoding="utf-8"?>
<styles>
<style name="ChatPanel"
buffer_zone="5"
font="serif-13"
scrollbar="true"
scrollbar_style="ModernScrollBar"
scroll_bottom="true"
textcolor="white"
textcolor_selected="gold"
text_align="left"
text_valign="center"
/>
<style name="MapDescription"
buffer_zone="8"
font="serif-12"
scrollbar="true"
scrollbar_style="ModernScrollBar"
scroll_bottom="true"
textcolor="white"
text_align="left"
text_valign="top"
/>
<style name="MapPlayerList"
buffer_zone="8"
font="serif-14"
scrollbar="true"
scrollbar_style="ModernScrollBar"
scroll_bottom="true"
textcolor="white"
text_align="left"
text_valign="top"
/>
</styles>

View File

@ -2,67 +2,104 @@
<!--
==========================================
- POST-GAME SUMMARY SCREEN -
- Options Window -
==========================================
-->
<objects>
<script file="gui/common/functions_civinfo.js"/>
<script file="gui/common/functions_utility.js" />
<script file="gui/common/functions_global_object.js" />
<script file="gui/session/music.js"/>
<script file="gui/options/options.js"/>
<!-- Add a translucent black background to fade out the menu page -->
<!-- Add a translucent black background to fade out the menu page -->
<object type="image" z="0" style="TranslucentPanel"/>
<!-- Settings Window -->
<object name="options" type="image" style="StonePanelLight" size="50%-190 50%-120 50%+190 50%+120">
<object style="StoneDialogTitleBar" type="text" size="50%-128 0%-16 50%+128 16">
Game Options
</object>
<object size="50%-190 50%-80 50%+140 50%+95">
<!-- Settings / shadows -->
<object size="0 10 100%-80 35" type="text" style="RightLabelText" ghost="true">Enable Shadows</object>
<object name="shadowsCheckbox" size="100%-56 15 100%-30 40" type="checkbox" style="StoneCrossBox" checked="true">
<action on="Load">this.checked = Engine.Renderer_GetShadowsEnabled();</action>
<action on="Press">Engine.Renderer_SetShadowsEnabled(this.checked);</action>
</object>
<!-- Settings / Shadow PCF -->
<object size="0 35 100%-80 60" type="text" style="RightLabelText" ghost="true">Enable Shadow Filtering</object>
<object name="shadowPCFCheckbox" size="100%-56 40 100%-30 65" type="checkbox" style="StoneCrossBox" checked="true">
<action on="Load">this.checked = Engine.Renderer_GetShadowPCFEnabled();</action>
<action on="Press">Engine.Renderer_SetShadowPCFEnabled(this.checked);</action>
</object>
<!-- Settings / Water -->
<!-- <object size="0 60 100%-80 85" type="text" style="RightLabelText" ghost="true">Enable Water Reflections</object>
<object name="fancyWaterCheckbox" size="100%-56 65 100%-30 90" type="checkbox" style="StoneCrossBox" checked="true">
<action on="Load">this.checked = Engine.Renderer_GetWaterNormalEnabled();</action>
<action on="Press">Engine.Renderer_SetWaterNormalEnabled(this.checked);</action>
</object>-->
<!-- Settings / Music-->
<object size="0 60 100%-80 85" type="text" style="RightLabelText" ghost="true">Enable Music</object>
<object size="100%-56 65 100%-30 90" type="checkbox" style="StoneCrossBox" checked="true">
<action on="Press">if (this.checked) startMusic(); else stopMusic();</action>
</object>
<!-- Settings / Dev Overlay -->
<!-- <object size="0 110 100%-80 135" type="text" style="RightLabelText" ghost="true">Developer Overlay</object>
<object size="100%-56 115 100%-30 140" type="checkbox" style="StoneCrossBox" checked="false">
<action on="Press">toggleDeveloperOverlay();</action>
</object>-->
</object>
<object type="button" style="StoneButton" size="50%-64 100%-64 50%+64 100%-32">
Cancel
<action on="Press"><![CDATA[Engine.PopGuiPage();]]></action>
</object>
<!-- Settings Window -->
<object name="options" type="image" style="StoneDialog" size="50%-466 50%-316 50%+466 50%+316">
<object style="TitleText" type="text" size="50%-128 0%-16 50%+128 16">Game Options</object>
<object name="SystemSettings" type="image" sprite="BackgroundIndentFillDark" size="16 16 316 100%-16">
<object style="TitleText" type="text" size="0 5 100% 25">System Settings</object>
<object size="0 25 65% 50" type="text" style="RightLabelText" ghost="true">Windowed Mode</object>
<object name="WindowedCFGLate" size="70% 30 70%+25 55" type="checkbox" style="StoneCrossBox">
<action on="Load">Engine.ConfigDB_GetValue("user", "windowed") === "true" ? this.checked = true : this.checked = false;</action>
<action on="Press">Engine.ConfigDB_CreateValue("user", "windowed", String(this.checked));</action>
</object>
<object size="0 50 65% 75" type="text" style="RightLabelText" ghost="true">Background Pause</object>
<object name="PauseCFGNow" size="70% 55 70%+25 80" type="checkbox" style="StoneCrossBox">
<action on="Load">Engine.ConfigDB_GetValue("user", "pauseonfocusloss") === "true" ? this.checked = true : this.checked = false;</action>
<action on="Press">Engine.ConfigDB_CreateValue("user", "pauseonfocusloss", String(this.checked));</action>
</object>
</object>
<object name="GraphicsSettings" type="image" sprite="BackgroundIndentFillDark" size="316 16 616 100%-16">
<object style="TitleText" type="text" size="0 5 100% 25">Graphics Settings</object>
<object size="0 25 65% 50" type="text" style="RightLabelText" ghost="true">Enable Shadows</object>
<object name="ShadowsCFG" size="70% 30 70%+25 55" type="checkbox" style="StoneCrossBox">
<action on="Load">this.checked = Engine.Renderer_GetShadowsEnabled();</action>
<action on="Press">Engine.Renderer_SetShadowsEnabled(this.checked);</action>
</object>
<object size="0 50 65% 75" type="text" style="RightLabelText" ghost="true">Enable Shadow Filtering</object>
<object name="ShadowPCFCFGNow" size="70% 55 70%+25 80" type="checkbox" style="StoneCrossBox">
<action on="Load">this.checked = Engine.Renderer_GetShadowPCFEnabled();</action>
<action on="Press">Engine.Renderer_SetShadowPCFEnabled(this.checked);</action>
</object>
</object>
<object name="SoundSettings" type="image" sprite="BackgroundIndentFillDark" size="616 16 916 100%-16">
<object style="TitleText" type="text" size="0 5 100% 25">Sound Settings</object>
<object size="0 25 65% 50" type="text" style="RightLabelText" ghost="true">Master Gain</object>
<object name="SMasterCFG" size="70% 25 70%+35 50" type="input" style="StoneInput">
<action on="Load">this.caption = Engine.ConfigDB_GetValue("user", "sound.mastergain");</action>
</object>
<object size="70%+35 25 70%+75 50" type="button" style="StoneButton">Save
<action on="Press">Engine.ConfigDB_CreateValue("user", "sound.mastergain", String(getGUIObjectByName("SMasterCFG").caption));</action>
</object>
<object size="0 50 65% 75" type="text" style="RightLabelText" ghost="true">Music Gain</object>
<object name="SMusicCFG" size="70% 50 70%+35 75" type="input" style="StoneInput">
<action on="Load">this.caption = Engine.ConfigDB_GetValue("user", "sound.musicgain");</action>
</object>
<object size="70%+35 50 70%+75 75" type="button" style="StoneButton">Save
<action on="Press">Engine.ConfigDB_CreateValue("user", "sound.musicgain", String(getGUIObjectByName("SMusicCFG").caption));</action>
</object>
</object>
<!--
Settings / shadows
<object size="0 10 100%-80 35" type="text" style="RightLabelText" ghost="true">Enable Shadows</object>
<object name="shadowsCheckbox" size="100%-56 15 100%-30 40" type="checkbox" style="StoneCrossBox" checked="true">
<action on="Load">this.checked = Engine.Renderer_GetShadowsEnabled();</action>
<action on="Press">Engine.Renderer_SetShadowsEnabled(this.checked);</action>
</object>
Settings / Shadow PCF
<object size="0 35 100%-80 60" type="text" style="RightLabelText" ghost="true">Enable Shadow Filtering</object>
<object name="shadowPCFCheckbox" size="100%-56 40 100%-30 65" type="checkbox" style="StoneCrossBox" checked="true">
<action on="Load">this.checked = Engine.Renderer_GetShadowPCFEnabled();</action>
<action on="Press">Engine.Renderer_SetShadowPCFEnabled(this.checked);</action>
</object>
Settings / Water
<object size="0 60 100%-80 85" type="text" style="RightLabelText" ghost="true">Enable Water Reflections</object>
<object name="fancyWaterCheckbox" size="100%-56 65 100%-30 90" type="checkbox" style="StoneCrossBox" checked="true">
<action on="Load">this.checked = Engine.Renderer_GetWaterNormalEnabled();</action>
<action on="Press">Engine.Renderer_SetWaterNormalEnabled(this.checked);</action>
</object>
Settings / Music
<object size="0 60 100%-80 85" type="text" style="RightLabelText" ghost="true">Enable Music</object>
<object size="100%-56 65 100%-30 90" type="checkbox" style="StoneCrossBox" checked="true">
<action on="Press">if (this.checked) startMusic(); else stopMusic();</action>
</object>
Settings / Dev Overlay
<object size="0 110 100%-80 135" type="text" style="RightLabelText" ghost="true">Developer Overlay</object>
<object size="100%-56 115 100%-30 140" type="checkbox" style="StoneCrossBox" checked="false">
<action on="Press">toggleDeveloperOverlay();</action>
</object>
-->
<object type="button" style="StoneButton" size="50%+16 100%-64 50%+144 100%-32">
Cancel
<action on="Press">Engine.PopGuiPage();</action>
</object>
<object type="button" style="StoneButton" size="50%-144 100%-64 50%-16 100%-32">
Save
<action on="Press">Engine.ConfigDB_WriteFile("user", "config/user.cfg");Engine.PopGuiPage();</action>
</object>
</object>
</objects>

View File

@ -0,0 +1,17 @@
<?xml version="1.0" encoding="utf-8"?>
<page>
<include>common/modern/styles.xml</include>
<include>common/modern/sprites.xml</include>
<include>common/modern/setup.xml</include>
<include>common/setup.xml</include>
<include>common/styles.xml</include>
<include>common/sprite1.xml</include>
<include>common/global.xml</include>
<include>common/common_sprites.xml</include>
<include>common/common_styles.xml</include>
<include>lobby/styles.xml</include>
<include>lobby/lobby.xml</include>
</page>

View File

@ -5,11 +5,8 @@
<include>common/sprite1.xml</include>
<include>common/icon_sprites.xml</include>
<include>common/init.xml</include>
<include>common/global.xml</include>
<include>common/common_sprites.xml</include>
<include>common/common_styles.xml</include>
<include>common/common_sprites.xml</include>
<include>common/common_styles.xml</include>
<include>options/options.xml</include>
</page>

View File

@ -0,0 +1,14 @@
<?xml version="1.0" encoding="utf-8"?>
<page>
<include>common/setup.xml</include>
<include>common/styles.xml</include>
<include>common/sprite1.xml</include>
<include>common/common_sprites.xml</include>
<include>common/common_styles.xml</include>
<include>gamesetup/styles.xml</include>
<include>summary/sprites.xml</include>
<include>lobby/prelobby.xml</include>
</page>

View File

@ -226,7 +226,7 @@ Status: $status.
<action on="Press">
closeMenu();
// Open Multiplayer connection window with join option.
Engine.PushGuiPage("page_gamesetup_mp.xml", "join");
Engine.PushGuiPage("page_gamesetup_mp.xml", { multiplayerGameType: "join" });
</action>
</object>
@ -241,10 +241,32 @@ Status: $status.
<action on="Press">
closeMenu();
// Open Multiplayer connection window with host option.
Engine.PushGuiPage("page_gamesetup_mp.xml", "host");
Engine.PushGuiPage("page_gamesetup_mp.xml", { multiplayerGameType: "host" });
</action>
</object>
</object>
<object name="subMenuMultiplayerLobbyButton"
type="button"
style="StoneButtonFancy"
size="0 64 100% 92"
tooltip_style="pgToolTip"
tooltip="Launch the multiplayer lobby."
>
Game Lobby
<action on="Press">
closeMenu();
// Open Multiplayer game lobby.
Engine.PushGuiPage("page_prelobby.xml", []);
</action>
<action on="load">
if (!Engine.StartXmppClient)
{
this.enabled = false;
this.tooltip = "Launch the multiplayer lobby. [DISABLED BY BUILD]";
}
</action>
</object>
</object>
<!-- submenuToolsAndOptions -->
<object name="submenuToolsAndOptions"
@ -259,8 +281,8 @@ Status: $status.
type="button"
size="0 0 100% 28"
tooltip_style="pgToolTip"
tooltip="Adjust game settings. [NOT YET IMPLEMENTED]"
enabled="false"
tooltip="Adjust game settings."
enabled="true"
>
Options
<action on="Press">
@ -375,7 +397,7 @@ Status: $status.
Multiplayer
<action on="Press">
closeMenu();
openMenu("submenuMultiplayer", (this.parent.size.top+this.size.top), (this.size.bottom-this.size.top), 2);
openMenu("submenuMultiplayer", (this.parent.size.top+this.size.top), (this.size.bottom-this.size.top), 3);
</action>
</object>

View File

@ -224,6 +224,12 @@ function handleNetMessage(message)
}
g_PlayerAssignments = message.hosts;
if (g_IsController)
{
var players = [ assignment.name for each (assignment in g_PlayerAssignments) ]
Engine.SendChangeStateGame(Object.keys(g_PlayerAssignments).length, players.join(", "));
}
break;

View File

@ -1,6 +1,9 @@
// Network Mode
var g_IsNetworked = false;
// Is this user in control of game settings (i.e. is a network server, or offline player)
var g_IsController;
// Cache the basic player data (name, civ, color)
var g_Players = [];
// Cache the useful civ data
@ -34,6 +37,9 @@ var g_GameEnded = false;
var g_Disconnected = false; // Lost connection to server
// Holds player states from the last tick
var g_CachedLastStates = "";
// Colors to flash when pop limit reached
const DEFAULT_POPULATION_COLOR = "white";
const POPULATION_ALERT_COLOR = "orange";
@ -94,7 +100,9 @@ function init(initData, hotloadData)
if (initData)
{
g_IsNetworked = initData.isNetworked; // Set network mode
g_IsController = initData.isController; // Set controller mode
g_PlayerAssignments = initData.playerAssignments;
g_MatchID = initData.attribs.matchID;
// Cache the player data
// (This may be updated at runtime by handleNetMessage)
@ -217,8 +225,9 @@ function leaveGame()
{
var extendedSimState = Engine.GuiInterfaceCall("GetExtendedSimulationState");
var playerState = extendedSimState.players[Engine.GetPlayerID()];
var mapSettings = Engine.GetMapSettings();
var gameResult;
if (g_Disconnected)
{
gameResult = "You have been disconnected."
@ -244,11 +253,13 @@ function leaveGame()
global.music.setState(global.music.states.DEFEAT);
}
var mapSettings = Engine.GetMapSettings();
stopAmbient();
endGame();
if (g_IsController)
{
Engine.SendUnregisterGame();
}
Engine.SwitchGuiPage("page_summary.xml", {
"gameResult" : gameResult,
"timeElapsed" : extendedSimState.timeElapsed,
@ -339,68 +350,64 @@ function onTick()
function checkPlayerState()
{
var simState = GetSimState();
var playerState = simState.players[Engine.GetPlayerID()];
// Once the game ends, we're done here.
if (g_GameEnded)
return;
if (!g_GameEnded)
// Send a game report for each player in this game.
var m_simState = GetSimState();
var playerState = m_simState.players[Engine.GetPlayerID()];
var tempStates = "";
for each (var player in m_simState.players) {tempStates += player.state + ",";}
if (g_CachedLastStates != tempStates)
{
// If the game is about to end, disable the ability to resign.
if (playerState.state != "active")
getGUIObjectByName("menuResignButton").enabled = false;
else
return;
if (playerState.state == "defeated")
{
g_GameEnded = true;
// TODO: DEFEAT_CUE is missing?
global.music.setState(global.music.states.DEFEAT);
closeMenu();
closeOpenDialogs();
if (Engine.IsAtlasRunning())
{
// If we're in Atlas, we can't leave the game
var btCaptions = ["OK"];
var btCode = [null];
var message = "Press OK to continue";
}
else
{
var btCaptions = ["Yes", "No"];
var btCode = [leaveGame, null];
var message = "Do you want to quit?";
}
messageBox(400, 200, message, "DEFEATED!", 0, btCaptions, btCode);
}
else if (playerState.state == "won")
{
g_GameEnded = true;
global.music.setState(global.music.states.VICTORY);
closeMenu();
closeOpenDialogs();
if (!getGUIObjectByName("devCommandsRevealMap").checked)
getGUIObjectByName("devCommandsRevealMap").checked = true;
if (Engine.IsAtlasRunning())
{
// If we're in Atlas, we can't leave the game
var btCaptions = ["OK"];
var btCode = [null];
var message = "Press OK to continue";
}
else
{
var btCaptions = ["Yes", "No"];
var btCode = [leaveGame, null];
var message = "Do you want to quit?";
}
messageBox(400, 200, message, "VICTORIOUS!", 0, btCaptions, btCode);
}
g_CachedLastStates = tempStates;
reportGame(Engine.GuiInterfaceCall("GetExtendedSimulationState"));
}
// If the local player hasn't finished playing, we return here to avoid the victory/defeat messages.
if (playerState.state == "active")
return;
// We can't resign once the game is over.
getGUIObjectByName("menuResignButton").enabled = false;
// Make sure nothing is open to avoid stacking.
closeMenu();
closeOpenDialogs();
// Make sure this doesn't run again.
g_GameEnded = true;
if (Engine.IsAtlasRunning())
{
// If we're in Atlas, we can't leave the game
var btCaptions = ["OK"];
var btCode = [null];
var message = "Press OK to continue";
}
else
{
var btCaptions = ["Yes", "No"];
var btCode = [leaveGame, null];
var message = "Do you want to quit?";
}
if (playerState.state == "defeated")
{
global.music.setState(global.music.states.DEFEAT);
messageBox(400, 200, message, "DEFEATED!", 0, btCaptions, btCode);
}
else if (playerState.state == "won")
{
global.music.setState(global.music.states.VICTORY);
// TODO: Reveal map directly instead of this silly proxy.
if (!getGUIObjectByName("devCommandsRevealMap").checked)
getGUIObjectByName("devCommandsRevealMap").checked = true;
messageBox(400, 200, message, "VICTORIOUS!", 0, btCaptions, btCode);
}
}
function changeGameSpeed(speed)
@ -688,3 +695,120 @@ function stopAmbient()
currentAmbient = null;
}
}
// Send a report on the game status to the lobby
function reportGame(extendedSimState)
{
// Resources gathered and used
var playerFoodGatheredString = "";
var playerWoodGatheredString = "";
var playerStoneGatheredString = "";
var playerMetalGatheredString = "";
var playerFoodUsedString = "";
var playerWoodUsedString = "";
var playerStoneUsedString = "";
var playerMetalUsedString = "";
// Resources exchanged
var playerFoodBoughtString = "";
var playerWoodBoughtString = "";
var playerStoneBoughtString = "";
var playerMetalBoughtString = "";
var playerFoodSoldString = "";
var playerWoodSoldString = "";
var playerStoneSoldString = "";
var playerMetalSoldString = "";
var playerTradeIncomeString = "";
// Unit Stats
var playerUnitsLostString = "";
var playerUnitsTrainedString = "";
var playerEnemyUnitsKilledString = "";
// Building stats
var playerBuildingsConstructedString = "";
var playerBuildingsLostString = "";
var playerEnemyBuildingsDestroyedString = "";
var playerCivCentersBuiltString = "";
var playerEnemyCivCentersDestroyedString = "";
// Tribute
var playerTributeSentString = "";
var playerTributeReceivedString = "";
// Various
var mapName = Engine.GetMapSettings().Name;
var playerStatesString = "";
var playerCivsString = "";
var playerPercentMapExploredString = "";
var playerTreasuresCollectedString = "";
// Serialize the statistics for each player into a comma-separated list.
for each (var player in extendedSimState.players)
{
playerStatesString += player.state + ",";
playerCivsString += player.civ + ",";
playerFoodGatheredString += player.statistics.resourcesGathered.food + ",";
playerWoodGatheredString += player.statistics.resourcesGathered.wood + ",";
playerStoneGatheredString += player.statistics.resourcesGathered.stone + ",";
playerMetalGatheredString += player.statistics.resourcesGathered.metal + ",";
playerFoodUsedString += player.statistics.resourcesUsed.food + ",";
playerWoodUsedString += player.statistics.resourcesUsed.wood + ",";
playerStoneUsedString += player.statistics.resourcesUsed.stone + ",";
playerMetalUsedString += player.statistics.resourcesUsed.metal + ",";
playerUnitsLostString += player.statistics.unitsLost + ",";
playerUnitsTrainedString += player.statistics.unitsTrained + ",";
playerEnemyUnitsKilledString += player.statistics.enemyUnitsKilled + ",";
playerBuildingsConstructedString += player.statistics.buildingsConstructed + ",";
playerBuildingsLostString += player.statistics.buildingsLost + ",";
playerEnemyBuildingsDestroyedString += player.statistics.enemyBuildingsDestroyed + ",";
playerFoodBoughtString += player.statistics.resourcesBought.food + ",";
playerWoodBoughtString += player.statistics.resourcesBought.wood + ",";
playerStoneBoughtString += player.statistics.resourcesBought.stone + ",";
playerMetalBoughtString += player.statistics.resourcesBought.metal + ",";
playerFoodSoldString += player.statistics.resourcesSold.food + ",";
playerWoodSoldString += player.statistics.resourcesSold.wood + ",";
playerStoneSoldString += player.statistics.resourcesSold.stone + ",";
playerMetalSoldString += player.statistics.resourcesSold.metal + ",";
playerTributeSentString += player.statistics.tributesSent + ",";
playerTributeReceivedString += player.statistics.tributesReceived + ",";
playerPercentMapExploredString += player.statistics.precentMapExplored = ",";
playerCivCentersBuiltString += player.statistics.civCentresBuilt + ",";
playerEnemyCivCentersDestroyedString += player.statistics.enemyCivCentresDestroyed + ",";
playerTreasuresCollectedString += player.statistics.treasuresCollected + ",";
playerTradeIncomeString += player.statistics.tradeIncome + ",";
}
// Send the report with serialized data
Engine.SendGameReport({
"timeElapsed" : extendedSimState.timeElapsed,
"playerStates" : playerStatesString,
"playerID": Engine.GetPlayerID(),
"matchID": g_MatchID,
"civs" : playerCivsString,
"mapName" : mapName,
"foodGathered": playerFoodGatheredString,
"woodGathered": playerWoodGatheredString,
"stoneGathered": playerStoneGatheredString,
"metalGathered": playerMetalGatheredString,
"foodUsed": playerFoodUsedString,
"woodUsed": playerWoodUsedString,
"stoneUsed": playerStoneUsedString,
"metalUsed": playerMetalUsedString,
"unitsLost": playerUnitsLostString,
"unitsTrained": playerUnitsTrainedString,
"enemyUnitsKilled": playerEnemyUnitsKilledString,
"buildingsLost": playerBuildingsLostString,
"buildingsConstructed": playerBuildingsConstructedString,
"enemyBuildingsDestroyed": playerEnemyBuildingsDestroyedString,
"foodBought": playerFoodBoughtString,
"woodBought": playerWoodBoughtString,
"stoneBought": playerStoneBoughtString,
"metalBought": playerMetalBoughtString,
"foodSold": playerFoodSoldString,
"woodSold": playerWoodSoldString,
"stoneSold": playerStoneSoldString,
"metalSold": playerMetalSoldString,
"tributeSent": playerTributeSentString,
"tributeReceived": playerTributeReceivedString,
"precentMapExplored": playerPercentMapExploredString,
"civCentersBuilt": playerCivCentersBuiltString,
"enemyCivCentersDestroyed": playerEnemyCivCentersDestroyedString,
"treasuresCollected": playerTreasuresCollectedString,
"tradeIncome": playerTradeIncomeString
});
}

View File

@ -247,9 +247,9 @@
<!-- Dev/cheat commands -->
<object name="devCommands" size="100%-156 50%-88 100%-8 50%+104" type="image" sprite="devCommandsBackground" z="40"
hidden="true" hotkey="session.devcommands.toggle">
<action on="Press">
toggleDeveloperOverlay();
</action>
<action on="Press">
if (!Engine.IsRankedGame()) toggleDeveloperOverlay();
</action>
<object size="0 0 100%-18 16" type="text" style="devCommandsText">Control all units</object>
<object size="100%-16 0 100% 16" type="checkbox" name="devControlAll" style="StoneCrossBox">
@ -537,6 +537,7 @@
<!-- Settings / Dev Overlay -->
<object size="0 310 100%-80 335" type="text" style="RightLabelText" ghost="true">Developer Overlay</object>
<object name="developerOverlayCheckbox" size="100%-56 315 100%-30 340" type="checkbox" style="StoneCrossBox" checked="false">
<action on="Load">if (Engine.IsRankedGame()) this.enabled = false;</action>
<action on="Press">toggleDeveloperOverlay();</action>
</object>
</object>
@ -1250,7 +1251,7 @@
size="50%-84 50%+128 50%+84 50%+160"
tooltip_style="sessionToolTip"
>
<object size="0 0 100% 100%" type="text" style="CenteredButtonText" name="disconnectedExitButtonText" ghost="true">Return to Main Menu</object>
<object size="0 0 100% 100%" type="text" style="CenteredButtonText" name="disconnectedExitButtonText" ghost="true">Exit</object>
<action on="Press">leaveGame()</action>
</object>

View File

@ -287,8 +287,17 @@
<object type="button" style="StoneButton" size="100%-164 100%-52 100%-24 100%-24">
Continue
<action on="Press">
Engine.SwitchGuiPage("page_pregame.xml");
<action on="Press"><![CDATA[
if(!Engine.HasXmppClient())
{
Engine.SwitchGuiPage("page_pregame.xml");
}
else
{
Engine.LobbySetPlayerPresence("available");
Engine.SwitchGuiPage("page_lobby.xml");
}
]]>
</action>
</object>
</object>

View File

@ -10,6 +10,7 @@ newoption { trigger = "minimal-flags", description = "Only set compiler/linker f
newoption { trigger = "without-nvtt", description = "Disable use of NVTT" }
newoption { trigger = "without-tests", description = "Disable generation of test projects" }
newoption { trigger = "without-pch", description = "Disable generation and usage of precompiled headers" }
newoption { trigger = "without-lobby", description = "Disable the use of gloox and the multiplayer lobby" }
newoption { trigger = "with-system-nvtt", description = "Search standard paths for nvidia-texture-tools library, instead of using bundled copy" }
newoption { trigger = "with-system-enet", description = "Search standard paths for libenet, instead of using bundled copy" }
newoption { trigger = "with-system-mozjs185", description = "Search standard paths for libmozjs185, instead of using bundled copy" }
@ -177,6 +178,10 @@ function project_set_build_flags()
defines { "CONFIG2_NVTT=0" }
end
if _OPTIONS["without-lobby"] then
defines { "CONFIG2_LOBBY=0" }
end
-- required for the lowlevel library. must be set from all projects that use it, otherwise it assumes it is
-- being used as a DLL (which is currently not the case in 0ad)
defines { "LIB_STATIC_LINK" }
@ -503,6 +508,8 @@ end
-- names of all static libs created. automatically added to the
-- main app project later (see explanation at end of this file)
static_lib_names = {}
static_lib_names_debug = {}
static_lib_names_release = {}
-- set up one of the static libraries into which the main engine code is split.
-- extra_params:
@ -567,6 +574,22 @@ function setup_all_libs ()
}
setup_static_lib_project("network", source_dirs, extern_libs, {})
<<<<<<< HEAD
if not _OPTIONS["without-lobby"] then
source_dirs = {
"lobby",
}
extern_libs = {
"spidermonkey",
"boost",
"gloox",
}
setup_static_lib_project("lobby", source_dirs, extern_libs, {})
end
=======
>>>>>>> 1a7a32ad84f645a7a907472ab2d3fd9be00d50c2
if _OPTIONS["use-shared-glooxwrapper"] and not _OPTIONS["build-shared-glooxwrapper"] then
table.insert(static_lib_names_debug, "glooxwrapper_dbg")
table.insert(static_lib_names_release, "glooxwrapper")
@ -636,7 +659,7 @@ function setup_all_libs ()
"zlib",
"boost",
"enet",
"libcurl"
"libcurl",
}
if not _OPTIONS["without-audio"] then
@ -686,7 +709,7 @@ function setup_all_libs ()
"spidermonkey",
"sdl", -- key definitions
"opengl",
"boost"
"boost",
}
setup_static_lib_project("gui", source_dirs, extern_libs, {})
@ -827,6 +850,10 @@ if not _OPTIONS["without-nvtt"] then
table.insert(used_extern_libs, "nvtt")
end
if not _OPTIONS["without-lobby"] then
table.insert(used_extern_libs, "gloox")
end
-- Bundles static libs together with main.cpp and builds game executable.
function setup_main_exe ()

View File

@ -35,6 +35,7 @@ CGUI
#include "CRadioButton.h"
#include "CInput.h"
#include "CList.h"
#include "COList.h"
#include "CDropDown.h"
#include "CProgressBar.h"
#include "CTooltip.h"
@ -442,6 +443,7 @@ void CGUI::Initialize()
AddObjectType("minimap", &CMiniMap::ConstructObject);
AddObjectType("input", &CInput::ConstructObject);
AddObjectType("list", &CList::ConstructObject);
AddObjectType("olist", &COList::ConstructObject);
AddObjectType("dropdown", &CDropDown::ConstructObject);
AddObjectType("tooltip", &CTooltip::ConstructObject);
}
@ -1753,7 +1755,7 @@ void CGUI::Xeromyces_ReadScrollBarStyle(XMBElement Element, CXeromyces* pFile)
{
XMBAttribute attr = attributes.Item(i);
CStr attr_name = pFile->GetAttributeString(attr.Name);
CStr attr_value (attr.Value);
CStr attr_value (attr.Value);
if (attr_value == "null")
continue;
@ -1761,6 +1763,14 @@ void CGUI::Xeromyces_ReadScrollBarStyle(XMBElement Element, CXeromyces* pFile)
if (attr_name == "name")
name = attr_value;
else
if (attr_name == "show_edge_buttons")
{
bool b;
if (!GUI<bool>::ParseString(attr_value.FromUTF8(), b))
LOGERROR(L"GUI: Error parsing '%hs' (\"%hs\")", attr_name.c_str(), attr_value.c_str());
else
scrollbar.m_UseEdgeButtons = b;
}
if (attr_name == "width")
{
float f;

View File

@ -41,7 +41,10 @@ void CGUIScrollBarVertical::SetPosFromMousePos(const CPos &mouse)
/**
* Calculate the position for the top of the item being scrolled
*/
m_Pos = m_PosWhenPressed + GetMaxPos() * (mouse.y - m_BarPressedAtPos.y) / (m_Length - GetStyle()->m_Width * 2 - m_BarSize);
float emptyBackground = m_Length - m_BarSize;
if (GetStyle()->m_UseEdgeButtons)
emptyBackground -= GetStyle()->m_Width * 2;
m_Pos = m_PosWhenPressed + GetMaxPos() * (mouse.y - m_BarPressedAtPos.y) / emptyBackground;
}
void CGUIScrollBarVertical::Draw()
@ -52,7 +55,6 @@ void CGUIScrollBarVertical::Draw()
return;
}
// Only draw the scrollbar if the GUI exists and there is something to scroll.
if (GetGUI() && GetMaxPos() != 1)
{
CRect outline = GetOuterRect();
@ -62,12 +64,12 @@ void CGUIScrollBarVertical::Draw()
0,
m_Z+0.1f,
CRect(outline.left,
outline.top+(m_UseEdgeButtons?GetStyle()->m_Width:0),
outline.top+(GetStyle()->m_UseEdgeButtons?GetStyle()->m_Width:0),
outline.right,
outline.bottom-(m_UseEdgeButtons?GetStyle()->m_Width:0))
outline.bottom-(GetStyle()->m_UseEdgeButtons?GetStyle()->m_Width:0))
);
if (m_UseEdgeButtons)
if (GetStyle()->m_UseEdgeButtons)
{
// Get Appropriate sprites
const CGUISpriteInstance *button_top, *button_bottom;
@ -136,7 +138,7 @@ CRect CGUIScrollBarVertical::GetBarRect() const
float from = m_Y;
float to = m_Y + m_Length - m_BarSize;
if (m_UseEdgeButtons)
if (GetStyle()->m_UseEdgeButtons)
{
from += GetStyle()->m_Width;
to -= GetStyle()->m_Width;

View File

@ -51,6 +51,8 @@ CInput::CInput()
AddSetting(GUIST_CStrW, "caption");
AddSetting(GUIST_int, "cell_id");
AddSetting(GUIST_CStrW, "font");
AddSetting(GUIST_CStrW, "mask_char");
AddSetting(GUIST_bool, "mask");
AddSetting(GUIST_int, "max_length");
AddSetting(GUIST_bool, "multiline");
AddSetting(GUIST_bool, "scrollbar");
@ -67,7 +69,6 @@ CInput::CInput()
// Add scroll-bar
CGUIScrollBarVertical * bar = new CGUIScrollBarVertical();
bar->SetRightAligned(true);
bar->SetUseEdgeButtons(true);
AddScrollBar(bar);
}
@ -1018,9 +1019,11 @@ void CInput::Draw()
bool scrollbar;
float buffer_zone;
bool multiline;
bool mask;
GUI<bool>::GetSetting(this, "scrollbar", scrollbar);
GUI<float>::GetSetting(this, "buffer_zone", buffer_zone);
GUI<bool>::GetSetting(this, "multiline", multiline);
GUI<bool>::GetSetting(this, "mask", mask);
if (scrollbar && multiline)
{
@ -1040,7 +1043,17 @@ void CInput::Draw()
// Get pointer of caption, it might be very large, and we don't
// want to copy it continuously.
CStrW *pCaption = (CStrW*)m_Settings["caption"].m_pSetting;
CStrW *pCaption = NULL;
wchar_t mask_char = L'*';
if (mask)
{
CStrW maskStr;
GUI<CStrW>::GetSetting(this, "mask_char", maskStr);
if (maskStr.length() > 0)
mask_char = maskStr[0];
}
else
pCaption = (CStrW*)m_Settings["caption"].m_pSetting;
CGUISpriteInstance *sprite=NULL, *sprite_selectarea=NULL;
int cell_id;
@ -1248,7 +1261,12 @@ void CInput::Draw()
}
if (i < (int)it->m_ListOfX.size())
x_pointer += (float)font.GetCharacterWidth((*pCaption)[it->m_ListStart + i]);
{
if (!mask)
x_pointer += (float)font.GetCharacterWidth((*pCaption)[it->m_ListStart + i]);
else
x_pointer += (float)font.GetCharacterWidth(mask_char);
}
}
if (done)
@ -1339,7 +1357,12 @@ void CInput::Draw()
}
if (i != (int)it->m_ListOfX.size())
textRenderer.PrintfAdvance(L"%lc", (*pCaption)[it->m_ListStart + i]);
{
if (!mask)
textRenderer.PrintfAdvance(L"%lc", (*pCaption)[it->m_ListStart + i]);
else
textRenderer.PrintfAdvance(L"%lc", mask_char);
}
// check it's now outside a one-liner, then we'll break
if (!multiline && i < (int)it->m_ListOfX.size())
@ -1382,12 +1405,23 @@ void CInput::UpdateText(int from, int to_before, int to_after)
CStrW font_name_w;
float buffer_zone;
bool multiline;
bool mask;
GUI<CStrW>::GetSetting(this, "font", font_name_w);
GUI<CStrW>::GetSetting(this, "caption", caption);
GUI<float>::GetSetting(this, "buffer_zone", buffer_zone);
GUI<bool>::GetSetting(this, "multiline", multiline);
GUI<bool>::GetSetting(this, "mask", mask);
CStrIntern font_name(font_name_w.ToUTF8());
wchar_t mask_char = L'*';
if (mask)
{
CStrW maskStr;
GUI<CStrW>::GetSetting(this, "mask_char", maskStr);
if (maskStr.length() > 0)
mask_char = maskStr[0];
}
// Ensure positions are valid after caption changes
m_iBufferPos = std::min(m_iBufferPos, (int)caption.size());
m_iBufferPos_Tail = std::min(m_iBufferPos_Tail, (int)caption.size());
@ -1577,7 +1611,10 @@ void CInput::UpdateText(int from, int to_before, int to_after)
caption[i] == L'-'*/)
last_word_started = i+1;
x_pos += (float)font.GetCharacterWidth(caption[i]);
if (!mask)
x_pos += (float)font.GetCharacterWidth(caption[i]);
else
x_pos += (float)font.GetCharacterWidth(mask_char);
if (x_pos >= GetTextAreaWidth() && multiline)
{

View File

@ -66,7 +66,6 @@ CList::CList() :
// Add scroll-bar
CGUIScrollBarVertical * bar = new CGUIScrollBarVertical();
bar->SetRightAligned(true);
bar->SetUseEdgeButtons(true);
AddScrollBar(bar);
}

View File

@ -86,7 +86,7 @@ protected:
* Sets up text, should be called every time changes has been
* made that can change the visual.
*/
void SetupText();
virtual void SetupText();
/**
* @see IGUIObject#HandleMessage()
@ -121,7 +121,7 @@ protected:
// Extended drawing interface, this is so that classes built on the this one
// can use other sprite names.
void DrawList(const int &selected, const CStr& _sprite,
virtual void DrawList(const int &selected, const CStr& _sprite,
const CStr& _sprite_selected, const CStr& _textcolor);
// Get the area of the list. This is so that it can easily be changed, like in CDropDown

318
source/gui/COList.cpp Normal file
View File

@ -0,0 +1,318 @@
#include "precompiled.h"
#include "COList.h"
#include "ps/CLogger.h"
COList::COList() : CList(),m_HeadingHeight(30.f)
{
AddSetting(GUIST_CGUISpriteInstance, "sprite_heading");
}
void COList::SetupText()
{
if (!GetGUI())
return;
CGUIList *pList;
GUI<CGUIList>::GetSettingPointer(this, "list_name", pList);
//ENSURE(m_GeneratedTexts.size()>=1);
m_ItemsYPositions.resize( pList->m_Items.size()+1 );
// Delete all generated texts. Some could probably be saved,
// but this is easier, and this function will never be called
// continuously, or even often, so it'll probably be okay.
std::vector<SGUIText*>::iterator it;
for (it=m_GeneratedTexts.begin(); it!=m_GeneratedTexts.end(); ++it)
{
if (*it)
delete *it;
}
m_GeneratedTexts.clear();
CStrW font;
if (GUI<CStrW>::GetSetting(this, "font", font) != PSRETURN_OK || font.empty())
// Use the default if none is specified
// TODO Gee: (2004-08-14) Don't define standard like this. Do it with the default style.
font = L"default";
//CGUIString caption;
bool scrollbar;
//GUI<CGUIString>::GetSetting(this, "caption", caption);
GUI<bool>::GetSetting(this, "scrollbar", scrollbar);
float width = GetListRect().GetWidth();
// remove scrollbar if applicable
if (scrollbar && GetScrollBar(0).GetStyle())
width -= GetScrollBar(0).GetStyle()->m_Width;
// Cache width for other use
m_TotalAvalibleColumnWidth = width;
float buffer_zone=0.f;
GUI<float>::GetSetting(this, "buffer_zone", buffer_zone);
for (unsigned int c=0; c<m_ObjectsDefs.size(); ++c)
{
SGUIText *text = new SGUIText();
CGUIString gui_string;
gui_string.SetValue(m_ObjectsDefs[c].m_Heading);
*text = GetGUI()->GenerateText(gui_string, font, width, buffer_zone, this);
AddText(text);
}
// Generate texts
float buffered_y = 0.f;
for (int i=0; i<(int)pList->m_Items.size(); ++i)
{
m_ItemsYPositions[i] = buffered_y;
for (unsigned int c=0; c<m_ObjectsDefs.size(); ++c)
{
CGUIList * pList_c;
GUI<CGUIList>::GetSettingPointer(this, m_ObjectsDefs[c].m_Id, pList_c);
SGUIText *text = new SGUIText();
*text = GetGUI()->GenerateText(pList_c->m_Items[i], font, width, buffer_zone, this);
if (c==0)
buffered_y += text->m_Size.cy;
AddText(text);
}
}
m_ItemsYPositions[pList->m_Items.size()] = buffered_y;
//if (! scrollbar)
// CalculateTextPosition(m_CachedActualSize, m_TextPos, *m_GeneratedTexts[0]);
// Setup scrollbar
if (scrollbar)
{
GetScrollBar(0).SetScrollRange( m_ItemsYPositions.back() );
GetScrollBar(0).SetScrollSpace( GetListRect().GetHeight() );
CRect rect = GetListRect();
GetScrollBar(0).SetX( rect.right );
GetScrollBar(0).SetY( rect.top );
GetScrollBar(0).SetZ( GetBufferedZ() );
GetScrollBar(0).SetLength( rect.bottom - rect.top );
}
}
CRect COList::GetListRect() const
{
return m_CachedActualSize + CRect(0, m_HeadingHeight, 0, 0);
}
void COList::HandleMessage(SGUIMessage &Message)
{
CList::HandleMessage(Message);
// switch (Message.type)
// {
// case GUIM_SETTINGS_UPDATED:
// if (Message.value.Find("list_") != -1)
// {
// SetupText();
// }
// }
}
bool COList::HandleAdditionalChildren(const XMBElement& child, CXeromyces* pFile)
{
int elmt_item = pFile->GetElementID("item");
int elmt_heading = pFile->GetElementID("heading");
int elmt_def = pFile->GetElementID("def");
if (child.GetNodeName() == elmt_item)
{
AddItem(child.GetText().FromUTF8(), child.GetText().FromUTF8());
return true;
}
else if (child.GetNodeName() == elmt_heading)
{
CStrW text (child.GetText().FromUTF8());
return true;
}
else if (child.GetNodeName() == elmt_def)
{
ObjectDef oDef;
XMBAttributeList attributes = child.GetAttributes();
for (int i=0; i<attributes.Count; ++i)
{
XMBAttribute attr = attributes.Item(i);
CStr attr_name (pFile->GetAttributeString(attr.Name));
CStr attr_value (attr.Value);
if (attr_name == "color")
{
CColor color;
if (!GUI<CColor>::ParseString(attr_value.FromUTF8(), color))
LOGERROR(L"GUI: Error parsing '%hs' (\"%ls\")", attr_name.c_str(), attr_value.c_str());
else oDef.m_TextColor = color;
}
else if (attr_name == "id")
{
oDef.m_Id = "list_"+attr_value;
}
else if (attr_name == "width")
{
float width;
if (!GUI<float>::ParseString(attr_value.FromUTF8(), width))
LOGERROR(L"GUI: Error parsing '%hs' (\"%ls\")", attr_name.c_str(), attr_value.c_str());
else
{
// Check if it's a relative value, and save as decimal if so.
if (attr_value.find("%") != std::string::npos)
{
width = width / 100.f;
}
oDef.m_Width = width;
}
}
else if (attr_name == "heading")
{
oDef.m_Heading = attr_value.FromUTF8();
}
}
m_ObjectsDefs.push_back(oDef);
AddSetting(GUIST_CGUIList, oDef.m_Id);
SetupText();
return true;
}
else
{
return false;
}
}
void COList::DrawList(const int &selected,
const CStr& _sprite,
const CStr& _sprite_selected,
const CStr& _textcolor)
{
float bz = GetBufferedZ();
// First call draw on ScrollBarOwner
bool scrollbar;
GUI<bool>::GetSetting(this, "scrollbar", scrollbar);
if (scrollbar)
{
// Draw scrollbar
IGUIScrollBarOwner::Draw();
}
if (GetGUI())
{
CRect rect = GetListRect();
CGUISpriteInstance *sprite=NULL, *sprite_selectarea=NULL;
int cell_id;
GUI<CGUISpriteInstance>::GetSettingPointer(this, _sprite, sprite);
GUI<CGUISpriteInstance>::GetSettingPointer(this, _sprite_selected, sprite_selectarea);
GUI<int>::GetSetting(this, "cell_id", cell_id);
CGUIList *pList;
GUI<CGUIList>::GetSettingPointer(this, "list_name", pList);
GetGUI()->DrawSprite(*sprite, cell_id, bz, rect);
float scroll=0.f;
if (scrollbar)
{
scroll = GetScrollBar(0).GetPos();
}
if (selected != -1)
{
ENSURE(selected >= 0 && selected+1 < (int)m_ItemsYPositions.size());
// Get rectangle of selection:
CRect rect_sel(rect.left, rect.top + m_ItemsYPositions[selected] - scroll,
rect.right, rect.top + m_ItemsYPositions[selected+1] - scroll);
if (rect_sel.top <= rect.bottom &&
rect_sel.bottom >= rect.top)
{
if (rect_sel.bottom > rect.bottom)
rect_sel.bottom = rect.bottom;
if (rect_sel.top < rect.top)
rect_sel.top = rect.top;
if (scrollbar)
{
// Remove any overlapping area of the scrollbar.
if (rect_sel.right > GetScrollBar(0).GetOuterRect().left &&
rect_sel.right <= GetScrollBar(0).GetOuterRect().right)
rect_sel.right = GetScrollBar(0).GetOuterRect().left;
if (rect_sel.left >= GetScrollBar(0).GetOuterRect().left &&
rect_sel.left < GetScrollBar(0).GetOuterRect().right)
rect_sel.left = GetScrollBar(0).GetOuterRect().right;
}
GetGUI()->DrawSprite(*sprite_selectarea, cell_id, bz+0.05f, rect_sel);
}
}
CColor color;
GUI<CColor>::GetSetting(this, _textcolor, color);
CGUISpriteInstance *sprite_heading=NULL;
GUI<CGUISpriteInstance>::GetSettingPointer(this, "sprite_heading", sprite_heading);
CRect rect_head(m_CachedActualSize.left, m_CachedActualSize.top, m_CachedActualSize.right,
m_CachedActualSize.top + m_HeadingHeight);
GetGUI()->DrawSprite(*sprite_heading, cell_id, bz, rect_head);
float xpos = 0;
for (unsigned int def=0; def< m_ObjectsDefs.size(); ++def)
{
DrawText(def, color, m_CachedActualSize.TopLeft() + CPos(xpos, 4), bz+0.1f, rect_head);
// Check if it's a decimal value, and if so, assume relative positioning.
if (m_ObjectsDefs[def].m_Width < 1 && m_ObjectsDefs[def].m_Width > 0)
xpos += m_ObjectsDefs[def].m_Width * m_TotalAvalibleColumnWidth;
else
xpos += m_ObjectsDefs[def].m_Width;
}
for (int i=0; i<(int)pList->m_Items.size(); ++i)
{
if (m_ItemsYPositions[i+1] - scroll < 0 ||
m_ItemsYPositions[i] - scroll > rect.GetHeight())
continue;
// Clipping area (we'll have to substract the scrollbar)
CRect cliparea = GetListRect();
if (scrollbar)
{
if (cliparea.right > GetScrollBar(0).GetOuterRect().left &&
cliparea.right <= GetScrollBar(0).GetOuterRect().right)
cliparea.right = GetScrollBar(0).GetOuterRect().left;
if (cliparea.left >= GetScrollBar(0).GetOuterRect().left &&
cliparea.left < GetScrollBar(0).GetOuterRect().right)
cliparea.left = GetScrollBar(0).GetOuterRect().right;
}
xpos = 0;
for (unsigned int def=0; def< m_ObjectsDefs.size(); ++def)
{
DrawText(m_ObjectsDefs.size() * (i+/*Heading*/1) + def, m_ObjectsDefs[def].m_TextColor, rect.TopLeft() + CPos(xpos, -scroll + m_ItemsYPositions[i]), bz+0.1f, cliparea);
// Check if it's a decimal value, and if so, assume relative positioning.
if (m_ObjectsDefs[def].m_Width < 1 && m_ObjectsDefs[def].m_Width > 0)
xpos += m_ObjectsDefs[def].m_Width * m_TotalAvalibleColumnWidth;
else
xpos += m_ObjectsDefs[def].m_Width;
}
}
}
}

64
source/gui/COList.h Normal file
View File

@ -0,0 +1,64 @@
#ifndef INCLUDED_COLIST
#define INCLUDED_COLIST
//--------------------------------------------------------
// Includes / Compiler directives
//--------------------------------------------------------
#include "GUI.h"
#include "CList.h"
//--------------------------------------------------------
// Macros
//--------------------------------------------------------
//--------------------------------------------------------
// Types
//--------------------------------------------------------
//--------------------------------------------------------
// Declarations
//--------------------------------------------------------
struct ObjectDef
{
CColor m_TextColor;
CStr m_Id;
float m_Width;
CStrW m_Heading;
};
/**
* Todo : add description
*
*/
class COList : public CList
{
GUI_OBJECT(COList)
public:
COList();
protected:
void SetupText();
void HandleMessage(SGUIMessage &Message);
/**
* Handle the \<item\> tag.
*/
virtual bool HandleAdditionalChildren(const XMBElement& child, CXeromyces* pFile);
void DrawList(const int &selected, const CStr& _sprite,
const CStr& _sprite_selected, const CStr& _textcolor);
virtual CRect GetListRect() const;
std::vector<ObjectDef> m_ObjectsDefs;
private:
float m_HeadingHeight;
// Width of space avalible for columns
float m_TotalAvalibleColumnWidth;
};
#endif // INCLUDED_COLIST

View File

@ -58,7 +58,6 @@ CText::CText()
// Add scroll-bar
CGUIScrollBarVertical * bar = new CGUIScrollBarVertical();
bar->SetRightAligned(true);
bar->SetUseEdgeButtons(true);
AddScrollBar(bar);
// Add text

View File

@ -424,7 +424,7 @@ protected:
* have any additional children (and this function should never be called).
*/
virtual bool HandleAdditionalChildren(const XMBElement& UNUSED(child),
CXeromyces* UNUSED(pFile)) { return false; }
CXeromyces* UNUSED(pFile)) { return false; }
/**
* Cached size, real size m_Size is actually dependent on resolution

View File

@ -32,7 +32,6 @@ IGUIScrollBar::IGUIScrollBar() : m_pStyle(NULL), m_pGUI(NULL),
m_ScrollRange(1.f), m_ScrollSpace(0.f), // MaxPos: not 0, due to division.
m_Length(200.f), m_Width(20.f),
m_BarSize(0.f), m_Pos(0.f),
m_UseEdgeButtons(true),
m_ButtonPlusPressed(false),
m_ButtonMinusPressed(false),
m_ButtonPlusHovered(false),
@ -48,20 +47,21 @@ IGUIScrollBar::~IGUIScrollBar()
void IGUIScrollBar::SetupBarSize()
{
if (!GetStyle())
{
return;
}
float min = GetStyle()->m_MinimumBarSize;
float max = GetStyle()->m_MaximumBarSize;
float length = m_Length;
// Check for edge buttons
if (m_UseEdgeButtons)
if (GetStyle()->m_UseEdgeButtons)
length -= GetStyle()->m_Width * 2.f;
// Check min and max are valid
if (min > length)
{
LOGWARNING(L"Minimum scrollbar size of %g is larger than the total scrollbar size of %g", min, length);
min = 0.f;
}
if (max < min)
max = length;

View File

@ -96,6 +96,12 @@ struct SGUIScrollBarStyle
* in pixels.
*/
float m_MaximumBarSize;
/**
* True if you want edge buttons, i.e. buttons that can be pressed in order
* to scroll.
*/
bool m_UseEdgeButtons;
//@}
//--------------------------------------------------------
@ -296,12 +302,6 @@ public:
*/
void SetBarPressed(bool b) { m_BarPressed = b; }
/**
* Set use edge buttons
* @param b True if edge buttons should be used
*/
void SetUseEdgeButtons(bool b) { m_UseEdgeButtons = b; }
/**
* Set Scroll bar style string
* @param style String with scroll bar style reference name
@ -345,12 +345,6 @@ protected:
//--------------------------------------------------------
//@{
/**
* True if you want edge buttons, i.e. buttons that can be pressed in order
* to scroll.
*/
bool m_UseEdgeButtons;
/**
* Width of the scroll bar
*/

View File

@ -35,6 +35,7 @@
#include "ps/CConsole.h"
#include "ps/Errors.h"
#include "ps/Game.h"
#include "ps/GUID.h"
#include "ps/World.h"
#include "ps/Hotkey.h"
#include "ps/Overlay.h"
@ -49,6 +50,8 @@
#include "ps/ConfigDB.h"
#include "renderer/scripting/JSInterface_Renderer.h"
#include "tools/atlas/GameInterface/GameLoop.h"
#include "lobby/IXmppClient.h"
#include "lobby/sha.h"
#include "simulation2/Simulation2.h"
#include "simulation2/components/ICmpAIManager.h"
@ -60,7 +63,6 @@
#include "simulation2/helpers/Selection.h"
#include "js/jsapi.h"
/*
* This file defines a set of functions that are available to GUI scripts, to allow
* interaction with the rest of the engine.
@ -178,35 +180,6 @@ void SetPlayerID(void* UNUSED(cbdata), int id)
g_Game->SetPlayerID(id);
}
std::wstring GetDefaultPlayerName(void* UNUSED(cbdata))
{
CStr playername;
CFG_GET_VAL("playername", String, playername);
std::wstring name = playername.FromUTF8();
if (!name.empty())
return name;
name = sys_get_user_name();
if (!name.empty())
return name;
return L"anonymous";
}
std::wstring GetDefaultMPServer(void* UNUSED(cbdata))
{
CStr server;
CFG_GET_VAL("multiplayerserver", String, server);
return server.FromUTF8();
}
void SaveMPConfig(void* UNUSED(cbdata), std::wstring playerName, std::wstring server)
{
g_ConfigDB.CreateValue(CFG_USER, "playername")->m_String = CStrW(playerName).ToUTF8();
g_ConfigDB.CreateValue(CFG_USER, "multiplayerserver")->m_String = CStrW(server).ToUTF8();
g_ConfigDB.WriteFile(CFG_USER);
}
void StartNetworkGame(void* UNUSED(cbdata))
{
ENSURE(g_NetServer);
@ -400,6 +373,11 @@ void OpenURL(void* UNUSED(cbdata), std::string url)
sys_open_url(url);
}
std::wstring GetMatchID(void* UNUSED(cbdata))
{
return ps_generate_guid().FromUTF8();
}
void RestartInAtlas(void* UNUSED(cbdata))
{
restart_mainloop_in_atlas();
@ -500,7 +478,7 @@ entity_id_t GetFollowedEntity(void* UNUSED(cbdata))
{
if (g_Game && g_Game->GetView())
return g_Game->GetView()->GetFollowedEntity();
return INVALID_ENTITY;
}
@ -635,6 +613,202 @@ void RewindTimeWarp(void* UNUSED(cbdata))
g_Game->GetTurnManager()->RewindTimeWarp();
}
/* Begin lobby related functions */
void StartXmppClient(void* cbdata, std::string sUsername, std::string sPassword, std::string sRoom, std::string sNick)
{
CGUIManager* guiManager = static_cast<CGUIManager*> (cbdata);
ENSURE(!g_XmppClient);
g_XmppClient = IXmppClient::create(guiManager->GetScriptInterface(), sUsername, sPassword, sRoom, sNick);
g_rankedGame = true;
}
void StartRegisterXmppClient(void* cbdata, std::string sUsername, std::string sPassword)
{
CGUIManager* guiManager = static_cast<CGUIManager*> (cbdata);
ENSURE(!g_XmppClient);
g_XmppClient = IXmppClient::create(guiManager->GetScriptInterface(), sUsername, sPassword, "", "", true);
}
bool HasXmppClient(void* UNUSED(cbdata))
{
return (g_XmppClient ? true : false);
}
void StopXmppClient(void* UNUSED(cbdata))
{
ENSURE(g_XmppClient);
SAFE_DELETE(g_XmppClient);
g_rankedGame = false;
}
void ConnectXmppClient(void* UNUSED(cbdata))
{
ENSURE(g_XmppClient);
g_XmppClient->connect();
}
void DisconnectXmppClient(void* UNUSED(cbdata))
{
ENSURE(g_XmppClient);
g_XmppClient->disconnect();
}
void RecvXmppClient(void* UNUSED(cbdata))
{
if (!g_XmppClient)
return;
g_XmppClient->recv();
}
void SendGetGameList(void* UNUSED(cbdata))
{
if (!g_XmppClient)
return;
g_XmppClient->SendIqGetGameList();
}
void SendGetBoardList(void* UNUSED(cbdata))
{
if (!g_XmppClient)
return;
g_XmppClient->SendIqGetBoardList();
}
void SendGameReport(void* UNUSED(cbdata), CScriptVal data)
{
if (!g_XmppClient)
return;
g_XmppClient->SendIqGameReport(data);
}
void SendRegisterGame(void* UNUSED(cbdata), CScriptVal data)
{
if (!g_XmppClient)
return;
g_XmppClient->SendIqRegisterGame(data);
}
void SendUnregisterGame(void* UNUSED(cbdata))
{
if (!g_XmppClient)
return;
g_XmppClient->SendIqUnregisterGame();
}
void SendChangeStateGame(void* UNUSED(cbdata), std::string nbp, std::string players)
{
if (!g_XmppClient)
return;
g_XmppClient->SendIqChangeStateGame(nbp, players);
}
CScriptVal GetPlayerList(void* UNUSED(cbdata))
{
if (!g_XmppClient)
return CScriptVal();
CScriptValRooted playerList = g_XmppClient->GUIGetPlayerList();
return playerList.get();
}
CScriptVal GetGameList(void* UNUSED(cbdata))
{
if (!g_XmppClient)
return CScriptVal();
CScriptValRooted gameList = g_XmppClient->GUIGetGameList();
return gameList.get();
}
CScriptVal GetBoardList(void* UNUSED(cbdata))
{
if (!g_XmppClient)
return CScriptVal();
CScriptValRooted boardList = g_XmppClient->GUIGetBoardList();
return boardList.get();
}
CScriptVal LobbyGuiPollMessage(void* UNUSED(cbdata))
{
if (!g_XmppClient)
return CScriptVal();
CScriptValRooted poll = g_XmppClient->GuiPollMessage();
return poll.get();
}
void LobbySendMessage(void* UNUSED(cbdata), std::string message)
{
if (!g_XmppClient)
return;
g_XmppClient->SendMUCMessage(message);
}
void LobbySetPlayerPresence(void* UNUSED(cbdata), std::string presence)
{
if (!g_XmppClient)
return;
g_XmppClient->SetPresence(presence);
}
void LobbySetNick(void* UNUSED(cbdata), std::string nick)
{
if (!g_XmppClient)
return;
g_XmppClient->SetNick(nick);
}
std::string LobbyGetNick(void* UNUSED(cbdata))
{
if (!g_XmppClient)
return "";
std::string nick;
g_XmppClient->GetNick(nick);
return nick;
}
void LobbyKick(void* UNUSED(cbdata), std::string nick, std::string reason)
{
if (!g_XmppClient)
return;
g_XmppClient->kick(nick, reason);
}
void LobbyBan(void* UNUSED(cbdata), std::string nick, std::string reason)
{
if (!g_XmppClient)
return;
g_XmppClient->ban(nick, reason);
}
std::string LobbyGetPlayerPresence(void* UNUSED(cbdata), std::string nickname)
{
if (!g_XmppClient)
return "";
std::string presence;
g_XmppClient->GetPresence(nickname, presence);
return presence;
}
/* End lobby related functions */
void QuickSave(void* UNUSED(cbdata))
{
g_Game->GetTurnManager()->QuickSave();
@ -650,6 +824,54 @@ void SetBoundingBoxDebugOverlay(void* UNUSED(cbdata), bool enabled)
ICmpSelectable::ms_EnableDebugOverlays = enabled;
}
// Non-public secure PBKDF2 hash function with salting and 1,337 iterations
void EncryptPassword(const std::string& username, std::string& password)
{
const int DIGESTSIZE = SHA_DIGEST_SIZE;
const int ITERATIONS = 1337;
static const byte salt_base[DIGESTSIZE] = {
244, 243, 249, 244, 32, 33, 34, 35, 10, 11, 12, 13, 14, 15, 16, 17,
18, 19, 20, 32, 33, 244, 224, 127, 129, 130, 140, 153, 133, 123, 234, 123 };
// initialize the salt buffer
byte salt_buffer[DIGESTSIZE] = {0};
SHA256 hash;
hash.update(salt_base, username.length());
hash.update(username.c_str(), username.length());
hash.finish(salt_buffer);
// PBKDF2 to create the buffer
byte encrypted[DIGESTSIZE];
pbkdf2(encrypted, (byte*)password.c_str(), password.length(), salt_buffer, DIGESTSIZE, ITERATIONS);
static const char base16[] = "0123456789ABCDEF";
char hex[2 * DIGESTSIZE];
for(int i = 0; i < DIGESTSIZE; ++i)
{
hex[i*2] = base16[encrypted[i] >> 4]; // 4 high bits
hex[i*2 + 1] = base16[encrypted[i] & 0x0F];// 4 low bits
}
password.assign(hex, sizeof(hex));
}
// Public hash interface.
std::string EncryptPassword(void* UNUSED(cbdata), std::string user, std::string pass)
{
EncryptPassword(user, pass);
return pass;
}
bool IsRankedGame(void* UNUSED(cbdata))
{
return g_rankedGame;
}
void SetRankedGame(void* UNUSED(cbdata), bool isRanked)
{
g_rankedGame = isRanked;
}
} // namespace
void GuiScriptingInit(ScriptInterface& scriptInterface)
@ -701,10 +923,8 @@ void GuiScriptingInit(ScriptInterface& scriptInterface)
scriptInterface.RegisterFunction<std::wstring, std::wstring, &SetCursor>("SetCursor");
scriptInterface.RegisterFunction<int, &GetPlayerID>("GetPlayerID");
scriptInterface.RegisterFunction<void, int, &SetPlayerID>("SetPlayerID");
scriptInterface.RegisterFunction<std::wstring, &GetDefaultPlayerName>("GetDefaultPlayerName");
scriptInterface.RegisterFunction<std::wstring, &GetDefaultMPServer>("GetDefaultMPServer");
scriptInterface.RegisterFunction<void, std::wstring, std::wstring, &SaveMPConfig>("SaveMPConfig");
scriptInterface.RegisterFunction<void, std::string, &OpenURL>("OpenURL");
scriptInterface.RegisterFunction<std::wstring, &GetMatchID>("GetMatchID");
scriptInterface.RegisterFunction<void, &RestartInAtlas>("RestartInAtlas");
scriptInterface.RegisterFunction<bool, &AtlasIsAvailable>("AtlasIsAvailable");
scriptInterface.RegisterFunction<bool, &IsAtlasRunning>("IsAtlasRunning");
@ -743,4 +963,35 @@ void GuiScriptingInit(ScriptInterface& scriptInterface)
scriptInterface.RegisterFunction<void, unsigned int, &EnableTimeWarpRecording>("EnableTimeWarpRecording");
scriptInterface.RegisterFunction<void, &RewindTimeWarp>("RewindTimeWarp");
scriptInterface.RegisterFunction<void, bool, &SetBoundingBoxDebugOverlay>("SetBoundingBoxDebugOverlay");
#if CONFIG2_LOBBY // Allow the lobby to be disabled
// Lobby functions
scriptInterface.RegisterFunction<void, std::string, std::string, std::string, std::string, &StartXmppClient>("StartXmppClient");
scriptInterface.RegisterFunction<void, std::string, std::string, &StartRegisterXmppClient>("StartRegisterXmppClient");
scriptInterface.RegisterFunction<bool, &HasXmppClient>("HasXmppClient");
scriptInterface.RegisterFunction<void, &StopXmppClient>("StopXmppClient");
scriptInterface.RegisterFunction<void, &ConnectXmppClient>("ConnectXmppClient");
scriptInterface.RegisterFunction<void, &DisconnectXmppClient>("DisconnectXmppClient");
scriptInterface.RegisterFunction<void, &RecvXmppClient>("RecvXmppClient");
scriptInterface.RegisterFunction<void, &SendGetGameList>("SendGetGameList");
scriptInterface.RegisterFunction<void, &SendGetBoardList>("SendGetBoardList");
scriptInterface.RegisterFunction<void, CScriptVal, &SendRegisterGame>("SendRegisterGame");
scriptInterface.RegisterFunction<void, CScriptVal, &SendGameReport>("SendGameReport");
scriptInterface.RegisterFunction<void, &SendUnregisterGame>("SendUnregisterGame");
scriptInterface.RegisterFunction<void, std::string, std::string, &SendChangeStateGame>("SendChangeStateGame");
scriptInterface.RegisterFunction<CScriptVal, &GetPlayerList>("GetPlayerList");
scriptInterface.RegisterFunction<CScriptVal, &GetGameList>("GetGameList");
scriptInterface.RegisterFunction<CScriptVal, &GetBoardList>("GetBoardList");
scriptInterface.RegisterFunction<CScriptVal, &LobbyGuiPollMessage>("LobbyGuiPollMessage");
scriptInterface.RegisterFunction<void, std::string, &LobbySendMessage>("LobbySendMessage");
scriptInterface.RegisterFunction<void, std::string, &LobbySetPlayerPresence>("LobbySetPlayerPresence");
scriptInterface.RegisterFunction<void, std::string, &LobbySetNick>("LobbySetNick");
scriptInterface.RegisterFunction<std::string, &LobbyGetNick>("LobbyGetNick");
scriptInterface.RegisterFunction<void, std::string, std::string, &LobbyKick>("LobbyKick");
scriptInterface.RegisterFunction<void, std::string, std::string, &LobbyBan>("LobbyBan");
scriptInterface.RegisterFunction<std::string, std::string, &LobbyGetPlayerPresence>("LobbyGetPlayerPresence");
scriptInterface.RegisterFunction<std::string, std::string, std::string, &EncryptPassword>("EncryptPassword");
scriptInterface.RegisterFunction<bool, &IsRankedGame>("IsRankedGame");
scriptInterface.RegisterFunction<void, bool, &SetRankedGame>("SetRankedGame");
#endif // CONFIG2_LOBBY
}

View File

@ -111,4 +111,9 @@
# define CONFIG2_NVTT 1
#endif
// allow use of lobby
#ifndef CONFIG2_LOBBY
# define CONFIG2_LOBBY 1
#endif
#endif // #ifndef INCLUDED_CONFIG2

View File

@ -0,0 +1,60 @@
/* Copyright (C) 2013 Wildfire Games.
* This file is part of 0 A.D.
*
* 0 A.D. is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 2 of the License, or
* (at your option) any later version.
*
* 0 A.D. is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with 0 A.D. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef IXMPPCLIENT_H
#define IXMPPCLIENT_H
class ScriptInterface;
class CScriptVal;
class CScriptValRooted;
class IXmppClient
{
public:
static IXmppClient* create(ScriptInterface& scriptInterface, const std::string& sUsername, const std::string& sPassword, const std::string& sRoom, const std::string& sNick, bool regOpt = false);
virtual ~IXmppClient() {}
virtual void connect() = 0;
virtual void disconnect() = 0;
virtual void recv() = 0;
virtual void SendIqGetGameList() = 0;
virtual void SendIqGetBoardList() = 0;
virtual void SendIqGameReport(CScriptVal data) = 0;
virtual void SendIqRegisterGame(CScriptVal data) = 0;
virtual void SendIqUnregisterGame() = 0;
virtual void SendIqChangeStateGame(const std::string& nbp, const std::string& players) = 0;
virtual void SetNick(const std::string& nick) = 0;
virtual void GetNick(std::string& nick) = 0;
virtual void kick(const std::string& nick, const std::string& reason) = 0;
virtual void ban(const std::string& nick, const std::string& reason) = 0;
virtual void SetPresence(const std::string& presence) = 0;
virtual void GetPresence(const std::string& nickname, std::string& presence) = 0;
virtual CScriptValRooted GUIGetPlayerList() = 0;
virtual CScriptValRooted GUIGetGameList() = 0;
virtual CScriptValRooted GUIGetBoardList() = 0;
virtual ScriptInterface& GetScriptInterface() = 0;
virtual CScriptValRooted GuiPollMessage() = 0;
virtual void SendMUCMessage(const std::string& message) = 0;
};
extern IXmppClient *g_XmppClient;
extern bool g_rankedGame;
#endif // XMPPCLIENT_H

View File

@ -0,0 +1,175 @@
/* Copyright (C) 2013 Wildfire Games.
* This file is part of 0 A.D.
*
* 0 A.D. is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 2 of the License, or
* (at your option) any later version.
*
* 0 A.D. is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with 0 A.D. If not, see <http://www.gnu.org/licenses/>.
*/
#include "precompiled.h"
#include "StanzaExtensions.h"
/******************************************************
* GameReport, fairly generic custom stanza extension used
* to report game statistics.
*/
GameReport::GameReport( const glooxwrapper::Tag* tag ):StanzaExtension( ExtGameReport )
{
if( !tag || tag->name() != "report" || tag->xmlns() != XMLNS_GAMEREPORT )
return;
// TODO if we want to handle receiving this stanza extension.
};
/**
* Required by gloox, used to serialize the GameReport into XML for sending.
*/
glooxwrapper::Tag* GameReport::tag() const
{
glooxwrapper::Tag* t = glooxwrapper::Tag::allocate( "report" );
t->setXmlns( XMLNS_GAMEREPORT );
std::vector<const glooxwrapper::Tag*>::const_iterator it = m_GameReport.begin();
for( ; it != m_GameReport.end(); ++it )
t->addChild( (*it)->clone() );
return t;
}
/**
* Required by gloox, used to find the GameReport element in a recived IQ.
*/
const glooxwrapper::string& GameReport::filterString() const
{
static const glooxwrapper::string filter = "/iq/report[@xmlns='" XMLNS_GAMEREPORT "']";
return filter;
}
glooxwrapper::StanzaExtension* GameReport::clone() const
{
GameReport* q = new GameReport();
return q;
}
/******************************************************
* BoardListQuery, custom IQ Stanza, used solely to
* request and receive leaderboard data from server.
*/
BoardListQuery::BoardListQuery( const glooxwrapper::Tag* tag ):StanzaExtension( ExtBoardListQuery )
{
if( !tag || tag->name() != "query" || tag->xmlns() != XMLNS_BOARDLIST )
return;
const glooxwrapper::ConstTagList boardTags = tag->findTagList_clone( "query/board" );
glooxwrapper::ConstTagList::const_iterator it = boardTags.begin();
for ( ; it != boardTags.end(); ++it )
m_BoardList.push_back( *it );
}
/**
* Required by gloox, used to find the BoardList element in a recived IQ.
*/
const glooxwrapper::string& BoardListQuery::filterString() const
{
static const glooxwrapper::string filter = "/iq/query[@xmlns='" XMLNS_BOARDLIST "']";
return filter;
}
/**
* Required by gloox, used to serialize the BoardList request into XML for sending.
*/
glooxwrapper::Tag* BoardListQuery::tag() const
{
glooxwrapper::Tag* t = glooxwrapper::Tag::allocate( "query" );
t->setXmlns( XMLNS_BOARDLIST );
std::vector<const glooxwrapper::Tag*>::const_iterator it = m_BoardList.begin();
for( ; it != m_BoardList.end(); ++it )
t->addChild( (*it)->clone() );
return t;
}
glooxwrapper::StanzaExtension* BoardListQuery::clone() const
{
BoardListQuery* q = new BoardListQuery();
return q;
}
BoardListQuery::~BoardListQuery()
{
std::vector<const glooxwrapper::Tag*>::const_iterator it = m_BoardList.begin();
for( ; it != m_BoardList.end(); ++it )
glooxwrapper::Tag::free(*it);
m_BoardList.clear();
}
/******************************************************
* GameListQuery, custom IQ Stanza, used to receive
* the listing of games from the server, and register/
* unregister/changestate games on the server.
*/
GameListQuery::GameListQuery( const glooxwrapper::Tag* tag ):StanzaExtension( ExtGameListQuery )
{
if( !tag || tag->name() != "query" || tag->xmlns() != XMLNS_GAMELIST )
return;
const glooxwrapper::Tag* c = tag->findTag_clone( "query/game" );
if (c)
m_Command = c->cdata();
glooxwrapper::Tag::free(c);
const glooxwrapper::ConstTagList games = tag->findTagList_clone( "query/game" );
glooxwrapper::ConstTagList::const_iterator it = games.begin();
for ( ; it != games.end(); ++it )
m_GameList.push_back( *it );
}
/**
* Required by gloox, used to find the GameList element in a recived IQ.
*/
const glooxwrapper::string& GameListQuery::filterString() const
{
static const glooxwrapper::string filter = "/iq/query[@xmlns='" XMLNS_GAMELIST "']";
return filter;
}
/**
* Required by gloox, used to serialize the game object into XML for sending.
*/
glooxwrapper::Tag* GameListQuery::tag() const
{
glooxwrapper::Tag* t = glooxwrapper::Tag::allocate( "query" );
t->setXmlns( XMLNS_GAMELIST );
// Check for register / unregister command
if(!m_Command.empty())
t->addChild(glooxwrapper::Tag::allocate("command", m_Command));
std::vector<const glooxwrapper::Tag*>::const_iterator it = m_GameList.begin();
for( ; it != m_GameList.end(); ++it )
t->addChild( (*it)->clone() );
return t;
}
glooxwrapper::StanzaExtension* GameListQuery::clone() const
{
GameListQuery* q = new GameListQuery();
return q;
}
GameListQuery::~GameListQuery()
{
std::vector<const glooxwrapper::Tag*>::const_iterator it = m_GameList.begin();
for( ; it != m_GameList.end(); ++it )
glooxwrapper::Tag::free(*it);
m_GameList.clear();
}

View File

@ -0,0 +1,89 @@
/* Copyright (C) 2013 Wildfire Games.
* This file is part of 0 A.D.
*
* 0 A.D. is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 2 of the License, or
* (at your option) any later version.
*
* 0 A.D. is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with 0 A.D. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef STANZAEXTENSIONS_H
#define STANZAEXTENSIONS_H
#include "glooxwrapper/glooxwrapper.h"
/// Global Gamelist Extension
#define ExtGameListQuery 1403
#define XMLNS_GAMELIST "jabber:iq:gamelist"
/// Global Boardlist Extension
#define ExtBoardListQuery 1404
#define XMLNS_BOARDLIST "jabber:iq:boardlist"
/// Global Boardlist Extension
#define ExtGameReport 1405
#define XMLNS_GAMEREPORT "jabber:iq:gamereport"
class GameReport : public glooxwrapper::StanzaExtension
{
public:
GameReport(const glooxwrapper::Tag* tag = 0);
// Following four methods are all required by gloox
virtual StanzaExtension* newInstance(const glooxwrapper::Tag* tag) const
{
return new GameReport(tag);
}
virtual const glooxwrapper::string& filterString() const;
virtual glooxwrapper::Tag* tag() const;
virtual glooxwrapper::StanzaExtension* clone() const;
std::vector<const glooxwrapper::Tag*> m_GameReport;
};
class GameListQuery : public glooxwrapper::StanzaExtension
{
public:
GameListQuery(const glooxwrapper::Tag* tag = 0);
// Following four methods are all required by gloox
virtual StanzaExtension* newInstance(const glooxwrapper::Tag* tag) const
{
return new GameListQuery(tag);
}
virtual const glooxwrapper::string& filterString() const;
virtual glooxwrapper::Tag* tag() const;
virtual glooxwrapper::StanzaExtension* clone() const;
~GameListQuery();
glooxwrapper::string m_Command;
std::vector<const glooxwrapper::Tag*> m_GameList;
};
class BoardListQuery : public glooxwrapper::StanzaExtension
{
public:
BoardListQuery(const glooxwrapper::Tag* tag = 0);
// Following four methods are all required by gloox
virtual StanzaExtension* newInstance(const glooxwrapper::Tag* tag) const
{
return new BoardListQuery(tag);
}
virtual const glooxwrapper::string& filterString() const;
virtual glooxwrapper::Tag* tag() const;
virtual glooxwrapper::StanzaExtension* clone() const;
~BoardListQuery();
std::vector<const glooxwrapper::Tag*> m_BoardList;
};
#endif

852
source/lobby/XmppClient.cpp Normal file
View File

@ -0,0 +1,852 @@
/* Copyright (C) 2013 Wildfire Games.
* This file is part of 0 A.D.
*
* 0 A.D. is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 2 of the License, or
* (at your option) any later version.
*
* 0 A.D. is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with 0 A.D. If not, see <http://www.gnu.org/licenses/>.
*/
#include "precompiled.h"
#include "XmppClient.h"
#include "StanzaExtensions.h"
// Debug
// TODO: Use builtin error/warning/logging functions.
#include <iostream>
#include "ps/CLogger.h"
// Gloox
#include "glooxwrapper/glooxwrapper.h"
// Game - script
#include "scriptinterface/ScriptInterface.h"
// Configuration
#include "ps/ConfigDB.h"
//global
IXmppClient *g_XmppClient = NULL;
bool g_rankedGame = false;
//debug
#if 1
#define DbgXMPP(x)
#else
#define DbgXMPP(x) std::cout << x << std::endl;
static std::string tag_xml(const glooxwrapper::IQ& iq)
{
std::string ret;
glooxwrapper::Tag* tag = iq.tag();
ret = tag->xml().to_string();
glooxwrapper::Tag::free(tag);
return ret;
}
#endif
static std::string tag_name(const glooxwrapper::IQ& iq)
{
std::string ret;
glooxwrapper::Tag* tag = iq.tag();
ret = tag->name().to_string();
glooxwrapper::Tag::free(tag);
return ret;
}
IXmppClient* IXmppClient::create(ScriptInterface& scriptInterface, const std::string& sUsername, const std::string& sPassword, const std::string& sRoom, const std::string& sNick, bool regOpt)
{
return new XmppClient(scriptInterface, sUsername, sPassword, sRoom, sNick, regOpt);
}
/**
* Construct the xmpp client
*/
XmppClient::XmppClient(ScriptInterface& scriptInterface, const std::string& sUsername, const std::string& sPassword, const std::string& sRoom, const std::string& sNick, bool regOpt)
: m_ScriptInterface(scriptInterface), m_client(NULL), m_mucRoom(NULL), m_registration(NULL), m_username(sUsername), m_password(sPassword), m_nick(sNick)
{
// Read lobby configuration from default.cfg
std::string sServer;
std::string sXpartamupp;
CFG_GET_VAL("lobby.server", String, sServer);
CFG_GET_VAL("lobby.xpartamupp", String, sXpartamupp);
m_xpartamuppId = sXpartamupp + "@" + sServer + "/CC";
glooxwrapper::JID clientJid(sUsername + "@" + sServer + "/0ad");
glooxwrapper::JID roomJid(sRoom + "@conference." + sServer + "/" + sNick);
// If we are connecting, use the full jid and a password
// If we are registering, only use the server name
if(!regOpt)
m_client = new glooxwrapper::Client(clientJid, sPassword);
else
m_client = new glooxwrapper::Client(sServer);
// Disable TLS as we haven't set a certificate on the server yet
m_client->setTls(gloox::TLSDisabled);
// Disable use of the SASL PLAIN mechanism, to prevent leaking credentials
// if the server doesn't list any supported SASL mechanism or the response
// has been modified to exclude those.
const int mechs = gloox::SaslMechAll ^ gloox::SaslMechPlain;
m_client->setSASLMechanisms(mechs);
m_client->registerConnectionListener( this );
m_client->setPresence(gloox::Presence::Available, -1);
m_client->disco()->setVersion( "Pyrogenesis", "0.0.15" );
m_client->disco()->setIdentity( "client", "bot" );
m_client->setCompression(false);
m_client->registerStanzaExtension( new GameListQuery() );
m_client->registerIqHandler( this, ExtGameListQuery);
m_client->registerStanzaExtension( new BoardListQuery() );
m_client->registerIqHandler( this, ExtBoardListQuery);
m_client->registerMessageHandler( this );
// Uncomment to see the raw stanzas
//m_client->logInstance().registerLogHandler( LogLevelDebug, LogAreaAll, this );
if (!regOpt)
{
// Create a Multi User Chat Room
m_mucRoom = new glooxwrapper::MUCRoom(m_client, roomJid, this, 0);
// Disable the history because its anoying
m_mucRoom->setRequestHistory(0, gloox::MUCRoom::HistoryMaxStanzas);
}
else
{
// Registration
m_registration = new glooxwrapper::Registration(m_client);
m_registration->registerRegistrationHandler(this);
}
}
/**
* Destroy the xmpp client
*/
XmppClient::~XmppClient()
{
DbgXMPP("XmppClient destroyed");
delete m_registration;
delete m_mucRoom;
// Workaround for memory leak in gloox 1.0/1.0.1
m_client->removePresenceExtension(gloox::ExtCaps);
delete m_client;
for (std::vector<const glooxwrapper::Tag*>::const_iterator it = m_GameList.begin(); it != m_GameList.end(); ++it)
glooxwrapper::Tag::free(*it);
for (std::vector<const glooxwrapper::Tag*>::const_iterator it = m_BoardList.begin(); it != m_BoardList.end(); ++it)
glooxwrapper::Tag::free(*it);
}
/// Game - script
ScriptInterface& XmppClient::GetScriptInterface()
{
return m_ScriptInterface;
}
/// Network
void XmppClient::connect()
{
m_client->connect(false);
}
void XmppClient::disconnect()
{
m_client->disconnect();
}
void XmppClient::recv()
{
m_client->recv(1);
}
/**
* Log (debug) Handler
*/
void XmppClient::handleLog(gloox::LogLevel level, gloox::LogArea area, const std::string& message)
{
std::cout << "log: level: " << level << ", area: " << area << ", message: " << message << std::endl;
}
/*****************************************************
* Connection handlers *
*****************************************************/
/**
* Handle connection
*/
void XmppClient::onConnect()
{
if (m_mucRoom)
{
CreateSimpleMessage("system", "connected");
m_mucRoom->join();
SendIqGetGameList();
SendIqGetBoardList();
}
if (m_registration)
m_registration->fetchRegistrationFields();
}
/**
* Handle disconnection
*/
void XmppClient::onDisconnect(gloox::ConnectionError error)
{
// Make sure we properly leave the room so that
// everything works if we decide to come back later
if (m_mucRoom)
m_mucRoom->leave();
// Clear game, board and player lists.
for (std::vector<const glooxwrapper::Tag*>::const_iterator it = m_GameList.begin(); it != m_GameList.end(); ++it)
glooxwrapper::Tag::free(*it);
for (std::vector<const glooxwrapper::Tag*>::const_iterator it = m_BoardList.begin(); it != m_BoardList.end(); ++it)
glooxwrapper::Tag::free(*it);
m_BoardList.clear();
m_GameList.clear();
m_PlayerMap.clear();
if(error == gloox::ConnAuthenticationFailed)
CreateSimpleMessage("system", "authentication failed", "error");
else
CreateSimpleMessage("system", "disconnected");
}
/**
* Handle TLS connection
*/
bool XmppClient::onTLSConnect(const glooxwrapper::CertInfo& info)
{
UNUSED2(info);
DbgXMPP("onTLSConnect");
DbgXMPP(
"status: " << info.status <<
"\nissuer: " << info.issuer <<
"\npeer: " << info.server <<
"\nprotocol: " << info.protocol <<
"\nmac: " << info.mac <<
"\ncipher: " << info.cipher <<
"\ncompression: " << info.compression );
return true;
}
/**
* Handle MUC room errors
*/
void XmppClient::handleMUCError(glooxwrapper::MUCRoom*, gloox::StanzaError err)
{
std::string msg = StanzaErrorToString(err);
CreateSimpleMessage("system", msg, "error");
}
/*****************************************************
* Requests to server *
*****************************************************/
/**
* Request a listing of active games from the server.
*/
void XmppClient::SendIqGetGameList()
{
glooxwrapper::JID xpartamuppJid(m_xpartamuppId);
// Send IQ
glooxwrapper::IQ iq(gloox::IQ::Get, xpartamuppJid);
iq.addExtension(new GameListQuery());
DbgXMPP("SendIqGetGameList [" << tag_xml(iq) << "]");
m_client->send(iq);
}
/**
* Request the leaderboard data from the server.
*/
void XmppClient::SendIqGetBoardList()
{
glooxwrapper::JID xpartamuppJid(m_xpartamuppId);
// Send IQ
glooxwrapper::IQ iq(gloox::IQ::Get, xpartamuppJid);
iq.addExtension(new BoardListQuery());
DbgXMPP("SendIqGetBoardList [" << tag_xml(iq) << "]");
m_client->send(iq);
}
/**
* Send game report containing numerous game properties to the server.
*
* @param data A JS array of game statistics
*/
void XmppClient::SendIqGameReport(CScriptVal data)
{
glooxwrapper::JID xpartamuppJid(m_xpartamuppId);
jsval dataval = data.get();
// Setup some base stanza attributes
GameReport* game = new GameReport();
glooxwrapper::Tag* report = glooxwrapper::Tag::allocate("game");
// Iterate through all the properties reported and add them to the stanza.
std::vector<std::string> properties;
m_ScriptInterface.EnumeratePropertyNamesWithPrefix(dataval, "", properties);
for (std::vector<int>::size_type i = 0; i != properties.size(); i++)
{
std::string value;
m_ScriptInterface.GetProperty(dataval, properties[i].c_str(), value);
report->addAttribute(properties[i], value);
}
// Add stanza to IQ
game->m_GameReport.push_back(report);
// Send IQ
glooxwrapper::IQ iq(gloox::IQ::Set, xpartamuppJid);
iq.addExtension(game);
DbgXMPP("SendGameReport [" << tag_xml(iq) << "]");
m_client->send(iq);
};
/**
* Send a request to register a game to the server.
*
* @param data A JS array of game attributes
*/
void XmppClient::SendIqRegisterGame(CScriptVal data)
{
glooxwrapper::JID xpartamuppJid(m_xpartamuppId);
jsval dataval = data.get();
// Setup some base stanza attributes
GameListQuery* g = new GameListQuery();
g->m_Command = "register";
glooxwrapper::Tag* game = glooxwrapper::Tag::allocate("game");
// Add a fake ip which will be overwritten by the ip stamp XMPP module on the server.
game->addAttribute("ip", "fake");
// Iterate through all the properties reported and add them to the stanza.
std::vector<std::string> properties;
m_ScriptInterface.EnumeratePropertyNamesWithPrefix(dataval, "", properties);
for (std::vector<int>::size_type i = 0; i != properties.size(); i++)
{
std::string value;
m_ScriptInterface.GetProperty(dataval, properties[i].c_str(), value);
game->addAttribute(properties[i], value);
}
// Push the stanza onto the IQ
g->m_GameList.push_back(game);
// Send IQ
glooxwrapper::IQ iq(gloox::IQ::Set, xpartamuppJid);
iq.addExtension(g);
DbgXMPP("SendIqRegisterGame [" << tag_xml(iq) << "]");
m_client->send(iq);
}
/**
* Send a request to unregister a game to the server.
*/
void XmppClient::SendIqUnregisterGame()
{
glooxwrapper::JID xpartamuppJid(m_xpartamuppId);
// Send IQ
GameListQuery* g = new GameListQuery();
g->m_Command = "unregister";
g->m_GameList.push_back(glooxwrapper::Tag::allocate( "game" ));
glooxwrapper::IQ iq( gloox::IQ::Set, xpartamuppJid );
iq.addExtension( g );
DbgXMPP("SendIqUnregisterGame [" << tag_xml(iq) << "]");
m_client->send( iq );
}
/**
* Send a request to change the state of a registered game on the server.
*
* A game can either be in the 'running' or 'waiting' state - the server
* decides which - but we need to update the current players that are
* in-game so the server can make the calculation.
*/
void XmppClient::SendIqChangeStateGame(const std::string& nbp, const std::string& players)
{
glooxwrapper::JID xpartamuppJid(m_xpartamuppId);
// Send IQ
GameListQuery* g = new GameListQuery();
g->m_Command = "changestate";
glooxwrapper::Tag* game = glooxwrapper::Tag::allocate("game");
game->addAttribute("nbp", nbp);
game->addAttribute("players", players);
g->m_GameList.push_back(game);
glooxwrapper::IQ iq(gloox::IQ::Set, xpartamuppJid);
iq.addExtension( g );
DbgXMPP("SendIqChangeStateGame [" << tag_xml(iq) << "]");
m_client->send(iq);
}
/*****************************************************
* Account registration *
*****************************************************/
void XmppClient::handleRegistrationFields(const glooxwrapper::JID&, int fields, glooxwrapper::string)
{
glooxwrapper::RegistrationFields vals;
vals.username = m_username;
vals.password = m_password;
m_registration->createAccount(fields, vals);
}
void XmppClient::handleRegistrationResult(const glooxwrapper::JID&, gloox::RegistrationResult result)
{
if (result == gloox::RegistrationSuccess)
{
CreateSimpleMessage("system", "registered");
}
else
{
std::string msg;
#define CASE(X, Y) case gloox::X: msg = Y; break
switch(result)
{
CASE(RegistrationNotAcceptable, "Registration not acceptable");
CASE(RegistrationConflict, "Registration conflict");
CASE(RegistrationNotAuthorized, "Registration not authorized");
CASE(RegistrationBadRequest, "Registration bad request");
CASE(RegistrationForbidden, "Registration forbidden");
CASE(RegistrationRequired, "Registration required");
CASE(RegistrationUnexpectedRequest, "Registration unexpected request");
CASE(RegistrationNotAllowed, "Registration not allowed");
default: msg = "Registration unknown error";
}
#undef CASE
CreateSimpleMessage("system", msg, "error");
}
disconnect();
}
void XmppClient::handleAlreadyRegistered(const glooxwrapper::JID&)
{
DbgXMPP("the account already exists");
}
void XmppClient::handleDataForm(const glooxwrapper::JID&, const glooxwrapper::DataForm&)
{
DbgXMPP("dataForm received");
}
void XmppClient::handleOOB(const glooxwrapper::JID&, const glooxwrapper::OOB&)
{
DbgXMPP("OOB registration requested");
}
/*****************************************************
* Requests from GUI *
*****************************************************/
/**
* Handle requests from the GUI for the list of players.
*
* @return A JS array containing all known players and their presences
*/
CScriptValRooted XmppClient::GUIGetPlayerList()
{
std::string presence;
CScriptValRooted playerList;
m_ScriptInterface.Eval("({})", playerList);
for(std::map<std::string, gloox::Presence::PresenceType>::const_iterator it = m_PlayerMap.begin(); it != m_PlayerMap.end(); ++it)
{
CScriptValRooted player;
GetPresenceString(it->second, presence);
m_ScriptInterface.Eval("({})", player);
m_ScriptInterface.SetProperty(player.get(), "name", it->first.c_str());
m_ScriptInterface.SetProperty(player.get(), "presence", presence.c_str());
m_ScriptInterface.SetProperty(playerList.get(), it->first.c_str(), player);
}
return playerList;
}
/**
* Handle requests from the GUI for the list of all active games.
*
* @return A JS array containing all known games
*/
CScriptValRooted XmppClient::GUIGetGameList()
{
CScriptValRooted gameList;
m_ScriptInterface.Eval("([])", gameList);
for(std::vector<const glooxwrapper::Tag*>::const_iterator it = m_GameList.begin(); it != m_GameList.end(); ++it)
{
CScriptValRooted game;
m_ScriptInterface.Eval("({})", game);
const char* stats[] = { "name", "ip", "state", "nbp", "tnbp", "players", "mapName", "niceMapName", "mapSize", "mapType", "victoryCondition" };
short stats_length = 11;
for (short i = 0; i < stats_length; i++)
m_ScriptInterface.SetProperty(game.get(), stats[i], (*it)->findAttribute(stats[i]).c_str());
m_ScriptInterface.CallFunctionVoid(gameList.get(), "push", game);
}
return gameList;
}
/**
* Handle requests from the GUI for leaderboard data.
*
* @return A JS array containing all known leaderboard data
*/
CScriptValRooted XmppClient::GUIGetBoardList()
{
CScriptValRooted boardList;
m_ScriptInterface.Eval("([])", boardList);
for(std::vector<const glooxwrapper::Tag*>::const_iterator it = m_BoardList.begin(); it != m_BoardList.end(); ++it)
{
CScriptValRooted board;
m_ScriptInterface.Eval("({})", board);
const char* attributes[] = { "name", "rank", "rating" };
short attributes_length = 3;
for (short i = 0; i < attributes_length; i++)
m_ScriptInterface.SetProperty(board.get(), attributes[i], (*it)->findAttribute(attributes[i]).c_str());
m_ScriptInterface.CallFunctionVoid(boardList.get(), "push", board);
}
return boardList;
}
/*****************************************************
* Message interfaces *
*****************************************************/
/**
* Send GUI message queue when queried.
*/
CScriptValRooted XmppClient::GuiPollMessage()
{
if (m_GuiMessageQueue.empty())
return CScriptValRooted();
CScriptValRooted r = m_GuiMessageQueue.front();
m_GuiMessageQueue.pop_front();
return r;
}
/**
* Send a standard MUC textual message.
*/
void XmppClient::SendMUCMessage(const std::string& message)
{
m_mucRoom->send(message);
}
/**
* Push a message onto the GUI queue.
*
* @param message Message to add to the queue
*/
void XmppClient::PushGuiMessage(const CScriptValRooted& message)
{
ENSURE(!message.undefined());
m_GuiMessageQueue.push_back(message);
}
/**
* Handle a standard MUC textual message.
*/
void XmppClient::handleMUCMessage(glooxwrapper::MUCRoom*, const glooxwrapper::Message& msg, bool)
{
DbgXMPP(msg.from().resource() << " said " << msg.body());
CScriptValRooted message;
m_ScriptInterface.Eval("({ 'type':'mucmessage'})", message);
m_ScriptInterface.SetProperty(message.get(), "from", msg.from().resource().to_string());
m_ScriptInterface.SetProperty(message.get(), "text", msg.body().to_string());
PushGuiMessage(message);
}
/**
* Handle a standard textual message.
*/
void XmppClient::handleMessage(const glooxwrapper::Message& msg, glooxwrapper::MessageSession *)
{
DbgXMPP("type " << msg.subtype() << ", subject " << msg.subject()
<< ", message " << msg.body() << ", thread id " << msg.thread());
CScriptValRooted message;
m_ScriptInterface.Eval("({'type':'message'})", message);
m_ScriptInterface.SetProperty(message.get(), "from", msg.from().username().to_string());
m_ScriptInterface.SetProperty(message.get(), "text", msg.body().to_string());
PushGuiMessage(message);
}
/**
* Handle portions of messages containing custom stanza extensions.
*/
bool XmppClient::handleIq(const glooxwrapper::IQ& iq)
{
DbgXMPP("handleIq [" << tag_xml(iq) << "]");
if(iq.subtype() == gloox::IQ::Result)
{
const GameListQuery* gq = iq.findExtension<GameListQuery>( ExtGameListQuery );
const BoardListQuery* bq = iq.findExtension<BoardListQuery>( ExtBoardListQuery );
if(gq)
{
for(std::vector<const glooxwrapper::Tag*>::const_iterator it = m_GameList.begin(); it != m_GameList.end(); ++it )
glooxwrapper::Tag::free(*it);
m_GameList.clear();
for(std::vector<const glooxwrapper::Tag*>::const_iterator it = gq->m_GameList.begin(); it != gq->m_GameList.end(); ++it)
m_GameList.push_back( (*it)->clone() );
CreateSimpleMessage("system", "gamelist updated", "internal");
}
if(bq)
{
for(std::vector<const glooxwrapper::Tag*>::const_iterator it = m_BoardList.begin(); it != m_BoardList.end(); ++it )
glooxwrapper::Tag::free(*it);
m_BoardList.clear();
for(std::vector<const glooxwrapper::Tag*>::const_iterator it = bq->m_BoardList.begin(); it != bq->m_BoardList.end(); ++it)
m_BoardList.push_back( (*it)->clone() );
CreateSimpleMessage("system", "boardlist updated", "internal");
}
}
else if(iq.subtype() == gloox::IQ::Error)
{
gloox::StanzaError err = iq.error_error();
std::string msg = StanzaErrorToString(err);
CreateSimpleMessage("system", msg, "error");
}
else
{
CreateSimpleMessage("system", std::string("unknown subtype : ") + tag_name(iq), "error");
}
return true;
}
/**
* Create a new detail message for the GUI.
*
* @param type General message type
* @param level Detailed message type
* @param text Body of the message
* @param data Optional field, used for auxiliary data
*/
void XmppClient::CreateSimpleMessage(const std::string& type, const std::string& text, const std::string& level, const std::string& data)
{
CScriptValRooted message;
m_ScriptInterface.Eval("({})", message);
m_ScriptInterface.SetProperty(message.get(), "type", type);
m_ScriptInterface.SetProperty(message.get(), "level", level);
m_ScriptInterface.SetProperty(message.get(), "text", text);
m_ScriptInterface.SetProperty(message.get(), "data", data);
PushGuiMessage(message);
}
/*****************************************************
* Presence and nickname *
*****************************************************/
/**
* Update local data when a user changes presence.
*/
void XmppClient::handleMUCParticipantPresence(glooxwrapper::MUCRoom*, const glooxwrapper::MUCRoomParticipant participant, const glooxwrapper::Presence& presence)
{
//std::string jid = participant.jid->full();
std::string nick = participant.nick->resource().to_string();
gloox::Presence::PresenceType presenceType = presence.presence();
if (presenceType == gloox::Presence::Unavailable)
{
if (!participant.newNick.empty() && (participant.flags & (gloox::UserNickChanged | gloox::UserSelf)))
{
// we have a nick change
m_PlayerMap[participant.newNick.to_string()] = gloox::Presence::Unavailable;
CreateSimpleMessage("muc", nick, "nick", participant.newNick.to_string());
}
else
CreateSimpleMessage("muc", nick, "leave");
DbgXMPP(nick << " left the room");
m_PlayerMap.erase(nick);
}
else
{
if (m_PlayerMap.find(nick) == m_PlayerMap.end())
CreateSimpleMessage("muc", nick, "join");
else
CreateSimpleMessage("muc", nick, "presence");
DbgXMPP(nick << " is in the room, presence : " << (int)presenceType);
m_PlayerMap[nick] = presenceType;
}
}
/**
* Request nick change, real change via mucRoomHandler.
*
* @param nick Desired nickname
*/
void XmppClient::SetNick(const std::string& nick)
{
m_mucRoom->setNick(nick);
}
/**
* Get current nickname.
*
* @param nick Variable to store the nickname in.
*/
void XmppClient::GetNick(std::string& nick)
{
nick = m_mucRoom->nick().to_string();
}
/**
* Kick a player from the current room.
*
* @param nick Nickname to be kicked
* @param reason Reason the player was kicked
*/
void XmppClient::kick(const std::string& nick, const std::string& reason)
{
m_mucRoom->kick(nick, reason);
}
/**
* Ban a player from the current room.
*
* @param nick Nickname to be banned
* @param reason Reason the player was banned
*/
void XmppClient::ban(const std::string& nick, const std::string& reason)
{
m_mucRoom->ban(nick, reason);
}
/**
* Change the xmpp presence of the client.
*
* @param presence A string containing the desired presence
*/
void XmppClient::SetPresence(const std::string& presence)
{
#define IF(x,y) if (presence == x) m_mucRoom->setPresence(gloox::Presence::y)
IF("available", Available);
else IF("chat", Chat);
else IF("away", Away);
else IF("playing", DND);
else IF("gone", XA);
else IF("offline", Unavailable);
// The others are not to be set
#undef IF
else LOGERROR(L"Unknown presence '%hs'", presence.c_str());
}
/**
* Get the current xmpp presence of the given nick.
*
* @param nick Nickname to look up presence for
* @param presence Variable to store the presence in
*/
void XmppClient::GetPresence(const std::string& nick, std::string& presence)
{
if (m_PlayerMap.find(nick) != m_PlayerMap.end())
GetPresenceString(m_PlayerMap[nick], presence);
else
presence = "offline";
}
/*****************************************************
* Utilities *
*****************************************************/
/**
* Convert a gloox presence type to string.
*
* @param p Presence to be converted
* @param presence Variable to store the converted presence string in
*/
void XmppClient::GetPresenceString(const gloox::Presence::PresenceType p, std::string& presence) const
{
switch(p)
{
#define CASE(x,y) case gloox::Presence::x: presence = y; break
CASE(Available, "available");
CASE(Chat, "chat");
CASE(Away, "away");
CASE(DND, "playing");
CASE(XA, "gone");
CASE(Unavailable, "offline");
CASE(Probe, "probe");
CASE(Error, "error");
CASE(Invalid, "invalid");
default:
LOGERROR(L"Unknown presence type '%d'", (int)p);
break;
#undef CASE
}
}
/**
* Convert a gloox stanza error type to string.
*
* @param err Error to be converted
* @return Converted error string
*/
std::string XmppClient::StanzaErrorToString(gloox::StanzaError err)
{
std::string msg;
#define CASE(X, Y) case gloox::X: return Y
switch (err)
{
CASE(StanzaErrorBadRequest, "Bad request");
CASE(StanzaErrorConflict, "Player name already in use");
CASE(StanzaErrorFeatureNotImplemented, "Feature not implemented");
CASE(StanzaErrorForbidden, "Forbidden");
CASE(StanzaErrorGone, "Recipient or server gone");
CASE(StanzaErrorInternalServerError, "Internal server error");
CASE(StanzaErrorItemNotFound, "Item not found");
CASE(StanzaErrorJidMalformed, "Jid malformed");
CASE(StanzaErrorNotAcceptable, "Not acceptable");
CASE(StanzaErrorNotAllowed, "Not allowed");
CASE(StanzaErrorNotAuthorized, "Not authorized");
CASE(StanzaErrorNotModified, "Not modified");
CASE(StanzaErrorPaymentRequired, "Payment required");
CASE(StanzaErrorRecipientUnavailable, "Recipient unavailable");
CASE(StanzaErrorRedirect, "Redirect");
CASE(StanzaErrorRegistrationRequired, "Registration required");
CASE(StanzaErrorRemoteServerNotFound, "Remote server not found");
CASE(StanzaErrorRemoteServerTimeout, "Remote server timeout");
CASE(StanzaErrorResourceConstraint, "Resource constraint");
CASE(StanzaErrorServiceUnavailable, "Service unavailable");
CASE(StanzaErrorSubscribtionRequired, "Subscribtion Required");
CASE(StanzaErrorUndefinedCondition, "Undefined condition");
CASE(StanzaErrorUnexpectedRequest, "Unexpected request");
CASE(StanzaErrorUnknownSender, "Unknown sender");
default:
return "Error undefined";
}
#undef CASE
}

140
source/lobby/XmppClient.h Normal file
View File

@ -0,0 +1,140 @@
/* Copyright (C) 2013 Wildfire Games.
* This file is part of 0 A.D.
*
* 0 A.D. is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 2 of the License, or
* (at your option) any later version.
*
* 0 A.D. is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with 0 A.D. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef XXXMPPCLIENT_H
#define XXXMPPCLIENT_H
#include "IXmppClient.h"
#include "glooxwrapper/glooxwrapper.h"
//game - script
#include <deque>
#include "scriptinterface/ScriptVal.h"
//Game - script
class ScriptInterface;
namespace glooxwrapper
{
class Client;
struct CertInfo;
}
class XmppClient : public IXmppClient, public glooxwrapper::ConnectionListener, public glooxwrapper::MUCRoomHandler, public glooxwrapper::IqHandler, public glooxwrapper::RegistrationHandler, public glooxwrapper::MessageHandler
{
NONCOPYABLE(XmppClient);
private:
//Game - script
ScriptInterface& m_ScriptInterface;
//Components
glooxwrapper::Client* m_client;
glooxwrapper::MUCRoom* m_mucRoom;
glooxwrapper::Registration* m_registration;
//Account infos
std::string m_username;
std::string m_password;
std::string m_nick;
std::string m_xpartamuppId;
public:
//Basic
XmppClient(ScriptInterface& scriptInterface, const std::string& sUsername, const std::string& sPassword, const std::string& sRoom, const std::string& sNick, bool regOpt = false);
virtual ~XmppClient();
//Network
void connect();
void disconnect();
void recv();
void SendIqGetGameList();
void SendIqGetBoardList();
void SendIqGameReport(CScriptVal data);
void SendIqRegisterGame(CScriptVal data);
void SendIqUnregisterGame();
void SendIqChangeStateGame(const std::string& nbp, const std::string& players);
void SetNick(const std::string& nick);
void GetNick(std::string& nick);
void kick(const std::string& nick, const std::string& reason);
void ban(const std::string& nick, const std::string& reason);
void SetPresence(const std::string& presence);
void GetPresence(const std::string& nickname, std::string& presence);
CScriptValRooted GUIGetPlayerList();
CScriptValRooted GUIGetGameList();
CScriptValRooted GUIGetBoardList();
//Script
ScriptInterface& GetScriptInterface();
protected:
/* Xmpp handlers */
/* MUC handlers */
virtual void handleMUCParticipantPresence(glooxwrapper::MUCRoom*, const glooxwrapper::MUCRoomParticipant, const glooxwrapper::Presence&);
virtual void handleMUCError(glooxwrapper::MUCRoom*, gloox::StanzaError);
virtual void handleMUCMessage(glooxwrapper::MUCRoom* room, const glooxwrapper::Message& msg, bool priv);
/* MUC handlers not supported by glooxwrapper */
// virtual bool handleMUCRoomCreation(glooxwrapper::MUCRoom*) {return false;}
// virtual void handleMUCSubject(glooxwrapper::MUCRoom*, const std::string&, const std::string&) {}
// virtual void handleMUCInviteDecline(glooxwrapper::MUCRoom*, const glooxwrapper::JID&, const std::string&) {}
// virtual void handleMUCInfo(glooxwrapper::MUCRoom*, int, const std::string&, const glooxwrapper::DataForm*) {}
// virtual void handleMUCItems(glooxwrapper::MUCRoom*, const std::list<gloox::Disco::Item*, std::allocator<gloox::Disco::Item*> >&) {}
/* Log handler */
virtual void handleLog(gloox::LogLevel level, gloox::LogArea area, const std::string& message);
/* ConnectionListener handlers*/
virtual void onConnect();
virtual void onDisconnect(gloox::ConnectionError e);
virtual bool onTLSConnect(const glooxwrapper::CertInfo& info);
/* Iq Handlers */
virtual bool handleIq(const glooxwrapper::IQ& iq);
virtual void handleIqID(const glooxwrapper::IQ&, int) {}
/* Registration Handlers */
virtual void handleRegistrationFields(const glooxwrapper::JID& /*from*/, int fields, glooxwrapper::string instructions );
virtual void handleRegistrationResult(const glooxwrapper::JID& /*from*/, gloox::RegistrationResult result);
virtual void handleAlreadyRegistered(const glooxwrapper::JID& /*from*/);
virtual void handleDataForm(const glooxwrapper::JID& /*from*/, const glooxwrapper::DataForm& /*form*/);
virtual void handleOOB(const glooxwrapper::JID& /*from*/, const glooxwrapper::OOB& oob);
/* Message Handler */
virtual void handleMessage(const glooxwrapper::Message& msg, glooxwrapper::MessageSession * session);
// Helpers
void GetPresenceString(const gloox::Presence::PresenceType p, std::string& presence) const;
std::string StanzaErrorToString(gloox::StanzaError err);
public:
/* Messages */
CScriptValRooted GuiPollMessage();
void SendMUCMessage(const std::string& message);
protected:
void PushGuiMessage(const CScriptValRooted& message);
void CreateSimpleMessage(const std::string& type, const std::string& text, const std::string& level = "standard", const std::string& data = "");
private:
/// Map of players
std::map<std::string, gloox::Presence::PresenceType> m_PlayerMap;
/// List of games
std::vector<const glooxwrapper::Tag*> m_GameList;
/// List of rankings
std::vector<const glooxwrapper::Tag*> m_BoardList;
/// Queue of messages
std::deque<CScriptValRooted> m_GuiMessageQueue;
};
#endif // XMPPCLIENT_H

347
source/lobby/sha.cpp Normal file
View File

@ -0,0 +1,347 @@
/**
* FIPS-180-2 compliant SHA-256 implementation
*
* Copyright (C) 2001-2003 Christophe Devine
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
#include "precompiled.h"
#include "sha.h"
#include <string.h>
#include <stdio.h>
#define GET_UINT32(n,b,i) \
{ \
(n) = ( (uint) (b)[(i) ] << 24 ) \
| ( (uint) (b)[(i) + 1] << 16 ) \
| ( (uint) (b)[(i) + 2] << 8 ) \
| ( (uint) (b)[(i) + 3] ); \
}
#define PUT_UINT32(n,b,i) \
{ \
(b)[(i) ] = (byte) ( ((n) >> 24) & 0xFF ); \
(b)[(i) + 1] = (byte) ( ((n) >> 16) & 0xFF ); \
(b)[(i) + 2] = (byte) ( ((n) >> 8) & 0xFF ); \
(b)[(i) + 3] = (byte) ( ((n) ) & 0xFF ); \
}
SHA256::SHA256()
{
init();
}
void SHA256::init()
{
total[0] = 0;
total[1] = 0;
state[0] = 0x6A09E667;
state[1] = 0xBB67AE85;
state[2] = 0x3C6EF372;
state[3] = 0xA54FF53A;
state[4] = 0x510E527F;
state[5] = 0x9B05688C;
state[6] = 0x1F83D9AB;
state[7] = 0x5BE0CD19;
}
void SHA256::transform(byte (&data)[64])
{
uint temp1, temp2, W[64];
uint A, B, C, D, E, F, G, H;
GET_UINT32( W[0], data, 0 );
GET_UINT32( W[1], data, 4 );
GET_UINT32( W[2], data, 8 );
GET_UINT32( W[3], data, 12 );
GET_UINT32( W[4], data, 16 );
GET_UINT32( W[5], data, 20 );
GET_UINT32( W[6], data, 24 );
GET_UINT32( W[7], data, 28 );
GET_UINT32( W[8], data, 32 );
GET_UINT32( W[9], data, 36 );
GET_UINT32( W[10], data, 40 );
GET_UINT32( W[11], data, 44 );
GET_UINT32( W[12], data, 48 );
GET_UINT32( W[13], data, 52 );
GET_UINT32( W[14], data, 56 );
GET_UINT32( W[15], data, 60 );
#define SHR(x,n) ((x & 0xFFFFFFFF) >> n)
#define ROTR(x,n) (SHR(x,n) | (x << (32 - n)))
#define S0(x) (ROTR(x, 7) ^ ROTR(x,18) ^ SHR(x, 3))
#define S1(x) (ROTR(x,17) ^ ROTR(x,19) ^ SHR(x,10))
#define S2(x) (ROTR(x, 2) ^ ROTR(x,13) ^ ROTR(x,22))
#define S3(x) (ROTR(x, 6) ^ ROTR(x,11) ^ ROTR(x,25))
#define F0(x,y,z) ((x & y) | (z & (x | y)))
#define F1(x,y,z) (z ^ (x & (y ^ z)))
#define R(t) \
( \
W[t] = S1(W[t - 2]) + W[t - 7] + \
S0(W[t - 15]) + W[t - 16] \
)
#define P(a,b,c,d,e,f,g,h,x,K) \
{ \
temp1 = h + S3(e) + F1(e,f,g) + K + x; \
temp2 = S2(a) + F0(a,b,c); \
d += temp1; h = temp1 + temp2; \
}
A = state[0];
B = state[1];
C = state[2];
D = state[3];
E = state[4];
F = state[5];
G = state[6];
H = state[7];
P( A, B, C, D, E, F, G, H, W[ 0], 0x428A2F98 );
P( H, A, B, C, D, E, F, G, W[ 1], 0x71374491 );
P( G, H, A, B, C, D, E, F, W[ 2], 0xB5C0FBCF );
P( F, G, H, A, B, C, D, E, W[ 3], 0xE9B5DBA5 );
P( E, F, G, H, A, B, C, D, W[ 4], 0x3956C25B );
P( D, E, F, G, H, A, B, C, W[ 5], 0x59F111F1 );
P( C, D, E, F, G, H, A, B, W[ 6], 0x923F82A4 );
P( B, C, D, E, F, G, H, A, W[ 7], 0xAB1C5ED5 );
P( A, B, C, D, E, F, G, H, W[ 8], 0xD807AA98 );
P( H, A, B, C, D, E, F, G, W[ 9], 0x12835B01 );
P( G, H, A, B, C, D, E, F, W[10], 0x243185BE );
P( F, G, H, A, B, C, D, E, W[11], 0x550C7DC3 );
P( E, F, G, H, A, B, C, D, W[12], 0x72BE5D74 );
P( D, E, F, G, H, A, B, C, W[13], 0x80DEB1FE );
P( C, D, E, F, G, H, A, B, W[14], 0x9BDC06A7 );
P( B, C, D, E, F, G, H, A, W[15], 0xC19BF174 );
P( A, B, C, D, E, F, G, H, R(16), 0xE49B69C1 );
P( H, A, B, C, D, E, F, G, R(17), 0xEFBE4786 );
P( G, H, A, B, C, D, E, F, R(18), 0x0FC19DC6 );
P( F, G, H, A, B, C, D, E, R(19), 0x240CA1CC );
P( E, F, G, H, A, B, C, D, R(20), 0x2DE92C6F );
P( D, E, F, G, H, A, B, C, R(21), 0x4A7484AA );
P( C, D, E, F, G, H, A, B, R(22), 0x5CB0A9DC );
P( B, C, D, E, F, G, H, A, R(23), 0x76F988DA );
P( A, B, C, D, E, F, G, H, R(24), 0x983E5152 );
P( H, A, B, C, D, E, F, G, R(25), 0xA831C66D );
P( G, H, A, B, C, D, E, F, R(26), 0xB00327C8 );
P( F, G, H, A, B, C, D, E, R(27), 0xBF597FC7 );
P( E, F, G, H, A, B, C, D, R(28), 0xC6E00BF3 );
P( D, E, F, G, H, A, B, C, R(29), 0xD5A79147 );
P( C, D, E, F, G, H, A, B, R(30), 0x06CA6351 );
P( B, C, D, E, F, G, H, A, R(31), 0x14292967 );
P( A, B, C, D, E, F, G, H, R(32), 0x27B70A85 );
P( H, A, B, C, D, E, F, G, R(33), 0x2E1B2138 );
P( G, H, A, B, C, D, E, F, R(34), 0x4D2C6DFC );
P( F, G, H, A, B, C, D, E, R(35), 0x53380D13 );
P( E, F, G, H, A, B, C, D, R(36), 0x650A7354 );
P( D, E, F, G, H, A, B, C, R(37), 0x766A0ABB );
P( C, D, E, F, G, H, A, B, R(38), 0x81C2C92E );
P( B, C, D, E, F, G, H, A, R(39), 0x92722C85 );
P( A, B, C, D, E, F, G, H, R(40), 0xA2BFE8A1 );
P( H, A, B, C, D, E, F, G, R(41), 0xA81A664B );
P( G, H, A, B, C, D, E, F, R(42), 0xC24B8B70 );
P( F, G, H, A, B, C, D, E, R(43), 0xC76C51A3 );
P( E, F, G, H, A, B, C, D, R(44), 0xD192E819 );
P( D, E, F, G, H, A, B, C, R(45), 0xD6990624 );
P( C, D, E, F, G, H, A, B, R(46), 0xF40E3585 );
P( B, C, D, E, F, G, H, A, R(47), 0x106AA070 );
P( A, B, C, D, E, F, G, H, R(48), 0x19A4C116 );
P( H, A, B, C, D, E, F, G, R(49), 0x1E376C08 );
P( G, H, A, B, C, D, E, F, R(50), 0x2748774C );
P( F, G, H, A, B, C, D, E, R(51), 0x34B0BCB5 );
P( E, F, G, H, A, B, C, D, R(52), 0x391C0CB3 );
P( D, E, F, G, H, A, B, C, R(53), 0x4ED8AA4A );
P( C, D, E, F, G, H, A, B, R(54), 0x5B9CCA4F );
P( B, C, D, E, F, G, H, A, R(55), 0x682E6FF3 );
P( A, B, C, D, E, F, G, H, R(56), 0x748F82EE );
P( H, A, B, C, D, E, F, G, R(57), 0x78A5636F );
P( G, H, A, B, C, D, E, F, R(58), 0x84C87814 );
P( F, G, H, A, B, C, D, E, R(59), 0x8CC70208 );
P( E, F, G, H, A, B, C, D, R(60), 0x90BEFFFA );
P( D, E, F, G, H, A, B, C, R(61), 0xA4506CEB );
P( C, D, E, F, G, H, A, B, R(62), 0xBEF9A3F7 );
P( B, C, D, E, F, G, H, A, R(63), 0xC67178F2 );
state[0] += A;
state[1] += B;
state[2] += C;
state[3] += D;
state[4] += E;
state[5] += F;
state[6] += G;
state[7] += H;
}
void SHA256::update(const void* input, uint length )
{
uint left, fill;
if( ! length ) return;
left = total[0] & 0x3F;
fill = 64 - left;
total[0] += length;
total[0] &= 0xFFFFFFFF;
if( total[0] < length )
total[1]++;
if( left && length >= fill )
{
memcpy( (void *) (buffer + left),
(void *) input, fill );
transform(buffer);
length -= fill;
input = (byte*)input + fill;
left = 0;
}
while( length >= 64 )
{
transform((byte(&)[64])input);
length -= 64;
input = (byte*)input + 64;
}
if( length )
{
memcpy( (void *) (buffer + left),
(void *) input, length );
}
}
static byte sha256_padding[64] =
{
0x80, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
};
void SHA256::finish(byte (&digest)[32] )
{
uint last, padn;
uint high, low;
byte msglen[8];
high = ( total[0] >> 29 )
| ( total[1] << 3 );
low = ( total[0] << 3 );
PUT_UINT32( high, msglen, 0 );
PUT_UINT32( low, msglen, 4 );
last = total[0] & 0x3F;
padn = ( last < 56 ) ? ( 56 - last ) : ( 120 - last );
update(sha256_padding, padn);
update(msglen, 8);
PUT_UINT32( state[0], digest, 0 );
PUT_UINT32( state[1], digest, 4 );
PUT_UINT32( state[2], digest, 8 );
PUT_UINT32( state[3], digest, 12 );
PUT_UINT32( state[4], digest, 16 );
PUT_UINT32( state[5], digest, 20 );
PUT_UINT32( state[6], digest, 24 );
PUT_UINT32( state[7], digest, 28 );
}
/**
* From BSD's PBKDF implementation:
*/
static void hmac_sha256(byte (&digest)[SHA_DIGEST_SIZE],
const byte* text, size_t text_len, const byte* key, size_t key_len)
{
SHA256 hash;
byte tk[SHA_DIGEST_SIZE]; // temporary key incase we need to pad the key with zero bytes
if (key_len > SHA_DIGEST_SIZE)
{
hash.update(key, key_len);
hash.finish(tk);
key = tk;
key_len = SHA_DIGEST_SIZE;
}
byte k_pad[SHA_DIGEST_SIZE];
memset(k_pad, 0, sizeof k_pad);
memcpy(k_pad, key, key_len);
for (int i = 0; i < SHA_DIGEST_SIZE; ++i)
k_pad[i] ^= 0x36;
hash.init();
hash.update(k_pad, SHA_DIGEST_SIZE);
hash.update(text, text_len);
hash.finish(digest);
memset(k_pad, 0, sizeof k_pad);
memcpy(k_pad, key, key_len);
for (int i = 0; i < SHA_DIGEST_SIZE; ++i)
k_pad[i] ^= 0x5c;
hash.init();
hash.update(k_pad, SHA_DIGEST_SIZE);
hash.update(digest, SHA_DIGEST_SIZE);
hash.finish(digest);
}
int pbkdf2(byte (&output)[SHA_DIGEST_SIZE],
const byte* key, size_t key_len,
const byte* salt, size_t salt_len,
unsigned rounds)
{
byte asalt[SHA_DIGEST_SIZE + 4], obuf[SHA_DIGEST_SIZE], d1[SHA_DIGEST_SIZE], d2[SHA_DIGEST_SIZE];
if (rounds < 1 || key_len == 0 || salt_len == 0)
return -1;
if (salt_len > SHA_DIGEST_SIZE) salt_len = SHA_DIGEST_SIZE; // length cap for the salt
memset(asalt, 0, salt_len);
memcpy(asalt, salt, salt_len);
for (unsigned count = 1; ; ++count)
{
asalt[salt_len + 0] = (count >> 24) & 0xff;
asalt[salt_len + 1] = (count >> 16) & 0xff;
asalt[salt_len + 2] = (count >> 8) & 0xff;
asalt[salt_len + 3] = count & 0xff;
hmac_sha256(d1, asalt, salt_len + 4, key, key_len);
memcpy(obuf, d1, SHA_DIGEST_SIZE);
for (unsigned i = 1; i < rounds; i++)
{
hmac_sha256(d2, d1, SHA_DIGEST_SIZE, key, key_len);
memcpy(d1, d2, SHA_DIGEST_SIZE);
for (unsigned j = 0; j < SHA_DIGEST_SIZE; j++)
obuf[j] ^= d1[j];
}
memcpy(output, obuf, SHA_DIGEST_SIZE);
key += SHA_DIGEST_SIZE;
if (key_len < SHA_DIGEST_SIZE)
break;
key_len -= SHA_DIGEST_SIZE;
};
return 0;
}

62
source/lobby/sha.h Normal file
View File

@ -0,0 +1,62 @@
/* Copyright (c) 2013 Wildfire Games
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be included
* in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
#ifndef SHA_INCLUDED
#define SHA_INCLUDED
#define SHA_DIGEST_SIZE 32
typedef unsigned char byte;
typedef unsigned int uint;
/**
* Structure for performing SHA256 encryption on arbitrary data
*/
struct SHA256
{
uint total[2];
uint state[8];
byte buffer[64];
SHA256();
void init();
void transform(byte (&data)[64]);
void update(const void* input, uint len);
void finish(byte (&digest)[32]);
};
/**
* Simple PBKDF2 implementation for hard to crack passwords
* @param output The output buffer for the digested hash
* @param key The initial key we want to hash
* @param key_len Length of the key in bytes
* @param salt The salt we use to iteratively hash the key.
* @param salt_len Length of the salt in bytes
* @param iterations Number of salting iterations
* @return 0 on success, -1 on error
*/
int pbkdf2(byte (&output)[SHA_DIGEST_SIZE],
const byte* key, size_t key_len,
const byte* salt, size_t salt_len,
unsigned iterations);
#endif

View File

@ -30,6 +30,7 @@
#include "ps/Compress.h"
#include "ps/CStr.h"
#include "ps/Game.h"
#include "ps/GUID.h"
#include "ps/Loader.h"
#include "scriptinterface/ScriptInterface.h"
#include "simulation2/Simulation2.h"
@ -68,7 +69,7 @@ private:
CNetClient::CNetClient(CGame* game) :
m_Session(NULL),
m_UserName(L"anonymous"),
m_GUID(GenerateGUID()), m_HostID((u32)-1), m_ClientTurnManager(NULL), m_Game(game)
m_GUID(ps_generate_guid()), m_HostID((u32)-1), m_ClientTurnManager(NULL), m_Game(game)
{
m_Game->SetTurnManager(NULL); // delete the old local turn manager so we don't accidentally use it
@ -250,7 +251,7 @@ void CNetClient::SendChatMessage(const std::wstring& text)
bool CNetClient::HandleMessage(CNetMessage* message)
{
// Handle non-FSM messages first
Status status = m_Session->GetFileTransferer().HandleMessageReceive(message);
if (status == INFO::OK)
return true;
@ -503,7 +504,7 @@ bool CNetClient::OnJoinSyncEndCommandBatch(void* context, CFsmEvent* event)
CEndCommandBatchMessage* endMessage = (CEndCommandBatchMessage*)event->GetParamRef();
client->m_ClientTurnManager->FinishedAllCommands(endMessage->m_Turn, endMessage->m_TurnLength);
// Execute all the received commands for the latest turn
client->m_ClientTurnManager->UpdateFastForward();
@ -555,25 +556,3 @@ bool CNetClient::OnInGame(void *context, CFsmEvent* event)
return true;
}
CStr CNetClient::GenerateGUID()
{
// TODO: Ideally this will be guaranteed unique (and verified
// cryptographically) since we'll rely on it to identify hosts
// and associate them with player controls (e.g. to support
// leaving/rejoining in-progress games), and we don't want
// a host to masquerade as someone else.
// For now, just try to pick a very random number.
CStr guid;
for (size_t i = 0; i < 2; ++i)
{
u32 r = 0;
sys_generate_random_bytes((u8*)&r, sizeof(r));
char buf[32];
sprintf_s(buf, ARRAY_SIZE(buf), "%08X", r);
guid += buf;
}
return guid;
}

View File

@ -210,8 +210,6 @@ private:
/// Globally unique identifier to distinguish users beyond the lifetime of a single network session
CStr m_GUID;
/// Initialise m_GUID with a random value
CStr GenerateGUID();
/// Queue of messages for GuiPoll
std::deque<CScriptValRooted> m_GuiMessageQueue;

View File

@ -0,0 +1,18 @@
/* Copyright (C) 2009 Wildfire Games.
* This file is part of 0 A.D.
*
* 0 A.D. is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 2 of the License, or
* (at your option) any later version.
*
* 0 A.D. is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with 0 A.D. If not, see <http://www.gnu.org/licenses/>.
*/
#include "precompiled.h"

View File

@ -0,0 +1,18 @@
/* Copyright (C) 2009 Wildfire Games.
* This file is part of 0 A.D.
*
* 0 A.D. is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 2 of the License, or
* (at your option) any later version.
*
* 0 A.D. is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with 0 A.D. If not, see <http://www.gnu.org/licenses/>.
*/
#include "lib/precompiled.h" // common precompiled header

41
source/ps/GUID.cpp Normal file
View File

@ -0,0 +1,41 @@
/* Copyright (C) 2013 Wildfire Games.
* This file is part of 0 A.D.
*
* 0 A.D. is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 2 of the License, or
* (at your option) any later version.
*
* 0 A.D. is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with 0 A.D. If not, see <http://www.gnu.org/licenses/>.
*/
#include "precompiled.h"
#include "lib/sysdep/sysdep.h"
#include "ps/CStr.h"
CStr ps_generate_guid(void)
{
// TODO: Ideally this will be guaranteed unique (and verified
// cryptographically) since we'll rely on it to identify hosts
// and associate them with player controls (e.g. to support
// leaving/rejoining in-progress games), and we don't want
// a host to masquerade as someone else.
// For now, just try to pick a very random number.
CStr guid;
for (size_t i = 0; i < 2; ++i)
{
u32 r = 0;
sys_generate_random_bytes((u8*)&r, sizeof(r));
char buf[32];
sprintf_s(buf, ARRAY_SIZE(buf), "%08X", r);
guid += buf;
}
return guid;
}

25
source/ps/GUID.h Normal file
View File

@ -0,0 +1,25 @@
/* Copyright (C) 2013 Wildfire Games.
* This file is part of 0 A.D.
*
* 0 A.D. is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 2 of the License, or
* (at your option) any later version.
*
* 0 A.D. is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with 0 A.D. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef INCLUDED_GUID
#define INCLUDED_GUID
#include "ps/CStr.h"
CStr ps_generate_guid(void);
#endif

View File

@ -87,6 +87,7 @@
#include "scriptinterface/ScriptInterface.h"
#include "scriptinterface/ScriptStats.h"
#include "simulation2/Simulation2.h"
#include "lobby/IXmppClient.h"
#include "soundmanager/scripting/JSInterface_Sound.h"
#include "soundmanager/ISoundManager.h"
#include "tools/atlas/GameInterface/GameLoop.h"
@ -684,6 +685,10 @@ void Shutdown(int UNUSED(flags))
{
EndGame();
#if CONFIG2_LOBBY
SAFE_DELETE(g_XmppClient);
#endif
ShutdownPs(); // Must delete g_GUI before g_ScriptingHost
in_reset_handlers();

View File

@ -92,7 +92,7 @@ JSBool JSI_VFS::BuildDirEntList(JSContext* cx, uintN argc, jsval* vp)
// get arguments
//
JSU_REQUIRE_MIN_PARAMS(1);
JSU_REQUIRE_PARAM_RANGE(1, 3);
CStrW path;
if (!ScriptInterface::FromJSVal(cx, JS_ARGV(cx, vp)[0], path))
@ -127,6 +127,22 @@ JSBool JSI_VFS::BuildDirEntList(JSContext* cx, uintN argc, jsval* vp)
return JS_TRUE;
}
// Return true iff the file exits
//
// if (fileExists(filename)) { ... }
// filename: VFS filename (may include path)
JSBool JSI_VFS::FileExists(JSContext* cx, uintN argc, jsval* vp)
{
JSU_REQUIRE_PARAMS(1);
CStrW filename;
if (!ScriptInterface::FromJSVal<CStrW> (cx, JS_ARGV(cx, vp)[0], filename))
return JS_FALSE;
JS_SET_RVAL(cx, vp, g_VFS->GetFileInfo(filename, 0) == INFO::OK ? JSVAL_TRUE : JSVAL_FALSE);
return JS_TRUE;
}
// Return time [seconds since 1970] of the last modification to the specified file.
//
@ -134,7 +150,7 @@ JSBool JSI_VFS::BuildDirEntList(JSContext* cx, uintN argc, jsval* vp)
// filename: VFS filename (may include path)
JSBool JSI_VFS::GetFileMTime(JSContext* cx, uintN argc, jsval* vp)
{
JSU_REQUIRE_MIN_PARAMS(1);
JSU_REQUIRE_PARAMS(1);
CStrW filename;
if (!ScriptInterface::FromJSVal(cx, JS_ARGV(cx, vp)[0], filename))
@ -155,7 +171,7 @@ JSBool JSI_VFS::GetFileMTime(JSContext* cx, uintN argc, jsval* vp)
// filename: VFS filename (may include path)
JSBool JSI_VFS::GetFileSize(JSContext* cx, uintN argc, jsval* vp)
{
JSU_REQUIRE_MIN_PARAMS(1);
JSU_REQUIRE_PARAMS(1);
CStrW filename;
if (!ScriptInterface::FromJSVal(cx, JS_ARGV(cx, vp)[0], filename))
@ -176,7 +192,7 @@ JSBool JSI_VFS::GetFileSize(JSContext* cx, uintN argc, jsval* vp)
// filename: VFS filename (may include path)
JSBool JSI_VFS::ReadFile(JSContext* cx, uintN argc, jsval* vp)
{
JSU_REQUIRE_MIN_PARAMS(1);
JSU_REQUIRE_PARAMS(1);
CStrW filename;
if (!ScriptInterface::FromJSVal(cx, JS_ARGV(cx, vp)[0], filename))
@ -209,7 +225,7 @@ JSBool JSI_VFS::ReadFile(JSContext* cx, uintN argc, jsval* vp)
// filename: VFS filename (may include path)
JSBool JSI_VFS::ReadFileLines(JSContext* cx, uintN argc, jsval* vp)
{
JSU_REQUIRE_MIN_PARAMS(1);
JSU_REQUIRE_PARAMS(1);
CStrW filename;
if (!ScriptInterface::FromJSVal(cx, JS_ARGV(cx, vp)[0], filename))

View File

@ -40,6 +40,12 @@ namespace JSI_VFS
// ready for use as a "filename" for the other functions.
JSBool BuildDirEntList(JSContext* cx, uintN argc, jsval* vp);
// Return true iff the file exists
//
// if (fileExists(filename) { ... }
// filename: VFS filename (may include path)
JSBool FileExists(JSContext* cx, uintN argc, jsval* vp);
// Return time [seconds since 1970] of the last modification to the specified file.
//
// mtime = getFileMTime(filename);

View File

@ -381,6 +381,7 @@ JSFunctionSpec ScriptFunctionTable[] =
// VFS (external)
JS_FUNC("buildDirEntList", JSI_VFS::BuildDirEntList, 1)
JS_FUNC("fileExists", JSI_VFS::FileExists, 1)
JS_FUNC("getFileMTime", JSI_VFS::GetFileMTime, 1)
JS_FUNC("getFileSize", JSI_VFS::GetFileSize, 1)
JS_FUNC("readFile", JSI_VFS::ReadFile, 1)

View File

@ -0,0 +1,31 @@
from config import elo_sure_win_difference, elo_k_factor_constant_rating
def get_rating_adjustment(rating, opponent_rating, games_played, opponent_games_played, result):
"""
Calculates the rating adjustment after a 1v1 game finishes using simplified ELO.
Arguments:
rating, opponent_rating - Ratings of the players before this game.
games_played, opponent_games_played - Number of games each player has played
before this game.
result - 1 for the first player (rating, games_played) won, 0 for draw, or
-1 for the second player (opponent_rating, opponent_games_played) won.
Returns:
The integer that should be subtracted from the loser's rating and added
to the winner's rating to get their new ratings.
TODO: Team games.
"""
opponent_volatility_influence = max(1, pow(min(games_played + 1, 50) / min(opponent_games_played + 1, 50), 0.5))
rating_k_factor = 0.75 * pow(elo_k_factor_constant_rating / min(elo_k_factor_constant_rating, (rating + opponent_rating) / 2), 0.5)
player_volatility = min(pow(1.1, games_played + 16), 25)
volatility = opponent_volatility_influence * player_volatility / rating_k_factor
difference = rating - opponent_rating
if result == 1:
return round(max(0, (difference + result * elo_sure_win_difference) / volatility))
elif result == -1:
return round(min(0, (difference + result * elo_sure_win_difference) / volatility))
else:
return round(difference / volatility)

View File

@ -0,0 +1,82 @@
#!/usr/bin/env python3
import sqlalchemy
from sqlalchemy import Column, ForeignKey, Integer, String
from sqlalchemy.orm import relationship, sessionmaker
from sqlalchemy.ext.declarative import declarative_base
engine = sqlalchemy.create_engine('sqlite:///lobby_rankings.sqlite3')
Session = sessionmaker(bind=engine)
session = Session()
Base = declarative_base()
class Player(Base):
__tablename__ = 'players'
id = Column(Integer, primary_key=True)
jid = Column(String(255))
rating = Column(Integer)
games = relationship('Game', secondary='players_info')
# These two relations really only exist to satisfy the linkage
# between PlayerInfo and Player and Game and player.
games_info = relationship('PlayerInfo', backref='player')
games_won = relationship('Game', backref='winner')
class PlayerInfo(Base):
__tablename__ = 'players_info'
id = Column(Integer, primary_key=True)
player_id = Column(Integer, ForeignKey('players.id'))
game_id = Column(Integer, ForeignKey('games.id'))
civ = String(20)
economyScore = Column(Integer)
militaryScore = Column(Integer)
explorationScore = Column(Integer)
# Store to avoid needlessly recomputing it.
totalScore = Column(Integer)
unitsTrained = Column(Integer)
unitsLost = Column(Integer)
unitsKilled = Column(Integer)
buildingsConstructed = Column(Integer)
buildingsLost = Column(Integer)
buildingsDestroyed = Column(Integer)
civCentersBuilt = Column(Integer)
civCentersDestroyed = Column(Integer)
percentMapExplored = Column(Integer)
foodGathered = Column(Integer)
foodUsed = Column(Integer)
woodGathered = Column(Integer)
woodUsed = Column(Integer)
stoneGathered = Column(Integer)
stoneUsed = Column(Integer)
metalGathered = Column(Integer)
metalUsed = Column(Integer)
vegetarianRatio = Column(Integer)
treasuresCollected = Column(Integer)
tributesSent = Column(Integer)
tributesRecieved = Column(Integer)
foodBought = Column(Integer)
foodSold = Column(Integer)
woodBought = Column(Integer)
woodSold = Column(Integer)
stoneBought = Column(Integer)
stoneSold = Column(Integer)
metalBought = Column(Integer)
metalSold = Column(Integer)
barterEfficiency = Column(Integer)
tradeIncome = Column(Integer)
class Game(Base):
__tablename__ = 'games'
id = Column(Integer, primary_key=True)
map = Column(String(80))
duration = Column(Integer)
winner_id = Column(Integer, ForeignKey('players.id'))
player_info = relationship('PlayerInfo', backref='game')
players = relationship('Player', secondary='players_info')
if __name__ == '__main__':
Base.metadata.create_all(engine)

View File

@ -0,0 +1,18 @@
include_dir = /usr/lib/ejabberd/include/
ebin_dir = /usr/lib/ejabberd/ebin/
module = mod_ipstamp.beam
all : ${module}
%.beam : %.erl
erlc -I ${include_dir} -pz ${ebin_dir} $<
install :
mv ${module} ${ebin_dir}
clean :
rm ${module}
restart :
rm -f /var/log/ejabberd/ejabberd.log
/etc/init.d/ejabberd restart

View File

@ -0,0 +1,86 @@
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
@@ Install ejabberd and the erlang compiler @@
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
# apt-get install ejabberd erlang-dev?
Configure it
# dpkg-reconfigure ejabberd
set the domain name (e.g. localhost if you installed it on your development computer)
and a login / password
You should now be able to connect to this XMPP server using a normal XMPP client (e.g. Empathy).
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
@@ Installation of the custom XMPP module @@
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
Go to it's source directory
$ cd source/tools/XpartaMuPP
Edit the file to set the domain on which is run the ejabberd server. (e.g. localhost)
Build and install it
$ make
# make install
Tell ejabberd that you want it to load the module
In /etc/ejabberd/ejabberd.cfg, add {mod_ipstamp, []}
in the Modules list "Modules enabled in all ejabberd virtual hosts"
Restart ejabberd
# service ejabberd restart
If something goes wrong read /var/log/ejabberd/ejabberd.log
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
@@ Ejabberd administration @@
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
Go http://localhost:5280/admin and connect
(the login looks like login@domain (e.g. adminjabber@localhost))
In "Acces rules" check that "register" is set to "[{allow,all}]"
You can see the list of registered / connected users in
"Virtual Hosts" >> domain name >> "users"
You must manually add a new user for XpartaMuPP.
Enter a login (use "xpartamupp" since that's what clients and the ipstamp module expect)
and password, then press "Add user"
Alternative - Command line :
Edit /etc/ejabberd.ejabberd.cfg and change {register, [{deny,all}]} to {register, [{allow,all}]}.
Register xpartamupp : # ejabberdctl register xpartamupp <domain> <Xpartamupp password>
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
@@ Run XpartaMuPP - XMPP Multiplayer Game Manager @@
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
You need to have python 3 installed
$ sudo apt-get install python3
You also need the SleekXmpp Python library
Go to to github and follow their instructions
https://github.com/fritzy/SleekXMPP
If you would like to run the leaderboard database,
$ sudo apt-get install sqlalchemy
Then execute the following command to setup the database.
$ ./source/tools/XpartaMuPP/LobbyRankings.py
Execute the following command to run the bot with default options
$ ./source/tools/XpartaMuPP/XpartaMuPP.py
or rather a similar command to run a properly configured program
$ ./source/tools/XpartaMuPP/XpartaMuPP.py --domain localhost --password XXXXXX --nickname WFGbot
Run ./source/tools/XpartaMuPP/XpartaMuPP.py -h for the full list of options
If everything is fine you should see something along these lines in your console
<<<<
INFO Negotiating TLS
INFO Using SSL version: 3
INFO Node set to: xpartamupp@lobby.wildfiregames.com/CC
INFO XpartaMuPP started
<<<<
Congratulations, you are running XpartaMuPP - the 0ad Multiplayer Game Manager.

View File

@ -0,0 +1,616 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""Copyright (C) 2013 Wildfire Games.
* This file is part of 0 A.D.
*
* 0 A.D. is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 2 of the License, or
* (at your option) any later version.
*
* 0 A.D. is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with 0 A.D. If not, see <http://www.gnu.org/licenses/>.
"""
import logging
import time
from optparse import OptionParser
import sleekxmpp
from sleekxmpp.stanza import Iq
from sleekxmpp.xmlstream import ElementBase, register_stanza_plugin, ET
from sleekxmpp.xmlstream.handler import Callback
from sleekxmpp.xmlstream.matcher import StanzaPath
from LobbyRanking import session as db, Game, Player, PlayerInfo
from ELO import get_rating_adjustment
from config import default_rating, leaderboard_minimum_games, leaderboard_active_games
## Class that contains and manages leaderboard data ##
class LeaderboardList():
def __init__(self, room):
self.room = room
def getOrCreatePlayer(self, JID):
"""
Stores a player(JID) in the database if they don't yet exist.
Returns either the newly created instance of
the Player model, or the one that already
exists in the database.
"""
players = db.query(Player).filter_by(jid=str(JID))
if not players.first():
player = Player(jid=str(JID), rating=default_rating)
db.add(player)
db.commit()
return player
return players.first()
def removePlayer(self, JID):
"""
Remove a player(JID) from database.
Returns the player that was removed, or None
if that player didn't exist.
"""
players = db.query(Player).filter_by(jid=JID)
player = players.first()
if not player:
return None
players.delete()
return player
def addGame(self, gamereport):
"""
Adds a game to the database and updates the data
on a player(JID) from game results.
Returns the created Game object, or None if
the creation failed for any reason.
Side effects:
Inserts a new Game instance into the database.
"""
# Discard any games still in progress.
if any(map(lambda state: state == 'active',
dict.values(gamereport['playerStates']))):
return None
players = map(lambda jid: db.query(Player).filter_by(jid=jid).first(),
dict.keys(gamereport['playerStates']))
winning_jid = list(dict.keys({jid: state for jid, state in
gamereport['playerStates'].items()
if state == 'won'}))[0]
def get(stat, jid):
return gamereport[stat][jid]
stats = {'civ': 'civs', 'foodGathered': 'foodGathered', 'foodUsed': 'foodUsed',
'woodGathered': 'woodGathered', 'woodUsed': 'woodUsed',
'stoneGathered': 'stoneGathered', 'stoneUsed': 'stoneUsed',
'metalGathered': 'metalGathered', 'metalUsed': 'metalUsed'}
playerInfos = []
for player in players:
jid = player.jid
playerinfo = PlayerInfo(player=player)
for dbname, reportname in stats.items():
setattr(playerinfo, dbname, get(reportname, jid))
playerInfos.append(playerinfo)
game = Game(map=gamereport['mapName'], duration=int(gamereport['timeElapsed']))
game.players.extend(players)
game.player_info.extend(playerInfos)
game.winner = db.query(Player).filter_by(jid=winning_jid).first()
db.add(game)
db.commit()
return game
def rateGame(self, game):
"""
Takes a game with 2 players and alters their ratings
based on the result of the game.
Returns self.
Side effects:
Changes the game's players' ratings in the database.
"""
player1 = game.players[0]
player2 = game.players[1]
# TODO: Support draws. Since it's impossible to draw in the game currently,
# the database model, and therefore this code, requires a winner.
# The Elo implementation does not, however.
result = 1 if player1 == game.winner else -1
rating_adjustment = get_rating_adjustment(player1.rating, player2.rating,
len(player1.games), len(player2.games), result)
player1.rating += rating_adjustment
player2.rating -= rating_adjustment
db.commit()
return self
def addAndRateGame(self, gamereport):
"""
Calls addGame and if the game has only two
players, also calls rateGame.
Returns the result of addGame.
"""
game = self.addGame(gamereport)
if game and len(game.players) == 2:
self.rateGame(game)
return game
def getBoard(self):
"""
Returns a dictionary of player rankings to
JIDs for sending.
"""
board = {}
players = db.query(Player).order_by(Player.rating.desc()).limit(100).all()
for rank, player in enumerate(players):
board[player.jid] = {'name': '@'.join(player.jid.split('@')[:-1]), 'rating': str(player.rating)}
return board
## Class to tracks all games in the lobby ##
class GameList():
def __init__(self):
self.gameList = {}
def addGame(self, JID, data):
"""
Add a game
"""
data['players-init'] = data['players']
data['nbp-init'] = data['nbp']
data['state'] = 'init'
self.gameList[str(JID)] = data
def removeGame(self, JID):
"""
Remove a game attached to a JID
"""
del self.gameList[str(JID)]
def getAllGames(self):
"""
Returns all games
"""
return self.gameList
def changeGameState(self, JID, data):
"""
Switch game state between running and waiting
"""
JID = str(JID)
if JID in self.gameList:
if self.gameList[JID]['nbp-init'] > data['nbp']:
logging.debug("change game (%s) state from %s to %s", JID, self.gameList[JID]['state'], 'waiting')
self.gameList[JID]['nbp'] = data['nbp']
self.gameList[JID]['state'] = 'waiting'
else:
logging.debug("change game (%s) state from %s to %s", JID, self.gameList[JID]['state'], 'running')
self.gameList[JID]['nbp'] = data['nbp']
self.gameList[JID]['state'] = 'running'
## Class which manages different game reports from clients ##
## and calls leaderboard functions as appropriate. ##
class ReportManager():
def __init__(self, leaderboard):
self.leaderboard = leaderboard
self.interimReportTracker = []
self.interimJIDTracker = []
def addReport(self, JID, rawGameReport):
"""
Adds a game to the interface between a raw report
and the leaderboard database.
"""
# cleanRawGameReport is a copy of rawGameReport with all reporter specific information removed.
cleanRawGameReport = rawGameReport.copy()
del cleanRawGameReport["playerID"]
if cleanRawGameReport not in self.interimReportTracker:
# Store the game.
appendIndex = len(self.interimReportTracker)
self.interimReportTracker.append(cleanRawGameReport)
# Initilize the JIDs and store the initial JID.
JIDs = [None] * self.getNumPlayers(rawGameReport)
JIDs[int(rawGameReport["playerID"])-1] = str(JID)
self.interimJIDTracker.append(JIDs)
else:
# We get the index at which the JIDs coresponding to the game are stored.
index = self.interimReportTracker.index(cleanRawGameReport)
# We insert the new report JID into the acending list of JIDs for the game.
JIDs = self.interimJIDTracker[index]
JIDs[int(rawGameReport["playerID"])-1] = str(JID)
self.interimJIDTracker[index] = JIDs
self.checkFull()
def expandReport(self, rawGameReport, JIDs):
"""
Takes an raw game report and re-formats it into
Python data structures leaving JIDs empty.
Returns a processed gameReport of type dict.
"""
processedGameReport = {}
for key in rawGameReport:
if rawGameReport[key].find(",") == -1:
processedGameReport[key] = rawGameReport[key]
else:
split = rawGameReport[key].split(",")
# Remove the false split positive.
split.pop()
# We just delete gaia for now.
split.pop(0)
statToJID = {}
for i, part in enumerate(split):
statToJID[JIDs[i]] = part
processedGameReport[key] = statToJID
#processedGameReport["numPlayers"] = self.getNumPlayers(rawGameReport)
return processedGameReport
def checkFull(self):
"""
Searches internal database to check if enough
reports have been submitted to add a game to
the leaderboard. If so, the report will be
interpolated and addAndRateGame will be
called with the result.
"""
i = 0
length = len(self.interimReportTracker)
while(i < length):
numPlayers = self.getNumPlayers(self.interimReportTracker[i])
numReports = 0
for JID in self.interimJIDTracker[i]:
if JID != None:
numReports += 1
if numReports == numPlayers:
self.leaderboard.addAndRateGame(self.expandReport(self.interimReportTracker[i], self.interimJIDTracker[i]))
del self.interimJIDTracker[i]
del self.interimReportTracker[i]
length -= 1
else:
i += 1
def getNumPlayers(self, rawGameReport):
"""
Computes the number of players in a raw gameReport.
Returns int, the number of players.
"""
# Find a key in the report which holds values for multiple players.
for key in rawGameReport:
if rawGameReport[key].find(",") != -1:
# Count the number of values, minus one for gaia and one for the false split positive.
return len(rawGameReport[key].split(","))-2
# Return -1 in case of failure.
return -1
## Class for custom gamelist stanza extension ##
class GameListXmppPlugin(ElementBase):
name = 'query'
namespace = 'jabber:iq:gamelist'
interfaces = set(('game', 'command'))
sub_interfaces = interfaces
plugin_attrib = 'gamelist'
def addGame(self, data):
itemXml = ET.Element("game", data)
self.xml.append(itemXml)
def getGame(self):
"""
Required to parse incoming stanzas with this
extension.
"""
game = self.xml.find('{%s}game' % self.namespace)
data = {}
for key, item in game.items():
data[key] = item
return data
## Class for custom boardlist stanza extension ##
class BoardListXmppPlugin(ElementBase):
name = 'query'
namespace = 'jabber:iq:boardlist'
interfaces = ('board')
sub_interfaces = interfaces
plugin_attrib = 'boardlist'
def addItem(self, name, rating):
itemXml = ET.Element("board", {"name": name, "rating": rating})
self.xml.append(itemXml)
## Class for custom gamereport stanza extension ##
class GameReportXmppPlugin(ElementBase):
name = 'report'
namespace = 'jabber:iq:gamereport'
plugin_attrib = 'gamereport'
interfaces = ('game')
sub_interfaces = interfaces
def getGame(self):
"""
Required to parse incoming stanzas with this
extension.
"""
game = self.xml.find('{%s}game' % self.namespace)
data = {}
for key, item in game.items():
data[key] = item
return data
## Main class which handles IQ data and sends new data ##
class XpartaMuPP(sleekxmpp.ClientXMPP):
"""
A simple list provider
"""
def __init__(self, sjid, password, room, nick):
sleekxmpp.ClientXMPP.__init__(self, sjid, password)
self.sjid = sjid
self.room = room
self.nick = nick
# Game collection
self.gameList = GameList()
# Init leaderboard object
self.leaderboard = LeaderboardList(room)
# gameReport to leaderboard abstraction
self.reportManager = ReportManager(self.leaderboard)
# Store mapping of nicks and XmppIDs, attached via presence stanza
self.nicks = {}
# Store client JIDs, attached via client request
self.JIDs = []
self.lastLeft = ""
register_stanza_plugin(Iq, GameListXmppPlugin)
register_stanza_plugin(Iq, BoardListXmppPlugin)
register_stanza_plugin(Iq, GameReportXmppPlugin)
self.register_handler(Callback('Iq Gamelist',
StanzaPath('iq/gamelist'),
self.iqhandler,
instream=True))
self.register_handler(Callback('Iq Boardlist',
StanzaPath('iq/boardlist'),
self.iqhandler,
instream=True))
self.register_handler(Callback('Iq GameReport',
StanzaPath('iq/gamereport'),
self.iqhandler,
instream=True))
self.add_event_handler("session_start", self.start)
self.add_event_handler("session_start", self.start)
self.add_event_handler("muc::%s::got_online" % self.room, self.muc_online)
self.add_event_handler("muc::%s::got_offline" % self.room, self.muc_offline)
def start(self, event):
"""
Process the session_start event
"""
self.plugin['xep_0045'].joinMUC(self.room, self.nick)
self.send_presence()
self.get_roster()
logging.info("XpartaMuPP started")
def muc_online(self, presence):
"""
Process presence stanza from a chat room.
"""
if presence['muc']['nick'] != self.nick:
# Check the jid isn't already in the lobby.
if str(presence['muc']['jid']) != self.lastLeft:
self.send_message(mto=presence['from'], mbody="Hello %s, welcome to the 0 A.D. lobby. Polish your weapons and get ready to fight!" %(presence['muc']['nick']), mtype='')
# Send Gamelist to new player
self.sendGameList(presence['muc']['jid'])
# Store player JID mapped to their nick
self.nicks[str(presence['muc']['jid'])] = presence['muc']['nick']
logging.debug("Client '%s' connected with a nick of '%s'." %(presence['muc']['jid'], presence['muc']['nick']))
def muc_offline(self, presence):
"""
Process presence stanza from a chat room.
"""
# Clean up after a player leaves
if presence['muc']['nick'] != self.nick:
for JID in self.gameList.getAllGames():
if self.gameList.getAllGames()[JID]['players'].split(',')[0] == presence['muc']['nick']:
self.gameList.removeGame(JID)
self.sendGameList()
break
self.lastLeft = str(presence['muc']['jid'])
del self.nicks[str(presence['muc']['jid'])]
def iqhandler(self, iq):
"""
Handle the custom stanzas
This method should be very robust because we could receive anything
"""
if iq['type'] == 'error':
logging.error('iqhandler error' + iq['error']['condition'])
#self.disconnect()
elif iq['type'] == 'get':
"""
Request lists.
"""
# We expect each client to register for future updates by sending at least one get request.
try:
self.leaderboard.getOrCreatePlayer(iq['from'])
if iq['from'] not in self.JIDs:
self.JIDs.append(iq['from'])
self.sendGameList(iq['from'])
self.sendBoardList(iq['from'])
except:
logging.error("Failed to initilize %s" % iq['from'].bare)
elif iq['type'] == 'result':
"""
Iq successfully received
"""
pass
elif iq['type'] == 'set':
if 'gamelist' in iq.values:
"""
Register-update / unregister a game
"""
command = iq['gamelist']['command']
if command == 'register':
# Add game
try:
self.gameList.addGame(iq['from'], iq['gamelist']['game'])
self.sendGameList()
except:
logging.error("Failed to process game registration data")
elif command == 'unregister':
# Remove game
try:
self.gameList.removeGame(iq['from'])
self.sendGameList()
except:
logging.error("Failed to process game unregistration data")
elif command == 'changestate':
# Change game status (waiting/running)
try:
self.gameList.changeGameState(iq['from'], iq['gamelist']['game'])
self.sendGameList()
except:
logging.error("Failed to process changestate data")
else:
logging.error("Failed to process command '%s' received from %s" % command, iq['from'].bare)
elif 'gamereport' in iq.values:
"""
Client is reporting end of game statistics
"""
try:
self.reportManager.addReport(iq['from'], iq['gamereport']['game'])
self.sendBoardList()
except:
logging.error("Failed to update game statistics for %s" % iq['from'].bare)
else:
logging.error("Failed to process stanza type '%s' received from %s" % iq['type'], iq['from'].bare)
def sendGameList(self, to = ""):
"""
Send a massive stanza with the whole game list.
If no target is passed the gamelist is broadcasted
to all clients.
"""
if to != "":
## Check recipient exists
if to not in self.JIDs:
logging.error("No player with the xmpp id '%s' known" % to.bare)
return
stz = GameListXmppPlugin()
## Pull games and add each to the stanza
games = self.gameList.getAllGames()
for JID in games:
g = games[JID]
# Only send the games that are in the 'init' state and games
# that are in the 'waiting' state which the receiving player is in. TODO
if g['state'] == 'init' or (g['state'] == 'waiting' and self.nicks[str(to)] in g['players-init']):
stz.addGame(g)
## Set additional IQ attributes
iq = self.Iq()
iq['type'] = 'result'
iq['to'] = to
iq.setPayload(stz)
## Try sending the stanza
try:
iq.send()
except:
logging.error("Failed to send game list")
else:
for JID in self.JIDs:
self.sendGameList(JID)
def sendBoardList(self, to = ""):
"""
Send the whole leaderboard list.
If no target is passed the boardlist is broadcasted
to all clients.
"""
if to != "":
## Check recipiant exists
if to not in self.JIDs:
logging.error("No player with the XmPP ID '%s' known" % to.bare)
return
stz = BoardListXmppPlugin()
## Pull leaderboard data and add it to the stanza
board = self.leaderboard.getBoard()
for i in board:
stz.addItem(board[i]['name'], board[i]['rating'])
## Set aditional IQ attributes
iq = self.Iq()
iq['type'] = 'result'
iq['to'] = to
iq.setPayload(stz)
## Try sending the stanza
try:
iq.send()
except:
logging.error("Failed to send leaderboard list")
else:
for JID in self.JIDs:
self.sendBoardList(JID)
## Main Program ##
if __name__ == '__main__':
# Setup the command line arguments.
optp = OptionParser()
# Output verbosity options.
optp.add_option('-q', '--quiet', help='set logging to ERROR',
action='store_const', dest='loglevel',
const=logging.ERROR, default=logging.INFO)
optp.add_option('-d', '--debug', help='set logging to DEBUG',
action='store_const', dest='loglevel',
const=logging.DEBUG, default=logging.INFO)
optp.add_option('-v', '--verbose', help='set logging to COMM',
action='store_const', dest='loglevel',
const=5, default=logging.INFO)
# XpartaMuPP configuration options
optp.add_option('-m', '--domain', help='set xpartamupp domain',
action='store', dest='xdomain',
default="lobby.wildfiregames.com")
optp.add_option('-l', '--login', help='set xpartamupp login',
action='store', dest='xlogin',
default="xpartamupp")
optp.add_option('-p', '--password', help='set xpartamupp password',
action='store', dest='xpassword',
default="XXXXXX")
optp.add_option('-n', '--nickname', help='set xpartamupp nickname',
action='store', dest='xnickname',
default="WFGbot")
optp.add_option('-r', '--room', help='set muc room to join',
action='store', dest='xroom',
default="arena")
opts, args = optp.parse_args()
# Setup logging.
logging.basicConfig(level=opts.loglevel,
format='%(levelname)-8s %(message)s')
# XpartaMuPP
xmpp = XpartaMuPP(opts.xlogin+'@'+opts.xdomain+'/CC', opts.xpassword, opts.xroom+'@conference.'+opts.xdomain, opts.xnickname)
xmpp.register_plugin('xep_0030') # Service Discovery
xmpp.register_plugin('xep_0004') # Data Forms
xmpp.register_plugin('xep_0045') # Multi-User Chat # used
xmpp.register_plugin('xep_0060') # PubSub
xmpp.register_plugin('xep_0199') # XMPP Ping
if xmpp.connect():
xmpp.process(block=False)
else:
logging.error("Unable to connect")

View File

@ -0,0 +1,22 @@
# Rating that new players should be inserted into the
# database with, before they've played any games.
default_rating = 1200
# Required minimum number of games to get on the
# leaderboard.
leaderboard_minimum_games = 10
# Required minimum number of games per month to
# qualify as an active player.
leaderboard_active_games = 5
# Difference between two ratings such that it is
# regarded as a "sure win" for the higher player.
# No points are gained or lost for such a game.
elo_sure_win_difference = 600
# Lower ratings "move faster" and change more
# dramatically than higher ones. Anything rating above
# this value moves at the same rate as this value.
elo_k_factor_constant_rating = 2200

View File

@ -0,0 +1,76 @@
%% Copyright (C) 2013 Wildfire Games.
%% This file is part of 0 A.D.
%%
%% 0 A.D. is free software: you can redistribute it and/or modify
%% it under the terms of the GNU General Public License as published by
%% the Free Software Foundation, either version 2 of the License, or
%% (at your option) any later version.
%%
%% 0 A.D. is distributed in the hope that it will be useful,
%% but WITHOUT ANY WARRANTY; without even the implied warranty of
%% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
%% GNU General Public License for more details.
%%
%% You should have received a copy of the GNU General Public License
%% along with 0 A.D. If not, see <http://www.gnu.org/licenses/>.
-module(mod_ipstamp).
-behaviour(gen_mod).
-include("ejabberd.hrl").
-export([start/2, stop/1, on_filter_packet/1]).
%% Domain on which run the ejabberd server
-define (Domain, "lobby.wildfiregames.com").
%% Login of the Xpartamupp jabber client
-define (XpartamuppLogin, "xpartamupp").
start(_Host, _Opts) ->
?INFO_MSG("mod_ipip starting", []),
ejabberd_hooks:add(filter_packet, global, ?MODULE, on_filter_packet, 50),
ok.
stop(_Host) ->
ejabberd_hooks:delete(filter_packet, global, ?MODULE, on_filter_packet, 50),
ok.
on_filter_packet({From, To, Packet} = Input) ->
{_,STo,_,_,_,_,_} = To,
{_,SFrom,_,_,_,_,_} = From,
if STo == "xpartamupp" ->
{_,SElement,LPacketInfo,LPacketQuery} = Packet,
if SElement == "iq" ->
{_, SType} = lists:keyfind("type",1,LPacketInfo),
if SType == "set" ->
{_,_,LXmlns,LGame} = lists:keyfind("query",2,LPacketQuery),
{_,SXmlns} = lists:keyfind("xmlns",1,LXmlns),
if SXmlns == "jabber:iq:gamelist" ->
{_,_,_,LCommand} = lists:keyfind("command",2,LGame),
{_,SCommand} = lists:keyfind(xmlcdata,1,LCommand),
if SCommand == <<"register">> ->
{_,_,KGame,_} = lists:keyfind("game",2,LGame),
Info = ejabberd_sm:get_user_info(SFrom,[?Domain],"0ad"),
{ip, {Ploc, _Port}} = lists:keyfind(ip, 1, Info),
SIp = inet_parse:ntoa(Ploc),
?INFO_MSG(string:concat("stamp ip: ",SIp), []),
{From,To,{xmlelement,"iq",LPacketInfo,[
{xmlelement,"query",[{"xmlns","jabber:iq:gamelist"}],[
{xmlelement,"game",lists:keyreplace("ip",1,KGame,{"ip",SIp}),[]},
{xmlelement,"command",[],[{xmlcdata,<<"register">>}]}
]
}
]}}
; true -> Input
end
; true -> Input
end
; true -> Input
end
; true -> Input
end
; true -> Input
end.