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:
parent
d7121f4f55
commit
bffe917914
@ -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.
|
||||
|
BIN
binaries/data/mods/public/art/textures/ui/global/modern/background.png
(Stored with Git LFS)
Normal file
BIN
binaries/data/mods/public/art/textures/ui/global/modern/background.png
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
binaries/data/mods/public/art/textures/ui/global/modern/border.png
(Stored with Git LFS)
Normal file
BIN
binaries/data/mods/public/art/textures/ui/global/modern/border.png
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
binaries/data/mods/public/art/textures/ui/global/modern/dialog-deco-bottom.png
(Stored with Git LFS)
Normal file
BIN
binaries/data/mods/public/art/textures/ui/global/modern/dialog-deco-bottom.png
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
binaries/data/mods/public/art/textures/ui/global/modern/dialog-deco-top.png
(Stored with Git LFS)
Normal file
BIN
binaries/data/mods/public/art/textures/ui/global/modern/dialog-deco-top.png
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
binaries/data/mods/public/art/textures/ui/global/modern/dropdown-arrow.png
(Stored with Git LFS)
Normal file
BIN
binaries/data/mods/public/art/textures/ui/global/modern/dropdown-arrow.png
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
binaries/data/mods/public/art/textures/ui/global/modern/gold-separator.png
(Stored with Git LFS)
Normal file
BIN
binaries/data/mods/public/art/textures/ui/global/modern/gold-separator.png
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
binaries/data/mods/public/art/textures/ui/global/modern/item-shading-left.png
(Stored with Git LFS)
Normal file
BIN
binaries/data/mods/public/art/textures/ui/global/modern/item-shading-left.png
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
binaries/data/mods/public/art/textures/ui/global/modern/item-shading-right.png
(Stored with Git LFS)
Normal file
BIN
binaries/data/mods/public/art/textures/ui/global/modern/item-shading-right.png
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
binaries/data/mods/public/art/textures/ui/global/modern/scrollback.png
(Stored with Git LFS)
Normal file
BIN
binaries/data/mods/public/art/textures/ui/global/modern/scrollback.png
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
binaries/data/mods/public/art/textures/ui/global/modern/scrollbar.png
(Stored with Git LFS)
Normal file
BIN
binaries/data/mods/public/art/textures/ui/global/modern/scrollbar.png
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
binaries/data/mods/public/art/textures/ui/global/modern/shadow-high.png
(Stored with Git LFS)
Normal file
BIN
binaries/data/mods/public/art/textures/ui/global/modern/shadow-high.png
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
binaries/data/mods/public/art/textures/ui/global/modern/shadow-low.png
(Stored with Git LFS)
Normal file
BIN
binaries/data/mods/public/art/textures/ui/global/modern/shadow-low.png
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
binaries/data/mods/public/art/textures/ui/global/modern/titlebar-left.png
(Stored with Git LFS)
Normal file
BIN
binaries/data/mods/public/art/textures/ui/global/modern/titlebar-left.png
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
binaries/data/mods/public/art/textures/ui/global/modern/titlebar-middle.png
(Stored with Git LFS)
Normal file
BIN
binaries/data/mods/public/art/textures/ui/global/modern/titlebar-middle.png
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
binaries/data/mods/public/art/textures/ui/global/modern/white-separator.png
(Stored with Git LFS)
Normal file
BIN
binaries/data/mods/public/art/textures/ui/global/modern/white-separator.png
(Stored with Git LFS)
Normal file
Binary file not shown.
@ -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"
|
||||
|
@ -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);
|
||||
|
||||
@ -290,3 +292,22 @@ function shuffleArray(source)
|
||||
}
|
||||
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);
|
||||
}
|
||||
|
16
binaries/data/mods/public/gui/common/modern/setup.xml
Normal file
16
binaries/data/mods/public/gui/common/modern/setup.xml
Normal 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>
|
255
binaries/data/mods/public/gui/common/modern/sprites.xml
Normal file
255
binaries/data/mods/public/gui/common/modern/sprites.xml
Normal 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>
|
81
binaries/data/mods/public/gui/common/modern/styles.xml
Normal file
81
binaries/data/mods/public/gui/common/modern/styles.xml
Normal 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>
|
@ -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"
|
||||
|
@ -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;
|
||||
|
||||
@ -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()
|
||||
@ -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()
|
||||
@ -1239,17 +1288,14 @@ 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
|
||||
if (g_IsController && g_GameAttributes.settings.PlayerData[playerSlot].AI && g_GameAttributes.settings.PlayerData[playerSlot].AI != "")
|
||||
else 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);
|
||||
}
|
||||
}
|
||||
|
||||
var assignBox = getGUIObjectByName("playerAssignment["+i+"]");
|
||||
assignBox.list = hostNameList;
|
||||
@ -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);
|
||||
}
|
||||
|
@ -141,7 +141,6 @@
|
||||
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.)"/>
|
||||
@ -256,7 +255,6 @@
|
||||
</object>
|
||||
|
||||
|
||||
|
||||
<!-- Chat window -->
|
||||
<object name="chatPanel" size="24 370 100%-435 100%-58" type="image" sprite="BackgroundIndentFillDark">
|
||||
<object name="chatText" size="2 2 100%-2 100%-26" type="text" style="ChatPanel"/>
|
||||
@ -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();
|
||||
if(!Engine.HasXmppClient())
|
||||
Engine.SwitchGuiPage("page_pregame.xml");
|
||||
else
|
||||
Engine.SwitchGuiPage("page_lobby.xml");
|
||||
]]>
|
||||
</action>
|
||||
</object>
|
||||
|
||||
</object>
|
||||
|
||||
</object>
|
||||
|
||||
</objects>
|
||||
|
@ -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":
|
||||
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,6 +44,8 @@ function cancelSetup()
|
||||
{
|
||||
if (g_IsConnecting)
|
||||
Engine.DisconnectNetworkGame();
|
||||
// Set player lobby presence
|
||||
Engine.LobbySetPlayerPresence("available");
|
||||
Engine.PopGuiPage();
|
||||
}
|
||||
|
||||
@ -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;
|
||||
}
|
||||
|
@ -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>
|
||||
|
@ -54,6 +54,7 @@
|
||||
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
|
||||
@ -120,7 +121,7 @@
|
||||
name CDATA #REQUIRED
|
||||
width CDATA #IMPLIED
|
||||
scroll_wheel %bool; #IMPLIED
|
||||
alwaysshown %bool; #IMPLIED
|
||||
show_edge_buttons %bool; #REQUIRED
|
||||
scroll_speed CDATA #IMPLIED
|
||||
sprite_button_top CDATA #IMPLIED
|
||||
sprite_button_top_pressed CDATA #IMPLIED
|
||||
|
797
binaries/data/mods/public/gui/lobby/lobby.js
Normal file
797
binaries/data/mods/public/gui/lobby/lobby.js
Normal 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");
|
||||
})();
|
189
binaries/data/mods/public/gui/lobby/lobby.xml
Normal file
189
binaries/data/mods/public/gui/lobby/lobby.xml
Normal 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>
|
148
binaries/data/mods/public/gui/lobby/prelobby.js
Normal file
148
binaries/data/mods/public/gui/lobby/prelobby.js
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
120
binaries/data/mods/public/gui/lobby/prelobby.xml
Normal file
120
binaries/data/mods/public/gui/lobby/prelobby.xml
Normal 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>
|
37
binaries/data/mods/public/gui/lobby/styles.xml
Normal file
37
binaries/data/mods/public/gui/lobby/styles.xml
Normal 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>
|
@ -2,14 +2,12 @@
|
||||
|
||||
<!--
|
||||
==========================================
|
||||
- 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"/>
|
||||
|
||||
@ -17,52 +15,91 @@
|
||||
<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 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="50%-190 50%-80 50%+140 50%+95">
|
||||
|
||||
<!-- Settings / shadows -->
|
||||
<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 -->
|
||||
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>
|
||||
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>-->
|
||||
</object>
|
||||
|
||||
<!-- Settings / Music-->
|
||||
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>
|
||||
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">
|
||||
-->
|
||||
<object type="button" style="StoneButton" size="50%+16 100%-64 50%+144 100%-32">
|
||||
Cancel
|
||||
<action on="Press"><![CDATA[Engine.PopGuiPage();]]></action>
|
||||
<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>
|
||||
|
17
binaries/data/mods/public/gui/page_lobby.xml
Normal file
17
binaries/data/mods/public/gui/page_lobby.xml
Normal 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>
|
@ -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>
|
||||
|
14
binaries/data/mods/public/gui/page_prelobby.xml
Normal file
14
binaries/data/mods/public/gui/page_prelobby.xml
Normal 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>
|
||||
|
@ -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,7 +241,29 @@ 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 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>
|
||||
@ -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>
|
||||
|
||||
|
@ -225,6 +225,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;
|
||||
|
||||
case "chat":
|
||||
|
@ -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,26 +350,36 @@ function onTick()
|
||||
|
||||
function checkPlayerState()
|
||||
{
|
||||
var simState = GetSimState();
|
||||
var playerState = simState.players[Engine.GetPlayerID()];
|
||||
|
||||
if (!g_GameEnded)
|
||||
{
|
||||
// If the game is about to end, disable the ability to resign.
|
||||
if (playerState.state != "active")
|
||||
getGUIObjectByName("menuResignButton").enabled = false;
|
||||
else
|
||||
// Once the game ends, we're done here.
|
||||
if (g_GameEnded)
|
||||
return;
|
||||
|
||||
if (playerState.state == "defeated")
|
||||
{
|
||||
g_GameEnded = true;
|
||||
// TODO: DEFEAT_CUE is missing?
|
||||
global.music.setState(global.music.states.DEFEAT);
|
||||
// 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)
|
||||
{
|
||||
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
|
||||
@ -372,35 +393,21 @@ function checkPlayerState()
|
||||
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")
|
||||
{
|
||||
g_GameEnded = true;
|
||||
global.music.setState(global.music.states.VICTORY);
|
||||
|
||||
closeMenu();
|
||||
closeOpenDialogs();
|
||||
|
||||
// TODO: Reveal map directly instead of this silly proxy.
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
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
|
||||
});
|
||||
}
|
||||
|
@ -248,7 +248,7 @@
|
||||
<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();
|
||||
if (!Engine.IsRankedGame()) toggleDeveloperOverlay();
|
||||
</action>
|
||||
|
||||
<object size="0 0 100%-18 16" type="text" style="devCommandsText">Control all units</object>
|
||||
@ -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>
|
||||
|
||||
|
@ -287,8 +287,17 @@
|
||||
|
||||
<object type="button" style="StoneButton" size="100%-164 100%-52 100%-24 100%-24">
|
||||
Continue
|
||||
<action on="Press">
|
||||
<action on="Press"><![CDATA[
|
||||
if(!Engine.HasXmppClient())
|
||||
{
|
||||
Engine.SwitchGuiPage("page_pregame.xml");
|
||||
}
|
||||
else
|
||||
{
|
||||
Engine.LobbySetPlayerPresence("available");
|
||||
Engine.SwitchGuiPage("page_lobby.xml");
|
||||
}
|
||||
]]>
|
||||
</action>
|
||||
</object>
|
||||
</object>
|
||||
|
@ -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 ()
|
||||
|
||||
|
@ -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);
|
||||
}
|
||||
@ -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;
|
||||
|
@ -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;
|
||||
|
@ -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())
|
||||
{
|
||||
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())
|
||||
{
|
||||
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;
|
||||
|
||||
if (!mask)
|
||||
x_pos += (float)font.GetCharacterWidth(caption[i]);
|
||||
else
|
||||
x_pos += (float)font.GetCharacterWidth(mask_char);
|
||||
|
||||
if (x_pos >= GetTextAreaWidth() && multiline)
|
||||
{
|
||||
|
@ -66,7 +66,6 @@ CList::CList() :
|
||||
// Add scroll-bar
|
||||
CGUIScrollBarVertical * bar = new CGUIScrollBarVertical();
|
||||
bar->SetRightAligned(true);
|
||||
bar->SetUseEdgeButtons(true);
|
||||
AddScrollBar(bar);
|
||||
}
|
||||
|
||||
|
@ -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
318
source/gui/COList.cpp
Normal 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
64
source/gui/COList.h
Normal 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
|
@ -58,7 +58,6 @@ CText::CText()
|
||||
// Add scroll-bar
|
||||
CGUIScrollBarVertical * bar = new CGUIScrollBarVertical();
|
||||
bar->SetRightAligned(true);
|
||||
bar->SetUseEdgeButtons(true);
|
||||
AddScrollBar(bar);
|
||||
|
||||
// Add text
|
||||
|
@ -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;
|
||||
|
||||
|
@ -97,6 +97,12 @@ struct SGUIScrollBarStyle
|
||||
*/
|
||||
float m_MaximumBarSize;
|
||||
|
||||
/**
|
||||
* True if you want edge buttons, i.e. buttons that can be pressed in order
|
||||
* to scroll.
|
||||
*/
|
||||
bool m_UseEdgeButtons;
|
||||
|
||||
//@}
|
||||
//--------------------------------------------------------
|
||||
/** @name Horizontal Sprites */
|
||||
@ -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
|
||||
*/
|
||||
|
@ -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();
|
||||
@ -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
|
||||
}
|
||||
|
@ -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
|
||||
|
60
source/lobby/IXmppClient.h
Normal file
60
source/lobby/IXmppClient.h
Normal 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
|
175
source/lobby/StanzaExtensions.cpp
Normal file
175
source/lobby/StanzaExtensions.cpp
Normal 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();
|
||||
}
|
89
source/lobby/StanzaExtensions.h
Normal file
89
source/lobby/StanzaExtensions.h
Normal 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
852
source/lobby/XmppClient.cpp
Normal 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
140
source/lobby/XmppClient.h
Normal 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
347
source/lobby/sha.cpp
Normal 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
62
source/lobby/sha.h
Normal 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
|
@ -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
|
||||
|
||||
@ -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;
|
||||
}
|
||||
|
@ -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;
|
||||
|
18
source/pch/lobby/precompiled.cpp
Normal file
18
source/pch/lobby/precompiled.cpp
Normal 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"
|
18
source/pch/lobby/precompiled.h
Normal file
18
source/pch/lobby/precompiled.h
Normal 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
41
source/ps/GUID.cpp
Normal 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
25
source/ps/GUID.h
Normal 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
|
@ -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();
|
||||
|
@ -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))
|
||||
|
@ -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);
|
||||
|
@ -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)
|
||||
|
31
source/tools/XpartaMuPP/ELO.py
Normal file
31
source/tools/XpartaMuPP/ELO.py
Normal 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)
|
82
source/tools/XpartaMuPP/LobbyRanking.py
Normal file
82
source/tools/XpartaMuPP/LobbyRanking.py
Normal 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)
|
||||
|
18
source/tools/XpartaMuPP/Makefile
Normal file
18
source/tools/XpartaMuPP/Makefile
Normal 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
|
86
source/tools/XpartaMuPP/README
Normal file
86
source/tools/XpartaMuPP/README
Normal 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.
|
||||
|
616
source/tools/XpartaMuPP/XpartaMuPP.py
Normal file
616
source/tools/XpartaMuPP/XpartaMuPP.py
Normal 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")
|
22
source/tools/XpartaMuPP/config.py
Normal file
22
source/tools/XpartaMuPP/config.py
Normal 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
|
||||
|
76
source/tools/XpartaMuPP/mod_ipstamp.erl
Normal file
76
source/tools/XpartaMuPP/mod_ipstamp.erl
Normal 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.
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user