# Refactored the networking code, and redesigned the game setup screen.

Major updates to most network classes.
Simplify CNetServer so it doesn't duplicate any client behaviour; all
players now run CNetClient.
Remove most player/slot management from networking code.
Wait for all players to finish loading before starting the simulation.
Remove CGameAttributes; attributes are now just a JS object.
Remove CPlayer; they are now just simulation entities.
Handle player colours via simulation system.
Add a default map for Atlas, so it always has something to load.
Move network documentation to Doxygen.
Remove lots of now-unused code.

This was SVN commit r7653.
This commit is contained in:
Ykkrosh 2010-06-30 21:41:04 +00:00
parent 303a3ff437
commit 1c0536bf08
85 changed files with 3107 additions and 6238 deletions

View File

@ -170,6 +170,8 @@
<color name="mustard">191 191 2</color>
<color name="brown">159 98 24</color>
<color name="transparent">0 0 0 0</color>
<!--
NOT YET CONVERTED NOT YET CONVERTED NOT YET CONVERTED NOT YET CONVERTED NOT YET CONVERTED NOT YET CONVERTED
NOT YET CONVERTED NOT YET CONVERTED NOT YET CONVERTED NOT YET CONVERTED NOT YET CONVERTED NOT YET CONVERTED

View File

@ -47,7 +47,7 @@
</sprite>
<sprite name="bkBorderBlack">
<image backcolor="black"
<image backcolor="black"
size="-1 -1 100%+1 0"
/>
<image backcolor="black"
@ -62,21 +62,19 @@
</sprite>
<sprite name="bkFillBlack">
<image backcolor="black"
size="0 0 100% 100%"
/>
<image backcolor="black"/>
</sprite>
<sprite name="bkFillWhite">
<image backcolor="white"
size="0 0 100% 100%"
/>
<image backcolor="white"/>
</sprite>
<sprite name="bkFillGray">
<image backcolor="gray"
size="0 0 100% 100%"
/>
<image backcolor="gray"/>
</sprite>
<sprite name="bkFillDarkBlue">
<image backcolor="63 127 210 255"/>
</sprite>
<!--
@ -145,13 +143,14 @@
/>
</sprite>
<sprite name="bkGraniteBorderBlack">
<image texture="global/tile/granite.dds"
size="0 0 100% 100%"
<sprite name="bkGraniteBorderBlack">
<image texture="global/tile/granite.dds"/>
<image
backcolor="transparent"
border="true"
bordercolor="black"
/>
</sprite>
</sprite>
<sprite name="textureSand">
<image texture="global/tile/sandstone.dds"
@ -481,6 +480,24 @@
/>
</sprite>
<sprite name="wheatIndentFillVeryLight">
<image backcolor="109 122 146"
size="0 0 100%-1 1"
/>
<image backcolor="220 223 228"
size="100%-1 0 100% 100%"
/>
<image backcolor="220 223 228"
size="0 100%-1 100% 100%"
/>
<image backcolor="109 122 146"
size="0 0 1 100%-1"
/>
<image backcolor="255 255 255 160"
size="1 1 100%-1 100%-1"
/>
</sprite>
<!--
==========================================
SCREENSHOT WATERMARK

View File

@ -135,6 +135,13 @@
textcolor="black"
/>
<style name="wheatInput"
sprite="wheatIndentFillVeryLight"
sprite_selectarea="bkFillDarkBlue"
textcolor="black"
textcolor_selected="white"
/>
<style name="wheatArrowLeft"
font="serif-14"
sprite="wheatCheckBoxOpen"
@ -207,8 +214,8 @@
sprite2_pressed="wheatArrowDnOver"
buffer_zone="10"
dropdown_size="100"
sprite_list="textureGranite"
dropdown_size="200"
sprite_list="bkGraniteBorderBlack"
sprite_selectarea="bkGrayBorderBlack"
textcolor_selected="white"

View File

@ -0,0 +1,259 @@
// Name displayed for unassigned player slots
const NAME_UNASSIGNED = "[color=\"90 90 90 255\"][unassigned]";
// Is this is a networked game, or offline
var g_IsNetworked;
// Is this user in control of game settings (i.e. is a network server, or offline player)
var g_IsController;
// Are we currently updating the GUI in response to network messages instead of user input
// (and therefore shouldn't send further messages to the network)
var g_IsInGuiUpdate;
// Default single-player player assignments
var g_PlayerAssignments = { "local": { "name": "You", "player": 1 } };
// Default game setup attributes
var g_GameAttributes = { "map": "Latium" };
// Number of players for currently selected map
var g_MaxPlayers = 8;
function init(attribs)
{
switch (attribs.type)
{
case "offline":
g_IsNetworked = false;
g_IsController = true;
break;
case "server":
g_IsNetworked = true;
g_IsController = true;
break;
case "client":
g_IsNetworked = true;
g_IsController = false;
break;
default:
error("Unexpected 'type' in gamesetup init: "+attribs.type);
}
// If we're a network client, disable all the map controls
// TODO: make them look visually disabled so it's obvious why they don't work
if (!g_IsController)
{
getGUIObjectByName("mapSelection").enabled = false;
for (var i = 0; i < g_MaxPlayers; ++i)
getGUIObjectByName("playerAssignment["+i+"]").enabled = false;
}
updatePlayerList();
}
function onTick()
{
if (g_IsNetworked)
{
while (true)
{
var message = Engine.PollNetworkClient();
if (!message)
break;
handleNetMessage(message);
}
}
}
function handleNetMessage(message)
{
warn("Net message: "+uneval(message));
switch (message.type)
{
case "gamesetup":
if (message.data) // (the host gets undefined data on first connect, so skip that)
g_GameAttributes = message.data;
onGameAttributesChange();
break;
case "players":
g_PlayerAssignments = message.hosts;
updatePlayerList();
break;
case "start":
Engine.PushGuiPage("page_loading.xml", { "attribs": g_GameAttributes });
break;
default:
error("Unrecognised net message type "+message.type);
}
}
// Initialise the list control containing all the available maps
function initMapNameList(object)
{
var mapPath = "maps/scenarios/"
// Get a list of map filenames
var mapArray = buildDirEntList(mapPath, "*.xml", false);
// Alphabetically sort the list, ignoring case
mapArray.sort(function (x, y) {
var lowerX = x.toLowerCase();
var lowerY = y.toLowerCase();
if (lowerX < lowerY) return -1;
else if (lowerX > lowerY) return 1;
else return 0;
});
// Remove the path and extension from each name, since we just want the filename
var mapNames = [ n.substring(mapPath.length, n.length-4) for each (n in mapArray) ];
// Select the default map
var selected = mapNames.indexOf(g_GameAttributes.map);
// Default to the first element if we can't find the one we searched for
if (selected == -1)
selected = 0;
// Update the list control
object.list = mapNames;
object.selected = selected;
}
// Called when the user selects a map from the list
function selectMap(name)
{
// Avoid recursion
if (g_IsInGuiUpdate)
return;
// Network clients can't change map
if (g_IsNetworked && !g_IsController)
return;
g_GameAttributes.map = name;
if (g_IsNetworked)
Engine.SetNetworkGameAttributes(g_GameAttributes);
else
onGameAttributesChange();
}
function onGameAttributesChange()
{
g_IsInGuiUpdate = true;
var mapName = g_GameAttributes.map;
var mapSelectionBox = getGUIObjectByName("mapSelection");
var mapIdx = mapSelectionBox.list.indexOf(mapName);
mapSelectionBox.selected = mapIdx;
getGUIObjectByName("mapInfoName").caption = mapName;
var description = "Sorry, no description available.";
// TODO: we ought to load map descriptions from the map itself, somehow.
// Just hardcode it now for testing.
if (mapName == "Latium")
{
description = "2 players. A fertile coastal region which was the birthplace of the Roman Empire. Plentiful natural resources let you build up a city and experiment with the game’s features in relative peace. Some more description could go here if you want as long as it’s not too long and still fits on the screen.";
}
getGUIObjectByName("mapInfoDescription").caption = description;
g_IsInGuiUpdate = false;
}
function launchGame()
{
if (g_IsNetworked && !g_IsController)
{
error("Only host can start game");
return;
}
if (g_IsNetworked)
{
Engine.SetNetworkGameAttributes(g_GameAttributes);
Engine.StartNetworkGame();
}
else
{
// Find the player ID which the user has been assigned to
var playerID = -1;
for (var i = 0; i < g_MaxPlayers; ++i)
{
var assignBox = getGUIObjectByName("playerAssignment["+i+"]");
if (assignBox.selected == 1)
playerID = i+1;
}
Engine.StartGame(g_GameAttributes, playerID);
Engine.PushGuiPage("page_loading.xml", { "attribs": g_GameAttributes });
}
}
function updatePlayerList()
{
g_IsInGuiUpdate = true;
var boxSpacing = 32;
var hostNameList = [NAME_UNASSIGNED];
var hostGuidList = [""];
var assignments = [];
for (var guid in g_PlayerAssignments)
{
var name = g_PlayerAssignments[guid].name;
var hostID = hostNameList.length;
hostNameList.push(name);
hostGuidList.push(guid);
assignments[g_PlayerAssignments[guid].player] = hostID;
}
for (var i = 0; i < g_MaxPlayers; ++i)
{
var box = getGUIObjectByName("playerBox["+i+"]");
var boxSize = box.size;
var h = boxSize.bottom - boxSize.top;
boxSize.top = i * boxSpacing;
boxSize.bottom = i * boxSpacing + h;
box.size = boxSize;
getGUIObjectByName("playerName["+i+"]").caption = "Player "+(i+1);
var assignBox = getGUIObjectByName("playerAssignment["+i+"]");
assignBox.list = hostNameList;
var selection = (assignments[i+1] || 0);
if (assignBox.selected != selection)
assignBox.selected = selection;
let playerID = i+1;
if (g_IsNetworked && g_IsController)
{
assignBox.onselectionchange = function ()
{
if (!g_IsInGuiUpdate)
Engine.AssignNetworkPlayer(playerID, hostGuidList[this.selected]);
};
}
else if (!g_IsNetworked)
{
assignBox.onselectionchange = function ()
{
if (!g_IsInGuiUpdate)
{
g_PlayerAssignments[hostGuidList[this.selected]].player = playerID;
updatePlayerList();
}
};
}
}
g_IsInGuiUpdate = false;
}

View File

@ -0,0 +1,74 @@
<?xml version="1.0" encoding="utf-8"?>
<objects>
<script file="gui/gamesetup/gamesetup.js"/>
<!-- Add a translucent black background to fade out the menu page -->
<object type="image" z="0" sprite="bkTranslucent"/>
<object type="image" style="wheatWindow" size="50%-400 50%-300 50%+400 50%+300">
<action on="Tick">
onTick();
</action>
<object style="wheatWindowTitleBar" type="text">Match setup</object>
<object type="button" style="wheatExit" tooltip_style="snToolTip">
<action on="Press"><![CDATA[
Engine.PopGuiPage();
]]></action>
</object>
<object size="0 0 250 100%">
<object name="mapSelection"
style="wheatList"
type="list"
size="0 0 100% 100%-200"
tooltip_style="onscreenToolTip"
tooltip="Select a map to play on.">
<action on="Load">initMapNameList(this);</action>
<action on="SelectionChange">selectMap(this.list[this.selected]);</action>
</object>
<object type="image" style="indentedPanel" size="0 100%-190 100% 100%">
<object name="mapInfoName" type="text" size="0 0 100% 30" font="serif-bold-18">[Map name]</object>
<object name="mapInfoDescription" type="text" size="0 24 100% 100%" font="serif-13">[Description]</object>
</object>
</object>
<object size="260 0 100% 100%-200" type="image" sprite="wheatIndentFillLight">
<repeat count="8">
<object name="playerBox[n]" size="0 0 100% 30">
<object name="playerName[n]" type="text" text_align="right" size="0 6 150 100%">[Player name]</object>
<object name="playerAssignment[n]" type="dropdown" style="wheatDropDown" size="150 2 300 100%"/>
</object>
</repeat>
</object>
<object name="onscreenToolTip"
type="text"
font="serif-14"
textcolor="white"
sprite="bkTranslucent"
hidden="true"
size="100%-300 100%-190 100% 100%-45"
>[Tooltip text]</object>
<object
type="button"
style="wheatButton"
font="serif-16"
size="100%-140 100%-40 100%+3 100%+3"
tooltip_style="onscreenToolTip"
tooltip="Click this button to start a new game with the current settings."
>Start game!
<action on="Press">launchGame();</action>
</object>
</object>
</objects>

View File

@ -0,0 +1,69 @@
var g_IsConnecting = false;
var g_GameType; // "server" or "client"
function init()
{
}
function startConnectionStatus(type)
{
g_GameType = type;
g_IsConnecting = true;
getGUIObjectByName("connectionStatus").caption = "Connecting to server...";
}
function onTick()
{
if (!g_IsConnecting)
return;
while (true)
{
var message = Engine.PollNetworkClient();
if (!message)
break;
warn("Net message: "+uneval(message));
switch (message.type)
{
case "netstatus":
switch (message.status)
{
case "connected":
getGUIObjectByName("connectionStatus").caption = "Registering with server...";
break;
case "authenticated":
Engine.PopGuiPage();
Engine.PushGuiPage("page_gamesetup.xml", { "type": g_GameType });
return; // don't process any more messages
default:
error("Unrecognised netstatus type "+message.status);
break;
}
break;
default:
error("Unrecognised net message type "+message.type);
break;
}
}
}
function switchSetupPage(oldpage, newpage)
{
getGUIObjectByName(oldpage).hidden = true;
getGUIObjectByName(newpage).hidden = false;
}
function startHost(playername, servername)
{
Engine.StartNetworkHost(playername);
startConnectionStatus("server");
// TODO: ought to do something(?) with servername
}
function startJoin(playername, ip)
{
Engine.StartNetworkJoin(playername, ip);
startConnectionStatus("client");
}

View File

@ -0,0 +1,156 @@
<?xml version="1.0" encoding="utf-8"?>
<objects>
<script file="gui/gamesetup/gamesetup_mp.js"/>
<!-- Add a translucent black background to fade out the menu page -->
<object type="image" z="0" sprite="bkTranslucent"/>
<object type="image" style="wheatWindow" size="50%-190 50%-120 50%+190 50%+120">
<action on="Tick">
onTick();
</action>
<object type="text" style="wheatWindowTitleBar">
Multiplayer
</object>
<object type="button" style="wheatExit">
<action on="Press"><![CDATA[
Engine.PopGuiPage();
]]></action>
</object>
<object name="pageSelectMode">
<object type="text" size="0 50%-60 100% 50%">
Please select whether you want to join a game or host your own game.
</object>
<object type="text" size="0 50%+22 200 50%+50" style="wheatTextHeadBlack">
Game Mode:
</object>
<object type="button" style="wheatButton" size="130 50%+20 230 50%+50">
Join Game
<action on="Press"><![CDATA[
switchSetupPage("pageSelectMode", "pageJoin");
]]></action>
</object>
<object type="button" style="wheatButton" size="250 50%+20 350 50%+50">
Host Game
<action on="Press"><![CDATA[
switchSetupPage("pageSelectMode", "pageHost");
]]></action>
</object>
</object>
<object name="pageHost" hidden="true">
<object type="text" size="0 0 400 30">
Setting up your server.
</object>
<object type="text" size="0 40 200 70" style="wheatTextHeadBlack">
Player name:
</object>
<object name="hostPlayerName" type="input" size="220 40 370 60" style="wheatInput">
<action on="Load"><![CDATA[
this.caption = Engine.GetDefaultPlayerName();
]]></action>
</object>
<object type="text" size="0 80 200 110" style="wheatTextHeadBlack">
Server name:
</object>
<object name="hostServerName" type="input" size="220 80 370 100" style="wheatInput">
<action on="Load"><![CDATA[
this.caption = Engine.GetDefaultPlayerName() + "'s game";
]]></action>
</object>
<object type="button" size="50%-125 200 50%-25 230" style="wheatButton">
Continue
<action on="Press"><![CDATA[
startHost(
getGUIObjectByName("hostPlayerName").caption,
getGUIObjectByName("hostServerName").caption);
switchSetupPage("pageHost", "pageConnecting");
]]></action>
</object>
<object type="button" style="wheatButton" size="50%+25 200 50%+125 230">
Back
<action on="Press"><![CDATA[
switchSetupPage("pageHost", "pageSelectMode");
]]></action>
</object>
</object>
<object name="pageJoin" hidden="true">
<object type="text" size="0 0 400 30">
Joining an existing game.
</object>
<object type="text" size="0 40 200 70" style="wheatTextHeadBlack">
Player name:
</object>
<object name="joinPlayerName" type="input" size="220 40 370 60" style="wheatInput">
<action on="Load"><![CDATA[
this.caption = Engine.GetDefaultPlayerName();
]]></action>
</object>
<object type="text" size="0 80 200 110" style="wheatTextHeadBlack">
Server Hostname or IP:
</object>
<object name="joinIP" type="input" size="220 80 370 100" style="wheatInput">
127.0.0.1
</object>
<object type="button" size="50%-125 200 50%-25 230" style="wheatButton">
Continue
<action on="Press"><![CDATA[
startJoin(
getGUIObjectByName("joinPlayerName").caption,
getGUIObjectByName("joinIP").caption);
switchSetupPage("pageJoin", "pageConnecting");
]]></action>
</object>
<object type="button" size="50%+25 200 50%+125 230" style="wheatButton">
Back
<action on="Press"><![CDATA[
switchSetupPage("pageJoin", "pageSelectMode");
]]></action>
</object>
</object>
<object name="pageConnecting" hidden="true">
<object name="connectionStatus" type="text" text_align="center" size="0 100 100% 120">
[Connection status]
</object>
<object type="button" size="100%-110 200 100%-10 230" style="wheatButton">
Cancel
<action on="Press"><![CDATA[
error("not implemented yet");
]]></action>
</object>
</object>
</object>
</objects>

View File

@ -0,0 +1,10 @@
<?xml version="1.0" encoding="utf-8"?>
<setup>
<tooltip
name="onscreenToolTip"
use_object="onscreenToolTip"
delay="0"
hide_object="true"
/>
</setup>

View File

@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<sprites>
</sprites>

View File

@ -0,0 +1,15 @@
<?xml version="1.0" encoding="utf-8"?>
<styles>
<style name="indentedPanel"
buffer_zone="5"
font="serif-14"
sprite="wheatIndentFillLight"
textcolor="black"
textcolor_selected="white"
text_align="left"
text_valign="center"
/>
</styles>

View File

@ -1,203 +1,203 @@
<!--
Types
-->
<!ENTITY % bool "(true|false)">
<!ENTITY % align "(left|center|right)">
<!ENTITY % valign "(top|center|bottom)">
<!--
Defines
-->
<!ENTITY % unique_settings
"name CDATA #IMPLIED
type CDATA 'empty'
style CDATA #IMPLIED"
>
<!ENTITY % base_settings
"absolute %bool; #IMPLIED
enabled %bool; #IMPLIED
ghost %bool; #IMPLIED
hidden %bool; #IMPLIED
size CDATA #IMPLIED
z CDATA #IMPLIED"
>
<!-- Defaults are not put in here, because it ruins the concept of styles -->
<!ENTITY % ex_settings
"buffer_zone CDATA #IMPLIED
button_width CDATA #IMPLIED
checked %bool; #IMPLIED
dropdown_size CDATA #IMPLIED
dropdown_buffer CDATA #IMPLIED
font CDATA #IMPLIED
fov_wedge_color CDATA #IMPLIED
hotkey CDATA #IMPLIED
cell_id CDATA #IMPLIED
input_initvalue_destroyed_at_focus %bool; #IMPLIED
max_length CDATA #IMPLIED
multiline %bool; #IMPLIED
rectcolor_selected CDATA #IMPLIED
scrollbar %bool; #IMPLIED
scrollbar_style CDATA #IMPLIED
sprite CDATA #IMPLIED
sprite2 CDATA #IMPLIED
sprite_bar CDATA #IMPLIED
sprite_background CDATA #IMPLIED
sprite_disabled CDATA #IMPLIED
sprite_list CDATA #IMPLIED
sprite2_disabled CDATA #IMPLIED
sprite_over CDATA #IMPLIED
sprite2_over CDATA #IMPLIED
sprite_pressed CDATA #IMPLIED
sprite2_pressed CDATA #IMPLIED
sprite_selectarea CDATA #IMPLIED
square_side CDATA #IMPLIED
textcolor CDATA #IMPLIED
textcolor_disabled CDATA #IMPLIED
textcolor_over CDATA #IMPLIED
textcolor_pressed CDATA #IMPLIED
textcolor_selected CDATA #IMPLIED
text_align %align; #IMPLIED
text_valign %valign; #IMPLIED
tooltip CDATA #IMPLIED
tooltip_style CDATA #IMPLIED"
>
<!--
<objects>
-->
<!ELEMENT objects (script|object)*>
<!ELEMENT script (#PCDATA)>
<!ATTLIST script
file CDATA #IMPLIED
>
<!ELEMENT object (#PCDATA|object|action|item)*> <!-- 'item' is used by "list" and "dropdown" -->
<!ATTLIST object
%unique_settings;
%base_settings;
%ex_settings;
>
<!ELEMENT action (#PCDATA)>
<!ATTLIST action
on CDATA #REQUIRED
file CDATA #IMPLIED
>
<!ELEMENT item (#PCDATA)>
<!ATTLIST item
enabled %bool; #IMPLIED
>
<!--
<styles>
-->
<!ELEMENT styles (style*)>
<!ELEMENT style EMPTY>
<!ATTLIST style
name CDATA #REQUIRED
%base_settings;
%ex_settings;
>
<!--
<setup>
-->
<!ELEMENT setup (icon|scrollbar|tooltip|color)*>
<!ELEMENT scrollbar EMPTY>
<!ELEMENT icon EMPTY>
<!ELEMENT tooltip EMPTY>
<!ELEMENT color (#PCDATA)>
<!--
<scrollbar>
-->
<!ATTLIST scrollbar
name CDATA #REQUIRED
width CDATA #IMPLIED
scroll_wheel %bool; #IMPLIED
alwaysshown %bool; #IMPLIED
scroll_speed CDATA #IMPLIED
sprite_button_top CDATA #IMPLIED
sprite_button_top_pressed CDATA #IMPLIED
sprite_button_top_disabled CDATA #IMPLIED
sprite_button_top_over CDATA #IMPLIED
sprite_button_bottom CDATA #IMPLIED
sprite_button_bottom_pressed CDATA #IMPLIED
sprite_button_bottom_disabled CDATA #IMPLIED
sprite_button_bottom_over CDATA #IMPLIED
sprite_bar_vertical CDATA #IMPLIED
sprite_bar_vertical_over CDATA #IMPLIED
sprite_bar_vertical_pressed CDATA #IMPLIED
sprite_back_vertical CDATA #IMPLIED
minimum_bar_size CDATA #IMPLIED
>
<!--
<icon>
-->
<!ATTLIST icon
name CDATA #REQUIRED
size CDATA #REQUIRED
sprite CDATA #REQUIRED
cell_id CDATA #IMPLIED
>
<!--
<tooltip>
-->
<!ATTLIST tooltip
name CDATA #REQUIRED
sprite CDATA #IMPLIED
anchor CDATA #IMPLIED
buffer_zone CDATA #IMPLIED
font CDATA #IMPLIED
maxwidth CDATA #IMPLIED
offset CDATA #IMPLIED
textcolor CDATA #IMPLIED
delay CDATA #IMPLIED
use_object CDATA #IMPLIED
hide_object CDATA #IMPLIED
>
<!--
<color>
-->
<!ATTLIST color
name CDATA #REQUIRED
>
<!--
<sprites>
-->
<!ELEMENT sprites (sprite*)>
<!ELEMENT sprite (effect?,image+)>
<!ELEMENT image (effect?)>
<!ELEMENT effect EMPTY>
<!ATTLIST sprite
name CDATA #REQUIRED
>
<!ATTLIST image
texture CDATA #IMPLIED
size CDATA #IMPLIED
texture_size CDATA #IMPLIED
real_texture_placement CDATA #IMPLIED
cell_size CDATA #IMPLIED
backcolor CDATA #IMPLIED
bordercolor CDATA #IMPLIED
border %bool; #IMPLIED
z_level CDATA #IMPLIED
>
<!ATTLIST effect
add_color CDATA #IMPLIED
multiply_color CDATA #IMPLIED
grayscale CDATA #IMPLIED
>
<!--
Types
-->
<!ENTITY % bool "(true|false)">
<!ENTITY % align "(left|center|right)">
<!ENTITY % valign "(top|center|bottom)">
<!--
Defines
-->
<!ENTITY % unique_settings
"name CDATA #IMPLIED
type CDATA 'empty'
style CDATA #IMPLIED"
>
<!ENTITY % base_settings
"absolute %bool; #IMPLIED
enabled %bool; #IMPLIED
ghost %bool; #IMPLIED
hidden %bool; #IMPLIED
size CDATA #IMPLIED
z CDATA #IMPLIED"
>
<!-- Defaults are not put in here, because it ruins the concept of styles -->
<!ENTITY % ex_settings
"buffer_zone CDATA #IMPLIED
button_width CDATA #IMPLIED
checked %bool; #IMPLIED
dropdown_size CDATA #IMPLIED
dropdown_buffer CDATA #IMPLIED
font CDATA #IMPLIED
fov_wedge_color CDATA #IMPLIED
hotkey CDATA #IMPLIED
cell_id CDATA #IMPLIED
input_initvalue_destroyed_at_focus %bool; #IMPLIED
max_length CDATA #IMPLIED
multiline %bool; #IMPLIED
rectcolor_selected CDATA #IMPLIED
scrollbar %bool; #IMPLIED
scrollbar_style CDATA #IMPLIED
sprite CDATA #IMPLIED
sprite2 CDATA #IMPLIED
sprite_bar CDATA #IMPLIED
sprite_background CDATA #IMPLIED
sprite_disabled CDATA #IMPLIED
sprite_list CDATA #IMPLIED
sprite2_disabled CDATA #IMPLIED
sprite_over CDATA #IMPLIED
sprite2_over CDATA #IMPLIED
sprite_pressed CDATA #IMPLIED
sprite2_pressed CDATA #IMPLIED
sprite_selectarea CDATA #IMPLIED
square_side CDATA #IMPLIED
textcolor CDATA #IMPLIED
textcolor_disabled CDATA #IMPLIED
textcolor_over CDATA #IMPLIED
textcolor_pressed CDATA #IMPLIED
textcolor_selected CDATA #IMPLIED
text_align %align; #IMPLIED
text_valign %valign; #IMPLIED
tooltip CDATA #IMPLIED
tooltip_style CDATA #IMPLIED"
>
<!--
<objects>
-->
<!ELEMENT objects (script|object)*>
<!ELEMENT script (#PCDATA)>
<!ATTLIST script
file CDATA #IMPLIED
>
<!ELEMENT object (#PCDATA|object|action|item)*> <!-- 'item' is used by "list" and "dropdown" -->
<!ATTLIST object
%unique_settings;
%base_settings;
%ex_settings;
>
<!ELEMENT action (#PCDATA)>
<!ATTLIST action
on CDATA #REQUIRED
file CDATA #IMPLIED
>
<!ELEMENT item (#PCDATA)>
<!ATTLIST item
enabled %bool; #IMPLIED
>
<!--
<styles>
-->
<!ELEMENT styles (style*)>
<!ELEMENT style EMPTY>
<!ATTLIST style
name CDATA #REQUIRED
%base_settings;
%ex_settings;
>
<!--
<setup>
-->
<!ELEMENT setup (icon|scrollbar|tooltip|color)*>
<!ELEMENT scrollbar EMPTY>
<!ELEMENT icon EMPTY>
<!ELEMENT tooltip EMPTY>
<!ELEMENT color (#PCDATA)>
<!--
<scrollbar>
-->
<!ATTLIST scrollbar
name CDATA #REQUIRED
width CDATA #IMPLIED
scroll_wheel %bool; #IMPLIED
alwaysshown %bool; #IMPLIED
scroll_speed CDATA #IMPLIED
sprite_button_top CDATA #IMPLIED
sprite_button_top_pressed CDATA #IMPLIED
sprite_button_top_disabled CDATA #IMPLIED
sprite_button_top_over CDATA #IMPLIED
sprite_button_bottom CDATA #IMPLIED
sprite_button_bottom_pressed CDATA #IMPLIED
sprite_button_bottom_disabled CDATA #IMPLIED
sprite_button_bottom_over CDATA #IMPLIED
sprite_bar_vertical CDATA #IMPLIED
sprite_bar_vertical_over CDATA #IMPLIED
sprite_bar_vertical_pressed CDATA #IMPLIED
sprite_back_vertical CDATA #IMPLIED
minimum_bar_size CDATA #IMPLIED
>
<!--
<icon>
-->
<!ATTLIST icon
name CDATA #REQUIRED
size CDATA #REQUIRED
sprite CDATA #REQUIRED
cell_id CDATA #IMPLIED
>
<!--
<tooltip>
-->
<!ATTLIST tooltip
name CDATA #REQUIRED
sprite CDATA #IMPLIED
anchor CDATA #IMPLIED
buffer_zone CDATA #IMPLIED
font CDATA #IMPLIED
maxwidth CDATA #IMPLIED
offset CDATA #IMPLIED
textcolor CDATA #IMPLIED
delay CDATA #IMPLIED
use_object CDATA #IMPLIED
hide_object CDATA #IMPLIED
>
<!--
<color>
-->
<!ATTLIST color
name CDATA #REQUIRED
>
<!--
<sprites>
-->
<!ELEMENT sprites (sprite*)>
<!ELEMENT sprite (effect?,image+)>
<!ELEMENT image (effect?)>
<!ELEMENT effect EMPTY>
<!ATTLIST sprite
name CDATA #REQUIRED
>
<!ATTLIST image
texture CDATA #IMPLIED
size CDATA #IMPLIED
texture_size CDATA #IMPLIED
real_texture_placement CDATA #IMPLIED
cell_size CDATA #IMPLIED
backcolor CDATA #IMPLIED
bordercolor CDATA #IMPLIED
border %bool; #IMPLIED
z_level CDATA #IMPLIED
>
<!ATTLIST effect
add_color CDATA #IMPLIED
multiply_color CDATA #IMPLIED
grayscale CDATA #IMPLIED
>

View File

@ -1,8 +1,11 @@
function init()
function init(data)
{
var mapName = "map";
if (data && data.attribs)
mapName = data.attribs.map;
// Set to "hourglass" cursor.
setCursor("cursor-wait");
console.write ("Loading " + g_GameAttributes.mapFile + " (" + g_GameAttributes.numPlayers + " players) ...");
// Choose random concept art for loading screen background (should depend on the selected civ later when this is specified).
var sprite = "";
@ -19,10 +22,10 @@ function init()
// janwas: main loop now sets progress / description, but that won't
// happen until the first timeslice completes, so set initial values.
getGUIObjectByName ("ldTitleBar").caption = "Loading Scenario ...";
getGUIObjectByName ("ldTitleBar").caption = "Loading Scenario";
getGUIObjectByName ("ldProgressBarText").caption = "";
getGUIObjectByName ("ldProgressBar").caption = 0;
getGUIObjectByName ("ldText").caption = "LOADING " + g_GameAttributes.mapFile + " ...\nPlease wait ...";
getGUIObjectByName ("ldText").caption = "Loading " + mapName + "\nPlease wait...";
// Pick a random tip of the day (each line is a separate tip).
var tipArray = readFileLines("gui/text/tips.txt");
@ -39,13 +42,10 @@ function reallyStartGame()
curr_music.fade(-1, 0.0, 5.0); // fade to 0 over 5 seconds
// This is a reserved function name that is executed by the engine when it is ready
// to start the game (ie loading progress has reached 100%).
// to start the game (i.e. loading progress has reached 100%).
// Switch GUI from loading screen to game session.
if (Engine.IsNewSimulation())
Engine.SwitchGuiPage("page_session_new.xml");
else
Engine.SwitchGuiPage("page_session.xml");
Engine.SwitchGuiPage("page_session_new.xml");
// Restore default cursor.
setCursor ("arrow-default");

View File

@ -0,0 +1,10 @@
<?xml version="1.0" encoding="utf-8"?>
<page>
<include>common/setup.xml</include>
<include>common/styles.xml</include>
<include>common/sprite1.xml</include>
<include>gamesetup/setup.xml</include>
<include>gamesetup/sprites.xml</include>
<include>gamesetup/styles.xml</include>
<include>gamesetup/gamesetup.xml</include>
</page>

View File

@ -0,0 +1,10 @@
<?xml version="1.0" encoding="utf-8"?>
<page>
<include>common/setup.xml</include>
<include>common/styles.xml</include>
<include>common/sprite1.xml</include>
<include>gamesetup/setup.xml</include>
<include>gamesetup/sprites.xml</include>
<include>gamesetup/styles.xml</include>
<include>gamesetup/gamesetup_mp.xml</include>
</page>

View File

@ -130,8 +130,7 @@ Watch for updates or get involved in the development: http://wildfiregames.com/0
>
<action on="Press"><![CDATA[
// Open Session Setup window.
sessionType = "Skirmish"
openSessionSetup ("");
Engine.PushGuiPage("page_gamesetup.xml", { type: "offline" });
]]></action>
</object>
@ -145,7 +144,7 @@ Watch for updates or get involved in the development: http://wildfiregames.com/0
>
<action on="Press"><![CDATA[
// Open Multiplayer connection window.
openMainMenuSubWindow ("pgMPModeSel");
Engine.PushGuiPage("page_gamesetup_mp.xml");
]]></action>
</object>
@ -350,834 +349,6 @@ Watch for updates or get involved in the development: http://wildfiregames.com/0
</object>
<!--
===============================================
- GAME SESSION SETUP SCREEN -
===============================================
-->
<!-- main object; the size of this equals to fullscreen at lowest resolution (800x600) -->
<object name="pgSessionSetup"
style="wheatWindow"
type="image"
hidden="true"
size="50%-400 50%-300 50%+400 50%+300"
>
<object name="pgSessionSetupTitleBar"
style="wheatWindowTitleBar"
type="button"
>
</object>
<object name="pgSessionSetupExitButton"
style="wheatExit"
type="button"
tooltip_style="snToolTip"
>
<action on="Press"><![CDATA[
closeSessionSetup();
]]></action>
</object>
<object name="pgSessionSetupTitle"
style="wheatTextHeadMaxBlack"
type="text"
size="10 0 150 28"
>Game Setup
</object>
<!--
==========================================
- MULTIPLAYER OBJECTS
- Not visible to Skirmish.
==========================================
-->
<object name="pgSessionSetupMP"
hidden="true"
>
<!--
==========================================
- HOST OBJECTS
- Not visible to Skirmish or Client.
==========================================
-->
<object name="pgSessionSetupMPHost"
hidden="true"
>
<!-- controls for lag bars and player kicking -->
<object name="pgSessionSetupMPKickP1"
style="wheatButton"
type="button"
size="500 120 520 140"
>K
<action on="Press"><![CDATA[
// Kick player here
]]></action>
</object>
</object>
<!--
==========================================
- CLIENT OBJECTS
- Not visible to Skirmish or Host.
==========================================
-->
<object name="pgSessionSetupMPClient"
hidden="true"
>
</object>
</object>
<!--
==========================================
- HOST/SKIRMISH OBJECTS
- Not visible to Client.
==========================================
-->
<object name="pgSessionSetupMaster"
hidden="true"
>
</object>
<!--
==========================================
- SKIRMISH OBJECTS
- Not visible to Host or Client.
==========================================
-->
<object name="pgSessionSetupSkirmish"
hidden="true"
>
</object>
<!--
If objects are only supposed to be visible in multiplayer, or are only available to hosts,
please put them in the containers above.
-->
<!--
==========================================
- CIV SELECTION CONTROLS
==========================================
-->
<object name="pgSessionSetupGrpCivSel"
style="wheatBorderBlack"
type="image"
size="10 30 33% 50%-25"
>
<object name="pgSessionSetupCivSelTxt"
type="text"
size="5 5 100%-5 25"
>Your Civilization:
</object>
<object name="pgSessionSetupCivSel"
style="wheatBorderBlack"
type="button"
size="30 35 100%-30 100%-45"
sprite="snEmblemHele"
tooltip_style="pgSessionSetupToolTip"
tooltip="Click here to change the civilization you would like to play."
/>
<object name="pgSessionSetupCivSelName"
style="wheatTextHeadMaxBlack"
type="text"
size="30 100%-35 100%-30 100%-5"
>Hellenes (i)
</object>
</object>
<!--
==========================================
- MAP CONTROLS
==========================================
-->
<object name="pgSessionSetupGrpMap"
style="wheatBorderBlack"
type="image"
size="10 50%-20 33% 100%-10"
>
<!-- MAP SELECTION -->
<object name="pgSessionSetupMapTxt"
type="text"
size="5 5 100%-5 25"
>Map:
</object>
<object name="pgSessionSetupMapTypeTxt"
type="text"
size="5 30 100%-5 55"
>Type:
</object>
<object name="pgSessionSetupMapType"
style="wheatDropDown"
type="dropdown"
size="50 25 100%-5 55"
>
<action on="Load"><![CDATA[
pushItem (this.name, "Random Map");
pushItem (this.name, "Real World Map");
setCurrItemValue (this.name, "Random Map");
]]></action>
</object>
<object name="pgSessionSetupMapNameTxt"
type="text"
size="5 65 100%-5 90"
>Name:
</object>
<object name="pgSessionSetupMapName"
style="wheatDropDown"
type="dropdown"
size="50 60 100%-5 90"
>
<action on="Load"><![CDATA[
// Note: We should set this in a .cfg or initialisation area, as it's used again later.
mapPath = "maps/scenarios/"
// Get a list of map names.
mapArray = buildDirEntList (mapPath, "*.xml", false);
// Alphabetically sort the list, ignoring case.
mapArray.sort(function (x, y) {
var lowerX = x.toLowerCase();
var lowerY = y.toLowerCase();
if(lowerX < lowerY) return -1;
else if(lowerX > lowerY) return 1;
else return 0;
});
for (mapIndex = 0; mapIndex < mapArray.length; mapIndex++)
{
// Remove the path and extension from each name, since we just want the filename.
// (Is there an equivalent "basename" function for that in JS?)
mapArray[mapIndex] = mapArray[mapIndex].substring (mapPath.length, mapArray[mapIndex].length-4)
// Add map name to the end of the object's list.
pushItem (this.name, mapArray[mapIndex]);
}
// Set default value (pick whatever's at the top of the list).
// getGUIObjectByName(this.name).selected = 0;
// No, on second thought, set it to the randomly generated map until we can remember the last choice.
setCurrItemValue (this.name, "Latium");
]]></action>
</object>
<object name="pgSessionSetupFoWTxt"
type="text"
size="5 100 110 120"
>Fog of War:
</object>
<object name="pgSessionSetupFoW"
style="wheatCrossBox"
type="checkbox"
size="120 100 140 120"
checked="true"
/>
<!--
- LINE OF SIGHT
-->
<object name="pgSessionSetupLosLabel"
type="text"
size="5 130 110 155"
>LOS:
</object>
<object name="pgSessionSetupLosSetting"
style="wheatDropDown"
type="dropdown"
size="115 125 100%-5 155"
>
<item>Normal</item>
<item>Explored</item>
<item>All Visible</item>
<action on="Load"><![CDATA[
getGUIObjectByName("pgSessionSetupLosSetting").selected = 0;
]]></action>
</object>
<!-- <object name="pgSessionSetupMapRevealTxt"
type="text"
size="5 135 110 155"
>Reveal Map:
</object>
<object name="pgSessionSetupMapRevealCheck"
style="wheatCrossBox"
type="checkbox"
size="120 135 140 155"
/>-->
<object name="pgSessionSetupMapSizeTxt"
type="text"
size="5 165 110 185"
>Map Size:
</object>
<object name="pgSessionSetupMapSize"
style="wheatDropDown"
type="dropdown"
size="115 160 100%-5 190"
>
<action on="Load"><![CDATA[
pushItem (this.name, "Tiny");
pushItem (this.name, "Small");
pushItem (this.name, "Medium");
pushItem (this.name, "Large");
pushItem (this.name, "Very Large");
setCurrItemValue (this.name, "Large");
]]></action>
</object>
<object name="pgSessionSetupMapLine1"
style="wheatBorderBlack"
type="image"
size="5 200 100%-5 200"
/>
<object name="pgSessionSetupMapInfo"
style="wheatBorderBlack"
type="text"
font="serif-12"
size="5 205 100%-5 100%-30"
>
[font="serif-bold-12"]Map Info[/font]
Developed by, well, WFG.
A large landmass with rivers, forests and coastal fishing grounds.
</object>
<object name="pgSessionSetupMoreMaps"
style="wheatButton"
type="button"
size="100%-135 100%-30 100%-5 100%-5"
enabled="false"
>
Get more maps
</object>
</object>
<object name="pgSessionSetupTempSq3"
style="wheatBorderBlack"
type="image"
size="33%+10 45 100%-10 50%+20"
>
<!--
==========================================
- MAIN MENU - PLAYER SLOTS
==========================================
-->
<object name="pgSessionSetupPlayerHead"
type="text"
size="5 5 100%-5 25"
>Other Players:
</object>
<object name="pgSessionSetupP1"
style="wheatBorderBlack"
type="button"
enabled="false"
size="5 30 205 60"
text_align="left"
/>
<object name="pgSessionSetupP2"
style="wheatDropDown"
type="dropdown"
size="5 65 205 100"
/>
<object name="pgSessionSetupP3"
style="wheatDropDown"
type="dropdown"
size="5 105 205 140"
/>
<object name="pgSessionSetupP4"
style="wheatDropDown"
type="dropdown"
size="5 145 205 180"
/>
<object name="pgSessionSetupP5"
style="wheatDropDown"
type="dropdown"
size="5 185 205 220"
/>
<object name="pgSessionSetupP6"
style="wheatDropDown"
type="dropdown"
size="5 225 205 260"
/>
<!-- Players 7 & 8 disabled for now due to spacing issues -->
<object name="pgSessionSetupP7"
enabled="false"
hidden="true"
style="wheatDropDown"
type="dropdown"
size="5 265 205 300"
/>
<object name="pgSessionSetupP8"
enabled="false"
hidden="true"
style="wheatDropDown"
type="dropdown"
size="5 305 205 340"
/>
</object>
<!--
==========================================
- GAME SETTINGS
==========================================
-->
<object name="pgSessionSetupGrpGameSettings"
style="wheatBorderBlack"
type="image"
size="33%+10 50%+30 67%-5 100%-10"
>
<object name="pgSessionSetupGameModeTxt"
type="text"
size="5 10 100 35"
>Game Mode:
</object>
<object name="pgSessionSetupGameMode"
style="wheatDropDown"
type="dropdown"
size="110 5 100%-10 35"
>
<action on="Load"><![CDATA[
pushItem (this.name, "Conquest");
pushItem (this.name, "Score");
setCurrItemValue (this.name, "Conquest");
]]></action>
</object>
<object name="pgSessionSetupStartPhaseTxt"
type="text"
size="5 45 110 70"
>Starting Phase:
</object>
<object name="pgSessionSetupStartPhase"
style="wheatDropDown"
type="dropdown"
size="110 40 100%-10 70"
>
<action on="Load"><![CDATA[
pushItem (this.name, "Village");
pushItem (this.name, "Town");
pushItem (this.name, "City");
setCurrItemValue (this.name, "Village");
]]></action>
</object>
<object name="pgSessionSetupResourcesTxt"
type="text"
size="5 80 110 105"
>Resources:
</object>
<object name="pgSessionSetupResources"
style="wheatDropDown"
type="dropdown"
size="110 75 100%-10 105"
>
<action on="Load"><![CDATA[
pushItem (this.name, "Low");
pushItem (this.name, "Normal");
pushItem (this.name, "High");
setCurrItemValue (this.name, "High");
]]></action>
</object>
<object name="pgSessionSetupSettingsLine1"
style="wheatBorderBlack"
type="image"
size="5 115 100%-5 115"
/>
</object>
<!--
==========================================
- HELP AND INFO OBJECTS
==========================================
-->
<object name="pgSessionSetupGrpHelpInfo"
style="wheatBorderBlack"
type="image"
size="67%+5 50%+30 100%-10 100%-80"
>
<object name="pgSessionSetupInfoTxt"
type="text"
size="10 10 100%-10 50%-10"
>This is info text.
</object>
<object name="pgSessionSetupToolTip"
style="fancyTextHeadWhite"
type="text"
hidden="true"
sprite="bkTranslucent"
size="10 50%-5 100%-10 100%-10"
>tooltipText
</object>
</object>
<!--
==========================================
- SCREENSHOT MODE CHECKBOX
==========================================
-->
<object name="pgSessionSetupScreenshotModeTxt"
type="text"
size="83%-125 100%-65 83%+10 100%-45"
>Screenshot Mode:
</object>
<object name="pgSessionSetupScreenshotMode"
style="wheatCrossBox"
type="checkbox"
size="83%+10 100%-65 83%+30 100%-45"
checked="false"
>
<action on="Load">this.checked = Engine.IsNewSimulation()</action>
</object>
<!--
==========================================
- GAME SETUP - BUTTONS
==========================================
-->
<object name="pgSessionSetupStartButton"
style="wheatButton"
type="button"
size="83%-125 100%-40 83%-25 100%-10"
tooltip_style="pgSessionSetupToolTip"
tooltip="Click this button to start a new game with the current settings."
>Start!
<action on="Press"><![CDATA[
launchGame();
]]></action>
</object>
<object name="pgSessionSetupCancelButton"
style="wheatButton"
type="button"
size="83%+25 100%-40 83%+125 100%-10"
tooltip_style="pgSessionSetupToolTip"
tooltip="Use this button if you are a coward and you don't dare to face the challenge of this game."
>Cancel
<action on="Press"><![CDATA[
closeSessionSetup();
]]></action>
</object>
</object>
<!--
===============================================
- MULTIPLAYER MODE SELECTION SCREEN -
===============================================
-->
<object name="pgMPModeSel"
style="wheatWindow"
type="text"
hidden="true"
size="50%-200 50%-90 50%+200 50%+90"
>Please select whether you want to join a game or host your own game.
<object name="pgMPModeSelTitleBar"
style="wheatWindowTitleBar"
type="button"
>Multiplayer
</object>
<object name="pgMPModeSelExitButton"
style="wheatExit"
type="button"
>
<action on="Press"><![CDATA[
closeMainMenuSubWindow ("pgMPModeSel");
]]></action>
</object>
<!--
==========================================
- MAIN MENU - HOST / JOIN CONTROLS
==========================================
-->
<object name="pgMPModeSelHostJoinTxt"
style="wheatTextHeadBlack"
type="text"
size="0 100%-70 200 199%-40"
>Game Mode
</object>
<object name="pgMPModeSelJoinButton"
style="wheatButton"
type="button"
size="130 100%-70 230 100%-40"
>Join Game
<action on="Press"><![CDATA[
// rudimentary client connection code; finally working now :)
guiSwitch ("pgMPModeSel", "pgMPJoin");
]]></action>
</object>
<!-- (need code for the game start procedure here) -->
<object name="pgMPModeSelHostButton"
style="wheatButton"
type="button"
size="250 100%-70 350 100%-40"
>Host Game
<action on="Press"><![CDATA[
guiSwitch ("pgMPModeSel", "pgMPHost");
]]></action>
</object>
</object>
<!--
===============================================
- MULTIPLAYER HOST GAME SCREEN -
===============================================
-->
<object name="pgMPHost"
style="wheatWindow"
type="image"
hidden="true"
size="50%-190 50%-120 50%+190 50%+120"
>
<object name="pgMPHostTitleBar"
style="wheatWindowTitleBar"
type="button"
>Host a game
</object>
<object name="pgMPHostExitButton"
style="wheatExit"
type="button"
>
<action on="Press"><![CDATA[
closeMainMenuSubWindow ("pgMPHost");
]]></action>
</object>
<!--
==========================================
- MAIN MENU - SERVER/GAME NAME CONTROLS
==========================================
-->
<object name="pgMPHostTxt1"
type="text"
size="0 0 400 30"
>Setting up your server.
</object>
<object name="pgMPHostGameNameTxt"
style="wheatTextHeadBlack"
type="text"
size="0 40 200 60"
>Game (Server) Name:
</object>
<object name="pgMPHostGameName"
style="wheatBorderBlack"
type="input"
size="220 40 370 60"
>My Game
</object>
<!--
==========================================
- MAIN MENU - WELCOME MESSAGE CONTROLS
==========================================
-->
<object name="pgMPHostWelcomeMsgTxt"
style="wheatTextHeadBlack"
type="text"
size="0 70 200 90"
>Welcome Message:
</object>
<object name="pgMPHostTxt2"
type="text"
size="0 100 370 180"
>You can enter some message which is displayed to new players upon joining here.
</object>
<object name="pgMPHostWelcomeMsg"
style="wheatBorderBlackNote"
type="input"
size="5 150 370 180"
>Uhm, welcome to my game. Have fun!
</object>
<!--
==========================================
- MAIN MENU - CONTINUE / BACK CONTROLS
==========================================
-->
<object name="pgMPHostContinueButton"
style="wheatButton"
type="button"
size="50%-125 200 50%-25 230"
>Continue
<action on="Press"><![CDATA[
initMPHost ("pgMPHost",
getGUIObjectByName ("pgMPHostGameName").caption,
getGUIObjectByName ("pgMPHostWelcomeMsg").caption);
]]></action>
</object>
<object name="pgMPHostBackButton"
style="wheatButton"
type="button"
size="50%+25 200 50%+125 230"
>Back
<action on="Press"><![CDATA[
guiSwitch ("pgMPHost", "pgMPModeSel");
]]></action>
</object>
</object>
<!--
===============================================
- MULTIPLAYER JOIN GAME SCREEN -
===============================================
-->
<object name="pgMPJoin"
style="wheatWindow"
type="image"
hidden="true"
size="50%-190 50%-120 50%+190 50%+120"
>
<object name="pgMPJoinTitleBar"
style="wheatWindowTitleBar"
type="button"
>Join a game
</object>
<object name="pgMPJoinExitButton"
style="wheatExit"
type="button"
>
<action on="Press"><![CDATA[
closeMainMenuSubWindow ("pgMPJoin");
]]></action>
</object>
<!--
==========================================
- MAIN MENU - SERVER NAME / IP CONTROLS
==========================================
-->
<object name="pgMPJoinTxt1"
type="text"
size="0 0 400 30"
>Joining an existing game.
</object>
<object name="pgMPJoinServerIPTxt"
style="wheatTextHeadBlack"
type="text"
size="0 40 200 60"
>Server Hostname or IP:
</object>
<object name="pgMPJoinServerIP"
style="wheatBorderBlack"
type="input"
size="220 40 370 60"
>127.0.0.1
</object>
<!--
==========================================
- MAIN MENU - CONTINUE / BACK CONTROLS
==========================================
-->
<object name="pgMPJoinSetupButton"
style="wheatButton"
type="button"
size="50%-125 200 50%-25 230"
>Continue
<action on="Press"><![CDATA[
initMPClient ("pgMPJoin",
getGUIObjectByName ("pgMPJoinServerIP").caption);
]]></action>
</object>
<object name="pgMPJoinBackButton"
style="wheatButton"
type="button"
size="50%+25 200 50%+125 230"
>Back
<action on="Press"><![CDATA[
guiSwitch ("pgMPJoin", "pgMPModeSel");
]]></action>
</object>
</object>
<!--
===============================================
- OPTIONS SCREEN -

View File

@ -31,6 +31,24 @@ function getHotloadData()
return { selection: g_Selection.selected };
}
function changeNetStatus(status)
{
var obj = getGUIObjectByName("netStatus");
switch (status)
{
case "normal":
obj.caption = "";
obj.hidden = true;
break;
case "waiting_for_connect":
obj.caption = "Waiting for other players to connect";
obj.hidden = false;
break;
default:
error("Unexpected net status "+status);
}
}
function onTick()
{
g_DevSettings.controlAll = getGUIObjectByName("devControlAll").checked;
@ -92,6 +110,9 @@ function updatePlayerDisplay(simState)
{
var playerState = simState.players[Engine.GetPlayerID()];
if (!playerState)
return;
getGUIObjectByName("resourceFood").caption = playerState.resourceCounts.food;
getGUIObjectByName("resourceWood").caption = playerState.resourceCounts.wood;
getGUIObjectByName("resourceStone").caption = playerState.resourceCounts.stone;

View File

@ -466,4 +466,6 @@
<object name="bandbox" type="image" sprite="bandbox" ghost="true" hidden="true" z="100"
/>
<object name="netStatus" type="text" style="netStatus" z="100" hidden="true"/>
</objects>

View File

@ -138,4 +138,8 @@
<image backcolor="0 0 0 85"/>
</sprite>
<sprite name="netStatusBackground">
<image backcolor="0 0 0 195"/>
</sprite>
</sprites>

View File

@ -103,4 +103,12 @@
text_valign="center"
/>
<style name="netStatus"
font="serif-bold-18"
textcolor="255 255 255"
text_align="center"
text_valign="center"
sprite="netStatusBackground"
/>
</styles>

BIN
binaries/data/mods/public/maps/scenarios/_default.xml (Stored with Git LFS) Normal file

Binary file not shown.

View File

@ -6,8 +6,9 @@ Player.prototype.Schema =
Player.prototype.Init = function()
{
this.playerID = undefined;
this.playerName = "Unknown";
this.civ = "celt";
this.name = "Unknown";
this.civ = "gaia";
this.colour = { "r": 0.0, "g": 0.0, "b": 0.0, "a": 1.0 };
this.popCount = 0;
this.popLimit = 50;
this.resourceCount = {
@ -18,20 +19,32 @@ Player.prototype.Init = function()
};
};
Player.prototype.GetColour = function()
{
// TODO: need proper colour support
if (this.playerID == 1)
return { "r": 0.0, "g": 0.0, "b": 1.0, "a": 1.0 };
else
return { "r": 1.0, "g": 0.0, "b": 0.0, "a": 1.0 };
};
Player.prototype.SetPlayerID = function(id)
{
this.playerID = id;
};
Player.prototype.SetName = function(name)
{
this.name = name;
};
Player.prototype.SetCiv = function(civcode)
{
this.civ = civcode;
};
Player.prototype.SetColour = function(r, g, b)
{
this.colour = { "r": r/255.0, "g": g/255.0, "b": b/255.0, "a": 1.0 };
};
Player.prototype.GetColour = function()
{
return this.colour;
};
Player.prototype.GetPopulationCount = function()
{
return this.popCount;

View File

@ -0,0 +1,14 @@
<?xml version="1.0" encoding="UTF-8"?>
<!-- Default player colours -->
<players>
<player id="0" name="Gaia" civ="gaia" r="255" g="255" b="255"/> <!-- white -->
<player id="1" name="Player 1" civ="celt" r="80" g="80" b="200"/> <!-- blue -->
<player id="2" name="Player 2" civ="hele" r="150" g="20" b="20"/> <!-- red -->
<player id="3" name="Player 3" civ="celt" r="50" g="165" b="5"/> <!-- green -->
<player id="4" name="Player 4" civ="hele" r="230" g="230" b="75"/> <!-- yellow -->
<player id="5" name="Player 5" civ="celt" r="50" g="170" b="170"/> <!-- turquoise -->
<player id="6" name="Player 6" civ="hele" r="160" g="80" b="200"/> <!-- purple -->
<player id="7" name="Player 7" civ="celt" r="235" g="120" b="20"/> <!-- orange -->
<player id="8" name="Player 8" civ="hele" r="64" g="64" b="64"/> <!-- charcoal -->
</players>

View File

@ -7,6 +7,10 @@ function ProcessCommand(player, cmd)
switch (cmd.type)
{
case "debug-print":
print(cmd.message);
break;
case "walk":
for each (var ent in cmd.entities)
{
@ -120,7 +124,7 @@ function ProcessCommand(player, cmd)
break;
default:
print("Ignoring unrecognised command type '" + cmd.type + "'\n");
error("Ignoring unrecognised command type '" + cmd.type + "'");
}
}

View File

@ -1,16 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<!-- List of default players for single-player maps -->
<players>
<player name="Gaia" rgb="255 255 255"/> <!-- white -->
<player name="Wijitmaker" rgb="80 80 200"/> <!-- blue -->
<player name="Matei" rgb="150 20 20"/> <!-- red -->
<player name="Aeros" rgb="50 165 5"/> <!-- green -->
<player name="Mythos_Ruler" rgb="230 230 75"/> <!-- yellow -->
<player name="Hyborian" rgb="50 170 170"/> <!-- turquoise -->
<player name="Ykkrosh" rgb="160 80 200"/> <!-- purple -->
<player name="Pyrolink" rgb="235 120 20"/> <!-- orange -->
<player name="Vaevictus_Music" rgb="64 64 64"/> <!-- charcoal -->
</players>

BIN
binaries/data/mods/public/temp/terrain.png (Stored with Git LFS)

Binary file not shown.

View File

@ -71,8 +71,8 @@ end
-- * defines: a table of symbols to define
extern_lib_defs = {
boost = {
unix_names = { "boost_signals-mt", "boost_filesystem-mt", "boost_system-mt" },
osx_names = { "boost_signals-mt", "boost_filesystem-mt", "boost_system-mt" }
unix_names = { "boost_signals-mt", "boost_filesystem-mt", "boost_system-mt", "boost_random-mt" },
osx_names = { "boost_signals-mt", "boost_filesystem-mt", "boost_system-mt", "boost_random-mt" }
},
cryptopp = {
win_names = { "cryptopp" },

View File

@ -3,7 +3,7 @@ PROJECT_NUMBER = trunk
TAB_SIZE = 4
INPUT = ../../source/simulation2 ../../source/scriptinterface
INPUT = ../../source/simulation2 ../../source/scriptinterface ../../source/network
INCLUDE_PATH = ../../source
EXAMPLE_PATH = ../../source
RECURSIVE = YES

View File

@ -244,7 +244,7 @@ void CGameViewImpl::ScriptingInit()
CJSObject<CGameViewImpl>::ScriptingInit("GameView");
}
int CGameView::Initialize(CGameAttributes* UNUSED(pAttribs))
int CGameView::Initialize()
{
CFG_GET_SYS_VAL( "view.scroll.speed", Float, m->ViewScrollSpeed );
CFG_GET_SYS_VAL( "view.rotate.speed", Float, m->ViewRotateSensitivity );
@ -267,10 +267,10 @@ int CGameView::Initialize(CGameAttributes* UNUSED(pAttribs))
void CGameView::RegisterInit(CGameAttributes *pAttribs)
void CGameView::RegisterInit()
{
// CGameView init
RegMemFun1(this, &CGameView::Initialize, pAttribs, L"CGameView init", 1);
RegMemFun(this, &CGameView::Initialize, L"CGameView init", 1);
// previously done by CGameView::InitResources
RegMemFun(g_TexMan.GetSingletonPtr(), &CTextureManager::LoadTerrainTextures, L"LoadTerrainTextures", 60);

View File

@ -27,7 +27,6 @@ extern float g_YMinOffset;
#include "lib/input.h" // InReaction - can't forward-declare enum
class CGame;
class CGameAttributes;
class CObjectManager;
class CCamera;
class CCinemaManager;
@ -67,8 +66,8 @@ public:
void SetViewport(const SViewPort& vp);
void RegisterInit(CGameAttributes *pAttribs);
int Initialize(CGameAttributes *pGameAttributes);
void RegisterInit();
int Initialize();
CObjectManager& GetObjectManager() const;

View File

@ -269,9 +269,12 @@ private:
// # entities+nonentities processed and total (for progress calc)
int completed_jobs, total_jobs;
// maximum used entity ID, so we can safely allocate new ones
entity_id_t max_uid;
void Init(const VfsPath& xml_filename);
void ReadPlayers();
void ReadTerrain(XMBElement parent);
void ReadEnvironment(XMBElement parent);
void ReadCamera(XMBElement parent);
@ -325,26 +328,73 @@ void CXMLReader::Init(const VfsPath& xml_filename)
// Find the maximum entity ID, so we can safely allocate new IDs without conflicts
int max_uid = SYSTEM_ENTITY;
max_uid = SYSTEM_ENTITY;
XMBElement ents = nodes.GetFirstNamedItem(xmb_file.GetElementID("Entities"));
XERO_ITER_EL(ents, ent)
{
utf16string uid = ent.GetAttributes().GetNamedItem(at_uid);
max_uid = std::max(max_uid, CStr(uid).ToInt());
max_uid = std::max(max_uid, (entity_id_t)CStr(uid).ToInt());
}
// Initialise player data
ReadPlayers();
}
void CXMLReader::ReadPlayers()
{
CmpPtr<ICmpPlayerManager> cmpPlayerMan(*m_MapReader.pSimulation2, SYSTEM_ENTITY);
debug_assert(!cmpPlayerMan.null());
// TODO: this should be loaded from the XML instead
size_t numPlayers = 4;
// TODO: we ought to read at least some of this data from the map file.
// For now, just always use the defaults.
std::map<int, CStrW> playerDefaultNames;
std::map<int, CStrW> playerDefaultCivs;
std::map<int, SColor3ub> playerDefaultColours;
CXeromyces playerDefaultFile;
if (playerDefaultFile.Load(L"simulation/data/players.xml") != PSRETURN_OK)
throw PSERROR_File_ReadFailed();
#define AT(x) int at_##x = playerDefaultFile.GetAttributeID(#x)
AT(id);
AT(name);
AT(civ);
AT(r); AT(g); AT(b);
#undef AT
XERO_ITER_EL(playerDefaultFile.GetRoot(), player)
{
XMBAttributeList attrs = player.GetAttributes();
int id = CStr(attrs.GetNamedItem(at_id)).ToInt();
playerDefaultNames[id] = attrs.GetNamedItem(at_name);
playerDefaultCivs[id] = attrs.GetNamedItem(at_civ);
SColor3ub colour;
colour.R = (u8)CStr(attrs.GetNamedItem(at_r)).ToInt();
colour.G = (u8)CStr(attrs.GetNamedItem(at_g)).ToInt();
colour.B = (u8)CStr(attrs.GetNamedItem(at_b)).ToInt();
playerDefaultColours[id] = colour;
}
size_t numPlayers = 9; // including Gaia
for (size_t i = 0; i < numPlayers; ++i)
{
int uid = ++max_uid;
entity_id_t ent = m_MapReader.pSimulation2->AddEntity(L"special/player", uid);
CmpPtr<ICmpPlayer> cmpPlayer(*m_MapReader.pSimulation2, ent);
debug_assert(!cmpPlayer.null());
cmpPlayer->SetName(playerDefaultNames[i]);
cmpPlayer->SetCiv(playerDefaultCivs[i]);
SColor3ub colour = playerDefaultColours[i];
cmpPlayer->SetColour(colour.R, colour.G, colour.B);
cmpPlayerMan->AddPlayer(ent);
}
}
@ -1013,10 +1063,6 @@ int CXMLReader::ReadOldEntities(XMBElement parent, double end_time)
debug_warn(L"Invalid map XML data");
}
CPlayer* player = NULL;
if (g_Game)
player = g_Game->GetPlayer(PlayerID);
// The old version uses a flat entity naming system, so we need
// to translate it into the hierarchical filename
if (TemplateName.Find(L"flora") == 0 || TemplateName.Find(L"fauna") == 0 || TemplateName.Find(L"geology") == 0 || TemplateName.Find(L"special") == 0)

View File

@ -35,8 +35,8 @@
#include "maths/MathUtil.h"
#include "maths/NUSpline.h"
#include "ps/Game.h"
#include "ps/Loader.h"
#include "ps/Player.h"
#include "ps/Filesystem.h"
#include "ps/XML/XMLWriter.h"
#include "renderer/SkyManager.h"

View File

@ -17,9 +17,9 @@
#include "precompiled.h"
#include "lib/ogl.h"
#include "Material.h"
#include "ps/Player.h"
#include "lib/ogl.h"
#include "ps/Game.h"
#include "ps/Overlay.h" // for CColor
@ -106,12 +106,8 @@ SMaterialColor CMaterial::GetPlayerColor()
if (m_PlayerID <= PLAYER_ID_LAST_VALID)
{
CPlayer* player = g_Game->GetPlayer(m_PlayerID);
if (player)
{
const SPlayerColour& c (player->GetColour());
return SMaterialColor(c.r, c.g, c.b, c.a);
}
CColor c(g_Game->GetPlayerColour(m_PlayerID));
return SMaterialColor(c.r, c.g, c.b, c.a);
}
// Oops, something failed.

View File

@ -26,9 +26,6 @@
#include "SkeletonAnimDef.h"
#include "UnitAnimation.h"
#include "ps/Game.h"
#include "ps/Player.h"
CUnit::CUnit(CObjectEntry* object, CObjectManager& objectManager,
const std::set<CStr>& actorSelections)
: m_Object(object), m_Model(object->m_Model->Clone()),

View File

@ -35,7 +35,8 @@
///////////////////////////////////////////////////////////////////////////////
// CUnitManager constructor
CUnitManager::CUnitManager()
CUnitManager::CUnitManager() :
m_ObjectManager(NULL)
{
}

View File

@ -33,9 +33,7 @@
#include "lib/bits.h"
#include "lib/timer.h"
#include "lib/sysdep/cpu.h"
#include "network/NetMessage.h"
#include "ps/Game.h"
#include "ps/Player.h"
#include "ps/Profile.h"
#include "ps/World.h"
#include "renderer/Renderer.h"

View File

@ -22,11 +22,14 @@
#include "graphics/Camera.h"
#include "graphics/GameView.h"
#include "gui/GUIManager.h"
#include "lib/sysdep/sysdep.h"
#include "maths/FixedVector3D.h"
#include "network/NetClient.h"
#include "network/NetServer.h"
#include "network/NetTurnManager.h"
#include "ps/CLogger.h"
#include "ps/Game.h"
#include "ps/Overlay.h"
#include "ps/Player.h"
#include "ps/GameSetup/Config.h"
#include "simulation2/Simulation2.h"
#include "simulation2/components/ICmpCommandQueue.h"
@ -34,6 +37,8 @@
#include "simulation2/components/ICmpTemplateManager.h"
#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.
@ -82,8 +87,8 @@ CScriptVal GuiInterfaceCall(void* cbdata, std::wstring name, CScriptVal data)
return JSVAL_VOID;
int player = -1;
if (g_Game && g_Game->GetLocalPlayer())
player = g_Game->GetLocalPlayer()->GetPlayerID();
if (g_Game)
player = g_Game->GetPlayerID();
CScriptValRooted arg (sim->GetScriptInterface().GetContext(), sim->GetScriptInterface().CloneValueFromOtherContext(guiManager->GetScriptInterface(), data.get()));
CScriptVal ret (gui->ScriptCall(player, name, arg.get()));
@ -133,11 +138,107 @@ std::wstring SetCursor(void* UNUSED(cbdata), std::wstring name)
int GetPlayerID(void* UNUSED(cbdata))
{
if (g_Game && g_Game->GetLocalPlayer())
return g_Game->GetLocalPlayer()->GetPlayerID();
if (g_Game)
return g_Game->GetPlayerID();
return -1;
}
std::wstring GetDefaultPlayerName(void* UNUSED(cbdata))
{
// TODO: this should come from a config file or something
std::wstring name = sys_get_user_name();
if (name.empty())
name = L"anonymous";
return name;
}
void StartNetworkGame(void* UNUSED(cbdata))
{
debug_assert(g_NetServer);
g_NetServer->StartGame();
}
void StartGame(void* cbdata, CScriptVal attribs, int playerID)
{
CGUIManager* guiManager = static_cast<CGUIManager*> (cbdata);
debug_assert(!g_NetServer);
debug_assert(!g_NetClient);
debug_assert(!g_Game);
g_Game = new CGame();
// Convert from GUI script context to sim script context
CSimulation2* sim = g_Game->GetSimulation2();
CScriptValRooted gameAttribs (sim->GetScriptInterface().GetContext(),
sim->GetScriptInterface().CloneValueFromOtherContext(guiManager->GetScriptInterface(), attribs.get()));
g_Game->SetPlayerID(playerID);
g_Game->StartGame(gameAttribs);
}
void SetNetworkGameAttributes(void* cbdata, CScriptVal attribs)
{
CGUIManager* guiManager = static_cast<CGUIManager*> (cbdata);
debug_assert(g_NetServer);
// Convert from GUI script context to net server script context
CScriptValRooted gameAttribs (g_NetServer->GetScriptInterface().GetContext(),
g_NetServer->GetScriptInterface().CloneValueFromOtherContext(guiManager->GetScriptInterface(), attribs.get()));
g_NetServer->UpdateGameAttributes(gameAttribs);
}
void StartNetworkHost(void* UNUSED(cbdata), std::wstring playerName)
{
debug_assert(!g_NetClient);
debug_assert(!g_NetServer);
debug_assert(!g_Game);
g_NetServer = new CNetServer();
bool ok = g_NetServer->SetupConnection();
debug_assert(ok); // TODO: need better error handling
g_Game = new CGame();
g_NetClient = new CNetClient(g_Game);
g_NetClient->SetUserName(playerName);
g_NetClient->SetupLocalConnection(*g_NetServer);
}
void StartNetworkJoin(void* UNUSED(cbdata), std::wstring playerName, std::string serverAddress)
{
debug_assert(!g_NetClient);
debug_assert(!g_NetServer);
debug_assert(!g_Game);
g_Game = new CGame();
g_NetClient = new CNetClient(g_Game);
g_NetClient->SetUserName(playerName);
g_NetClient->SetupConnection(serverAddress);
}
// TODO: we need some way to disconnect the server/client
CScriptVal PollNetworkClient(void* cbdata)
{
CGUIManager* guiManager = static_cast<CGUIManager*> (cbdata);
debug_assert(g_NetClient);
CScriptValRooted poll = g_NetClient->GuiPoll();
// Convert from net client context to GUI script context
return guiManager->GetScriptInterface().CloneValueFromOtherContext(g_NetClient->GetScriptInterface(), poll.get());
}
void AssignNetworkPlayer(void* UNUSED(cbdata), int playerID, std::string guid)
{
debug_assert(g_NetServer);
g_NetServer->AssignPlayer(playerID, guid);
}
} // namespace
void GuiScriptingInit(ScriptInterface& scriptInterface)
@ -158,7 +259,17 @@ void GuiScriptingInit(ScriptInterface& scriptInterface)
scriptInterface.RegisterFunction<std::vector<entity_id_t>, int, int, int, int, int, &PickFriendlyEntitiesInRect>("PickFriendlyEntitiesInRect");
scriptInterface.RegisterFunction<CFixedVector3D, int, int, &GetTerrainAtPoint>("GetTerrainAtPoint");
// Network / game setup functions
scriptInterface.RegisterFunction<void, &StartNetworkGame>("StartNetworkGame");
scriptInterface.RegisterFunction<void, CScriptVal, int, &StartGame>("StartGame");
scriptInterface.RegisterFunction<void, std::wstring, &StartNetworkHost>("StartNetworkHost");
scriptInterface.RegisterFunction<void, std::wstring, std::string, &StartNetworkJoin>("StartNetworkJoin");
scriptInterface.RegisterFunction<CScriptVal, &PollNetworkClient>("PollNetworkClient");
scriptInterface.RegisterFunction<void, CScriptVal, &SetNetworkGameAttributes>("SetNetworkGameAttributes");
scriptInterface.RegisterFunction<void, int, std::string, &AssignNetworkPlayer>("AssignNetworkPlayer");
// Misc functions
scriptInterface.RegisterFunction<std::wstring, std::wstring, &SetCursor>("SetCursor");
scriptInterface.RegisterFunction<int, &GetPlayerID>("GetPlayerID");
scriptInterface.RegisterFunction<std::wstring, &GetDefaultPlayerName>("GetDefaultPlayerName");
}

View File

@ -41,25 +41,27 @@ that of Atlas depending on commandline parameters.
#include "lib/external_libraries/sdl.h"
#include "lib/res/sound/snd_mgr.h"
#include "ps/CConsole.h"
#include "ps/Filesystem.h"
#include "ps/Game.h"
#include "ps/Globals.h"
#include "ps/Hotkey.h"
#include "ps/Loader.h"
#include "ps/Profile.h"
#include "ps/Pyrogenesis.h"
#include "ps/Util.h"
#include "ps/VideoMode.h"
#include "ps/GameSetup/GameSetup.h"
#include "ps/GameSetup/Atlas.h"
#include "ps/GameSetup/Config.h"
#include "ps/GameSetup/CmdLineArgs.h"
#include "ps/Loader.h"
#include "ps/Filesystem.h"
#include "ps/CConsole.h"
#include "ps/Profile.h"
#include "ps/Util.h"
#include "ps/Game.h"
#include "ps/Hotkey.h"
#include "ps/Globals.h"
#include "ps/VideoMode.h"
#include "ps/XML/Xeromyces.h"
#include "network/NetClient.h"
#include "network/NetServer.h"
#include "network/NetSession.h"
#include "graphics/Camera.h"
#include "graphics/GameView.h"
#include "scripting/ScriptingHost.h"
#include "simulation2/Simulation2.h"
#include "sound/CMusicPlayer.h"
#include "gui/GUIManager.h"
@ -228,7 +230,7 @@ static void Frame()
// If we are not running a multiplayer game, disable updates when the game is
// minimized or out of focus and relinquish the CPU a bit, in order to make
// debugging easier.
if( !g_NetClient && !g_NetServer && !g_app_has_focus )
if( !g_NetClient && !g_app_has_focus )
{
need_update = false;
// don't use SDL_WaitEvent: don't want the main loop to freeze until app focus is restored

View File

@ -1,151 +0,0 @@
/* 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/>.
*/
#ifndef INCLUDED_NETWORK_JSEVENTS
#define INCLUDED_NETWORK_JSEVENTS
#include "ServerSession.h"
#include "scripting/DOMEvent.h"
enum ENetworkJSEvents
{
NET_JS_EVENT_START_GAME,
NET_JS_EVENT_CHAT,
NET_JS_EVENT_CONNECT_COMPLETE,
NET_JS_EVENT_DISCONNECT,
NET_JS_EVENT_CLIENT_CONNECT,
NET_JS_EVENT_CLIENT_DISCONNECT,
NET_JS_EVENT_LAST
};
class CStartGameEvent: public CScriptEvent
{
public:
CStartGameEvent():
CScriptEvent(L"startGame", NET_JS_EVENT_START_GAME, false)
{}
};
class CChatEvent: public CScriptEvent
{
CStrW m_Sender;
CStrW m_Message;
public:
CChatEvent(const CStrW& sender, const CStrW& message):
CScriptEvent(L"chat", NET_JS_EVENT_CHAT, false ),
m_Sender(sender),
m_Message(message)
{
AddLocalProperty(L"sender", &m_Sender, true);
AddLocalProperty(L"message", &m_Message, true);
}
};
class CConnectCompleteEvent: public CScriptEvent
{
CStrW m_Message;
bool m_Success;
public:
CConnectCompleteEvent(const CStrW& message, bool success):
CScriptEvent(L"connectComplete", NET_JS_EVENT_CONNECT_COMPLETE, false),
m_Message(message),
m_Success(success)
{
AddLocalProperty(L"message", &m_Message, true);
AddLocalProperty(L"success", &m_Success, true);
}
};
class CDisconnectEvent: public CScriptEvent
{
CStrW m_Message;
public:
CDisconnectEvent(const CStrW& message):
CScriptEvent(L"disconnect", NET_JS_EVENT_DISCONNECT, false),
m_Message(message)
{
AddLocalProperty(L"message", &m_Message, true);
}
};
class CClientConnectDisconnectCommon: public CScriptEvent
{
int m_SessionID;
CStrW m_Name;
CNetServerSession *m_pSession;
public:
CClientConnectDisconnectCommon(const wchar_t* UNUSED(eventName), int UNUSED(eventType),
int sessionID, const CStrW& name, CNetServerSession* pSession)
: CScriptEvent(L"clientConnect", NET_JS_EVENT_CLIENT_CONNECT, false),
m_SessionID(sessionID),
m_Name(name),
m_pSession(pSession)
{
AddLocalProperty(L"id", &m_SessionID, true);
AddLocalProperty(L"name", &m_Name, true);
if (m_pSession)
AddLocalProperty(L"session", &m_pSession, true);
}
};
struct CClientConnectEvent: public CClientConnectDisconnectCommon
{
CClientConnectEvent(int sessionID, const CStrW& name):
CClientConnectDisconnectCommon(
L"clientConnect",
NET_JS_EVENT_CLIENT_CONNECT,
sessionID,
name,
NULL)
{}
CClientConnectEvent(CNetServerSession *pSession):
CClientConnectDisconnectCommon(
L"clientConnect",
NET_JS_EVENT_CLIENT_CONNECT,
pSession->GetID(),
pSession->GetName(),
pSession)
{}
};
struct CClientDisconnectEvent: public CClientConnectDisconnectCommon
{
CClientDisconnectEvent(int sessionID, const CStrW& name):
CClientConnectDisconnectCommon(
L"clientDisconnect",
NET_JS_EVENT_CLIENT_DISCONNECT,
sessionID,
name,
NULL)
{}
CClientDisconnectEvent(CNetServerSession *pSession):
CClientConnectDisconnectCommon(
L"clientDisconnect",
NET_JS_EVENT_CLIENT_DISCONNECT,
pSession->GetID(),
pSession->GetName(),
pSession)
{}
};
#endif

View File

@ -15,623 +15,380 @@
* along with 0 A.D. If not, see <http://www.gnu.org/licenses/>.
*/
/**
*-----------------------------------------------------------------------------
* FILE : NetClient.cpp
* PROJECT : 0 A.D.
* DESCRIPTION : Network client class implementation file
*-----------------------------------------------------------------------------
*/
// INCLUDES
#include "precompiled.h"
#include "NetClient.h"
#include "NetJsEvents.h"
#include "Network.h"
#include "NetServer.h"
#include "NetMessage.h"
#include "NetSession.h"
#include "NetTurnManager.h"
#include "scripting/DOMEvent.h"
#include "scripting/JSConversions.h"
#include "scripting/ScriptableObject.h"
#include "ps/CConsole.h"
#include "ps/CLogger.h"
#include "ps/CStr.h"
#include "ps/Game.h"
#include "ps/Globals.h"
#include "ps/GameAttributes.h"
#include "scriptinterface/ScriptInterface.h"
#include "simulation2/Simulation2.h"
// DECLARATIONS
#define LOG_CATEGORY L"net"
#include <boost/nondet_random.hpp>
#include <boost/random.hpp>
CNetClient *g_NetClient = NULL;
//-----------------------------------------------------------------------------
// Name: CServerPlayer()
// Desc: Constructor
//-----------------------------------------------------------------------------
CServerPlayer::CServerPlayer( uint sessionID, const CStr& nickname )
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_SessionID = sessionID;
m_Nickname = nickname;
}
m_Game->SetTurnManager(NULL); // delete the old local turn manager so we don't accidentally use it
//-----------------------------------------------------------------------------
// Name: ~CServerPlayer()
// Desc: Destructor
//-----------------------------------------------------------------------------
CServerPlayer::~CServerPlayer( void )
{
}
void* context = this;
//-----------------------------------------------------------------------------
// Name: ScriptingInit()
// Desc:
//-----------------------------------------------------------------------------
void CServerPlayer::ScriptingInit( void )
{
AddProperty(L"id", &CServerPlayer::m_SessionID, true);
AddProperty(L"name", &CServerPlayer::m_Nickname, true);
// Set up transitions for session
AddTransition(NCS_UNCONNECTED, (uint)NMT_CONNECT_COMPLETE, NCS_CONNECT, (void*)&OnConnect, context);
CJSObject<CServerPlayer>::ScriptingInit( "NetClient_ServerSession" );
}
AddTransition(NCS_CONNECT, (uint)NMT_SERVER_HANDSHAKE, NCS_HANDSHAKE, (void*)&OnHandshake, context);
//-----------------------------------------------------------------------------
// Name: CNetClient()
// Desc: Constructor
//-----------------------------------------------------------------------------
CNetClient::CNetClient( ScriptInterface& scriptInterface, CGame* pGame, CGameAttributes* pGameAttribs )
: CNetHost( scriptInterface ), m_JsPlayers( &m_Players )
{
m_ClientTurnManager = NULL;
AddTransition(NCS_HANDSHAKE, (uint)NMT_SERVER_HANDSHAKE_RESPONSE, NCS_AUTHENTICATE, (void*)&OnHandshakeResponse, context);
m_pLocalPlayerSlot = NULL;
m_pGame = pGame;
m_pGameAttributes = pGameAttribs;
AddTransition(NCS_AUTHENTICATE, (uint)NMT_AUTHENTICATE_RESULT, NCS_INITIAL_GAMESETUP, (void*)&OnAuthenticate, context);
g_ScriptingHost.SetGlobal("g_NetClient", OBJECT_TO_JSVAL(GetScript()));
}
AddTransition(NCS_INITIAL_GAMESETUP, (uint)NMT_GAME_SETUP, NCS_PREGAME, (void*)&OnGameSetup, context);
//-----------------------------------------------------------------------------
// Name: ~CNetClient()
// Desc: Destructor
//-----------------------------------------------------------------------------
CNetClient::~CNetClient()
{
// Release resources
PlayerMap::iterator it = m_Players.begin();
for ( ; it != m_Players.end(); it++ )
{
CServerPlayer *pCurrPlayer = it->second;
if ( pCurrPlayer ) delete pCurrPlayer;
}
AddTransition(NCS_PREGAME, (uint)NMT_GAME_SETUP, NCS_PREGAME, (void*)&OnGameSetup, context);
AddTransition(NCS_PREGAME, (uint)NMT_PLAYER_ASSIGNMENT, NCS_PREGAME, (void*)&OnPlayerAssignment, context);
AddTransition(NCS_PREGAME, (uint)NMT_GAME_START, NCS_LOADING, (void*)&OnGameStart, context);
m_Players.clear();
AddTransition(NCS_LOADING, (uint)NMT_GAME_SETUP, NCS_LOADING, (void*)&OnGameSetup, context);
AddTransition(NCS_LOADING, (uint)NMT_PLAYER_ASSIGNMENT, NCS_LOADING, (void*)&OnPlayerAssignment, context);
AddTransition(NCS_LOADING, (uint)NMT_LOADED_GAME, NCS_INGAME, (void*)&OnLoadedGame, context);
g_ScriptingHost.SetGlobal("g_NetClient", JSVAL_NULL);
}
AddTransition(NCS_INGAME, (uint)NMT_GAME_SETUP, NCS_INGAME, (void*)&OnGameSetup, context);
AddTransition(NCS_INGAME, (uint)NMT_PLAYER_ASSIGNMENT, NCS_INGAME, (void*)&OnPlayerAssignment, context);
AddTransition(NCS_INGAME, (uint)NMT_SIMULATION_COMMAND, NCS_INGAME, (void*)&OnInGame, context);
AddTransition(NCS_INGAME, (uint)NMT_SYNC_ERROR, NCS_INGAME, (void*)&OnInGame, context);
AddTransition(NCS_INGAME, (uint)NMT_END_COMMAND_BATCH, NCS_INGAME, (void*)&OnInGame, context);
//-----------------------------------------------------------------------------
// Name: ScriptingInit()
// Desc:
//-----------------------------------------------------------------------------
void CNetClient::ScriptingInit()
{
AddMethod<bool, &CNetClient::SetupConnection>("beginConnect", 1);
AddProperty(L"onStartGame", &CNetClient::m_OnStartGame);
AddProperty(L"onChat", &CNetClient::m_OnChat);
AddProperty(L"onConnectComplete", &CNetClient::m_OnConnectComplete);
AddProperty(L"onDisconnect", &CNetClient::m_OnDisconnect);
AddProperty(L"onClientConnect", &CNetClient::m_OnPlayerJoin);
AddProperty(L"onClientDisconnect", &CNetClient::m_OnPlayerLeave);
AddProperty(L"password", &CNetClient::m_Password);
AddProperty<CStr>(L"playerName", &CNetClient::m_Nickname);
AddProperty(L"sessions", &CNetClient::m_JsPlayers);
CJSMap< PlayerMap >::ScriptingInit("NetClient_SessionMap");
CJSObject<CNetClient>::ScriptingInit("NetClient");
}
//-----------------------------------------------------------------------------
// Name: Run()
// Desc: Connect to server and start main loop
//-----------------------------------------------------------------------------
bool CNetClient::SetupConnection( JSContext* UNUSED(pContext), uintN argc, jsval* argv )
{
uint port = DEFAULT_HOST_PORT;
// Validate parameters
if ( argc == 0 ) return false;
// Build host information
CStr host = g_ScriptingHost.ValueToString( argv[0] );
if ( argc == 2 ) port = ToPrimitive< uint >( argv[ 1 ] );
// Create client host
if ( !Create() ) return false;
// Connect to server
return Connect( host, port );
}
//-----------------------------------------------------------------------------
// Name: SetupSession()
// Desc: Setup client session upon creation
//-----------------------------------------------------------------------------
bool CNetClient::SetupSession( CNetSession* pSession )
{
// Validate parameters
if ( !pSession ) return false;
FsmActionCtx* pContext = pSession->GetFsmActionCtx();
pContext->pHost = this;
pContext->pSession = pSession;
// Setup transitions for session
pSession->AddTransition( NCS_CONNECT, ( uint )NMT_SERVER_HANDSHAKE, NCS_HANDSHAKE, (void*)&OnHandshake, pContext );
pSession->AddTransition( NCS_HANDSHAKE, ( uint )NMT_ERROR, NCS_CONNECT, (void*)&OnError, pContext );
pSession->AddTransition( NCS_HANDSHAKE, ( uint )NMT_SERVER_HANDSHAKE_RESPONSE, NCS_AUTHENTICATE, (void*)&OnHandshake, pContext );
pSession->AddTransition( NCS_AUTHENTICATE, ( uint )NMT_ERROR, NCS_CONNECT, (void*)&OnError, pContext );
pSession->AddTransition( NCS_AUTHENTICATE, ( uint )NMT_AUTHENTICATE_RESULT, NCS_PREGAME, (void*)&OnAuthenticate, pContext );
pSession->AddTransition( NCS_PREGAME, ( uint )NMT_ERROR, NCS_CONNECT, (void*)&OnError, pContext );
pSession->AddTransition( NCS_PREGAME, ( uint )NMT_GAME_SETUP, NCS_PREGAME, (void*)&OnPreGame, pContext );
pSession->AddTransition( NCS_PREGAME, ( uint )NMT_ASSIGN_PLAYER_SLOT, NCS_PREGAME, (void*)&OnPreGame, pContext );
pSession->AddTransition( NCS_PREGAME, ( uint )NMT_PLAYER_CONFIG, NCS_PREGAME, (void*)&OnPreGame, pContext );
pSession->AddTransition( NCS_PREGAME, ( uint )NMT_PLAYER_JOIN, NCS_PREGAME, (void*)&OnPlayerJoin, pContext );
pSession->AddTransition( NCS_PREGAME, ( uint )NMT_GAME_START, NCS_INGAME, (void*)&OnStartGame_, pContext );
pSession->AddTransition( NCS_INGAME, ( uint )NMT_CHAT, NCS_INGAME, (void*)&OnChat, pContext );
pSession->AddTransition( NCS_INGAME, ( uint )NMT_SIMULATION_COMMAND, NCS_INGAME, (void*)&OnInGame, pContext );
pSession->AddTransition( NCS_INGAME, ( uint )NMT_SYNC_ERROR, NCS_INGAME, (void*)&OnInGame, pContext );
pSession->AddTransition( NCS_INGAME, ( uint )NMT_END_COMMAND_BATCH, NCS_INGAME, (void*)&OnInGame, pContext );
// TODO: add chat
// Set first state
pSession->SetFirstState( NCS_CONNECT );
return true;
SetFirstState(NCS_UNCONNECTED);
}
//-----------------------------------------------------------------------------
// Name: HandleConnect()
// Desc: Called when the client successfully connected to server
//-----------------------------------------------------------------------------
bool CNetClient::HandleConnect( CNetSession* pSession )
CNetClient::~CNetClient()
{
// Validate parameters
if ( !pSession ) return false;
return true;
delete m_Session;
}
//-----------------------------------------------------------------------------
// Name: HandleDisconnect()
// Desc: Called when the client disconnected from the server
//-----------------------------------------------------------------------------
bool CNetClient::HandleDisconnect( CNetSession *pSession )
void CNetClient::SetUserName(const CStrW& username)
{
// Validate parameters
if ( !pSession ) return false;
debug_assert(!m_Session); // must be called before we start the connection
return true;
m_UserName = username;
}
//-----------------------------------------------------------------------------
// Name: OnError()
// Desc:
//-----------------------------------------------------------------------------
bool CNetClient::OnError( void* pContext, CFsmEvent* pEvent )
bool CNetClient::SetupConnection(const CStr& server)
{
// Validate parameters
if ( !pEvent || !pContext ) return false;
CNetClientSessionRemote* session = new CNetClientSessionRemote(*this);
bool ok = session->Connect(PS_DEFAULT_PORT, server);
SetAndOwnSession(session);
return ok;
}
// Error event?
if ( pEvent->GetType() != (uint)NMT_ERROR ) return true;
void CNetClient::SetupLocalConnection(CNetServer& server)
{
CNetClientSessionLocal* session = new CNetClientSessionLocal(*this, server);
SetAndOwnSession(session);
}
CNetClient* pClient = ( CNetClient* )( ( FsmActionCtx* )pContext )->pHost;
debug_assert( pClient );
void CNetClient::SetAndOwnSession(CNetClientSession* session)
{
delete m_Session;
m_Session = session;
}
CErrorMessage* pMessage = ( CErrorMessage* )pEvent->GetParamRef();
if ( pMessage )
void CNetClient::Poll()
{
if (m_Session)
m_Session->Poll();
}
CScriptValRooted CNetClient::GuiPoll()
{
if (m_GuiMessageQueue.empty())
return CScriptValRooted();
CScriptValRooted r = m_GuiMessageQueue.front();
m_GuiMessageQueue.pop_front();
return r;
}
void CNetClient::PushGuiMessage(const CScriptValRooted& message)
{
debug_assert(!message.undefined());
m_GuiMessageQueue.push_back(message);
}
std::wstring CNetClient::TestReadGuiMessages()
{
std::wstring r;
while (true)
{
LOG( CLogger::Error, LOG_CATEGORY, L"CNetClient::OnError(): Error description %hs", pMessage->m_Error );
CScriptValRooted msg = GuiPoll();
if (msg.undefined())
break;
r += GetScriptInterface().ToString(msg.get()) + L"\n";
}
return r;
}
if ( pClient->m_OnConnectComplete.Defined() )
ScriptInterface& CNetClient::GetScriptInterface()
{
return m_Game->GetSimulation2()->GetScriptInterface();
}
void CNetClient::PostPlayerAssignmentsToScript()
{
CScriptValRooted msg;
GetScriptInterface().Eval("({'type':'players', 'hosts':{}})", msg);
CScriptValRooted hosts;
GetScriptInterface().GetProperty(msg.get(), "hosts", hosts);
for (PlayerAssignmentMap::iterator it = m_PlayerAssignments.begin(); it != m_PlayerAssignments.end(); ++it)
{
CScriptValRooted host;
GetScriptInterface().Eval("({})", host);
GetScriptInterface().SetProperty(host.get(), "name", std::wstring(it->second.m_Name), false);
GetScriptInterface().SetProperty(host.get(), "player", it->second.m_PlayerID, false);
GetScriptInterface().SetProperty(hosts.get(), it->first, host, false);
}
PushGuiMessage(msg);
}
bool CNetClient::SendMessage(const CNetMessage* message)
{
return m_Session->SendMessage(message);
}
void CNetClient::HandleConnect()
{
Update((uint)NMT_CONNECT_COMPLETE, NULL);
}
void CNetClient::HandleDisconnect()
{
// TODO: should do something
}
bool CNetClient::HandleMessage(CNetMessage* message)
{
// Update FSM
bool ok = Update(message->GetType(), message);
if (!ok)
LOGERROR(L"Net client: Error running FSM update (type=%d state=%d)", (int)message->GetType(), (int)GetCurrState());
return ok;
}
void CNetClient::LoadFinished()
{
m_Game->ChangeNetStatus(CGame::NET_WAITING_FOR_CONNECT);
CLoadedGameMessage loaded;
SendMessage(&loaded);
}
bool CNetClient::OnConnect(void* context, CFsmEvent* event)
{
debug_assert(event->GetType() == (uint)NMT_CONNECT_COMPLETE);
CNetClient* client = (CNetClient*)context;
CScriptValRooted msg;
client->GetScriptInterface().Eval("({'type':'netstatus','status':'connected'})", msg);
client->PushGuiMessage(msg);
return true;
}
bool CNetClient::OnHandshake(void* context, CFsmEvent* event)
{
debug_assert(event->GetType() == (uint)NMT_SERVER_HANDSHAKE);
CNetClient* client = (CNetClient*)context;
CCliHandshakeMessage handshake;
handshake.m_MagicResponse = PS_PROTOCOL_MAGIC_RESPONSE;
handshake.m_ProtocolVersion = PS_PROTOCOL_VERSION;
handshake.m_SoftwareVersion = PS_PROTOCOL_VERSION;
client->SendMessage(&handshake);
return true;
}
bool CNetClient::OnHandshakeResponse(void* context, CFsmEvent* event)
{
debug_assert(event->GetType() == (uint)NMT_SERVER_HANDSHAKE_RESPONSE);
CNetClient* client = (CNetClient*)context;
CAuthenticateMessage authenticate;
authenticate.m_GUID = client->m_GUID;
authenticate.m_Name = client->m_UserName;
authenticate.m_Password = L""; // TODO
client->SendMessage(&authenticate);
return true;
}
bool CNetClient::OnAuthenticate(void* context, CFsmEvent* event)
{
debug_assert(event->GetType() == (uint)NMT_AUTHENTICATE_RESULT);
CNetClient* client = (CNetClient*)context;
CAuthenticateResultMessage* message = (CAuthenticateResultMessage*)event->GetParamRef();
LOGMESSAGE(L"Net: Authentication result: host=%d, %ls", message->m_HostID, message->m_Message.c_str() );
client->m_HostID = message->m_HostID;
CScriptValRooted msg;
client->GetScriptInterface().Eval("({'type':'netstatus','status':'authenticated'})", msg);
client->PushGuiMessage(msg);
return true;
}
bool CNetClient::OnGameSetup(void* context, CFsmEvent* event)
{
debug_assert(event->GetType() == (uint)NMT_GAME_SETUP);
CNetClient* client = (CNetClient*)context;
CGameSetupMessage* message = (CGameSetupMessage*)event->GetParamRef();
client->m_GameAttributes = message->m_Data;
CScriptValRooted msg;
client->GetScriptInterface().Eval("({'type':'gamesetup'})", msg);
client->GetScriptInterface().SetProperty(msg.get(), "data", message->m_Data, false);
client->PushGuiMessage(msg);
return true;
}
bool CNetClient::OnPlayerAssignment(void* context, CFsmEvent* event)
{
debug_assert(event->GetType() == (uint)NMT_PLAYER_ASSIGNMENT);
CNetClient* client = (CNetClient*)context;
CPlayerAssignmentMessage* message = (CPlayerAssignmentMessage*)event->GetParamRef();
// Unpack the message
client->m_PlayerAssignments.clear();
for (size_t i = 0; i < message->m_Hosts.size(); ++i)
{
PlayerAssignment assignment;
assignment.m_Name = message->m_Hosts[i].m_Name;
assignment.m_PlayerID = message->m_Hosts[i].m_PlayerID;
client->m_PlayerAssignments[message->m_Hosts[i].m_GUID] = assignment;
}
client->PostPlayerAssignmentsToScript();
return true;
}
bool CNetClient::OnGameStart(void* context, CFsmEvent* event)
{
debug_assert(event->GetType() == (uint)NMT_GAME_START);
CNetClient* client = (CNetClient*)context;
// Find the player assigned to our GUID
int player = -1;
if (client->m_PlayerAssignments.find(client->m_GUID) != client->m_PlayerAssignments.end())
player = client->m_PlayerAssignments[client->m_GUID].m_PlayerID;
client->m_ClientTurnManager = new CNetClientTurnManager(*client->m_Game->GetSimulation2(), *client, client->m_HostID);
client->m_Game->SetPlayerID(player);
client->m_Game->StartGame(client->m_GameAttributes);
CScriptValRooted msg;
client->GetScriptInterface().Eval("({'type':'start'})", msg);
client->PushGuiMessage(msg);
return true;
}
bool CNetClient::OnLoadedGame(void* context, CFsmEvent* event)
{
debug_assert(event->GetType() == (uint)NMT_LOADED_GAME);
CNetClient* client = (CNetClient*)context;
// All players have loaded the game - start running the turn manager
// so that the game begins
client->m_Game->SetTurnManager(client->m_ClientTurnManager);
client->m_Game->ChangeNetStatus(CGame::NET_NORMAL);
return true;
}
bool CNetClient::OnInGame(void *context, CFsmEvent* event)
{
// TODO: should split each of these cases into a separate method
CNetClient* client = (CNetClient*)context;
CNetMessage* message = (CNetMessage*)event->GetParamRef();
if (message)
{
if (message->GetType() == NMT_SIMULATION_COMMAND)
{
CConnectCompleteEvent connectComplete( ( CStrW )pMessage->m_Error, false );
pClient->m_OnConnectComplete.DispatchEvent( pClient->GetScript(), &connectComplete );
CSimulationMessage* simMessage = static_cast<CSimulationMessage*> (message);
client->m_ClientTurnManager->OnSimulationMessage(simMessage);
}
else if (message->GetType() == NMT_SYNC_ERROR)
{
CSyncErrorMessage* syncMessage = static_cast<CSyncErrorMessage*> (message);
client->m_ClientTurnManager->OnSyncError(syncMessage->m_Turn, syncMessage->m_HashExpected);
}
else if (message->GetType() == NMT_END_COMMAND_BATCH)
{
CEndCommandBatchMessage* endMessage = static_cast<CEndCommandBatchMessage*> (message);
client->m_ClientTurnManager->FinishedAllCommands(endMessage->m_Turn);
}
}
return true;
}
//-----------------------------------------------------------------------------
// Name: OnPlayer()
// Desc:
//-----------------------------------------------------------------------------
bool CNetClient::OnPlayerJoin( void* pContext, CFsmEvent* pEvent )
CStr CNetClient::GenerateGUID()
{
// Validate parameters
if ( !pEvent || !pContext ) return false;
// 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.
// Connect event?
if ( pEvent->GetType() != NMT_PLAYER_JOIN ) return true;
boost::random_device rng;
boost::uniform_int<u32> dist(0, std::numeric_limits<u32>::max());
boost::variate_generator<boost::random_device&, boost::uniform_int<u32> > gen(rng, dist);
CNetClient* pClient = ( CNetClient* )( ( FsmActionCtx* )pContext )->pHost;
debug_assert( pClient );
CPlayerJoinMessage* pMessage = ( CPlayerJoinMessage* )pEvent->GetParamRef();
if ( pMessage )
CStr guid;
for (size_t i = 0; i < 2; ++i)
{
for ( uint i = 0; i < pMessage->m_Clients.size(); i++ )
{
pClient->OnPlayer( pMessage->m_Clients[ i ].m_SessionID, pMessage->m_Clients[ i ].m_Name );
}
pClient->OnConnectComplete();
char buf[32];
sprintf_s(buf, ARRAY_SIZE(buf), "%08X", gen());
guid += buf;
}
return true;
}
//-----------------------------------------------------------------------------
// Name: OnConnectComplete()
// Desc:
//-----------------------------------------------------------------------------
void CNetClient::OnConnectComplete( )
{
if ( m_OnConnectComplete.Defined() )
{
CConnectCompleteEvent connectComplete( "OK", true );
m_OnConnectComplete.DispatchEvent( GetScript(), &connectComplete );
}
}
//-----------------------------------------------------------------------------
// Name: OnHandshake()
// Desc:
//-----------------------------------------------------------------------------
bool CNetClient::OnHandshake( void* pContext, CFsmEvent* pEvent )
{
// Validate parameters
if ( !pEvent || !pContext ) return false;
CNetClient* pClient = ( CNetClient* )( ( FsmActionCtx* )pContext )->pHost;
CNetSession* pSession = ( ( FsmActionCtx* )pContext )->pSession;
debug_assert( pClient );
debug_assert( pSession );
switch ( pEvent->GetType() )
{
//case NMT_ERROR:
// CNetClient::OnError( pContext, pEvent );
// break;
case NMT_SERVER_HANDSHAKE:
{
CCliHandshakeMessage handshake;
handshake.m_MagicResponse = PS_PROTOCOL_MAGIC_RESPONSE;
handshake.m_ProtocolVersion = PS_PROTOCOL_VERSION;
handshake.m_SoftwareVersion = PS_PROTOCOL_VERSION;
pClient->SendMessage( pSession, &handshake );
}
break;
case NMT_SERVER_HANDSHAKE_RESPONSE:
{
CAuthenticateMessage authenticate;
authenticate.m_Name = pClient->m_Nickname;
authenticate.m_Password = pClient->m_Password;
pClient->SendMessage( pSession, &authenticate );
}
break;
}
return true;
}
//-----------------------------------------------------------------------------
// Name: OnAuthenticate()
// Desc:
//-----------------------------------------------------------------------------
bool CNetClient::OnAuthenticate( void* pContext, CFsmEvent* pEvent )
{
// Validate parameters
if ( !pEvent || !pContext ) return false;
CNetClient* pClient = ( CNetClient* )( ( FsmActionCtx* )pContext )->pHost;
UNUSED2(pClient);
CNetSession* pSession = ( ( FsmActionCtx* )pContext )->pSession;
debug_assert( pClient );
debug_assert( pSession );
if ( pEvent->GetType() == (uint)NMT_ERROR )
{
// return CNetClient::OnError( pContext, pEvent );
}
else if ( pEvent->GetType() == NMT_AUTHENTICATE_RESULT )
{
CAuthenticateResultMessage* pMessage =( CAuthenticateResultMessage* )pEvent->GetParamRef();
if ( !pMessage ) return true;
LOG(CLogger::Error, LOG_CATEGORY, L"CNetClient::OnAuthenticate(): Authentication result: %ls", pMessage->m_Message.c_str() );
pSession->SetID( pMessage->m_SessionID );
LOG(CLogger::Error, LOG_CATEGORY, L"CNetClient::OnAuthenticate(): My session ID is %d", pMessage->m_SessionID);
}
return true;
}
//-----------------------------------------------------------------------------
// Name: OnPreGame()
// Desc:
//-----------------------------------------------------------------------------
bool CNetClient::OnPreGame( void* pContext, CFsmEvent* pEvent )
{
// Validate parameters
if ( !pEvent || !pContext ) return false;
CNetClient* pClient = ( CNetClient* )( ( FsmActionCtx* )pContext )->pHost;
CNetSession* pSession = ( CNetSession* )( ( FsmActionCtx* )pContext )->pSession;
debug_assert( pClient );
debug_assert( pSession );
switch ( pEvent->GetType() )
{
case NMT_PLAYER_LEAVE:
{
CPlayerLeaveMessage* pMessage = ( CPlayerLeaveMessage* )pEvent->GetParamRef();
if ( !pMessage ) return false;
pClient->OnPlayerLeave( pMessage->m_SessionID );
}
break;
case NMT_GAME_SETUP:
{
CGameSetupMessage* pMessage = ( CGameSetupMessage* )pEvent->GetParamRef();
for ( uint i = 0; i < pMessage->m_Values.size(); i++ )
{
pClient->m_pGameAttributes->SetValue( pMessage->m_Values[ i ].m_Name, pMessage->m_Values[ i ].m_Value );
}
}
break;
case NMT_ASSIGN_PLAYER_SLOT:
{
CAssignPlayerSlotMessage* pMessage = ( CAssignPlayerSlotMessage* )pEvent->GetParamRef();
// FIXME Validate slot id to prevent us from going boom
CPlayerSlot* pSlot = pClient->m_pGameAttributes->GetSlot( pMessage->m_SlotID );
if ( pSlot == pClient->m_pLocalPlayerSlot ) {
pClient->m_pLocalPlayerSlot = NULL;
}
switch ( pMessage->m_Assignment )
{
case ASSIGN_SESSION:
{
// TODO: Check where is the best place to assign client's session ID
if ( pSession->GetID() == pMessage->m_SessionID )
{
pClient->m_pLocalPlayerSlot = pSlot;
}
pSlot->AssignToSessionID( pMessage->m_SessionID );
}
break;
case ASSIGN_CLOSED:
pSlot->AssignClosed();
break;
case ASSIGN_OPEN:
pSlot->AssignOpen();
break;
default:
LOG( CLogger::Warning, LOG_CATEGORY, L"Invalid slot assignment %hs", pMessage->ToString().c_str() );
break;
}
}
break;
case NMT_PLAYER_CONFIG:
{
CPlayerConfigMessage* pMessage = ( CPlayerConfigMessage* )pEvent->GetParamRef();
// FIXME Check player ID
CPlayer* pPlayer = pClient->m_pGameAttributes->GetPlayer( pMessage->m_PlayerID );
for ( uint i = 0; i < pMessage->m_Values.size(); i++ )
{
pPlayer->SetValue( pMessage->m_Values[i].m_Name, pMessage->m_Values[i].m_Value );
}
}
break;
}
return true;
}
//-----------------------------------------------------------------------------
// Name: OnInGame()
// Desc:
//-----------------------------------------------------------------------------
bool CNetClient::OnInGame( void *pContext, CFsmEvent* pEvent )
{
// Validate parameters
if ( !pEvent || !pContext ) return false;
CNetClient* pClient = ( CNetClient* )( ( FsmActionCtx* )pContext )->pHost;
CNetMessage* pMessage = ( CNetMessage* )pEvent->GetParamRef();
if ( pMessage )
{
if (pMessage->GetType() == NMT_SIMULATION_COMMAND)
{
CSimulationMessage* simMessage = static_cast<CSimulationMessage*> (pMessage);
pClient->m_ClientTurnManager->OnSimulationMessage(simMessage);
}
else if (pMessage->GetType() == NMT_SYNC_ERROR)
{
CSyncErrorMessage* syncMessage = static_cast<CSyncErrorMessage*> (pMessage);
pClient->m_ClientTurnManager->OnSyncError(syncMessage->m_Turn, syncMessage->m_HashExpected);
}
else if ( pMessage->GetType() == NMT_END_COMMAND_BATCH )
{
CEndCommandBatchMessage* pMessage = ( CEndCommandBatchMessage* )pEvent->GetParamRef();
if ( !pMessage ) return false;
CEndCommandBatchMessage* endMessage = static_cast<CEndCommandBatchMessage*> (pMessage);
pClient->m_ClientTurnManager->FinishedAllCommands(endMessage->m_Turn);
}
}
return true;
}
//-----------------------------------------------------------------------------
// Name: OnChat()
// Desc:
//-----------------------------------------------------------------------------
bool CNetClient::OnChat( void* pContext, CFsmEvent* pEvent )
{
// Validate parameters
if ( !pEvent || !pContext ) return false;
CNetClient* pClient = ( CNetClient* )( ( FsmActionCtx* )pContext )->pHost;
if ( pEvent->GetType() == NMT_CHAT )
{
CChatMessage* pMessage = ( CChatMessage* )pEvent->GetParamRef();
if ( !pMessage ) return false;
g_Console->ReceivedChatMessage( pMessage->m_Sender, pMessage->m_Message );
if ( pClient->m_OnChat.Defined() )
{
CChatEvent evt( pMessage->m_Sender, pMessage->m_Message );
pClient->m_OnChat.DispatchEvent( pClient->GetScript(), &evt );
}
}
return true;
}
//-----------------------------------------------------------------------------
// Name: OnStartGame()
// Desc:
//-----------------------------------------------------------------------------
void CNetClient::OnStartGame( void )
{
if ( m_OnStartGame.Defined() )
{
CStartGameEvent event;
m_OnStartGame.DispatchEvent( GetScript(), &event );
}
}
//-----------------------------------------------------------------------------
// Name: OnStartGame_()
// Desc:
//-----------------------------------------------------------------------------
bool CNetClient::OnStartGame_( void* pContext, CFsmEvent* pEvent )
{
// Validate parameters
if ( !pEvent || !pContext ) return false;
CNetClient* pClient = ( CNetClient* )( ( FsmActionCtx* )pContext )->pHost;
CNetSession* pSession = ( ( FsmActionCtx* )pContext )->pSession;
pClient->m_ClientTurnManager = new CNetClientTurnManager(*pClient->m_pGame->GetSimulation2(), *pClient, pClient->GetLocalPlayer()->GetPlayerID(), pSession->GetID());
pClient->m_pGame->SetTurnManager(pClient->m_ClientTurnManager);
pClient->OnStartGame();
return true;
}
//-----------------------------------------------------------------------------
// Name: OnPlayerJoin()
// Desc:
//-----------------------------------------------------------------------------
void CNetClient::OnPlayer( uint ID, const CStr& name )
{
CServerPlayer* pNewPlayer = new CServerPlayer( ID, name );
if ( !pNewPlayer ) return;
// Store new player
m_Players[ ID ] = pNewPlayer;
// Call JS Callback
if ( m_OnPlayerJoin.Defined() )
{
CClientConnectEvent event( ID, name );
m_OnPlayerJoin.DispatchEvent( GetScript(), &event );
}
}
//-----------------------------------------------------------------------------
// Name: OnPlayerLeave()
// Desc:
//-----------------------------------------------------------------------------
void CNetClient::OnPlayerLeave( uint ID )
{
// Lookup player
PlayerMap::iterator it = m_Players.find( ID );
if ( it == m_Players.end() )
{
LOG( CLogger::Warning, LOG_CATEGORY, L"CNetClient::OnPlayerLeav(): No such player %d.", ID );
return;
}
// Call JS Callback
if ( m_OnPlayerLeave.Defined() && it->second )
{
CClientDisconnectEvent event( it->second->GetSessionID(), it->second->GetNickname() );
m_OnPlayerLeave.DispatchEvent( GetScript(), &event );
}
// Remove player from internal map
m_Players.erase( it );
}
//-----------------------------------------------------------------------------
// Name: StartGame()
// Desc:
//-----------------------------------------------------------------------------
int CNetClient::StartGame( void )
{
assert ( m_pGame );
assert ( m_pGameAttributes );
if ( m_pGame->StartGame( m_pGameAttributes ) != PSRETURN_OK ) return -1;
return 0;
}
//-----------------------------------------------------------------------------
// Name: GetLocalPlayer()
// Desc:
//-----------------------------------------------------------------------------
CPlayer* CNetClient::GetLocalPlayer()
{
return m_pLocalPlayerSlot->GetPlayer();
return guid;
}

View File

@ -15,131 +15,195 @@
* along with 0 A.D. If not, see <http://www.gnu.org/licenses/>.
*/
/**
*-----------------------------------------------------------------------------
* FILE : NetClient.h
* PROJECT : 0 A.D.
* DESCRIPTION : Network client class interface file
*-----------------------------------------------------------------------------
*/
#ifndef NETCLIENT_H
#define NETCLIENT_H
// INCLUDES
#include "NetSession.h"
#include "NetHost.h"
#include "NetTurnManager.h"
#include "network/fsm.h"
#include "network/NetHost.h"
#include "scriptinterface/ScriptVal.h"
#include "ps/CStr.h"
#include "scripting/ScriptObject.h"
#include "scripting/ScriptableObject.h"
#include "ps/scripting/JSMap.h"
#include <map>
#include <deque>
// DECLARATIONS
class CGame;
class CNetClientSession;
class CNetClientTurnManager;
class CNetServer;
class ScriptInterface;
// NetClient session FSM states
enum
{
NCS_CONNECT = 200,
NCS_HANDSHAKE = 300,
NCS_AUTHENTICATE = 400,
NCS_PREGAME = 500,
NCS_INGAME = 600
NCS_UNCONNECTED,
NCS_CONNECT,
NCS_HANDSHAKE,
NCS_AUTHENTICATE,
NCS_INITIAL_GAMESETUP,
NCS_PREGAME,
NCS_LOADING,
NCS_INGAME
};
class CPlayerSlot;
class CPlayer;
class CGame;
class CGameAttributes;
class CServerPlayer;
typedef std::map< uint, CServerPlayer* > PlayerMap;
class CServerPlayer : public CJSObject< CServerPlayer >
{
NONCOPYABLE(CServerPlayer);
public:
CServerPlayer( uint sessionID, const CStr& nickname );
~CServerPlayer( void );
static void ScriptingInit( void );
uint GetSessionID( void ) const { return m_SessionID; }
const CStr GetNickname( void ) const { return m_Nickname; }
private:
uint m_SessionID; // Player session ID
CStr m_Nickname; // Player nickname
};
class CNetClient: public CNetHost,
public CJSObject<CNetClient>
/**
* Network client.
* This code is run by every player (including the host, if they are not
* a dedicated server).
* It provides an interface between the GUI, the network (via CNetClientSession),
* and the game (via CGame and CNetClientTurnManager).
*/
class CNetClient : public CFsm
{
NONCOPYABLE(CNetClient);
public:
/**
* Construct a client associated with the given game object.
* The game must exist for the lifetime of this object.
*/
CNetClient(CGame* game);
CNetClient( ScriptInterface& scriptInterface, CGame* pGame, CGameAttributes* pGameAttributes );
~CNetClient( void );
virtual ~CNetClient();
bool CreateSession ( void );
void OnPlayer ( uint ID, const CStr& name );
void OnPlayerLeave ( uint ID );
/**
* Set the user's name that will be displayed to all players.
* This must not be called after the connection setup.
*/
void SetUserName(const CStrW& username);
// Get a pointer to our player
CPlayer* GetLocalPlayer();
/**
* Set up a connection to the remote networked server.
* @param server IP address or host name to connect to
* @return true on success, false on connection failure
*/
bool SetupConnection(const CStr& server);
CJSMap< PlayerMap > m_JsPlayers;
/**
* Set up a connection to the local server on the current machine.
* @param server object to connect to
*/
void SetupLocalConnection(CNetServer& server);
CStr m_Nickname;
CStr m_Password;
/**
* Poll the connection for messages from the server and process them, and send
* any queued messages.
* This must be called frequently (i.e. once per frame).
*/
void Poll();
CPlayerSlot *m_pLocalPlayerSlot;
CGame *m_pGame;
CGameAttributes *m_pGameAttributes;
/**
* Retrieves the next queued GUI message, and removes it from the queue.
* The returned value is in the GetScriptInterface() JS context.
*
* This is the only mechanism for the networking code to send messages to
* the GUI - it is pull-based (instead of push) so the engine code does not
* need to know anything about the code structure of the GUI scripts.
*
* The structure of the messages is <code>{ "type": "...", ... }</code>.
* The exact types and associated data are not specified anywhere - the
* implementation and GUI scripts must make the same assumptions.
*
* @return next message, or the value 'undefined' if the queue is empty
*/
CScriptValRooted GuiPoll();
// JS event scripts
CScriptObject m_OnStartGame;
CScriptObject m_OnChat;
CScriptObject m_OnConnectComplete;
CScriptObject m_OnDisconnect;
CScriptObject m_OnPlayerJoin;
CScriptObject m_OnPlayerLeave;
/**
* Add a message to the queue, to be read by GuiPoll.
* The script value must be in the GetScriptInterface() JS context.
*/
void PushGuiMessage(const CScriptValRooted& message);
static void ScriptingInit( void );
int StartGame( void );
/**
* Return a concatenation of all messages in the GUI queue,
* for test cases to easily verify the queue contents.
*/
std::wstring TestReadGuiMessages();
protected:
/**
* Get the script interface associated with this network client,
* which is equivalent to the one used by the CGame in the constructor.
*/
ScriptInterface& GetScriptInterface();
virtual bool SetupSession ( CNetSession* pSession );
virtual bool HandleConnect ( CNetSession* pSession );
virtual bool HandleDisconnect ( CNetSession *pSession );
/**
* Send a message to the server.
* @param message message to send
* @return true on success
*/
bool SendMessage(const CNetMessage* message);
virtual void OnConnectComplete ( void );
virtual void OnStartGame ( void );
/**
* Call when the network connection has been successfully initiated.
*/
void HandleConnect();
/**
* Call when the network connection has been lost.
*/
void HandleDisconnect();
/**
* Call when a message has been received from the network.
*/
bool HandleMessage(CNetMessage* message);
/**
* Call when the game has started and all data files have been loaded,
* to signal to the server that we are ready to begin the game.
*/
void LoadFinished();
private:
// Net message / FSM transition handlers
static bool OnConnect(void* context, CFsmEvent* event);
static bool OnHandshake(void* context, CFsmEvent* event);
static bool OnHandshakeResponse(void* context, CFsmEvent* event);
static bool OnAuthenticate(void* context, CFsmEvent* event);
static bool OnChat(void* context, CFsmEvent* event);
static bool OnGameSetup(void* context, CFsmEvent* event);
static bool OnPlayerAssignment(void* context, CFsmEvent* event);
static bool OnInGame(void* context, CFsmEvent* event);
static bool OnGameStart(void* context, CFsmEvent* event);
static bool OnLoadedGame(void* context, CFsmEvent* event);
static bool OnError ( void* pContext, CFsmEvent* pEvent );
static bool OnPlayerJoin ( void* pContext, CFsmEvent* pEvent );
static bool OnHandshake ( void* pContext, CFsmEvent* pEvent );
static bool OnAuthenticate ( void* pContext, CFsmEvent* pEvent );
static bool OnPreGame ( void* pContext, CFsmEvent* pEvent );
static bool OnInGame ( void* pContext, CFsmEvent* pEvent );
static bool OnChat ( void* pContext, CFsmEvent* pEvent );
static bool OnStartGame_ ( void* pContext, CFsmEvent* pEvent );
/**
* Take ownership of a session object, and use it for all network communication.
*/
void SetAndOwnSession(CNetClientSession* session);
bool SetupConnection( JSContext *cx, uintN argc, jsval *argv );
/**
* Push a message onto the GUI queue listing the current player assignments.
*/
void PostPlayerAssignmentsToScript();
PlayerMap m_Players; // List of online players
CGame *m_Game;
CStrW m_UserName;
/// Current network session (or NULL if not connected)
CNetClientSession* m_Session;
/// Turn manager associated with the current game (or NULL if we haven't started the game yet)
CNetClientTurnManager* m_ClientTurnManager;
/// Unique-per-game identifier of this client, used to identify the sender of simulation commands
u32 m_HostID;
/// Latest copy of game setup attributes heard from the server
CScriptValRooted m_GameAttributes;
/// Latest copy of player assignments heard from the server
PlayerAssignmentMap m_PlayerAssignments;
/// 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;
};
/// Global network client for the standard game
extern CNetClient *g_NetClient;
#endif // NETCLIENT_H

View File

@ -16,364 +16,40 @@
*/
#include "precompiled.h"
#include "NetHost.h"
#include "NetSession.h"
#include "NetMessage.h"
#include "NetHost.h"
#include "network/NetMessage.h"
#include "ps/CLogger.h"
#include "simulation2/Simulation2.h"
#include <enet/enet.h>
static const int ENET_DEFAULT_CHANNEL = 0;
static const int CONNECT_TIMEOUT = 5000;
static const int DISCONNECT_TIMEOUT = 1000;
//-----------------------------------------------------------------------------
// Name: CNetHost()
// Desc: Constructor
//-----------------------------------------------------------------------------
CNetHost::CNetHost(ScriptInterface& scriptInterface) :
m_ScriptInterface(scriptInterface)
bool CNetHost::SendMessage(const CNetMessage* message, ENetPeer* peer, const char* peerName)
{
m_Host = NULL;
}
//-----------------------------------------------------------------------------
// Name: ~CNetHost()
// Desc: Destructor
//-----------------------------------------------------------------------------
CNetHost::~CNetHost()
{
// Shutdown(); // TODO: should do something like this except don't call HandleDisconnect()
}
//-----------------------------------------------------------------------------
// Name: Create()
// Desc: Creates a client host
//-----------------------------------------------------------------------------
bool CNetHost::Create()
{
debug_assert(!m_Host);
// Create ENet host
m_Host = enet_host_create(NULL, 1, 0, 0);
if (!m_Host)
ENetPacket* packet = CreatePacket(message);
if (!packet)
return false;
LOGMESSAGE(L"Net: Sending message %hs of size %lu to %s", message->ToString().c_str(), (unsigned long)packet->dataLength, peerName);
// Let ENet send the message to peer
if (enet_peer_send(peer, DEFAULT_CHANNEL, packet) < 0)
{
LOGERROR(L"Net: Failed to send packet to peer");
return false;
}
// Don't call enet_host_flush now - let it queue up all the packets
// and send them during the next frame
//
// TODO: we should flush explicitly at some appropriate point before the next frame
return true;
}
//-----------------------------------------------------------------------------
// Name: Create()
// Desc: Creates a server host
//-----------------------------------------------------------------------------
bool CNetHost::Create(u16 port, size_t maxPeers)
ENetPacket* CNetHost::CreatePacket(const CNetMessage* message)
{
ENetAddress addr;
// Bind to default host
addr.host = ENET_HOST_ANY;
addr.port = port;
// Create ENet server
m_Host = enet_host_create(&addr, maxPeers, 0, 0);
if (!m_Host)
return false;
return true;
}
//-----------------------------------------------------------------------------
// Name: Shutdown()
// Desc: Shuts down network server and releases any resources
//-----------------------------------------------------------------------------
void CNetHost::Shutdown()
{
// Disconnect and release each peer
PeerSessionList::iterator it = m_PeerSessions.begin();
for (; it != m_PeerSessions.end(); it++)
{
if (!it->pSession)
continue;
Disconnect(it->pSession);
delete it->pSession;
}
m_PeerSessions.clear();
// Destroy server
if (m_Host)
enet_host_destroy(m_Host);
m_Host = NULL;
}
//-----------------------------------------------------------------------------
// Name: Connect()
// Desc: Connects to the specified remote host
// Note: Only clients use this method for connection to server
//-----------------------------------------------------------------------------
bool CNetHost::Connect(const CStr& host, u16 port)
{
debug_assert(m_Host);
// Bind to specified host
ENetAddress addr;
addr.port = port;
if (enet_address_set_host(&addr, host.c_str()) < 0)
return false;
// Initiate connection, allocate one channel
ENetPeer* pPeer = enet_host_connect(m_Host, &addr, 1);
if (!pPeer)
return false;
// Wait a few seconds for the connection to succeed
// TODO: we ought to poll asynchronously so we can update the GUI while waiting
ENetEvent event;
if (enet_host_service(m_Host, &event, CONNECT_TIMEOUT) > 0 && event.type == ENET_EVENT_TYPE_CONNECT)
{
// Connection succeeded
CNetSession* pNewSession = new CNetSession(this, event.peer);
if (!SetupSession(pNewSession))
{
delete pNewSession;
return false;
}
LOGMESSAGE(L"Net: Successfully connected to server %hs:%d", host.c_str(), port);
// Successfully handled?
if (!HandleConnect(pNewSession))
{
delete pNewSession;
return false;
}
// Store the only server session
PeerSession item;
item.pPeer = event.peer;
item.pSession = pNewSession;
m_PeerSessions.push_back(item);
return true;
}
LOGERROR(L"Net: Connection to server %hs:%d failed", host.c_str(), port);
// Timed out or a host was disconnected
enet_peer_reset(pPeer);
return false;
}
//-----------------------------------------------------------------------------
// Name: ConnectAsync()
// Desc: Connects to the specified remote host
// Note: Only clients use this method for connection to server
//-----------------------------------------------------------------------------
bool CNetHost::ConnectAsync(const CStr& host, u16 port)
{
debug_assert(m_Host);
// Bind to specified host
ENetAddress addr;
addr.port = port;
if (enet_address_set_host(&addr, host.c_str()) < 0)
return false;
// Initiate connection, allocate one channel
ENetPeer* pPeer = enet_host_connect(m_Host, &addr, 1);
if (!pPeer)
return false;
return true;
}
//-----------------------------------------------------------------------------
// Name: Disconnect()
// Desc: Disconnects the specified session from the host
//-----------------------------------------------------------------------------
bool CNetHost::Disconnect(CNetSession* pSession)
{
// Validate parameters
if (!pSession)
return false;
debug_assert(m_Host);
debug_assert(pSession->m_Peer);
// Disconnect peer
enet_peer_disconnect(pSession->m_Peer, 0);
// Allow a few seconds for the disconnect to succeed
ENetEvent event;
while (enet_host_service(m_Host, &event, DISCONNECT_TIMEOUT) > 0)
{
switch (event.type)
{
case ENET_EVENT_TYPE_RECEIVE:
// Drop any received packets
enet_packet_destroy(event.packet);
break;
case ENET_EVENT_TYPE_DISCONNECT:
// Disconnect received for peer
if (!HandleDisconnect(pSession))
return false;
break;
}
}
// Disconnect attempt didn't succeed, force connection down
enet_peer_reset(pSession->m_Peer);
return true;
}
//-----------------------------------------------------------------------------
// Name: ProcessEvents()
// Desc: Wait for events and shuttles packets between the host and its peers
//-----------------------------------------------------------------------------
void CNetHost::Poll()
{
debug_assert(m_Host);
// Poll host for events
ENetEvent event;
while (enet_host_service(m_Host, &event, 0) > 0)
{
switch (event.type)
{
case ENET_EVENT_TYPE_CONNECT:
{
// A new client has connected, handle it
CNetSession* pSession = new CNetSession(this, event.peer);
// Setup new session
if (!SetupSession(pSession))
{
delete pSession;
break;
}
LOGMESSAGE(L"Net: A new client connected from %x:%u", event.peer->address.host, event.peer->address.port);
// Successfully handled?
if (!HandleConnect(pSession))
{
delete pSession;
break;
}
event.peer->data = pSession;
// Add new item to internal list
PeerSession item;
item.pPeer = event.peer;
item.pSession = pSession;
m_PeerSessions.push_back( item );
break;
}
case ENET_EVENT_TYPE_DISCONNECT:
{
// Client has disconnected, handle it
PeerSessionList::iterator it = m_PeerSessions.begin();
for (; it != m_PeerSessions.end(); it++)
{
// Is this our session?
if (it->pPeer == event.peer)
{
LOGMESSAGE(L"Net: %p disconnected", event.peer->data);
// Successfully handled?
if (HandleDisconnect(it->pSession))
m_PeerSessions.erase(it);
}
}
break;
}
case ENET_EVENT_TYPE_RECEIVE:
{
// A new data packet was received from client, handle message
PeerSessionList::iterator it = m_PeerSessions.begin();
for (; it != m_PeerSessions.end(); it++)
{
// Is this our session?
if (it->pPeer == event.peer)
{
bool ok = false;
// Create message from raw data
CNetMessage* pNewMessage = CNetMessageFactory::CreateMessage(event.packet->data, event.packet->dataLength, m_ScriptInterface);
if (pNewMessage)
{
LOGMESSAGE(L"Message %hs of size %lu was received from %p", pNewMessage->ToString().c_str(), (unsigned long)pNewMessage->GetSerializedLength(), event.peer->data);
ok = HandleMessageReceive(pNewMessage, it->pSession);
delete pNewMessage;
}
// Done using the packet
enet_packet_destroy(event.packet);
// TODO: what should we do if ok is false?
// For now, just carry on as if nothing bad happened
}
}
break;
}
}
}
}
//-----------------------------------------------------------------------------
// Name: Broadcast()
// Desc: Broadcast the specified message to connected clients
//-----------------------------------------------------------------------------
void CNetHost::Broadcast(const CNetMessage* pMessage)
{
// Validate parameters
if (!pMessage)
return;
// Loop through the list of sessions and send the message to each
for (uint i = 0; i < GetSessionCount(); i++)
{
CNetSession* pCurrSession = GetSession(i);
if (!pCurrSession)
continue;
SendMessage(pCurrSession, pMessage);
}
}
//-----------------------------------------------------------------------------
// Name: SendMessage()
// Desc: Sends the specified message to peer
//-----------------------------------------------------------------------------
bool CNetHost::SendMessage(const CNetSession* pSession, const CNetMessage* pMessage)
{
// Validate parameters
if (!pMessage || !pSession)
return false;
debug_assert(pSession->m_Peer);
debug_assert(m_Host);
size_t size = pMessage->GetSerializedLength();
size_t size = message->GetSerializedLength();
debug_assert(size); // else we'll fail when accessing the 0th element
@ -382,86 +58,12 @@ bool CNetHost::SendMessage(const CNetSession* pSession, const CNetMessage* pMess
buffer.resize(size);
// Save message to internal buffer
pMessage->Serialize(&buffer[0]);
message->Serialize(&buffer[0]);
// Create a reliable packet
ENetPacket* pPacket = enet_packet_create(&buffer[0], size, ENET_PACKET_FLAG_RELIABLE);
if (!pPacket)
return false;
ENetPacket* packet = enet_packet_create(&buffer[0], size, ENET_PACKET_FLAG_RELIABLE);
if (!packet)
LOGERROR(L"Net: Failed to construct packet");
// Let ENet send the message to peer
if (enet_peer_send(pSession->m_Peer, ENET_DEFAULT_CHANNEL, pPacket) < 0)
{
// ENet failed to send the packet
LOGERROR(L"Net: Failed to send ENet packet to peer");
return false;
}
else
{
LOGMESSAGE(L"Net: Message %hs of size %lu was sent to %p",
pMessage->ToString().c_str(), (unsigned long)size, pSession->m_Peer->data);
}
// Don't call enet_host_flush - let it queue up all the packets
// and send them during the next frame
return true;
}
//-----------------------------------------------------------------------------
// Name: ReceiveMessage()
// Desc: Receives a message from client if incoming packets are available
//-----------------------------------------------------------------------------
CNetMessage* CNetHost::ReceiveMessage(const CNetSession* pSession)
{
// Validate parameters
if (!pSession)
return NULL;
debug_assert(pSession->m_Peer);
// Let ENet receive a message from peer
ENetPacket* pPacket = enet_peer_receive(pSession->m_Peer, ENET_DEFAULT_CHANNEL);
if (!pPacket)
return NULL;
// Create new message
return CNetMessageFactory::CreateMessage(pPacket->data, pPacket->dataLength, m_ScriptInterface);
}
//-----------------------------------------------------------------------------
// Name: HandleMessageReceive()
// Desc: Allow application to handle message recive
//-----------------------------------------------------------------------------
bool CNetHost::HandleMessageReceive(CNetMessage* pMessage, CNetSession* pSession)
{
// Validate parameters
if (!pSession || !pMessage)
return false;
// Update FSM
return pSession->Update(pMessage->GetType(), pMessage);
}
//-----------------------------------------------------------------------------
// Name: GetSessionCount()
// Desc: Returns the number of sessions the host manages
//-----------------------------------------------------------------------------
size_t CNetHost::GetSessionCount() const
{
return m_PeerSessions.size();
}
//-----------------------------------------------------------------------------
// Name: GetSession()
// Desc: Rteurns the session for the index
//-----------------------------------------------------------------------------
CNetSession* CNetHost::GetSession(size_t index)
{
// Validate parameter
if (index >= GetSessionCount())
return NULL;
return m_PeerSessions[index].pSession;
return packet;
}

View File

@ -18,130 +18,45 @@
#ifndef NETHOST_H
#define NETHOST_H
#include "fsm.h"
#include "ps/CStr.h"
#include <vector>
/**
* @file
* Various declarations shared by networking code.
*/
typedef struct _ENetPeer ENetPeer;
typedef struct _ENetPacket ENetPacket;
typedef struct _ENetHost ENetHost;
class CNetSession;
class CNetHost;
class CNetMessage;
class ScriptInterface;
struct PeerSession
struct PlayerAssignment
{
ENetPeer* pPeer;
CNetSession* pSession;
CStrW m_Name; // player name
i32 m_PlayerID; // the player that the given host controls, or -1 if none (observer)
};
typedef std::vector<PeerSession> PeerSessionList;
typedef std::map<CStr, PlayerAssignment> PlayerAssignmentMap; // map from GUID -> assignment
/**
* Wrapper around ENet host concept
*/
class CNetHost
{
NONCOPYABLE(CNetHost);
public:
CNetHost(ScriptInterface& scriptInterface);
virtual ~CNetHost();
bool Create();
bool Create(u16 port, size_t maxPeers);
void Shutdown();
static const int DEFAULT_CHANNEL = 0;
/**
* Indicates whether the host is currently a server
*
* @return Boolean indicating whether the host is a server
* Transmit a message to the given peer.
* @param message message to send
* @param peer peer to send to
* @param peerName name of peer for debug logs
* @return true on success, false on failure
*/
virtual bool IsServer() const { return false; }
static bool SendMessage(const CNetMessage* message, ENetPeer* peer, const char* peerName);
/**
* Returns the number of sessions for the host
*
* @return The number of sessions
* Construct an ENet packet by serialising the given message.
* @return NULL on failure
*/
size_t GetSessionCount() const;
/**
* Returns the session object for the specified index
*
* @param index Index for session
* @return Session object for index or NULL if not found
*/
CNetSession* GetSession(size_t index);
/**
* Connects to foreign host synchronously
*
* @param host Foreign host name
* @param port Port on which the foreign host listens
* @return true on success, false on failure
*/
bool Connect(const CStr& host, u16 port);
/**
* Connects to foreign host asynchronously (i.e. without waiting for the connection
* to succeed or to time out)
*
* @param host Foreign host name
* @param port Port on which the foreign host listens
* @return true on success, false on failure
*/
bool ConnectAsync(const CStr& host, u16 port);
/**
* Disconnects session from host
*
* @param pSession Session representing peer
* @return true on success, false otherwise
*/
bool Disconnect(CNetSession* pSession);
/**
* Listens for incoming connections and dispatches host events
*/
void Poll();
/**
* Broadcast the specified message to connected clients
*
* @param pMessage Message to broadcast
*/
void Broadcast(const CNetMessage* pMessage);
/**
* Send the specified message to client
*
* @param pMessage The message to send
*/
bool SendMessage(const CNetSession* pSession, const CNetMessage* pMessage);
protected:
// Allow application to handle new client connect
virtual bool SetupSession(CNetSession* pSession) = 0;
virtual bool HandleConnect(CNetSession* pSession) = 0;
virtual bool HandleDisconnect(CNetSession* pSession) = 0;
private:
/**
* Receive a message from client if available
*/
CNetMessage* ReceiveMessage(const CNetSession* pSession);
bool HandleMessageReceive(CNetMessage* pMessage, CNetSession* pSession);
ScriptInterface& m_ScriptInterface;
ENetHost* m_Host; // Represents this host
PeerSessionList m_PeerSessions; // Session list of connected peers
static ENetPacket* CreatePacket(const CNetMessage* message);
};
#endif // NETHOST_H

View File

@ -1,164 +0,0 @@
/* 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/>.
*/
/*
*-----------------------------------------------------------------------------
* FILE : NetJsEvents.h
* PROJECT : 0 A.D.
* DESCRIPTION : Definitions for JavaScript events used by the network
* system
*-----------------------------------------------------------------------------
*/
#ifndef NETJSEVENTS_H
#define NETJSEVENTS_H
// INCLUDES
#include "NetSession.h"
#include "scripting/DOMEvent.h"
// DEFINES
enum NetJsEvents
{
JS_EVENT_CLIENT_CONNECT,
JS_EVENT_CONNECT_COMPLETE,
JS_EVENT_CLIENT_DISCONNECT,
JS_EVENT_DISCONNECT,
JS_EVENT_START_GAME,
JS_EVENT_CHAT,
JS_EVENT_LAST
};
class CStartGameEvent: public CScriptEvent
{
public:
CStartGameEvent():
CScriptEvent(L"startGame", JS_EVENT_START_GAME, false)
{}
};
class CChatEvent: public CScriptEvent
{
CStr m_Sender;
CStr m_Message;
public:
CChatEvent(const CStr& sender, const CStr& message):
CScriptEvent(L"chat", JS_EVENT_CHAT, false ),
m_Sender(sender),
m_Message(message)
{
AddLocalProperty(L"sender", &m_Sender, true);
AddLocalProperty(L"message", &m_Message, true);
}
};
class CConnectCompleteEvent: public CScriptEvent
{
CStr m_Message;
bool m_Success;
public:
CConnectCompleteEvent(const CStr& message, bool success):
CScriptEvent(L"connectComplete", JS_EVENT_CONNECT_COMPLETE, false),
m_Message(message),
m_Success(success)
{
AddLocalProperty(L"message", &m_Message, true);
AddLocalProperty(L"success", &m_Success, true);
}
};
class CDisconnectEvent: public CScriptEvent
{
CStrW m_Message;
public:
CDisconnectEvent(const CStr& message):
CScriptEvent(L"disconnect", JS_EVENT_DISCONNECT, false),
m_Message(message)
{
AddLocalProperty(L"message", &m_Message, true);
}
};
class CClientConnectDisconnectCommon: public CScriptEvent
{
uint m_SessionID;
CStr m_Name;
CNetSession *m_Session;
public:
CClientConnectDisconnectCommon(const wchar_t* UNUSED(eventName), int UNUSED(eventType),
int sessionID, const CStr& name, CNetSession* pSession)
: CScriptEvent(L"clientConnect", JS_EVENT_CLIENT_CONNECT, false),
m_SessionID(sessionID),
m_Name(name),
m_Session(pSession)
{
AddLocalProperty(L"id", &m_SessionID, true);
AddLocalProperty(L"name", &m_Name, true);
if (m_Session)
AddLocalProperty(L"session", &m_Session, true);
}
};
struct CClientConnectEvent: public CClientConnectDisconnectCommon
{
CClientConnectEvent(int sessionID, const CStr& name):
CClientConnectDisconnectCommon(
L"clientConnect",
JS_EVENT_CLIENT_CONNECT,
sessionID,
name,
NULL)
{}
CClientConnectEvent(CNetSession *pSession):
CClientConnectDisconnectCommon(
L"clientConnect",
JS_EVENT_CLIENT_CONNECT,
pSession->GetID(),
pSession->GetName(),
pSession)
{}
};
struct CClientDisconnectEvent: public CClientConnectDisconnectCommon
{
CClientDisconnectEvent(int sessionID, const CStr& name):
CClientConnectDisconnectCommon(
L"clientDisconnect",
JS_EVENT_CLIENT_DISCONNECT,
sessionID,
name,
NULL)
{}
CClientDisconnectEvent(CNetSession *pSession):
CClientConnectDisconnectCommon(
L"clientDisconnect",
JS_EVENT_CLIENT_DISCONNECT,
pSession->GetID(),
pSession->GetName(),
pSession)
{}
};
#endif // NETJSEVENTS_H

View File

@ -15,20 +15,11 @@
* along with 0 A.D. If not, see <http://www.gnu.org/licenses/>.
*/
/**
*-----------------------------------------------------------------------------
* FILE : NetMessage.cpp
* PROJECT : 0 A.D.
* DESCRIPTION :
*-----------------------------------------------------------------------------
*/
// INCLUDES
#include "precompiled.h"
#include "ps/CLogger.h"
#include "Network.h"
#include "NetMessage.h"
#include "ps/CLogger.h"
#include "ps/Game.h"
#include "simulation2/Simulation2.h"
@ -36,9 +27,6 @@
#define ALLNETMSGS_IMPLEMENT
#include "NetMessages.h"
// DEFINES
#define LOG_CATEGORY L"net"
//-----------------------------------------------------------------------------
// Name: CNetMessage()
// Desc: Constructor
@ -46,7 +34,6 @@
CNetMessage::CNetMessage( void )
{
m_Type = NMT_INVALID;
m_Dirty = false;
}
//-----------------------------------------------------------------------------
@ -56,7 +43,6 @@ CNetMessage::CNetMessage( void )
CNetMessage::CNetMessage( NetMessageType type )
{
m_Type = type;
m_Dirty = false;
}
//-----------------------------------------------------------------------------
@ -65,7 +51,6 @@ CNetMessage::CNetMessage( NetMessageType type )
//-----------------------------------------------------------------------------
CNetMessage::~CNetMessage( void )
{
m_Dirty = false;
}
//-----------------------------------------------------------------------------
@ -159,19 +144,15 @@ CNetMessage* CNetMessageFactory::CreateMessage(const void* pData,
switch ( header.GetType() )
{
case NMT_GAME_SETUP:
pNewMessage = new CGameSetupMessage;
pNewMessage = new CGameSetupMessage(scriptInterface);
break;
case NMT_ASSIGN_PLAYER_SLOT:
pNewMessage = new CAssignPlayerSlotMessage;
case NMT_PLAYER_ASSIGNMENT:
pNewMessage = new CPlayerAssignmentMessage;
break;
case NMT_PLAYER_CONFIG:
pNewMessage = new CPlayerConfigMessage;
break;
case NMT_PLAYER_JOIN:
pNewMessage = new CPlayerJoinMessage;
case NMT_LOADED_GAME:
pNewMessage = new CLoadedGameMessage;
break;
case NMT_SERVER_HANDSHAKE:
@ -182,14 +163,6 @@ CNetMessage* CNetMessageFactory::CreateMessage(const void* pData,
pNewMessage = new CSrvHandshakeResponseMessage;
break;
case NMT_CONNECT_COMPLETE:
pNewMessage = new CConnectCompleteMessage;
break;
case NMT_ERROR:
pNewMessage = new CErrorMessage;
break;
case NMT_CLIENT_HANDSHAKE:
pNewMessage = new CCliHandshakeMessage;
break;
@ -227,7 +200,7 @@ CNetMessage* CNetMessageFactory::CreateMessage(const void* pData,
break;
default:
LOG(CLogger::Error, LOG_CATEGORY, L"CNetMessageFactory::CreateMessage(): Unknown message received" );
LOGERROR(L"CNetMessageFactory::CreateMessage(): Unknown message type '%d' received", header.GetType());
break;
}
@ -236,3 +209,18 @@ CNetMessage* CNetMessageFactory::CreateMessage(const void* pData,
return pNewMessage;
}
CNetMessage* CNetMessageFactory::CloneMessage( const CNetMessage* message, ScriptInterface& scriptInterface )
{
// TODO: maybe this could be implemented more efficiently,
// particularly for script messages where serialisation is
// relatively expensive
size_t len = message->GetSerializedLength();
u8* buffer = new u8[len];
u8* newbuf = message->Serialize(buffer);
if (!newbuf)
return NULL;
debug_assert(newbuf == buffer+len);
return CreateMessage(buffer, len, scriptInterface);
}

View File

@ -15,22 +15,10 @@
* along with 0 A.D. If not, see <http://www.gnu.org/licenses/>.
*/
/**
*-----------------------------------------------------------------------------
* FILE : NetMessage.h
* PROJECT : 0 A.D.
* DESCRIPTION : Defines the basic interface for network messages
*-----------------------------------------------------------------------------
*/
#ifndef NETMESSAGE_H
#define NETMESSAGE_H
// INCLUDES
#include "Serialization.h"
#include "ps/Vector2D.h"
#include <map>
// We need the enum from NetMessages.h, but we can't create any classes in
// NetMessages.h, since they in turn require CNetMessage to be defined
@ -38,13 +26,9 @@
#include "NetMessages.h"
#undef ALLNETMSGS_DONT_CREATE_NMTS
/*
CLASS : CNetMessage
DESCRIPTION : CNetMessage is the base class for all network messages
exchanged within the game.
NOTES :
/**
* The base class for all network messages exchanged within the game.
*/
class CNetMessage : public ISerializable
{
NONCOPYABLE(CNetMessage);
@ -58,27 +42,11 @@ public:
virtual ~CNetMessage( void );
/**
* Retrieves the message header. If changes are made on header,SetDirty
* must be called on the header's message
*
* @return Message header
* Retrieves the message type.
* @return Message type
*/
//const CNetMessageHeader& GetHeader( void ) const { return m_Header; }
NetMessageType GetType( void ) const { return m_Type; }
/**
* Returns whether the message has changed since its last use
*
* @return true if it changed or false otherwise
*/
bool GetDirty( void ) const { return m_Dirty; }
/**
* Specify the message has changed since its last use
*
*/
void SetDirty( void ) { m_Dirty = true; }
/**
* Serialize the message into the specified buffer parameter. The size
* required by the buffer parameter can be found by a call to
@ -102,23 +70,6 @@ public:
*/
virtual const u8* Deserialize( const u8* pStart, const u8* pEnd );
/**
* Deserializes the specified message from the specified buffer using
* registered deserializers.
*
* @param messageType Message type
* @param pBuffer Buffer from which to deserialize
* @param bufferSize The size in bytes of the buffer
* @return A pointer to a newly created
* CNetMessage, or NULL if the message was
* not correctly deserialized.
*/
static CNetMessage* Deserialize(
NetMessageType type,
const u8* pBuffer,
uint bufferSize );
//static CNetMessage* Deserialize(ENetMessageType type, u8 *buffer, uint length);
/**
* Retrieves the size in bytes of the serialized message. Before calling
* Serialize, the memory size for the buffer where to serialize the message
@ -136,7 +87,6 @@ public:
virtual CStr ToString( void ) const;
private:
bool m_Dirty; // Message has been modified
NetMessageType m_Type; // Message type
};
@ -159,13 +109,13 @@ public:
*/
static CNetMessage* CreateMessage( const void* pData, size_t dataSize, ScriptInterface& scriptInterface );
private:
// Not implemented
CNetMessageFactory( void );
~CNetMessageFactory( void );
CNetMessageFactory( const CNetMessageFactory& );
CNetMessageFactory& operator=( const CNetMessageFactory& );
/**
* Clone a message object into a new scripting context.
* @param message message to clone (can come from any script context)
* @param scriptInterface script context to use for the new message
* @return new message, or NULL on failure
*/
static CNetMessage* CloneMessage( const CNetMessage* message, ScriptInterface& scriptInterface );
};
/**
@ -190,6 +140,24 @@ private:
ScriptInterface& m_ScriptInterface;
};
/**
* Special message type for updated to game startup settings.
*/
class CGameSetupMessage : public CNetMessage
{
public:
CGameSetupMessage(ScriptInterface& scriptInterface);
CGameSetupMessage(ScriptInterface& scriptInterface, jsval data);
virtual u8* Serialize(u8* pBuffer) const;
virtual const u8* Deserialize(const u8* pStart, const u8* pEnd);
virtual size_t GetSerializedLength() const;
virtual CStr ToString() const;
CScriptValRooted m_Data;
private:
ScriptInterface& m_ScriptInterface;
};
// This time, the classes are created
#include "NetMessages.h"

View File

@ -19,6 +19,7 @@
#include "NetMessage.h"
#include "lib/utf8.h"
#include "scriptinterface/ScriptInterface.h"
#include "simulation2/serialization/BinarySerializer.h"
#include "simulation2/serialization/StdDeserializer.h"
@ -145,12 +146,58 @@ size_t CSimulationMessage::GetSerializedLength() const
CStr CSimulationMessage::ToString() const
{
std::string source;
if (!m_ScriptInterface.CallFunction(m_Data.get(), "toSource", source))
source = "ERROR";
std::string source = utf8_from_wstring(m_ScriptInterface.ToString(m_Data.get()));
std::stringstream stream;
stream << "CSimulationMessage { m_Client: " << m_Client << ", m_Player: " << m_Player << ", m_Turn: " << m_Turn << ", m_Data: " << source << " }";
return stream.str();
}
CGameSetupMessage::CGameSetupMessage(ScriptInterface& scriptInterface) :
CNetMessage(NMT_GAME_SETUP), m_ScriptInterface(scriptInterface)
{
}
CGameSetupMessage::CGameSetupMessage(ScriptInterface& scriptInterface, jsval data) :
CNetMessage(NMT_GAME_SETUP), m_ScriptInterface(scriptInterface),
m_Data(scriptInterface.GetContext(), data)
{
}
u8* CGameSetupMessage::Serialize(u8* pBuffer) const
{
// TODO: ought to handle serialization exceptions
u8* pos = CNetMessage::Serialize(pBuffer);
CBufferBinarySerializer serializer(m_ScriptInterface, pos);
serializer.ScriptVal("command", m_Data);
return serializer.GetBuffer();
}
const u8* CGameSetupMessage::Deserialize(const u8* pStart, const u8* pEnd)
{
// TODO: ought to handle serialization exceptions
const u8* pos = CNetMessage::Deserialize(pStart, pEnd);
std::istringstream stream(std::string(pos, pEnd));
CStdDeserializer deserializer(m_ScriptInterface, stream);
deserializer.ScriptVal(m_Data);
return pEnd;
}
size_t CGameSetupMessage::GetSerializedLength() const
{
CLengthBinarySerializer serializer(m_ScriptInterface);
serializer.ScriptVal("command", m_Data);
return CNetMessage::GetSerializedLength() + serializer.GetLength();
}
CStr CGameSetupMessage::ToString() const
{
std::string source = utf8_from_wstring(m_ScriptInterface.ToString(m_Data.get()));
std::stringstream stream;
stream << "CGameSetupMessage { m_Data: " << source << " }";
return stream.str();
}

View File

@ -16,36 +16,28 @@
*/
/**
*-----------------------------------------------------------------------------
* FILE : NetMessages.h
* PROJECT : 0 A.D.
* DESCRIPTION : The list of messages used by the network subsystem
*-----------------------------------------------------------------------------
* @file
* The list of messages used by the network subsystem.
*/
#ifndef NETMESSAGES_H
#define NETMESSAGES_H
// INCLUDES
#include "ps/CStr.h"
#include "scripting/JSSerialization.h"
#include "scriptinterface/ScriptVal.h"
// DEFINES
#define PS_PROTOCOL_MAGIC 0x5073013f // 'P', 's', 0x01, '?'
#define PS_PROTOCOL_MAGIC_RESPONSE 0x50630121 // 'P', 'c', 0x01, '!'
#define PS_PROTOCOL_VERSION 0x01010002 // Arbitrary protocol
#define PS_PROTOCOL_VERSION 0x01010003 // Arbitrary protocol
#define PS_DEFAULT_PORT 0x5073 // 'P', 's'
// Defines the list of message types. The order of the list must not change
// Defines the list of message types. The order of the list must not change.
// The message types having a negative value are used internally and not sent
// over the network. The message types used for network communication have
// positive values.
enum NetMessageType
{
NMT_ERROR = -256, // Delivery of error states
NMT_CONNECT_COMPLETE, // Connection is complete
NMT_CLOSE_REQUEST, // Close connection request
NMT_CONNECT_COMPLETE = -256, // Connection is complete
NMT_INVALID = 0, // Invalid message
NMT_SERVER_HANDSHAKE, // Handshake stage
NMT_CLIENT_HANDSHAKE,
@ -53,18 +45,11 @@ enum NetMessageType
NMT_AUTHENTICATE, // Authentication stage
NMT_AUTHENTICATE_RESULT,
NMT_CHAT, // Common chat message
NMT_PLAYER_JOIN, // Pre-game stage
NMT_PLAYER_LEAVE,
NMT_GAME_SETUP,
NMT_ASSIGN_PLAYER_SLOT,
NMT_PLAYER_CONFIG,
NMT_FILES_REQUIRED,
NMT_FILE_REQUEST,
NMT_FILE_CHUNK,
NMT_FILE_CHUNK_ACK,
NMT_FILE_PROGRESS,
NMT_PLAYER_ASSIGNMENT,
NMT_LOADED_GAME,
NMT_GAME_START,
NMT_END_COMMAND_BATCH, // In-game stage
NMT_END_COMMAND_BATCH,
NMT_SYNC_CHECK,
NMT_SYNC_ERROR,
NMT_SIMULATION_COMMAND,
@ -80,22 +65,6 @@ enum AuthenticateResultCode
ARC_NICK_INVALID,
};
enum
{
CHAT_RECIPIENT_FIRST = 0xFFFD,
CHAT_RECIPIENT_ENEMIES = 0xFFFD,
CHAT_RECIPIENT_ALLIES = 0xFFFE,
CHAT_RECIPIENT_ALL = 0xFFFF
};
enum
{
ASSIGN_OPEN,
ASSIGN_CLOSED,
ASSIGN_AI,
ASSIGN_SESSION
};
#endif // NETMESSAGES_H
#ifdef CREATING_NMT
@ -112,7 +81,7 @@ START_NMT_CLASS_(SrvHandshake, NMT_SERVER_HANDSHAKE)
NMT_FIELD_INT(m_SoftwareVersion, u32, 4)
END_NMT_CLASS()
START_NMT_CLASS_(CliHandshake,NMT_CLIENT_HANDSHAKE)
START_NMT_CLASS_(CliHandshake, NMT_CLIENT_HANDSHAKE)
NMT_FIELD_INT(m_MagicResponse, u32, 4)
NMT_FIELD_INT(m_ProtocolVersion, u32, 4)
NMT_FIELD_INT(m_SoftwareVersion, u32, 4)
@ -125,14 +94,14 @@ START_NMT_CLASS_(SrvHandshakeResponse, NMT_SERVER_HANDSHAKE_RESPONSE)
END_NMT_CLASS()
START_NMT_CLASS_(Authenticate, NMT_AUTHENTICATE)
NMT_FIELD(CStr8, m_GUID)
NMT_FIELD(CStrW, m_Name)
//NMT_FIELD(CPasswordHash, m_Password)
NMT_FIELD(CStrW, m_Password)
END_NMT_CLASS()
START_NMT_CLASS_(AuthenticateResult, NMT_AUTHENTICATE_RESULT)
NMT_FIELD_INT(m_Code, u32, 4)
NMT_FIELD_INT(m_SessionID, u32, 2)
NMT_FIELD_INT(m_HostID, u32, 2)
NMT_FIELD(CStrW, m_Message)
END_NMT_CLASS()
@ -142,36 +111,15 @@ START_NMT_CLASS_(Chat, NMT_CHAT)
NMT_FIELD(CStrW, m_Message)
END_NMT_CLASS()
START_NMT_CLASS_(PlayerJoin, NMT_PLAYER_JOIN)
NMT_START_ARRAY(m_Clients)
NMT_FIELD_INT(m_SessionID, u32, 2)
NMT_FIELD(CStr, m_Name)
NMT_END_ARRAY()
END_NMT_CLASS()
START_NMT_CLASS_(PlayerLeave, NMT_PLAYER_LEAVE)
NMT_FIELD_INT(m_SessionID, u32, 2)
END_NMT_CLASS()
START_NMT_CLASS_(GameSetup, NMT_GAME_SETUP)
NMT_START_ARRAY(m_Values)
START_NMT_CLASS_(PlayerAssignment, NMT_PLAYER_ASSIGNMENT)
NMT_START_ARRAY(m_Hosts)
NMT_FIELD(CStr8, m_GUID)
NMT_FIELD(CStrW, m_Name)
NMT_FIELD(CStrW, m_Value)
NMT_FIELD_INT(m_PlayerID, u8, 1)
NMT_END_ARRAY()
END_NMT_CLASS()
START_NMT_CLASS_(AssignPlayerSlot, NMT_ASSIGN_PLAYER_SLOT)
NMT_FIELD_INT(m_SlotID, u32, 2)
NMT_FIELD_INT(m_Assignment, u32, 1)
NMT_FIELD_INT(m_SessionID, u32, 2) // Only applicable for PS_ASSIGN_SESSION
END_NMT_CLASS()
START_NMT_CLASS_(PlayerConfig, NMT_PLAYER_CONFIG)
NMT_FIELD_INT(m_PlayerID, u32, 2)
NMT_START_ARRAY(m_Values)
NMT_FIELD(CStrW, m_Name)
NMT_FIELD(CStrW, m_Value)
NMT_END_ARRAY()
START_NMT_CLASS_(LoadedGame, NMT_LOADED_GAME)
END_NMT_CLASS()
START_NMT_CLASS_(GameStart, NMT_GAME_START)

File diff suppressed because it is too large Load Diff

View File

@ -15,54 +15,36 @@
* along with 0 A.D. If not, see <http://www.gnu.org/licenses/>.
*/
/**
*-----------------------------------------------------------------------------
* FILE : NetServer.h
* PROJECT : 0 A.D.
* DESCRIPTION : Network server class interface file
*-----------------------------------------------------------------------------
*/
#ifndef NETSERVER_H
#define NETSERVER_H
// INCLUDES
#include "Network.h"
#include "NetHost.h"
#include "NetSession.h"
#include "NetTurnManager.h"
#include "scripting/ScriptableObject.h"
#include "ps/scripting/JSMap.h"
#include "ps/Player.h"
#include "ps/Game.h"
#include "scripting/ScriptObject.h"
#include <map>
#include "scriptinterface/ScriptVal.h"
#include <vector>
// DECLARATIONS
#define SERVER_SESSIONID 1
#define CLIENT_MIN_SESSIONID 100
#define MAX_CLIENTS 8
#define MAX_OBSERVERS 5
#define DEFAULT_SERVER_NAME L"Noname Server"
#define DEFAULT_PLAYER_NAME L"Noname Player"
#define DEFAULT_WELCOME_MESSAGE L"Noname Server Welcome Message"
#define DEFAULT_HOST_PORT 0x5073
class CGameAttributes;
class CNetClientSessionLocal;
class CNetServerSession;
class CNetServerTurnManager;
class CFsmEvent;
class ScriptInterface;
class CPlayerAssignmentMessage;
enum NetServerState
{
// We haven't opened the port yet, we're just setting some stuff up.
// This is probably equivalent to the first "Start Network Game" screen
SERVER_STATE_PREBIND,
SERVER_STATE_UNCONNECTED,
// The server is open and accepting connections. This is the screen where
// rules are set up by the operator and where players join and select civs
// and stuff.
SERVER_STATE_PREGAME,
// All the hosts are connected and are loading the game
SERVER_STATE_LOADING,
// The one with all the killing ;-)
SERVER_STATE_INGAME,
@ -71,214 +53,157 @@ enum NetServerState
SERVER_STATE_POSTGAME
};
/**
* Server session representation of client state
*/
enum
{
NSS_HANDSHAKE = 1300,
NSS_AUTHENTICATE = 1400,
NSS_PREGAME = 1500,
NSS_INGAME = 1600
NSS_HANDSHAKE,
NSS_AUTHENTICATE,
NSS_PREGAME,
NSS_INGAME
};
enum
{
NMT_APP_PLAYER_LEAVE = NMT_LAST + 100,
NMT_APP_PREGAME = NMT_LAST + 200,
NMT_APP_OBSERVER = NMT_LAST + 300
};
typedef std::map< uint, CNetSession* > IDSessionMap;
typedef std::vector< CNetSession* > SessionList;
/*
CLASS : CNetServer
DESCRIPTION : CNetServer implements a network server for the game.
It receives data and connection requests from clients.
Under the hood, it uses ENet library to manage connected
peers and bandwidth among these.
NOTES :
*/
class CNetServer : public CNetHost,
public CJSObject<CNetServer>
/**
* Network server.
* Handles all the coordination between players.
* One person runs this object, and every player (including the host) connects their CNetClient to it.
*
* TODO: ideally the ENet server would run in a separate thread so it can receive
* and forward messages with minimal latency. But that's not supported now.
*
* TODO: we need to be much more careful at handling client states, to cope with
* e.g. people being in the middle of connecting or authenticating when we start the game.
*/
class CNetServer
{
NONCOPYABLE(CNetServer);
public:
CNetServer( ScriptInterface& scriptInterface, CGame* pGame, CGameAttributes* pGameAttributes );
virtual ~CNetServer( void );
bool Start ( JSContext *pContext, uintN argc, jsval *argv );
CNetServer();
virtual ~CNetServer();
/**
* Returns true indicating the host acts as a server
*
* @return Always true
* Get the current server's connection/game state.
*/
virtual bool IsServer( void ) const { return true; }
NetServerState GetState() const { return m_State; }
/**
* Adds a new session to the list of sessions
*
* @param pSession New session to add
* Begin listening for network connections.
* @return true on success, false on error (e.g. port already in use)
*/
void AddSession( CNetSession* pSession );
bool SetupConnection();
/**
* Removes the specified session from the list of sessions. If the session
* isn't found it returns NULL otherwise it returns the session object found.
*
* @param pSession Session to remove
* @return The session object if found, NULL otherwise
* Poll the connections for messages from clients and process them, and send
* any queued messages.
* This must be called frequently (i.e. once per frame).
*/
CNetSession* RemoveSession( CNetSession* pSession );
virtual void Poll();
/**
* Returns the session object for the specified ID
*
* @param sessionID The session ID
* @return A pointer to session for the specified ID or
* NULL if not found
* Send a message to the given network peer.
*/
CNetSession* GetSessionByID( uint sessionID );
bool SendMessage(ENetPeer* peer, const CNetMessage* message);
/**
* Send a message to the given local client.
*/
void SendLocalMessage(CNetClientSessionLocal& clientSession, const CNetMessage* message);
/**
* Send a message to all clients who have completed the full connection process
* (i.e. are in the pre-game or in-game states).
*/
bool Broadcast(const CNetMessage* message);
/**
* Register a local client with this server (equivalent to a remote client connecting
* over the network).
*/
void AddLocalClientSession(CNetClientSessionLocal& clientSession);
/**
* Call from the GUI to update the player assignments.
* The given GUID will be (re)assigned to the given player ID.
* Any player currently using that ID will be unassigned.
* The changes will be propagated to all clients.
*/
void AssignPlayer(int playerID, const CStr& guid);
/**
* Call from the GUI to notify all clients that they should start loading the game.
*/
void StartGame();
/**
* Call from the GUI to update the game setup attributes.
* This must be called at least once before starting the game.
* The changes will be propagated to all clients.
* @param attrs game attributes, in the script context of GetScriptInterface()
*/
void UpdateGameAttributes(const CScriptValRooted& attrs);
/**
* Get the script context used for game attributes.
*/
ScriptInterface& GetScriptInterface();
protected:
virtual bool SetupSession ( CNetSession* pSession );
virtual bool HandleConnect ( CNetSession* pSession );
virtual bool HandleDisconnect ( CNetSession *pSession );
/// Callback for autostart; called when a player has finished connecting
virtual void OnAddPlayer() { }
/// Callback for autostart; called when a player has left the game
virtual void OnRemovePlayer() { }
private:
void AddPlayer(const CStr& guid, const CStrW& name);
void RemovePlayer(const CStr& guid);
void SendPlayerAssignments();
PlayerAssignmentMap m_PlayerAssignments;
CScriptValRooted m_GameAttributes;
void SetupSession(CNetServerSession* session);
bool HandleConnect(CNetServerSession* session);
bool HandleDisconnect(CNetServerSession* session);
void OnUserJoin(CNetServerSession* session);
static bool OnClientHandshake(void* context, CFsmEvent* event);
static bool OnAuthenticate(void* context, CFsmEvent* event);
static bool OnInGame(void* context, CFsmEvent* event);
static bool OnChat(void* context, CFsmEvent* event);
static bool OnLoadedGame(void* context, CFsmEvent* event);
void CheckGameLoadStatus(CNetServerSession* changedSession);
void ConstructPlayerAssignmentMessage(CPlayerAssignmentMessage& message);
bool HandleMessageReceive(const CNetMessage* message, CNetServerSession* session);
/**
* Loads the player properties into the specified message
*
* @param pMessage Message where to load player properties
* @param pPlayer Player for which we load the properties
* Internal script context for (de)serializing script messages.
* (TODO: we shouldn't bother deserializing (except for debug printing of messages),
* we should just forward messages blindly and efficiently.)
*/
void BuildPlayerConfigMessage(
CPlayerConfigMessage* pMessage,
CPlayer* pPlayer );
ScriptInterface* m_ScriptInterface;
/**
* Callback function used by the BuildPlayerSetupMessage to iterate over
* the player properties. It will be called for each property of the player
*
* @param name Property name
* @param pProperty Pointer to player property
* @param pData Context pointer passed on iteration startup
*/
static void PlayerConfigMessageCallback(
const CStrW& name,
ISynchedJSProperty* pProperty,
void* pData );
ENetHost* m_Host;
std::vector<ENetPeer*> m_Peers;
std::vector<CNetServerSession*> m_Sessions;
/**
* Loads game properties into the specified message
*
* @param pMessage Message where to load game properties
*/
void BuildGameSetupMessage( CGameSetupMessage* pMessage );
/**
* Loads player slot properties into the specified message
*
* @param pMessage Message where to load player properties
* @param pPlayerSlot Player slot properties
*/
void BuildPlayerSlotAssignmentMessage(
CAssignPlayerSlotMessage* pMessage,
CPlayerSlot* pPlayerSlot );
std::vector<std::pair<CNetServerSession*, CNetMessage*> > m_LocalMessageQueue;
/**
* Callback function used by the BuildGameSetupMessage to iterate over the
* game properties. It will be called for each property of the game
*
* @param name Property name
* @param pProperty Pointer to game property
* @param pData Context pointer passed on iteration startup
*/
static void GameSetupMessageCallback(
const CStrW& name,
ISynchedJSProperty *pProperty,
void *pData );
NetServerState m_State;
/**
* Retrieves a free session ID from the recycled sessions list
*
* @return Free session ID
*/
uint GetFreeSessionID( void ) const;
CStrW m_ServerName;
CStrW m_WelcomeMessage;
int m_Port;
IDSessionMap m_IDSessions; // List of connected ID and session pairs
public:
void SetPlayerPassword ( const CStr& password );
CStrW GetPlayerName ( void ) const { return m_PlayerName; }
NetServerState GetState ( void ) const { return m_State; }
int StartGame ( void );
static void ScriptingInit ( void );
protected:
// Assign a session ID to the session. Do this just before calling AddSession
void AssignSessionID( CNetSession* pSession );
// Call the JS callback for incoming events
void OnPlayerChat ( const CStrW& from, const CStrW& message );
virtual void OnPlayerJoin ( CNetSession* pSession );
virtual void OnPlayerLeave ( CNetSession* pSession );
void SetupPlayer ( CNetSession* pSession );
//static bool OnPlayerJoin ( void* pContext, CFsmEvent* pEvent );
static bool OnError ( void* pContext, CFsmEvent* pEvent );
static bool OnHandshake ( void* pContext, CFsmEvent* pEvent );
static bool OnAuthenticate ( void* pContext, CFsmEvent* pEvent );
static bool OnPreGame ( void* pContext, CFsmEvent* pEvent );
static bool OnInGame ( void* pContext, CFsmEvent* pEvent );
static bool OnChat ( void* pContext, CFsmEvent* pEvent );
// Ask the server if the session is allowed to start observing.
//
// Returns:
// true if the session should be made an observer
// false otherwise
virtual bool AllowObserver( CNetSession* pSession );
public:
CGame* m_Game; // Pointer to actual game
protected:
CGameAttributes* m_GameAttributes; // Stores game attributes
private:
CJSMap< IDSessionMap > m_JsSessions;
/*
All sessions that have observer status (observer as in watcher - simple
chatters don't have an entry here, only in m_Sessions).
Sessions are added here after they have successfully requested observer
status.
*/
SessionList m_Observers;
uint m_MaxObservers; // Maximum number of observers
NetServerState m_State; // Holds server state
CStrW m_Name; // Server name
CStrW m_WelcomeMessage; // Nice welcome message
CStrW m_PlayerName; // Player name
CStrW m_PlayerPassword; // Player password
int m_Port; // The listening port
CScriptObject m_OnChat;
CScriptObject m_OnClientConnect;
CScriptObject m_OnClientDisconnect;
static void AttributeUpdate ( const CStrW& name, const CStrW& newValue, void* pData);
static void PlayerAttributeUpdate ( const CStrW& name, const CStrW& value, CPlayer* pPlayer, void* pData );
static void PlayerSlotAssignment ( void* pData, CPlayerSlot* pPlayerSlot );
u32 m_NextHostID;
CNetServerTurnManager* m_ServerTurnManager;
};
/// Global network server for the standard game
extern CNetServer *g_NetServer;
#endif // NETSERVER_H

View File

@ -17,65 +17,228 @@
#include "precompiled.h"
#include "NetSession.h"
#include "NetClient.h"
#include "NetServer.h"
#include "NetMessage.h"
#include "ps/CLogger.h"
#include "scriptinterface/ScriptInterface.h"
static const uint INVALID_SESSION = 0;
#include <enet/enet.h>
//-----------------------------------------------------------------------------
// Name: CNetSession()
// Desc: Constructor
//-----------------------------------------------------------------------------
CNetSession::CNetSession(CNetHost* pHost, ENetPeer* pPeer)
static const int CHANNEL_COUNT = 1;
CNetClientSession::CNetClientSession(CNetClient& client) :
m_Client(client)
{
m_Host = pHost;
m_Peer = pPeer;
m_ID = INVALID_SESSION;
m_PlayerSlot = NULL;
}
//-----------------------------------------------------------------------------
// Name: ~CNetSession()
// Desc: Destructor
//-----------------------------------------------------------------------------
CNetSession::~CNetSession()
CNetClientSession::~CNetClientSession()
{
m_Peer = NULL;
}
//-----------------------------------------------------------------------------
// Name: SetName()
// Desc: Set a new name for the session
//-----------------------------------------------------------------------------
void CNetSession::SetName(const CStr& name)
CNetClientSessionRemote::CNetClientSessionRemote(CNetClient& client) :
CNetClientSession(client), m_Host(NULL), m_Server(NULL)
{
m_Name = name;
}
//-----------------------------------------------------------------------------
// Name: SetID()
// Desc: Set new ID for this session
//-----------------------------------------------------------------------------
void CNetSession::SetID(uint ID)
CNetClientSessionRemote::~CNetClientSessionRemote()
{
m_ID = ID;
}
//-----------------------------------------------------------------------------
// Name: SetPlayerSlot()
// Desc: Set the player slot for this session
//-----------------------------------------------------------------------------
void CNetSession::SetPlayerSlot(CPlayerSlot* pPlayerSlot)
bool CNetClientSessionRemote::Connect(u16 port, const CStr& server)
{
m_PlayerSlot = pPlayerSlot;
debug_assert(!m_Host);
debug_assert(!m_Server);
// Create ENet host
ENetHost* host = enet_host_create(NULL, 1, 0, 0);
if (!host)
return false;
// Bind to specified host
ENetAddress addr;
addr.port = port;
if (enet_address_set_host(&addr, server.c_str()) < 0)
return false;
// Initiate connection to server
ENetPeer* peer = enet_host_connect(host, &addr, CHANNEL_COUNT);
if (!peer)
return false;
m_Host = host;
m_Server = peer;
return true;
}
//-----------------------------------------------------------------------------
// Name: ScriptingInit()
// Desc:
//-----------------------------------------------------------------------------
void CNetSession::ScriptingInit()
void CNetClientSessionRemote::Disconnect()
{
AddProperty(L"id", &CNetSession::m_ID);
AddProperty(L"name", &CNetSession::m_Name);
debug_assert(m_Host && m_Server);
CJSObject<CNetSession>::ScriptingInit("NetSession");
// TODO: ought to do reliable async disconnects, probably
enet_peer_disconnect_now(m_Server, 0);
enet_host_destroy(m_Host);
m_Host = NULL;
m_Server = NULL;
}
void CNetClientSessionRemote::Poll()
{
debug_assert(m_Host && m_Server);
ENetEvent event;
while (enet_host_service(m_Host, &event, 0) > 0)
{
switch (event.type)
{
case ENET_EVENT_TYPE_CONNECT:
{
debug_assert(event.peer == m_Server);
// Report the server address
char hostname[256] = "(error)";
enet_address_get_host_ip(&event.peer->address, hostname, ARRAY_SIZE(hostname));
LOGMESSAGE(L"Net client: Connected to %hs:%u", hostname, event.peer->address.port);
GetClient().HandleConnect();
break;
}
case ENET_EVENT_TYPE_DISCONNECT:
{
debug_assert(event.peer == m_Server);
GetClient().HandleDisconnect();
break;
}
case ENET_EVENT_TYPE_RECEIVE:
{
CNetMessage* msg = CNetMessageFactory::CreateMessage(event.packet->data, event.packet->dataLength, GetClient().GetScriptInterface());
if (msg)
{
LOGMESSAGE(L"Net client: Received message %hs of size %lu from server", msg->ToString().c_str(), (unsigned long)msg->GetSerializedLength());
bool ok = GetClient().HandleMessage(msg);
debug_assert(ok); // TODO
delete msg;
}
enet_packet_destroy(event.packet);
break;
}
}
}
}
bool CNetClientSessionRemote::SendMessage(const CNetMessage* message)
{
debug_assert(m_Host && m_Server);
return CNetHost::SendMessage(message, m_Server, "server");
}
CNetClientSessionLocal::CNetClientSessionLocal(CNetClient& client, CNetServer& server) :
CNetClientSession(client), m_Server(server), m_ServerSession(NULL)
{
server.AddLocalClientSession(*this);
client.HandleConnect();
}
void CNetClientSessionLocal::Poll()
{
for (size_t i = 0; i < m_LocalMessageQueue.size(); ++i)
{
CNetMessage* msg = m_LocalMessageQueue[i];
LOGMESSAGE(L"Net client: Received local message %hs of size %lu from server", msg->ToString().c_str(), (unsigned long)msg->GetSerializedLength());
bool ok = GetClient().HandleMessage(msg);
debug_assert(ok); // TODO
delete msg;
}
m_LocalMessageQueue.clear();
}
void CNetClientSessionLocal::Disconnect()
{
// TODO
}
bool CNetClientSessionLocal::SendMessage(const CNetMessage* message)
{
LOGMESSAGE(L"Net client: Sending local message %hs to server", message->ToString().c_str());
m_Server.SendLocalMessage(*this, message);
return true;
}
void CNetClientSessionLocal::AddLocalMessage(const CNetMessage* message)
{
// Clone into the client's script context
CNetMessage* clonedMessage = CNetMessageFactory::CloneMessage(message, GetClient().GetScriptInterface());
if (!clonedMessage)
return;
m_LocalMessageQueue.push_back(clonedMessage);
}
CNetServerSession::CNetServerSession(CNetServer& server) :
m_Server(server)
{
}
CNetServerSession::~CNetServerSession()
{
}
CNetServerSessionRemote::CNetServerSessionRemote(CNetServer& server, ENetPeer* peer) :
CNetServerSession(server), m_Peer(peer)
{
}
void CNetServerSessionRemote::Disconnect()
{
// TODO
}
bool CNetServerSessionRemote::SendMessage(const CNetMessage* message)
{
return GetServer().SendMessage(m_Peer, message);
}
CNetServerSessionLocal::CNetServerSessionLocal(CNetServer& server, CNetClientSessionLocal& clientSession) :
CNetServerSession(server), m_ClientSession(clientSession)
{
}
void CNetServerSessionLocal::Disconnect()
{
// TODO
}
bool CNetServerSessionLocal::SendMessage(const CNetMessage* message)
{
LOGMESSAGE(L"Net server: Sending local message %hs to %p", message->ToString().c_str(), &m_ClientSession);
m_ClientSession.AddLocalMessage(message);
return true;
}

View File

@ -18,90 +18,171 @@
#ifndef NETSESSION_H
#define NETSESSION_H
#include "fsm.h"
#include "scripting/ScriptableObject.h"
#include "network/fsm.h"
#include "network/NetHost.h"
#include "ps/CStr.h"
#include "scriptinterface/ScriptVal.h"
class CPlayerSlot;
class CNetHost;
class CNetSession;
typedef struct _ENetPeer ENetPeer;
class CNetClient;
class CNetServer;
struct FsmActionCtx
class CNetServerSessionLocal; // forward declaration, needed because of circular references
/**
* @file
* Network client/server sessions.
*
* Each session has two classes: CNetClientSession runs on the client,
* and CNetServerSession runs on the server.
* A client runs one session at once; a server typically runs many.
*
* There are two variants of each session: Remote (the normal ENet-based
* network session) and Local (a shortcut when the client and server are
* running inside the same process and don't need the network to communicate).
*/
/**
* The client end of a network session.
* Provides an abstraction of the network interface, allowing communication with the server.
*/
class CNetClientSession
{
CNetHost* pHost;
CNetSession* pSession;
NONCOPYABLE(CNetClientSession);
public:
CNetClientSession(CNetClient& client);
virtual ~CNetClientSession();
virtual void Poll() = 0;
virtual void Disconnect() = 0;
virtual bool SendMessage(const CNetMessage* message) = 0;
CNetClient& GetClient() { return m_Client; }
private:
CNetClient& m_Client;
};
/**
* CNetSession is a wrapper class around the ENet peer concept
* which represents a peer from a network connection. A
* network session is spawned by CNetServer each time a
* client connects and destroyed when it disconnects. When a
* new message is received from a client, its representing
* session object's message handler is called for processing
* that message.
* CNetSession is also a state machine. All client requests
* are delegated to the current state. The current
* CNetSessionState object's methods will change the current
* state as appropriate.
* ENet-based implementation of CNetClientSession.
*/
class CNetSession : public CFsm,
public CJSObject<CNetSession>
class CNetClientSessionRemote : public CNetClientSession
{
NONCOPYABLE(CNetSession);
friend class CNetHost;
NONCOPYABLE(CNetClientSessionRemote);
public:
CNetClientSessionRemote(CNetClient& client);
~CNetClientSessionRemote();
virtual ~CNetSession();
bool Connect(u16 port, const CStr& server);
/**
* Retrieves the name of the session
*
* @return Session name
*/
const CStrW& GetName() const { return m_Name; }
virtual void Poll();
virtual void Disconnect();
virtual bool SendMessage(const CNetMessage* message);
/**
* Set the new name for the session
*
* @param name The session new name
*/
void SetName(const CStr& name);
/**
* Retrieves the ID of the session
*
* @return Session ID
*/
uint GetID() const { return m_ID; }
/**
* Set the ID for this session
*
* @param New session ID
*/
void SetID(uint ID);
FsmActionCtx* GetFsmActionCtx() { return &m_FsmActionCtx; }
void SetPlayerSlot(CPlayerSlot* pPlayerSlot);
CPlayerSlot* GetPlayerSlot() { return m_PlayerSlot; }
static void ScriptingInit();
ENetPacket* CreatePacket(const CNetMessage* message);
private:
ENetHost* m_Host;
ENetPeer* m_Server;
};
// Only the hosts can create sessions
CNetSession(CNetHost* pHost, ENetPeer* pPeer);
/**
* Local implementation of CNetClientSession, for use with servers
* running in the same process.
*/
class CNetClientSessionLocal : public CNetClientSession
{
NONCOPYABLE(CNetClientSessionLocal);
CNetHost* m_Host; // The associated local host
ENetPeer* m_Peer; // Represents the peer host
uint m_ID; // Session ID
CStrW m_Name; // Session name
CPlayerSlot* m_PlayerSlot;
FsmActionCtx m_FsmActionCtx;
public:
CNetClientSessionLocal(CNetClient& client, CNetServer& server);
void SetServerSession(CNetServerSessionLocal* session) { m_ServerSession = session; }
CNetServerSessionLocal* GetServerSession() { return m_ServerSession; }
virtual void Poll();
virtual void Disconnect();
virtual bool SendMessage(const CNetMessage* message);
void AddLocalMessage(const CNetMessage* message);
private:
CNetServer& m_Server;
CNetServerSessionLocal* m_ServerSession;
std::vector<CNetMessage*> m_LocalMessageQueue;
};
/**
* The server's end of a network session.
* Represents an abstraction of the state of the client, storing all the per-client data
* needed by the server.
*/
class CNetServerSession : public CFsm
{
NONCOPYABLE(CNetServerSession);
public:
CNetServerSession(CNetServer& server);
virtual ~CNetServerSession();
CNetServer& GetServer() { return m_Server; }
const CStr& GetGUID() const { return m_GUID; }
void SetGUID(const CStr& guid) { m_GUID = guid; }
const CStrW& GetUserName() const { return m_UserName; }
void SetUserName(const CStrW& name) { m_UserName = name; }
u32 GetHostID() const { return m_HostID; }
void SetHostID(u32 id) { m_HostID = id; }
virtual void Disconnect() = 0;
virtual bool SendMessage(const CNetMessage* message) = 0;
private:
CNetServer& m_Server;
CStr m_GUID;
CStrW m_UserName;
u32 m_HostID;
};
/**
* ENet-based implementation of CNetServerSession.
*/
class CNetServerSessionRemote : public CNetServerSession
{
NONCOPYABLE(CNetServerSessionRemote);
public:
CNetServerSessionRemote(CNetServer& server, ENetPeer* peer);
virtual void Disconnect();
virtual bool SendMessage(const CNetMessage* message);
private:
ENetPeer* m_Peer;
};
/**
* Local implementation of CNetServerSession, for use with clients
* running in the same process.
*/
class CNetServerSessionLocal : public CNetServerSession
{
NONCOPYABLE(CNetServerSessionLocal);
public:
CNetServerSessionLocal(CNetServer& server, CNetClientSessionLocal& clientSession);
virtual void Disconnect();
virtual bool SendMessage(const CNetMessage* message);
private:
CNetClientSessionLocal& m_ClientSession;
};
#endif // NETSESSION_H

View File

@ -19,11 +19,14 @@
#include "NetTurnManager.h"
#include "NetServer.h"
#include "NetClient.h"
#include "network/NetServer.h"
#include "network/NetClient.h"
#include "network/NetMessage.h"
#include "gui/GUIManager.h"
#include "maths/MathUtil.h"
#include "ps/Profile.h"
#include "ps/Pyrogenesis.h"
#include "simulation2/Simulation2.h"
static const int TURN_LENGTH = 200; // TODO: this should be a variable controlled by the server depending on latency
@ -41,9 +44,9 @@ static std::string Hexify(const std::string& s)
return str.str();
}
CNetTurnManager::CNetTurnManager(CSimulation2& simulation, int playerId, int clientId) :
CNetTurnManager::CNetTurnManager(CSimulation2& simulation, int clientId) :
m_Simulation2(simulation), m_CurrentTurn(0), m_ReadyTurn(1), m_DeltaTime(0),
m_PlayerId(playerId), m_ClientId(clientId), m_HasSyncError(false)
m_PlayerId(-1), m_ClientId(clientId), m_HasSyncError(false)
{
// When we are on turn n, we schedule new commands for n+2.
// We know that all other clients have finished scheduling commands for n (else we couldn't have got here).
@ -54,6 +57,11 @@ CNetTurnManager::CNetTurnManager(CSimulation2& simulation, int playerId, int cli
m_QueuedCommands.resize(COMMAND_DELAY + 1);
}
void CNetTurnManager::SetPlayerID(int playerId)
{
m_PlayerId = playerId;
}
bool CNetTurnManager::Update(float frameLength)
{
m_DeltaTime += frameLength;
@ -186,8 +194,7 @@ void CNetClientTurnManager::PostCommand(CScriptValRooted data)
// Transmit command to server
CSimulationMessage msg(m_Simulation2.GetScriptInterface(), m_ClientId, m_PlayerId, m_CurrentTurn + COMMAND_DELAY, data.get());
CNetSession* session = m_NetClient.GetSession(0);
m_NetClient.SendMessage(session, &msg);
m_NetClient.SendMessage(&msg);
// Add to our local queue
//AddCommand(m_ClientId, m_PlayerId, data, m_CurrentTurn + COMMAND_DELAY);
@ -204,8 +211,7 @@ void CNetClientTurnManager::NotifyFinishedOwnCommands(u32 turn)
CEndCommandBatchMessage msg;
msg.m_TurnLength = TURN_LENGTH;
msg.m_Turn = turn;
CNetSession* session = m_NetClient.GetSession(0);
m_NetClient.SendMessage(session, &msg);
m_NetClient.SendMessage(&msg);
}
void CNetClientTurnManager::NotifyFinishedUpdate(u32 turn, const std::string& hash)
@ -218,8 +224,7 @@ void CNetClientTurnManager::NotifyFinishedUpdate(u32 turn, const std::string& ha
CSyncCheckMessage msg;
msg.m_Turn = turn;
msg.m_Hash = hash;
CNetSession* session = m_NetClient.GetSession(0);
m_NetClient.SendMessage(session, &msg);
m_NetClient.SendMessage(&msg);
}
void CNetClientTurnManager::OnSimulationMessage(CSimulationMessage* msg)
@ -230,36 +235,33 @@ void CNetClientTurnManager::OnSimulationMessage(CSimulationMessage* msg)
void CNetServerTurnManager::PostCommand(CScriptValRooted data)
void CNetLocalTurnManager::PostCommand(CScriptValRooted data)
{
#ifdef NETTURN_LOG
NETTURN_LOG(L"PostCommand()\n");
#endif
// Transmit command to all clients
CSimulationMessage msg(m_Simulation2.GetScriptInterface(), m_ClientId, m_PlayerId, m_CurrentTurn + COMMAND_DELAY, data.get());
m_NetServer.Broadcast(&msg);
// Add to our local queue
AddCommand(m_ClientId, m_PlayerId, data, m_CurrentTurn + COMMAND_DELAY);
// Add directly to the next turn, ignoring COMMAND_DELAY,
// because we don't need to compensate for network latency
AddCommand(m_ClientId, m_PlayerId, data, m_CurrentTurn + 1);
}
void CNetServerTurnManager::NotifyFinishedOwnCommands(u32 turn)
void CNetLocalTurnManager::NotifyFinishedOwnCommands(u32 turn)
{
#ifdef NETTURN_LOG
NETTURN_LOG(L"NotifyFinishedOwnCommands(%d)\n", turn);
#endif
NotifyFinishedClientCommands(m_ClientId, turn);
FinishedAllCommands(turn);
}
void CNetServerTurnManager::NotifyFinishedUpdate(u32 turn, const std::string& hash)
void CNetLocalTurnManager::NotifyFinishedUpdate(u32 UNUSED(turn), const std::string& UNUSED(hash))
{
#ifdef NETTURN_LOG
NETTURN_LOG(L"NotifyFinishedUpdate(%d, %s)\n", turn, Hexify(hash).c_str());
#endif
}
NotifyFinishedClientUpdate(m_ClientId, turn, hash);
void CNetLocalTurnManager::OnSimulationMessage(CSimulationMessage* UNUSED(msg))
{
debug_warn(L"This should never be called");
}
CNetServerTurnManager::CNetServerTurnManager(CNetServer& server) :
m_NetServer(server), m_ReadyTurn(1)
{
}
void CNetServerTurnManager::NotifyFinishedClientCommands(int client, u32 turn)
@ -291,8 +293,7 @@ void CNetServerTurnManager::NotifyFinishedClientCommands(int client, u32 turn)
msg.m_Turn = turn;
m_NetServer.Broadcast(&msg);
// Move ourselves to the next turn
FinishedAllCommands(m_ReadyTurn + 1);
m_ReadyTurn = turn;
}
void CNetServerTurnManager::NotifyFinishedClientUpdate(int client, u32 turn, const std::string& hash)
@ -318,7 +319,7 @@ void CNetServerTurnManager::NotifyFinishedClientUpdate(int client, u32 turn, con
break;
// Assume the host is correct (maybe we should choose the most common instead to help debugging)
std::string expected = it->second[m_ClientId];
std::string expected = it->second.begin()->second;
for (std::map<int, std::string>::iterator cit = it->second.begin(); cit != it->second.end(); ++cit)
{
@ -335,9 +336,6 @@ void CNetServerTurnManager::NotifyFinishedClientUpdate(int client, u32 turn, con
msg.m_HashExpected = expected;
m_NetServer.Broadcast(&msg);
// Process it ourselves
OnSyncError(it->first, expected);
break;
}
}
@ -355,37 +353,3 @@ void CNetServerTurnManager::InitialiseClient(int client)
// TODO: do we need some kind of UninitialiseClient in case they leave?
}
void CNetServerTurnManager::OnSimulationMessage(CSimulationMessage* msg)
{
// Send it back to all clients immediately
m_NetServer.Broadcast(msg);
// TODO: we should do some validation of ownership (clients can't send commands on behalf of opposing players)
// TODO: we shouldn't send the message back to the client that first sent it
// Process it ourselves
AddCommand(msg->m_Client, msg->m_Player, msg->m_Data, msg->m_Turn);
}
void CNetLocalTurnManager::PostCommand(CScriptValRooted data)
{
// Add directly to the next turn, ignoring COMMAND_DELAY,
// because we don't need to compensate for network latency
AddCommand(m_ClientId, m_PlayerId, data, m_CurrentTurn + 1);
}
void CNetLocalTurnManager::NotifyFinishedOwnCommands(u32 turn)
{
FinishedAllCommands(turn);
}
void CNetLocalTurnManager::NotifyFinishedUpdate(u32 UNUSED(turn), const std::string& UNUSED(hash))
{
}
void CNetLocalTurnManager::OnSimulationMessage(CSimulationMessage* UNUSED(msg))
{
debug_warn(L"This should never be called");
}

View File

@ -32,10 +32,10 @@ class CSimulation2;
* Each player performs the simulation for turn N.
* User input is translated into commands scheduled for execution in turn N+2 which are
* distributed to all other clients.
* After a while, a player want to perform the simulation for turn N+1,
* After a while, a player wants to perform the simulation for turn N+1,
* which first requires that it has all the other clients' commands for turn N+1.
* In that case, it does the simulation and tells all the other clients it has finished
* sending commands for turn N+2, and it starts sending commands for turn N+3.
* In that case, it does the simulation and tells all the other clients (via the server)
* it has finished sending commands for turn N+2, and it starts sending commands for turn N+3.
*
* Commands are redistributed immediately by the server.
* To ensure a consistent execution of commands, they are each associated with a
@ -43,19 +43,24 @@ class CSimulation2;
*/
/**
* Common network turn system (used by clients, servers, and offline games).
* Common network turn system (used by clients and offline games).
*/
class CNetTurnManager
{
NONCOPYABLE(CNetTurnManager);
public:
/**
* Construct for a given player ID, and a given network session ID.
* Construct for a given network session ID.
*/
CNetTurnManager(CSimulation2& simulation, int playerId, int clientId);
CNetTurnManager(CSimulation2& simulation, int clientId);
virtual ~CNetTurnManager() { }
/**
* Set the current user's player ID, which will be added into command messages.
*/
void SetPlayerID(int playerId);
/**
* Advance the simulation by a certain time. If this brings us past the current
* turn length, the next turn is processed and the function returns true.
@ -127,11 +132,14 @@ protected:
bool m_HasSyncError;
};
/**
* Implementation of CNetTurnManager for network clients.
*/
class CNetClientTurnManager : public CNetTurnManager
{
public:
CNetClientTurnManager(CSimulation2& simulation, CNetClient& client, int playerId, int clientId) :
CNetTurnManager(simulation, playerId, clientId), m_NetClient(client)
CNetClientTurnManager(CSimulation2& simulation, CNetClient& client, int clientId) :
CNetTurnManager(simulation, clientId), m_NetClient(client)
{
}
@ -147,11 +155,14 @@ protected:
CNetClient& m_NetClient;
};
class CNetServerTurnManager : public CNetTurnManager
/**
* Implementation of CNetTurnManager for offline games.
*/
class CNetLocalTurnManager : public CNetTurnManager
{
public:
CNetServerTurnManager(CSimulation2& simulation, CNetServer& server, int playerId, int clientId) :
CNetTurnManager(simulation, playerId, clientId), m_NetServer(server)
CNetLocalTurnManager(CSimulation2& simulation) :
CNetTurnManager(simulation, 0)
{
}
@ -159,6 +170,23 @@ public:
virtual void PostCommand(CScriptValRooted data);
protected:
virtual void NotifyFinishedOwnCommands(u32 turn);
virtual void NotifyFinishedUpdate(u32 turn, const std::string& hash);
};
/**
* The server-side counterpart to CNetClientTurnManager.
* Records the turn state of each client, and sends turn advancement messages
* when all clients are ready.
*/
class CNetServerTurnManager
{
public:
CNetServerTurnManager(CNetServer& server);
void NotifyFinishedClientCommands(int client, u32 turn);
void NotifyFinishedClientUpdate(int client, u32 turn, const std::string& hash);
@ -166,9 +194,8 @@ public:
void InitialiseClient(int client);
protected:
virtual void NotifyFinishedOwnCommands(u32 turn);
virtual void NotifyFinishedUpdate(u32 turn, const std::string& hash);
/// The latest turn for which we have received all commands from all clients
u32 m_ReadyTurn;
// Client ID -> ready turn number (the latest turn for which all commands have been received from that client)
std::map<int, u32> m_ClientsReady;
@ -183,22 +210,4 @@ protected:
CNetServer& m_NetServer;
};
class CNetLocalTurnManager : public CNetTurnManager
{
public:
CNetLocalTurnManager(CSimulation2& simulation) :
CNetTurnManager(simulation, 1, 0)
{
}
virtual void OnSimulationMessage(CSimulationMessage* msg);
virtual void PostCommand(CScriptValRooted data);
protected:
virtual void NotifyFinishedOwnCommands(u32 turn);
virtual void NotifyFinishedUpdate(u32 turn, const std::string& hash);
};
#endif // INCLUDED_NETTURNMANAGER

View File

@ -1,34 +0,0 @@
/* Copyright (C) 2010 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 "Network.h"
CStr CErrorMessage::ToString() const
{
return CStr("NetErrorMessage: ") + m_Error;
}
CStr CConnectCompleteMessage::ToString() const
{
return CStr("ConnectCompleteMessage");
}
CStr CCloseRequestMessage::ToString() const
{
return CStr("CloseRequestMessage");
}

View File

@ -1,62 +0,0 @@
/* Copyright (C) 2010 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_NETWORK_NETWORK
#define INCLUDED_NETWORK_NETWORK
#include "ps/Pyrogenesis.h"
#include "NetMessage.h"
class CErrorMessage : public CNetMessage
{
public:
const char* m_Error;
CErrorMessage() :
CNetMessage(NMT_ERROR), m_Error(NULL)
{
}
CErrorMessage(const char* error) :
CNetMessage(NMT_ERROR), m_Error(error)
{
}
virtual CStr ToString() const;
};
struct CCloseRequestMessage : public CNetMessage
{
CCloseRequestMessage() :
CNetMessage(NMT_CLOSE_REQUEST)
{
}
virtual CStr ToString() const;
};
struct CConnectCompleteMessage : public CNetMessage
{
CConnectCompleteMessage() :
CNetMessage(NMT_CONNECT_COMPLETE)
{
}
virtual CStr ToString() const;
};
#endif

View File

@ -1,4 +1,4 @@
/* Copyright (C) 2009 Wildfire Games.
/* Copyright (C) 2010 Wildfire Games.
* This file is part of 0 A.D.
*
* 0 A.D. is free software: you can redistribute it and/or modify

View File

@ -1,4 +1,4 @@
/* Copyright (C) 2009 Wildfire Games.
/* Copyright (C) 2010 Wildfire Games.
* This file is part of 0 A.D.
*
* 0 A.D. is free software: you can redistribute it and/or modify
@ -15,14 +15,6 @@
* along with 0 A.D. If not, see <http://www.gnu.org/licenses/>.
*/
/**
*-----------------------------------------------------------------------------
* FILE : fsm.cpp
* PROJECT : 0 A.D.
* DESCRIPTION : Finite state machine class implementation
*-----------------------------------------------------------------------------
*/
// INCLUDES
#include "precompiled.h"
#include "fsm.h"
@ -306,7 +298,7 @@ CFsmTransition* CFsm::AddTransition(
//-----------------------------------------------------------------------------
// Name: AddTransition()
// Desc: Adds a new transistion to the state machine
// Desc: Adds a new transition to the state machine
//-----------------------------------------------------------------------------
CFsmTransition* CFsm::AddTransition(
unsigned int state,

View File

@ -1,4 +1,4 @@
/* Copyright (C) 2009 Wildfire Games.
/* Copyright (C) 2010 Wildfire Games.
* This file is part of 0 A.D.
*
* 0 A.D. is free software: you can redistribute it and/or modify
@ -15,14 +15,6 @@
* along with 0 A.D. If not, see <http://www.gnu.org/licenses/>.
*/
/**
*-----------------------------------------------------------------------------
* FILE : fsm.h
* PROJECT : 0 A.D.
* DESCRIPTION : Finite state machine class definitions
*-----------------------------------------------------------------------------
*/
#ifndef FSM_H
#define FSM_H
@ -53,14 +45,11 @@ typedef std::map< unsigned int, CFsmEvent* > EventMap;
typedef std::vector< CFsmTransition* > TransitionList;
typedef std::vector< CallbackFunction > CallbackList;
/*
CLASS : CFsmEvent
DESCRIPTION : CFsmEvent class represents a signal in the state machine
that a change has occurred.
NOTES : The CFsmEvent objects are under the control of CFsm so
they are created and deleted via CFsm.
*/
/**
* Represents a signal in the state machine that a change has occurred.
* The CFsmEvent objects are under the control of CFsm so
* they are created and deleted via CFsm.
*/
class CFsmEvent
{
NONCOPYABLE(CFsmEvent);
@ -79,13 +68,9 @@ private:
};
/*
CLASS : CFsmTransition
DESCRIPTION : The CFsmTransition class is an association of event, condition,
action and next state.
NOTES :
*/
/**
* An association of event, condition, action and next state.
*/
class CFsmTransition
{
NONCOPYABLE(CFsmTransition);
@ -118,18 +103,17 @@ private:
CallbackList m_Conditions; // List of conditions for transition
};
/*
CLASS : CFsm
DESCRIPTION : CFsm manages states, events, actions and transitions
between states. It provides an interface for advertising
events and track the current state. The implementation is
a Mealy state machine, so the system respond to events
and execute some action.
NOTES : A Mealy state machine has behaviour associated with state
transitions; Mealy machines are event driven where an
event triggers a state transition
*/
/**
* Manages states, events, actions and transitions
* between states. It provides an interface for advertising
* events and track the current state. The implementation is
* a Mealy state machine, so the system respond to events
* and execute some action.
*
* A Mealy state machine has behaviour associated with state
* transitions; Mealy machines are event driven where an
* event triggers a state transition
*/
class CFsm
{
NONCOPYABLE(CFsm);
@ -141,13 +125,11 @@ public:
/**
* Constructs the state machine. This method must be overriden so that
* connections are constructed for the particular state machine implemented
*
*/
virtual void Setup( void );
/**
* Clear event, action and condition lists and reset state machine
*
*/
void Shutdown( void );
@ -177,8 +159,9 @@ public:
bool IsValidEvent ( unsigned int eventType ) const;
virtual bool IsDone ( void ) const;
private:
protected:
void SetCurrState ( unsigned int state );
private:
bool IsFirstTime ( void ) const;
bool m_Done; // FSM work is done

View File

@ -20,67 +20,14 @@
#include "lib/external_libraries/sdl.h"
#include "network/NetServer.h"
#include "network/NetClient.h"
#include "ps/ConfigDB.h"
#include "network/NetTurnManager.h"
#include "ps/CLogger.h"
#include "ps/Game.h"
#include "ps/Filesystem.h"
#include "ps/GameAttributes.h"
#include "ps/Loader.h"
#include "ps/XML/Xeromyces.h"
#include "scriptinterface/ScriptInterface.h"
class TestNetServer : public CNetServer
{
public:
TestNetServer(ScriptInterface& scriptInterface, CGameAttributes& gameAttributes) :
CNetServer(scriptInterface, NULL, &gameAttributes), m_HasConnection(false)
{
}
bool m_HasConnection;
protected:
virtual void OnPlayerJoin(CNetSession* pSession)
{
debug_printf(L"# player joined\n");
for (size_t slot = 0; slot < m_GameAttributes->GetSlotCount(); ++slot)
{
if (m_GameAttributes->GetSlot(slot)->GetAssignment() == SLOT_OPEN)
{
debug_printf(L"# assigning slot %d\n", slot);
m_GameAttributes->GetSlot(slot)->AssignToSession(pSession);
m_HasConnection = true;
break;
}
}
}
virtual void OnPlayerLeave(CNetSession* UNUSED(pSession))
{
debug_printf(L"# player left\n");
}
};
class TestNetClient : public CNetClient
{
public:
TestNetClient(ScriptInterface& scriptInterface, CGameAttributes& gameAttributes) :
CNetClient(scriptInterface, NULL, &gameAttributes), m_HasConnection(false)
{
}
bool m_HasConnection;
protected:
virtual void OnConnectComplete()
{
debug_printf(L"# connect complete\n");
m_HasConnection = true;
}
virtual void OnStartGame()
{
debug_printf(L"# start game\n");
}
};
#include "simulation2/Simulation2.h"
class TestNetComms : public CxxTest::TestSuite
{
@ -91,41 +38,38 @@ public:
TS_ASSERT_OK(g_VFS->Mount(L"", DataDir()/L"mods/public", VFS_MOUNT_MUST_EXIST));
TS_ASSERT_OK(g_VFS->Mount(L"cache", DataDir()/L"_testcache"));
CXeromyces::Startup();
new ScriptingHost;
CGameAttributes::ScriptingInit();
CNetServer::ScriptingInit();
CNetClient::ScriptingInit();
new CConfigDB;
}
void tearDown()
{
delete &g_ConfigDB;
CGameAttributes::ScriptingShutdown();
CNetServer::ScriptingShutdown();
CNetClient::ScriptingShutdown();
delete &g_ScriptingHost;
CXeromyces::Terminate();
g_VFS.reset();
DeleteDirectory(DataDir()/L"_testcache");
}
void connect(TestNetServer& server, TestNetClient& client)
bool clients_are_all(const std::vector<CNetClient*>& clients, uint state)
{
TS_ASSERT(server.Start(NULL, 0, NULL));
TS_ASSERT(client.Create());
TS_ASSERT(client.ConnectAsync("127.0.0.1", DEFAULT_HOST_PORT));
for (size_t j = 0; j < clients.size(); ++j)
if (clients[j]->GetCurrState() != state)
return false;
return true;
}
void connect(CNetServer& server, const std::vector<CNetClient*>& clients)
{
TS_ASSERT(server.SetupConnection());
clients[0]->SetupLocalConnection(server);
for (size_t j = 1; j < clients.size(); ++j)
TS_ASSERT(clients[j]->SetupConnection("127.0.0.1"));
for (size_t i = 0; ; ++i)
{
debug_printf(L".");
// debug_printf(L".");
server.Poll();
client.Poll();
for (size_t j = 0; j < clients.size(); ++j)
clients[j]->Poll();
if (server.m_HasConnection && client.m_HasConnection)
if (server.GetState() == SERVER_STATE_PREGAME && clients_are_all(clients, NCS_PREGAME))
break;
if (i > 20)
@ -138,28 +82,102 @@ public:
}
}
void disconnect(CNetServer& server, const std::vector<CNetClient*>& clients)
{
for (size_t i = 0; ; ++i)
{
// debug_printf(L".");
server.Poll();
for (size_t j = 0; j < clients.size(); ++j)
clients[j]->Poll();
if (server.GetState() == SERVER_STATE_UNCONNECTED && clients_are_all(clients, NCS_UNCONNECTED))
break;
if (i > 20)
{
TS_FAIL("disconnection timeout");
break;
}
SDL_Delay(100);
}
}
void wait(CNetServer& server, const std::vector<CNetClient*>& clients, size_t msecs)
{
for (size_t i = 0; i < msecs/10; ++i)
{
server.Poll();
for (size_t j = 0; j < clients.size(); ++j)
clients[j]->Poll();
SDL_Delay(10);
}
}
void test_basic_DISABLED()
{
ScriptInterface scriptInterface("Engine");
// This doesn't actually test much, it just runs a very quick multiplayer game
// and prints a load of debug output so you can see if anything funny's going on
CGameAttributes gameAttributesServer;
CGameAttributes gameAttributesClient;
TestNetServer server(scriptInterface, gameAttributesServer);
TestNetClient client(scriptInterface, gameAttributesClient);
connect(server, client);
client.CNetHost::Shutdown();
server.CNetHost::Shutdown();
}
TestStdoutLogger logger;
void TODO_test_destructor()
{
ScriptInterface scriptInterface("Engine");
std::vector<CNetClient*> clients;
CGameAttributes gameAttributesServer;
CGameAttributes gameAttributesClient;
TestNetServer server(scriptInterface, gameAttributesServer);
TestNetClient client(scriptInterface, gameAttributesClient);
connect(server, client);
// run in Valgrind; this shouldn't leak
CGame client1Game(true);
CGame client2Game(true);
CGame client3Game(true);
CNetServer server;
CScriptValRooted attrs;
server.GetScriptInterface().Eval("({map:'Latium',thing:'example'})", attrs);
server.UpdateGameAttributes(attrs);
CNetClient client1(&client1Game);
CNetClient client2(&client2Game);
CNetClient client3(&client3Game);
clients.push_back(&client1);
clients.push_back(&client2);
clients.push_back(&client3);
connect(server, clients);
debug_printf(L"%ls", client1.TestReadGuiMessages().c_str());
server.StartGame();
server.Poll();
SDL_Delay(100);
for (size_t j = 0; j < clients.size(); ++j)
{
clients[j]->Poll();
TS_ASSERT_OK(LDR_NonprogressiveLoad());
clients[j]->LoadFinished();
}
wait(server, clients, 100);
{
CScriptValRooted cmd;
client1.GetScriptInterface().Eval("({type:'debug-print', message:'[>>> client1 test sim command]\\n'})", cmd);
client1Game.GetTurnManager()->PostCommand(cmd);
}
{
CScriptValRooted cmd;
client2.GetScriptInterface().Eval("({type:'debug-print', message:'[>>> client2 test sim command]\\n'})", cmd);
client2Game.GetTurnManager()->PostCommand(cmd);
}
wait(server, clients, 100);
client1Game.GetTurnManager()->Update(1.0f);
client2Game.GetTurnManager()->Update(1.0f);
client3Game.GetTurnManager()->Update(1.0f);
wait(server, clients, 100);
client1Game.GetTurnManager()->Update(1.0f);
client2Game.GetTurnManager()->Update(1.0f);
client3Game.GetTurnManager()->Update(1.0f);
wait(server, clients, 100);
}
};

View File

@ -719,23 +719,10 @@ void CConsole::SaveHistory()
void CConsole::SendChatMessage(const wchar_t *pText)
{
CNetHost *pHost = NULL;
if ( pHost )
if (g_NetClient)
{
CChatMessage chat;
chat.m_Recipient = CHAT_RECIPIENT_ALL;
chat.m_Message = pText;
if ( pHost->IsServer() )
{
CNetServer* pServer = ( CNetServer* )pHost;
chat.m_Sender = 0;
ReceivedChatMessage( pServer->GetPlayerName(), chat.m_Message.c_str() );
}
pHost->Broadcast( &chat );
// TODO
// g_NetClient3->SendChatMessage(pText);
}
}

View File

@ -136,6 +136,8 @@ CLogger::~CLogger ()
void CLogger::WriteMessage(const wchar_t* message)
{
++m_NumberOfMessages;
// if (m_UseDebugPrintf)
// debug_printf(L"MESSAGE: %ls\n", message);
*m_MainLog << L"<p>" << message << L"</p>\n";
m_MainLog->flush();
@ -354,7 +356,6 @@ void CLogger::PushRenderMessage(ELogMethod method, const wchar_t* message)
}
}
void CLogger::CleanupRenderQueue()
{
if (m_RenderMessages.empty())
@ -397,3 +398,15 @@ std::wstring TestLogger::GetOutput()
{
return m_Stream.str();
}
TestStdoutLogger::TestStdoutLogger()
{
m_OldLogger = g_Logger;
g_Logger = new CLogger(&std::wcout, &blackHoleStream, false, false);
}
TestStdoutLogger::~TestStdoutLogger()
{
delete g_Logger;
g_Logger = m_OldLogger;
}

View File

@ -132,4 +132,17 @@ private:
std::wstringstream m_Stream;
};
/**
* Helper class for unit tests - redirects all log output to stdout.
*/
class TestStdoutLogger
{
NONCOPYABLE(TestStdoutLogger);
public:
TestStdoutLogger();
~TestStdoutLogger();
private:
CLogger* m_OldLogger;
};
#endif

View File

@ -29,20 +29,23 @@
#include "graphics/UnitManager.h"
#include "lib/timer.h"
#include "network/NetClient.h"
#include "network/NetServer.h"
#include "network/NetTurnManager.h"
#include "ps/CConsole.h"
#include "ps/CLogger.h"
#include "ps/CStr.h"
#include "ps/GameAttributes.h"
#include "ps/Loader.h"
#include "ps/Overlay.h"
#include "ps/Profile.h"
#include "ps/World.h"
#include "scripting/ScriptingHost.h"
#include "scriptinterface/ScriptInterface.h"
#include "simulation2/Simulation2.h"
#include "simulation2/components/ICmpPlayer.h"
#include "simulation2/components/ICmpPlayerManager.h"
#include "gui/GUIManager.h"
class CNetServer;
extern CNetServer *g_NetServer;
extern bool g_GameRestarted;
/**
@ -54,18 +57,19 @@ CGame *g_Game=NULL;
* Constructor
*
**/
CGame::CGame():
CGame::CGame(bool disableGraphics):
m_World(new CWorld(this)),
m_Simulation2(new CSimulation2(&m_World->GetUnitManager(), m_World->GetTerrain())),
m_GameView(new CGameView(this)),
m_pLocalPlayer(NULL),
m_GameView(disableGraphics ? NULL : new CGameView(this)),
m_GameStarted(false),
m_Paused(false),
m_SimRate(1.0f)
m_SimRate(1.0f),
m_PlayerID(-1)
{
// Need to set the CObjectManager references after various objects have
// been initialised, so do it here rather than via the initialisers above.
m_World->GetUnitManager().SetObjectManager(m_GameView->GetObjectManager());
if (m_GameView)
m_World->GetUnitManager().SetObjectManager(m_GameView->GetObjectManager());
m_TurnManager = new CNetLocalTurnManager(*m_Simulation2); // this will get replaced if we're a net server/client
@ -83,7 +87,8 @@ CGame::CGame():
CGame::~CGame()
{
// Again, the in-game call tree is going to be different to the main menu one.
g_Profiler.StructuralReset();
if (CProfileManager::IsInitialised())
g_Profiler.StructuralReset();
delete m_TurnManager;
delete m_GameView;
@ -95,7 +100,11 @@ void CGame::SetTurnManager(CNetTurnManager* turnManager)
{
if (m_TurnManager)
delete m_TurnManager;
m_TurnManager = turnManager;
if (m_TurnManager)
m_TurnManager->SetPlayerID(m_PlayerID);
}
@ -103,24 +112,26 @@ void CGame::SetTurnManager(CNetTurnManager* turnManager)
* Initializes the game with the set of attributes provided.
* Makes calls to initialize the game view, world, and simulation objects.
* Calls are made to facilitate progress reporting of the initialization.
*
* @param CGameAttributes * pAttribs pointer to the game attribute values
* @return PSRETURN 0
**/
PSRETURN CGame::RegisterInit(CGameAttributes* pAttribs)
void CGame::RegisterInit(const CScriptValRooted& attribs)
{
std::wstring mapFile;
m_Simulation2->GetScriptInterface().GetProperty(attribs.get(), "map", mapFile);
mapFile += L".pmp";
LDR_BeginRegistering();
// RC, 040804 - GameView needs to be initialized before World, otherwise GameView initialization
// overwrites anything stored in the map file that gets loaded by CWorld::Initialize with default
// values. At the minute, it's just lighting settings, but could be extended to store camera position.
// Storing lighting settings in the game view seems a little odd, but it's no big deal; maybe move it at
// values. At the minute, it's just lighting settings, but could be extended to store camera position.
// Storing lighting settings in the game view seems a little odd, but it's no big deal; maybe move it at
// some point to be stored in the world object?
m_GameView->RegisterInit(pAttribs);
m_World->RegisterInit(pAttribs);
if (m_GameView)
m_GameView->RegisterInit();
m_World->RegisterInit(mapFile);
LDR_EndRegistering();
return 0;
}
/**
* Game initialization has been completed. Set game started flag and start the session.
*
@ -129,7 +140,7 @@ PSRETURN CGame::RegisterInit(CGameAttributes* pAttribs)
PSRETURN CGame::ReallyStartGame()
{
// Call the reallyStartGame GUI function, but only if it exists
if (g_GUI->HasPages())
if (g_GUI && g_GUI->HasPages())
{
jsval fval, rval;
JSBool ok = JS_GetProperty(g_ScriptingHost.getContext(), g_GUI->GetScriptObject(), "reallyStartGame", &fval);
@ -138,11 +149,15 @@ PSRETURN CGame::ReallyStartGame()
ok = JS_CallFunctionValue(g_ScriptingHost.getContext(), g_GUI->GetScriptObject(), fval, 0, NULL, &rval);
}
if (g_NetClient)
g_NetClient->LoadFinished();
debug_printf(L"GAME STARTED, ALL INIT COMPLETE\n");
m_GameStarted=true;
// The call tree we've built for pregame probably isn't useful in-game.
g_Profiler.StructuralReset();
if (CProfileManager::IsInitialised())
g_Profiler.StructuralReset();
// Mark terrain as modified so the minimap can repaint (is there a cleaner way of handling this?)
g_GameRestarted = true;
@ -150,56 +165,37 @@ PSRETURN CGame::ReallyStartGame()
return 0;
}
/**
* Prepare to start the game.
* Set up the players list then call RegisterInit that initializes the game and is used to report progress.
*
* @param CGameAttributes * pGameAttributes game attributes for initialization.
* @return PSRETURN 0 if successful,
* error information if not.
**/
PSRETURN CGame::StartGame(CGameAttributes *pAttribs)
void CGame::ChangeNetStatus(ENetStatus status)
{
try
if (g_GUI && g_GUI->HasPages())
{
// JW: this loop is taken from ScEd and fixes lack of player color.
// TODO: determine proper number of players.
// SB: Only do this for non-network games
if (!g_NetClient && !g_NetServer)
const char* statusStr = "?";
switch (status)
{
for (int i=1; i<8; ++i)
pAttribs->GetSlot(i)->AssignLocal();
case NET_WAITING_FOR_CONNECT: statusStr = "waiting_for_connect"; break;
case NET_NORMAL: statusStr = "normal"; break;
}
pAttribs->FinalizeSlots();
m_NumPlayers=pAttribs->GetSlotCount();
// Player 0 = Gaia - allocate one extra
m_Players.resize(m_NumPlayers + 1);
for (size_t i=0;i <= m_NumPlayers;i++)
m_Players[i]=pAttribs->GetPlayer(i);
if (g_NetClient)
{
// TODO
m_pLocalPlayer = g_NetClient->GetLocalPlayer();
debug_assert(m_pLocalPlayer && "Darn it! We weren't assigned to a slot!");
}
else
{
m_pLocalPlayer=m_Players[1];
}
RegisterInit(pAttribs);
g_GUI->GetScriptInterface().CallFunctionVoid(OBJECT_TO_JSVAL(g_GUI->GetScriptObject()), "changeNetStatus", statusStr);
}
catch (PSERROR_Game& e)
{
return e.getCode();
}
return 0;
}
int CGame::GetPlayerID()
{
return m_PlayerID;
}
void CGame::SetPlayerID(int playerID)
{
m_PlayerID = playerID;
if (m_TurnManager)
m_TurnManager->SetPlayerID(m_PlayerID);
}
void CGame::StartGame(const CScriptValRooted& attribs)
{
RegisterInit(attribs);
}
// TODO: doInterpolate is optional because Atlas interpolates explicitly,
// so that it has more control over the update rate. The game might want to
@ -219,6 +215,9 @@ bool CGame::Update(double deltaTime, bool doInterpolate)
if (m_Paused)
return true;
if (!m_TurnManager)
return true;
deltaTime *= m_SimRate;
bool ok = true;
@ -240,6 +239,9 @@ bool CGame::Update(double deltaTime, bool doInterpolate)
void CGame::Interpolate(float frameLength)
{
if (!m_TurnManager)
return;
m_TurnManager->Interpolate(frameLength);
}
@ -312,32 +314,15 @@ void CGame::EndGame()
break;
}
}
/**
* Get the player object from the players list at the provided index.
*
* @param idx sequential position in the list.
* @return CPlayer * pointer to player requested.
**/
CPlayer *CGame::GetPlayer(size_t idx)
{
if (idx > m_NumPlayers)
{
// debug_warn(L"Invalid player ID");
// LOG(CLogger::Error, "", "Invalid player ID %d (outside 0..%d)", idx, m_NumPlayers);
return m_Players[0];
}
// Be a bit more paranoid - maybe m_Players hasn't been set large enough
else if (idx >= m_Players.size())
{
debug_warn(L"Invalid player ID");
LOG(CLogger::Error, L"", L"Invalid player ID %lu (not <=%lu - internal error?)", (unsigned long)idx, (unsigned long)m_Players.size());
if (m_Players.size() != 0)
return m_Players[0];
else
return NULL; // the caller will probably crash because of this,
// but at least we've reported the error
}
else
return m_Players[idx];
static CColor BrokenColor(0.3f, 0.3f, 0.3f, 1.0f);
CColor CGame::GetPlayerColour(int player) const
{
CmpPtr<ICmpPlayerManager> cmpPlayerManager(*m_Simulation2, SYSTEM_ENTITY);
if (cmpPlayerManager.null())
return BrokenColor;
CmpPtr<ICmpPlayer> cmpPlayer(*m_Simulation2, cmpPlayerManager->GetPlayerByID(player));
if (cmpPlayer.null())
return BrokenColor;
return cmpPlayer->GetColour();
}

View File

@ -1,4 +1,4 @@
/* Copyright (C) 2009 Wildfire Games.
/* Copyright (C) 2010 Wildfire Games.
* This file is part of 0 A.D.
*
* 0 A.D. is free software: you can redistribute it and/or modify
@ -30,15 +30,9 @@
class CWorld;
class CSimulation2;
class CGameView;
class CPlayer;
class CGameAttributes;
class CNetTurnManager;
/**
* Default player limit (not counting the Gaia player)
* This may be overridden by system.cfg ("max_players")
**/
#define PS_MAX_PLAYERS 8
class CScriptValRooted;
struct CColor;
/**
* The container that holds the rules, resources and attributes of the game.
@ -61,18 +55,6 @@ class CGame
* pointer to the CGameView object representing the view into the game world.
**/
CGameView *m_GameView;
/**
* pointer to the local CPlayer object operating on the game world.
**/
CPlayer *m_pLocalPlayer;
/**
* STL vectors of pointers to all CPlayer objects(including gaia) operating on the game world.
**/
std::vector<CPlayer *> m_Players;
/**
* number of players operating on the game world(not including gaia).
**/
size_t m_NumPlayers;
/**
* the game has been initialized and ready for use if true.
**/
@ -81,6 +63,9 @@ class CGame
* scale multiplier for simulation rate.
**/
float m_SimRate;
int m_PlayerID;
/**
* enumerated values for game status.
**/
@ -94,9 +79,15 @@ class CGame
} GameStatus;
CNetTurnManager* m_TurnManager;
public:
CGame();
enum ENetStatus
{
NET_WAITING_FOR_CONNECT, /// we have loaded the game; waiting for other players to finish loading
NET_NORMAL /// running the game
};
CGame(bool disableGraphics = false);
~CGame();
/**
@ -104,15 +95,14 @@ public:
**/
bool m_Paused;
/*
Initialize all local state and members for playing a game described by
the attribute class, and start the game.
Return: 0 on OK - a PSRETURN code otherwise
*/
PSRETURN StartGame(CGameAttributes *pGameAttributes);
void StartGame(const CScriptValRooted& attribs);
PSRETURN ReallyStartGame();
/**
* Notify the game of changes in the network connection status.
*/
void ChangeNetStatus(ENetStatus status);
/*
Perform all per-frame updates
*/
@ -123,41 +113,10 @@ public:
void UpdateGameStatus();
void EndGame();
/**
* Get pointer to the local player object.
*
* @return CPlayer * the value of m_pLocalPlayer.
**/
inline CPlayer *GetLocalPlayer()
{ return m_pLocalPlayer; }
/**
* Change the pointer to the local player object.
*
* @param CPlayer * pLocalPlayer pointer to a valid player object.
**/
inline void SetLocalPlayer(CPlayer *pLocalPlayer)
{ m_pLocalPlayer=pLocalPlayer; }
// PT: No longer inline, because it does too much error checking. When
// everything stops trying to access players before they're loaded, feel
// free to put the inline version back.
CPlayer *GetPlayer(size_t idx);
int GetPlayerID();
void SetPlayerID(int playerID);
/**
* Get a reference to the m_Players vector.
*
* @return std::vector<CPlayer*> * reference to m_Players.
**/
inline std::vector<CPlayer*>* GetPlayers()
{ return( &m_Players ); }
/**
* Get m_NumPlayers.
*
* @return the number of players (not including gaia)
**/
inline size_t GetNumPlayers() const
{ return m_NumPlayers; }
CColor GetPlayerColour(int player) const;
/**
* Get m_GameStarted.
@ -207,7 +166,7 @@ public:
* @return float value of m_SimRate.
**/
inline float GetSimRate() const
{ return m_SimRate; }
{ return m_SimRate; }
/**
* Replace the current turn manager.
@ -216,12 +175,10 @@ public:
void SetTurnManager(CNetTurnManager* turnManager);
CNetTurnManager* GetTurnManager() const
{
return m_TurnManager;
}
{ return m_TurnManager; }
private:
PSRETURN RegisterInit(CGameAttributes* pAttribs);
void RegisterInit(const CScriptValRooted& attribs);
};
extern CGame *g_Game;

View File

@ -1,436 +0,0 @@
/* 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"
#include <sstream>
#include "GameAttributes.h"
#include "Game.h"
#include "ConfigDB.h"
#include "network/NetSession.h"
#include "CLogger.h"
#include "ps/XML/Xeromyces.h"
#include "simulation/LOSManager.h"
CGameAttributes *g_GameAttributes;
CPlayerSlot::CPlayerSlot(size_t slotID, CPlayer *pPlayer):
m_SlotID(slotID),
m_Assignment(SLOT_OPEN),
m_pSession(NULL),
m_SessionID(0),
m_pPlayer(pPlayer),
m_Callback(NULL)
{
//AddProperty(L"session", (GetFn)&CPlayerSlot::JSI_GetSession);
AddLocalProperty(L"session", &m_pSession, true );
AddLocalProperty(L"player", &m_pPlayer, true );
}
CPlayerSlot::~CPlayerSlot()
{}
void CPlayerSlot::ScriptingInit()
{
AddMethod<bool, &CPlayerSlot::JSI_AssignClosed>("assignClosed", 0);
AddMethod<bool, &CPlayerSlot::JSI_AssignToSession>("assignToSession", 1);
AddMethod<bool, &CPlayerSlot::JSI_AssignLocal>("assignLocal", 0);
AddMethod<bool, &CPlayerSlot::JSI_AssignOpen>("assignOpen", 0);
AddProperty(L"assignment", &CPlayerSlot::JSI_GetAssignment);
// AddMethod<bool, &CPlayerSlot::JSI_AssignAI>("assignAI", <num_args>);
CJSObject<CPlayerSlot>::ScriptingInit("PlayerSlot");
}
jsval CPlayerSlot::JSI_GetSession(JSContext* UNUSED(cx))
{
if (m_pSession)
return OBJECT_TO_JSVAL(m_pSession->GetScript());
else
return JSVAL_NULL;
}
jsval CPlayerSlot::JSI_GetAssignment(JSContext* UNUSED(cx))
{
switch (m_Assignment)
{
case SLOT_CLOSED:
return g_ScriptingHost.UCStringToValue(L"closed");
case SLOT_OPEN:
return g_ScriptingHost.UCStringToValue(L"open");
case SLOT_SESSION:
return g_ScriptingHost.UCStringToValue(L"session");
/* case SLOT_AI:*/
default:
return INT_TO_JSVAL(m_Assignment);
}
}
bool CPlayerSlot::JSI_AssignClosed(JSContext* UNUSED(cx), uintN UNUSED(argc), jsval* UNUSED(argv))
{
AssignClosed();
return true;
}
bool CPlayerSlot::JSI_AssignOpen(JSContext* UNUSED(cx), uintN UNUSED(argc), jsval* UNUSED(argv))
{
AssignOpen();
return true;
}
bool CPlayerSlot::JSI_AssignToSession(JSContext* UNUSED(cx), uintN argc, jsval* argv)
{
if (argc != 1)
return false;
CNetSession* pSession = ToNative<CNetSession>(argv[0]);
if (pSession)
{
AssignToSession(pSession);
return true;
}
else
return true;
}
bool CPlayerSlot::JSI_AssignLocal(JSContext* UNUSED(cx), uintN UNUSED(argc), jsval* UNUSED(argv))
{
AssignToSessionID(1);
return true;
}
void CPlayerSlot::CallCallback()
{
if (m_Callback)
m_Callback(m_CallbackData, this);
}
void CPlayerSlot::SetAssignment(EPlayerSlotAssignment assignment,
CNetSession *pSession, int sessionID)
{
m_Assignment=assignment;
m_pSession=pSession;
m_SessionID=sessionID;
CallCallback();
}
void CPlayerSlot::AssignClosed()
{
SetAssignment(SLOT_CLOSED, NULL, -1);
}
void CPlayerSlot::AssignOpen()
{
SetAssignment(SLOT_OPEN, NULL, -1);
}
void CPlayerSlot::AssignToSession(CNetSession *pSession)
{
SetAssignment(SLOT_SESSION, pSession, pSession->GetID());
m_pPlayer->SetName(pSession->GetName());
}
void CPlayerSlot::AssignToSessionID(int id)
{
SetAssignment(SLOT_SESSION, NULL, id);
}
void CPlayerSlot::AssignLocal()
{
AssignToSessionID(1);
}
namespace PlayerSlotArray_JS
{
JSBool GetProperty( JSContext* cx, JSObject* obj, jsval id, jsval* vp )
{
CGameAttributes *pInstance=(CGameAttributes *)JS_GetPrivate(cx, obj);
if (!JSVAL_IS_INT(id))
return JS_FALSE;
size_t index=ToPrimitive<size_t>(id);
if (index >= pInstance->m_NumSlots)
return JS_FALSE;
*vp=OBJECT_TO_JSVAL(pInstance->m_PlayerSlots[index]->GetScript());
return JS_TRUE;
}
JSBool SetProperty( JSContext* UNUSED(cx), JSObject* UNUSED(obj), jsval UNUSED(id), jsval* UNUSED(vp) )
{
return JS_FALSE;
}
JSClass Class = {
"PlayerSlotArray", JSCLASS_HAS_PRIVATE,
JS_PropertyStub, JS_PropertyStub,
GetProperty, SetProperty,
JS_EnumerateStub, JS_ResolveStub,
JS_ConvertStub, JS_FinalizeStub
};
JSBool Construct( JSContext* cx, JSObject* obj, uintN argc, jsval* UNUSED(argv), jsval* rval )
{
if (argc != 0)
return JS_FALSE;
JSObject *newObj=JS_NewObject(cx, &Class, NULL, obj);
*rval=OBJECT_TO_JSVAL(newObj);
return JS_TRUE;
}
};
CGameAttributes::CGameAttributes():
m_MapFile("test01.pmp"),
m_ResourceLevel("default"),
m_StartingPhase("default"),
m_LOSSetting(LOS_SETTING_NORMAL),
m_FogOfWar(true),
m_GameMode("default"),
m_ScreenshotMode(false),
m_NumSlots(8),
m_UpdateCB(NULL),
m_PlayerUpdateCB(NULL),
m_PlayerSlotAssignmentCB(NULL)
{
m_PlayerSlotArrayJS=g_ScriptingHost.CreateCustomObject("PlayerSlotArray");
JS_AddRoot(g_ScriptingHost.GetContext(), &m_PlayerSlotArrayJS);
JS_SetPrivate(g_ScriptingHost.GetContext(), m_PlayerSlotArrayJS, this);
AddSynchedProperty(L"mapFile", &m_MapFile);
AddSynchedProperty(L"resourceLevel", &m_ResourceLevel);
AddSynchedProperty(L"startingPhase", &m_StartingPhase);
AddSynchedProperty(L"numSlots", &m_NumSlots, &CGameAttributes::OnNumSlotsUpdate);
AddSynchedProperty(L"losSetting", &m_LOSSetting);
AddSynchedProperty(L"fogOfWar", &m_FogOfWar);
AddSynchedProperty(L"gameMode", &m_GameMode);
AddSynchedProperty(L"screenshotMode", &m_ScreenshotMode);
CXeromyces XeroFile;
if (XeroFile.Load(L"temp/players.xml") != PSRETURN_OK)
{
LOG(CLogger::Error, L"", L"Failed to load players list (temp/players.xml)");
// Basic default players
m_Players.push_back(new CPlayer(0));
m_Players.back()->SetName(L"Gaia");
m_Players.back()->SetColour(SPlayerColour(1.0f, 1.0f, 1.0f));
m_Players.push_back(new CPlayer(1));
m_Players.back()->SetName(L"Player 1");
m_Players.back()->SetColour(SPlayerColour(0.4375f, 0.4375f, 0.8125f));
}
else
{
int at_name = XeroFile.GetAttributeID("name");
int at_rgb = XeroFile.GetAttributeID("rgb");
XMBElement root = XeroFile.GetRoot();
XERO_ITER_EL(root, player)
{
XMBAttributeList attr = player.GetAttributes();
m_Players.push_back(new CPlayer((int)m_Players.size()));
m_Players.back()->SetName(attr.GetNamedItem(at_name));
std::stringstream str;
str << CStr(attr.GetNamedItem(at_rgb));
int r, g, b;
if (str >> r >> g >> b)
m_Players.back()->SetColour(SPlayerColour(r/255.0f, g/255.0f, b/255.0f));
}
}
std::vector<CPlayer *>::iterator it=m_Players.begin();
++it; // Skip Gaia - gaia doesn't account for a slot
for (;it != m_Players.end();++it)
{
m_PlayerSlots.push_back(new CPlayerSlot((*it)->GetPlayerID()-1, *it));
}
// The first player is always the server player in MP, or the local player
// in SP
m_PlayerSlots[0]->AssignToSessionID(1);
}
CGameAttributes::~CGameAttributes()
{
std::vector<CPlayerSlot *>::iterator it=m_PlayerSlots.begin();
while (it != m_PlayerSlots.end())
{
delete (*it)->GetPlayer();
delete *it;
++it;
}
// PT: ??? - Gaia isn't a player slot, but still needs to be deleted; but
// this feels rather unpleasantly inconsistent with the above code, so maybe
// there's a better way to avoid the memory leak.
delete m_Players[0];
JS_RemoveRoot(g_ScriptingHost.GetContext(), &m_PlayerSlotArrayJS);
}
void CGameAttributes::ScriptingInit()
{
g_ScriptingHost.DefineCustomObjectType(&PlayerSlotArray_JS::Class,
PlayerSlotArray_JS::Construct, 0, NULL, NULL, NULL, NULL);
AddMethod<jsval_t, &CGameAttributes::JSI_GetOpenSlot>("getOpenSlot", 0);
AddProperty(L"slots", &CGameAttributes::JSI_GetPlayerSlots);
AddProperty(L"getUsedSlotsAmount", &CGameAttributes::JSI_GetUsedSlotsAmount);
CJSObject<CGameAttributes>::ScriptingInit("GameAttributes");
}
jsval_t CGameAttributes::JSI_GetOpenSlot(JSContext* UNUSED(cx), uintN UNUSED(argc), jsval* UNUSED(argv))
{
std::vector <CPlayerSlot *>::iterator it;
for (it = m_PlayerSlots.begin();it != m_PlayerSlots.end();++it)
{
if ((*it)->GetAssignment() == SLOT_OPEN)
return OBJECT_TO_JSVAL((*it)->GetScript());
}
return JSVAL_NULL;
}
jsval CGameAttributes::JSI_GetUsedSlotsAmount(JSContext* UNUSED(cx))
{
int i = 0;
std::vector <CPlayerSlot *>::iterator it;
for (it = m_PlayerSlots.begin();it != m_PlayerSlots.end();++it)
{
if ((*it)->GetAssignment() != SLOT_OPEN)
{
i++;
}
else
{
return INT_TO_JSVAL(i);
}
}
return INT_TO_JSVAL(i);
}
jsval CGameAttributes::JSI_GetPlayerSlots(JSContext* UNUSED(cx))
{
return OBJECT_TO_JSVAL(m_PlayerSlotArrayJS);
}
void CGameAttributes::OnNumSlotsUpdate(CSynchedJSObjectBase *owner)
{
CGameAttributes *pInstance=(CGameAttributes*)owner;
// Clamp to a preset upper bound.
CConfigValue *val=g_ConfigDB.GetValue(CFG_MOD, "max_players");
uint maxPlayers=PS_MAX_PLAYERS;
if (val)
val->GetUnsignedInt(maxPlayers);
if (pInstance->m_NumSlots > maxPlayers)
pInstance->m_NumSlots = maxPlayers;
// Resize array according to new number of slots (note that the array
// size will go up to maxPlayers (above), but will never be made smaller -
// this to avoid losing player pointers and make sure they are all
// reclaimed in the end - it's just simpler that way ;-) )
if (pInstance->m_NumSlots > pInstance->m_PlayerSlots.size())
{
for (size_t i=pInstance->m_PlayerSlots.size();i<=pInstance->m_NumSlots;i++)
{
CPlayer *pNewPlayer=new CPlayer(i+1);
pNewPlayer->SetUpdateCallback(pInstance->m_PlayerUpdateCB,
pInstance->m_PlayerUpdateCBData);
pInstance->m_Players.push_back(pNewPlayer);
CPlayerSlot *pNewSlot=new CPlayerSlot(i, pNewPlayer);
pNewSlot->SetCallback(pInstance->m_PlayerSlotAssignmentCB,
pInstance->m_PlayerSlotAssignmentCBData);
pInstance->m_PlayerSlots.push_back(pNewSlot);
}
}
}
CPlayer *CGameAttributes::GetPlayer(size_t id)
{
if (id < m_Players.size())
return m_Players[id];
else
{
LOG(CLogger::Error, L"", L"CGameAttributes::GetPlayer(): Attempt to get player %lu (while there only are %lu players)", (unsigned long)id, (unsigned long)m_Players.size());
return m_Players[0];
}
}
CPlayerSlot *CGameAttributes::GetSlot(size_t index)
{
if (index < m_PlayerSlots.size())
return m_PlayerSlots[index];
else
{
LOG(CLogger::Error, L"", L"CGameAttributes::GetSlot(): Attempt to get slot %lu (while there only are %lu slots)", (unsigned long)index, (unsigned long)m_PlayerSlots.size());
return m_PlayerSlots[0];
}
}
void CGameAttributes::FinalizeSlots()
{
for (size_t i=0; i<m_PlayerSlots.size(); i++) {
CPlayerSlot *slot = m_PlayerSlots[i];
EPlayerSlotAssignment assignment = slot->GetAssignment();
if (assignment != SLOT_OPEN && assignment != SLOT_CLOSED)
{
slot->GetPlayer()->SetActive(true);
}
}
}
void CGameAttributes::SetValue(const CStrW& name, const CStrW& value)
{
ISynchedJSProperty *prop=GetSynchedProperty(name);
if (prop)
{
prop->FromString(value);
}
}
void CGameAttributes::Update(const CStrW& name, ISynchedJSProperty *attrib)
{
if (m_UpdateCB)
m_UpdateCB(name, attrib->ToString(), m_UpdateCBData);
}
void CGameAttributes::SetPlayerUpdateCallback(CPlayer::UpdateCallback *cb, void *userdata)
{
m_PlayerUpdateCB=cb;
m_PlayerUpdateCBData=userdata;
for (size_t i=0;i<m_Players.size();i++)
{
m_Players[i]->SetUpdateCallback(cb, userdata);
}
}
void CGameAttributes::SetPlayerSlotAssignmentCallback(PlayerSlotAssignmentCB *cb, void *userdata)
{
m_PlayerSlotAssignmentCB=cb;
m_PlayerSlotAssignmentCBData=userdata;
for (size_t i=0;i<m_PlayerSlots.size();i++)
{
m_PlayerSlots[i]->SetCallback(cb, userdata);
}
}

View File

@ -1,215 +0,0 @@
/* 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/>.
*/
#ifndef INCLUDED_GAMEATTRIBUTES
#define INCLUDED_GAMEATTRIBUTES
#include "Player.h"
#include "scripting/SynchedJSObject.h"
#include "simulation/LOSManager.h"
//class CNetServerSession;
class CNetSession;
class CGameAttributes;
class CPlayerSlot;
enum EPlayerSlotAssignment
{
SLOT_CLOSED,
SLOT_OPEN,
SLOT_SESSION,
SLOT_AI
};
typedef void (PlayerSlotAssignmentCB)(void *data, CPlayerSlot *);
class CPlayerSlot: public CJSObject<CPlayerSlot>
{
size_t m_SlotID;
EPlayerSlotAssignment m_Assignment;
//CNetServerSession *m_pSession;
CNetSession *m_pSession;
int m_SessionID;
CPlayer *m_pPlayer;
PlayerSlotAssignmentCB *m_Callback;
void *m_CallbackData;
bool JSI_AssignClosed(JSContext *cx, uintN argc, jsval *argv);
// Assign to a session, takes one argument (a NetSession object)
bool JSI_AssignToSession(JSContext *cx, uintN argc, jsval *argv);
// Assign to the local player in SP or Server Player in MP
bool JSI_AssignLocal(JSContext *cx, uintN argc, jsval *argv);
bool JSI_AssignOpen(JSContext *cx, uintN argc, jsval *argv);
// TODO This will wait until there actually is AI to set up
// bool JSI_AssignAI(JSContext *cx, uintN argc, jsval *argv);
jsval JSI_GetSession(JSContext* cx);
jsval JSI_GetAssignment(JSContext* cx);
void CallCallback();
//void SetAssignment(EPlayerSlotAssignment, CNetServerSession *pSession, int sessionID);
void SetAssignment(EPlayerSlotAssignment, CNetSession *pSession, int sessionID);
protected:
friend class CGameAttributes;
inline void SetSlotID(size_t slotID)
{ m_SlotID=slotID; }
public:
CPlayerSlot(size_t slotID, CPlayer *pPlayer);
~CPlayerSlot();
inline CPlayer *GetPlayer()
{ return m_pPlayer; }
inline int GetSessionID()
{ return m_SessionID; }
inline size_t GetSlotID()
{ return m_SlotID; }
// Only applicable on the server host, and may return NULL if the slot
// is not assigned to a server session.
//inline CNetServerSession *GetSession()
//{ return m_pSession; }
inline CNetSession *GetSession()
{ return m_pSession; }
inline void SetCallback(PlayerSlotAssignmentCB *callback, void *data)
{
m_Callback=callback;
m_CallbackData=data;
}
inline EPlayerSlotAssignment GetAssignment()
{ return m_Assignment; }
// Reset any assignment the slot might have had before and mark the slot as
// closed.
void AssignClosed();
// [Server] Assign the slot to a connected session
//void AssignToSession(CNetServerSession *pSession);
void AssignToSession(CNetSession *pSession);
// [Client] The slot has been assigned by the server to a session ID, mirror
// the assignment
void AssignToSessionID(int sessionID);
// Reset any assignment the slot might have before and mark the slot as free
void AssignOpen();
// Assign to the local player in SP or Server Player in MP
void AssignLocal();
// TODO This will wait until there actually is AI to set up
// void AssignAI();
static void ScriptingInit();
};
namespace PlayerSlotArray_JS
{
JSBool GetProperty( JSContext* cx, JSObject* obj, jsval id, jsval* vp );
}
class CGameAttributes:
public CSynchedJSObject<CGameAttributes>
{
public:
typedef void (UpdateCallback)(const CStrW& name, const CStrW& newValue, void *data);
CStrW m_MapFile;
CStrW m_ResourceLevel;
CStrW m_StartingPhase;
CStrW m_GameMode;
int m_LOSSetting; // ELOSSetting
bool m_FogOfWar;
bool m_ScreenshotMode;
// Note: we must use the un-internationalized name of the resource level and starting phase
private:
friend JSBool PlayerSlotArray_JS::GetProperty( JSContext* cx, JSObject* obj, jsval id, jsval* vp );
size_t m_NumSlots;
// All players in the game. m_Players[0] is the Gaia Player, like in CGame.
// m_Players[1..n] have a corresponding player slot in m_PlayerSlots[0..n-1]
std::vector <CPlayer *> m_Players;
std::vector <CPlayerSlot *> m_PlayerSlots;
JSObject *m_PlayerSlotArrayJS;
UpdateCallback *m_UpdateCB;
void *m_UpdateCBData;
CPlayer::UpdateCallback *m_PlayerUpdateCB;
void *m_PlayerUpdateCBData;
PlayerSlotAssignmentCB *m_PlayerSlotAssignmentCB;
void *m_PlayerSlotAssignmentCBData;
virtual void Update(const CStrW& name, ISynchedJSProperty *attrib);
static void OnNumSlotsUpdate(CSynchedJSObjectBase *owner);
jsval JSI_GetPlayerSlots(JSContext* cx);
jsval_t JSI_GetOpenSlot(JSContext *cx, uintN argc, jsval *argv);
jsval JSI_GetUsedSlotsAmount(JSContext* cx);
public:
CGameAttributes();
virtual ~CGameAttributes();
void SetValue(const CStrW& name, const CStrW& value);
inline void SetUpdateCallback(UpdateCallback *cb, void *userdata)
{
m_UpdateCB=cb;
m_UpdateCBData=userdata;
}
inline size_t GetSlotCount()
{ return m_NumSlots; }
inline CStrW GetGameMode()
{ return m_GameMode; }
// Remove all slots that are either opened or closed, so that all slots have
// an assignment and a player. Player IDs will be assigned in the same order
// as the slot indexes, but without holes in the numbering.
void FinalizeSlots();
// Get the player object for the passed Player ID
CPlayer *GetPlayer(size_t id);
// Get the slot object with the specified index
CPlayerSlot *GetSlot(size_t index);
void SetPlayerUpdateCallback(CPlayer::UpdateCallback *cb, void *userdata);
void SetPlayerSlotAssignmentCallback(PlayerSlotAssignmentCB *cb, void *userdata);
static void ScriptingInit();
};
extern CGameAttributes *g_GameAttributes;
#endif

View File

@ -37,7 +37,6 @@
#include "ps/Filesystem.h"
#include "ps/Font.h"
#include "ps/Game.h"
#include "ps/GameAttributes.h"
#include "ps/Globals.h"
#include "ps/Hotkey.h"
#include "ps/Loader.h"
@ -71,6 +70,8 @@
#include "scripting/DOMEvent.h"
#include "scripting/ScriptableComplex.h"
#include "scriptinterface/ScriptInterface.h"
#include "maths/scripting/JSInterface_Vector3D.h"
#include "graphics/scripting/JSInterface_Camera.h"
@ -306,21 +307,10 @@ static void RegisterJavascriptInterfaces()
JSI_Sound::ScriptingInit();
// scripting
SColour::ScriptingInit();
CScriptEvent::ScriptingInit();
// ps
JSI_Console::init();
CGameAttributes::ScriptingInit();
CPlayerSlot::ScriptingInit();
CPlayer::ScriptingInit();
PlayerCollection::Init( "PlayerCollection" );
// network
CNetClient::ScriptingInit();
CNetServer::ScriptingInit();
CNetSession::ScriptingInit();
CServerPlayer::ScriptingInit();
// GUI
CGUI::ScriptingInit();
@ -482,12 +472,13 @@ static void InitInput()
in_add_handler(HotkeyInputHandler);
in_add_handler(GlobalsInputHandler);
// gui_handler needs to be registered after (i.e. called before!) the
// hotkey handler so that input boxes can be typed in without
// setting off hotkeys.
in_add_handler(gui_handler);
// must be registered after (called before) the GUI which relies on these globals
in_add_handler(GlobalsInputHandler);
}
@ -573,7 +564,7 @@ void EndGame()
}
void Shutdown(int flags)
void Shutdown(int UNUSED(flags))
{
MICROLOG(L"Shutdown");
@ -582,11 +573,6 @@ void Shutdown(int flags)
ShutdownPs(); // Must delete g_GUI before g_ScriptingHost
if (! (flags & INIT_NO_SIM))
{
SAFE_DELETE(g_GameAttributes);
}
// destroy actor related stuff
TIMER_BEGIN(L"shutdown actor stuff");
delete &g_MaterialManager;
@ -803,14 +789,6 @@ void Init(const CmdLineArgs& args, int flags)
ogl_WarnIfError();
InitRenderer();
if (! (flags & INIT_NO_SIM))
{
g_GameAttributes = new CGameAttributes;
// Register a few Game/Network JS globals
g_ScriptingHost.SetGlobal("g_GameAttributes", OBJECT_TO_JSVAL(g_GameAttributes->GetScript()));
}
InitInput();
ogl_WarnIfError();
@ -833,66 +811,49 @@ void RenderGui(bool RenderingState)
class AutostartNetServer : public CNetServer
{
public:
AutostartNetServer(CGame *pGame, CGameAttributes *pGameAttributes, int maxPlayers) :
CNetServer(pGame->GetSimulation2()->GetScriptInterface(), pGame, pGameAttributes), m_NumPlayers(1), m_MaxPlayers(maxPlayers)
AutostartNetServer(const CStr& map, int maxPlayers) :
CNetServer(), m_NeedsStart(false), m_NumPlayers(0), m_MaxPlayers(maxPlayers)
{
CScriptValRooted attrs;
GetScriptInterface().Eval("({})", attrs);
GetScriptInterface().SetProperty(attrs.get(), "map", std::string(map), false);
UpdateGameAttributes(attrs);
}
protected:
virtual void OnPlayerJoin(CNetSession* pSession)
{
for (size_t slot = 0; slot < m_GameAttributes->GetSlotCount(); ++slot)
{
if (m_GameAttributes->GetSlot(slot)->GetAssignment() == SLOT_OPEN)
{
m_GameAttributes->GetSlot(slot)->AssignToSession(pSession);
break;
}
}
protected:
virtual void OnAddPlayer()
{
m_NumPlayers++;
debug_printf(L"# player joined (got %d, need %d)\n", (int)m_NumPlayers, (int)m_MaxPlayers);
if (m_NumPlayers >= m_MaxPlayers)
{
g_GUI->SwitchPage(L"page_loading.xml", JSVAL_VOID);
int ret = StartGame();
debug_assert(ret == 0);
}
m_NeedsStart = true; // delay until next Poll, so the new player has been fully processed
}
virtual void OnPlayerLeave(CNetSession* UNUSED(pSession))
virtual void OnRemovePlayer()
{
debug_warn(L"client left?!");
m_NumPlayers--;
}
virtual void Poll()
{
if (m_NeedsStart)
{
StartGame();
m_NeedsStart = false;
}
CNetServer::Poll();
}
private:
bool m_NeedsStart;
size_t m_NumPlayers;
size_t m_MaxPlayers;
};
class AutostartNetClient : public CNetClient
{
public:
AutostartNetClient(CGame *pGame, CGameAttributes *pGameAttributes) :
CNetClient(pGame->GetSimulation2()->GetScriptInterface(), pGame, pGameAttributes)
{
}
protected:
virtual void OnConnectComplete()
{
debug_printf(L"# connect complete\n");
}
virtual void OnStartGame()
{
g_GUI->SwitchPage(L"page_loading.xml", JSVAL_VOID);
int ret = StartGame();
debug_assert(ret == 0);
}
};
static bool Autostart(const CmdLineArgs& args)
{
/*
@ -908,12 +869,6 @@ static bool Autostart(const CmdLineArgs& args)
g_Game = new CGame();
g_GameAttributes->m_MapFile = autostartMap + ".pmp";
// Make the whole world visible
g_GameAttributes->m_LOSSetting = LOS_SETTING_ALL_VISIBLE;
g_GameAttributes->m_FogOfWar = false;
if (args.Has("autostart-host"))
{
InitPs(true, L"page_loading.xml");
@ -922,32 +877,33 @@ static bool Autostart(const CmdLineArgs& args)
if (args.Has("autostart-players"))
maxPlayers = args.Get("autostart-players").ToUInt();
g_NetServer = new AutostartNetServer(g_Game, g_GameAttributes, maxPlayers);
// TODO: player name, etc
bool ok = g_NetServer->Start(NULL, 0, NULL);
g_NetServer = new AutostartNetServer(autostartMap, maxPlayers);
bool ok = g_NetServer->SetupConnection();
debug_assert(ok);
g_NetClient = new CNetClient(g_Game);
// TODO: player name, etc
g_NetClient->SetupLocalConnection(*g_NetServer);
}
else if (args.Has("autostart-client"))
{
InitPs(true, L"page_loading.xml");
bool ok;
g_NetClient = new AutostartNetClient(g_Game, g_GameAttributes);
g_NetClient = new CNetClient(g_Game);
// TODO: player name, etc
ok = g_NetClient->Create();
debug_assert(ok);
ok = g_NetClient->Connect(args.Get("autostart-ip"), DEFAULT_HOST_PORT);
bool ok = g_NetClient->SetupConnection(args.Get("autostart-ip"));
debug_assert(ok);
}
else
{
for (int i = 1; i < 8; ++i)
g_GameAttributes->GetSlot(i)->AssignLocal();
CScriptValRooted attrs;
g_Game->GetSimulation2()->GetScriptInterface().Eval("({})", attrs);
g_Game->GetSimulation2()->GetScriptInterface().SetProperty(attrs.get(), "map", std::string(autostartMap), false);
PSRETURN ret = g_Game->StartGame(g_GameAttributes);
debug_assert(ret == PSRETURN_OK);
g_Game->SetPlayerID(1);
g_Game->StartGame(attrs);
LDR_NonprogressiveLoad();
ret = g_Game->ReallyStartGame();
PSRETURN ret = g_Game->ReallyStartGame();
debug_assert(ret == PSRETURN_OK);
InitPs(true, L"page_session_new.xml");

View File

@ -1,4 +1,4 @@
/* Copyright (C) 2009 Wildfire Games.
/* Copyright (C) 2010 Wildfire Games.
* This file is part of 0 A.D.
*
* 0 A.D. is free software: you can redistribute it and/or modify
@ -42,11 +42,7 @@ enum InitFlags
// skip initializing the in-game GUI.
// needed by map editor because it uses its own GUI.
INIT_NO_GUI = 2,
// skip initializing the simulation.
// used by actor viewer because it doesn't need the simulation code.
INIT_NO_SIM = 4
INIT_NO_GUI = 2
};
/**

View File

@ -1,178 +0,0 @@
/* 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"
#include "Player.h"
#include "network/NetMessage.h"
#include "ps/scripting/JSCollection.h"
#include "simulation/LOSManager.h"
CPlayer::CPlayer(size_t playerID):
m_PlayerID(playerID),
m_Name(CStrW(L"Player #")+CStrW((unsigned)playerID)),
m_Civilization(L""),
m_Colour(0.7f, 0.7f, 0.7f),
m_UpdateCB(0),
m_Active(false)
{
// m_LOSToken = LOS_GetTokenFor(playerID);
// Initialize diplomacy: we need to be neutral to Gaia and enemy to everyone else;
// however, if we are Gaia, we'll just be neutral to everyone; finally, everyone
// will be allied with themselves.
m_DiplomaticStance[0] = DIPLOMACY_NEUTRAL;
for(int i=1; i<=PS_MAX_PLAYERS; i++)
{
m_DiplomaticStance[i] = (m_PlayerID==0 ? DIPLOMACY_NEUTRAL : DIPLOMACY_ENEMY);
}
m_DiplomaticStance[m_PlayerID] = DIPLOMACY_ALLIED;
AddSynchedProperty( L"name", &m_Name );
AddSynchedProperty( L"civilization", &m_Civilization );
// HACK - since we have to use setColour to update this, we don't want to
// expose a colour property. Meanwhile, we want to have a property "colour"
// available to be able to use the update/sync system.
// So, this is only added to the SynchedProperties list and not also passed
// to CJSObject's list
ISynchedJSProperty *prop=new CSynchedJSProperty<SPlayerColour>(L"colour", &m_Colour, this);
m_SynchedProperties[L"colour"]=prop;
// HACK - maintain diplomacy synced in the same way, by adding a dummy property for each stance
for(int i=0; i<=PS_MAX_PLAYERS; i++)
{
CStrW name = CStrW(L"diplomaticStance_") + CStrW(i);
ISynchedJSProperty *prop=new CSynchedJSProperty<int>(name, &m_DiplomaticStance[i], this);
m_SynchedProperties[name]=prop;
}
}
CPlayer::~CPlayer()
{
// Side-effect of HACKs - since these properties are not passed to CJSObject's list,
// they don't get freed automatically
delete m_SynchedProperties[L"colour"];
for(int i=0; i<=PS_MAX_PLAYERS; i++)
{
CStrW name = CStrW(L"diplomaticStance_") + CStrW(i);
delete m_SynchedProperties[name];
}
}
void CPlayer::ScriptingInit()
{
g_ScriptingHost.DefineConstant("DIPLOMACY_ENEMY", DIPLOMACY_ENEMY);
g_ScriptingHost.DefineConstant("DIPLOMACY_NEUTRAL", DIPLOMACY_NEUTRAL);
g_ScriptingHost.DefineConstant("DIPLOMACY_ALLIED", DIPLOMACY_ALLIED);
AddMethod<CStrW, &CPlayer::JSI_ToString>("toString", 0);
AddMethod<void, &CPlayer::JSI_SetColour>("setColour", 1);
AddMethod<jsval_t, &CPlayer::JSI_GetColour>("getColour", 0);
AddMethod<void, &CPlayer::JSI_SetDiplomaticStance>("setDiplomaticStance", 2);
AddMethod<jsval_t, &CPlayer::JSI_GetDiplomaticStance>("getDiplomaticStance", 1);
AddProperty( L"id", &CPlayer::m_PlayerID, true );
AddProperty( L"active", &CPlayer::m_Active, true );
AddProperty( L"name", &CPlayer::m_Name, true );
// MT: Work out how this fits with the Synched stuff...
// AddClassProperty( L"name", &CPlayer::m_Name );
// AddClassProperty( L"colour", &CPlayer::m_Colour );
CJSObject<CPlayer>::ScriptingInit( "Player" );
}
void CPlayer::Update(const CStrW& name, ISynchedJSProperty *prop)
{
if (m_UpdateCB)
m_UpdateCB(name, prop->ToString(), this, m_UpdateCBData);
}
void CPlayer::SetValue(const CStrW& name, const CStrW& value)
{
ISynchedJSProperty *prop=GetSynchedProperty(name);
if (prop)
{
prop->FromString(value);
}
}
bool CPlayer::ValidateCommand(CNetMessage* UNUSED(pMsg))
{
return true;
}
CStrW CPlayer::JSI_ToString( JSContext* UNUSED(cx), uintN UNUSED(argc), jsval* UNUSED(argv) )
{
return L"[object Player: " + m_Name + L"]";
}
void CPlayer::JSI_SetColour( JSContext* UNUSED(cx), uintN UNUSED(argc), jsval* argv )
{
m_Colour=*( ToNative<SPlayerColour>(argv[0]) );
ISynchedJSProperty *prop=GetSynchedProperty(L"colour");
Update(L"colour", prop);
}
jsval_t CPlayer::JSI_GetColour( JSContext* UNUSED(cx), uintN UNUSED(argc), jsval* UNUSED(argv) )
{
//ISynchedJSProperty *prop=GetSynchedProperty(L"colour");
//return prop->Get(cx, this);
SPlayerColour* col = &m_Colour;
return ToJSVal(col);
}
void CPlayer::JSI_SetDiplomaticStance(JSContext *cx, uintN UNUSED(argc), jsval *argv)
{
try
{
CPlayer* player = ToPrimitive<CPlayer*>( argv[0] );
int stance = ToPrimitive<int>( argv[1] );
if (! (stance==DIPLOMACY_ENEMY || stance==DIPLOMACY_NEUTRAL || stance==DIPLOMACY_ALLIED))
{
JS_ReportError(cx, "Argument 2 must be a valid stance ID");
return;
}
m_DiplomaticStance[player->m_PlayerID] = (EDiplomaticStance) stance;
CStrW name = CStrW(L"diplomaticStance_") + CStrW((unsigned)player->m_PlayerID);
ISynchedJSProperty *prop=GetSynchedProperty(name);
Update(name, prop);
}
catch( PSERROR_Scripting_ConversionFailed )
{
JS_ReportError( cx, "Could not convert argument 1 to a Player object" );
}
}
jsval_t CPlayer::JSI_GetDiplomaticStance(JSContext *cx, uintN UNUSED(argc), jsval *argv)
{
try
{
CPlayer* player = ToPrimitive<CPlayer*>( argv[0] );
return ToJSVal( (int) m_DiplomaticStance[player->m_PlayerID] );
}
catch( PSERROR_Scripting_ConversionFailed )
{
JS_ReportError( cx, "Could not convert argument 1 to a Player object" );
return JSVAL_VOID;
}
}

View File

@ -1,126 +0,0 @@
/* 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/>.
*/
#ifndef INCLUDED_PLAYER
#define INCLUDED_PLAYER
#include "CStr.h"
#include "scripting/SynchedJSObject.h"
#include "scripting/ScriptableObject.h"
#include "scripting/ScriptCustomTypes.h"
#include "ps/scripting/JSCollection.h"
#include "Game.h"
class CNetMessage;
class HEntity;
class CTechnology;
typedef SColour SPlayerColour;
enum EDiplomaticStance
{
DIPLOMACY_ENEMY,
DIPLOMACY_NEUTRAL,
DIPLOMACY_ALLIED
};
const size_t invalidPlayerId = ~size_t(0); // rationale: see Unit.h
class CPlayer : public CSynchedJSObject<CPlayer>
{
public:
typedef void (UpdateCallback)(const CStrW& name, const CStrW& value, CPlayer *player, void *userdata);
private:
CStrW m_Name;
CStrW m_Civilization; // Note: this must be the un-internationalized name of the civ
size_t m_PlayerID;
size_t m_LOSToken;
SPlayerColour m_Colour;
int /*EDiplomaticStance*/ m_DiplomaticStance[PS_MAX_PLAYERS+1];
std::vector<CTechnology*> m_ActiveTechs;
bool m_Active; // Is this an active player, or a dummy?
UpdateCallback *m_UpdateCB;
void *m_UpdateCBData;
virtual void Update(const CStrW& name, ISynchedJSProperty *prop);
public:
CPlayer( size_t playerID );
~CPlayer();
bool ValidateCommand(CNetMessage *pMsg);
inline size_t GetPlayerID() const
{ return m_PlayerID; }
inline void SetPlayerID(size_t id)
{ m_PlayerID=id; }
inline size_t GetLOSToken() const
{ return m_LOSToken; }
inline const CStrW& GetName() const
{ return m_Name; }
inline void SetName(const CStrW& name)
{ m_Name = name; }
inline const SPlayerColour &GetColour() const
{ return m_Colour; }
inline void SetColour(const SPlayerColour &colour)
{ m_Colour = colour; }
inline EDiplomaticStance GetDiplomaticStance(CPlayer* other) const
{ return (EDiplomaticStance)m_DiplomaticStance[other->m_PlayerID]; }
inline void SetDiplomaticStance(CPlayer* other, EDiplomaticStance stance)
{ m_DiplomaticStance[other->m_PlayerID] = stance; }
inline void SetUpdateCallback(UpdateCallback *cb, void *userdata)
{
m_UpdateCB=cb;
m_UpdateCBData=userdata;
}
void SetValue(const CStrW& name, const CStrW& value);
inline void AddActiveTech(CTechnology* tech)
{
m_ActiveTechs.push_back(tech);
}
inline const std::vector<CTechnology*>& GetActiveTechs()
{
return m_ActiveTechs;
}
inline bool IsActive() const
{ return m_Active; }
inline void SetActive(bool active)
{ m_Active = active; }
// JS Interface Functions
CStrW JSI_ToString( JSContext* context, uintN argc, jsval* argv );
void JSI_SetColour(JSContext *context, uintN argc, jsval *argv);
jsval_t JSI_GetColour(JSContext *context, uintN argc, jsval *argv);
void JSI_SetDiplomaticStance(JSContext *context, uintN argc, jsval *argv);
jsval_t JSI_GetDiplomaticStance(JSContext *context, uintN argc, jsval *argv);
static void ScriptingInit();
};
typedef CJSCollection<CPlayer*, &CPlayer::JSI_class> PlayerCollection;
#endif

View File

@ -35,7 +35,6 @@
#include "ps/CStr.h"
#include "ps/Errors.h"
#include "ps/Game.h"
#include "ps/GameAttributes.h"
#include "ps/Loader.h"
#include "ps/LoaderThunks.h"
#include "ps/World.h"
@ -68,25 +67,30 @@ CWorld::CWorld(CGame *pGame):
/**
* Sets up the game world and loads required world resources.
*
* @param CGameAttributes * pGameAttributes pointer to the game attribute values.
**/
void CWorld::Initialize(CGameAttributes *pAttribs)
void CWorld::Initialize(const CStrW& mapFile)
{
// Load the map, if one was specified
if (pAttribs->m_MapFile.length())
if (mapFile.length())
{
VfsPath mapfilename(VfsPath(L"maps/scenarios/")/(std::wstring)pAttribs->m_MapFile);
VfsPath mapfilename(VfsPath(L"maps/scenarios/")/(std::wstring)mapFile);
CMapReader* reader = 0;
try {
try
{
reader = new CMapReader;
CTriggerManager* pTriggerManager = NULL;
reader->LoadMap(mapfilename, m_Terrain, m_UnitManager, g_Renderer.GetWaterManager(),
g_Renderer.GetSkyManager(), &g_LightEnv, m_pGame->GetView()->GetCamera(), m_pGame->GetView()->GetCinema(),
reader->LoadMap(mapfilename, m_Terrain, m_UnitManager,
CRenderer::IsInitialised() ? g_Renderer.GetWaterManager() : NULL,
CRenderer::IsInitialised() ? g_Renderer.GetSkyManager() : NULL,
&g_LightEnv,
m_pGame->GetView() ? m_pGame->GetView()->GetCamera() : NULL,
m_pGame->GetView() ? m_pGame->GetView()->GetCinema() : NULL,
pTriggerManager, m_pGame->GetSimulation2(), NULL);
// fails immediately, or registers for delay loading
} catch (PSERROR_File& err) {
}
catch (PSERROR_File& err)
{
delete reader;
LOG(CLogger::Error, LOG_CATEGORY, L"Failed to load map %ls: %hs", mapfilename.string().c_str(), err.what());
throw PSERROR_Game_World_MapLoadFailed();
@ -97,12 +101,10 @@ void CWorld::Initialize(CGameAttributes *pAttribs)
/**
* Initializes the game world with the attributes provided.
*
* @param CGameAttributes * pGameAttributes pointer to the game attribute values.
**/
void CWorld::RegisterInit(CGameAttributes *pAttribs)
void CWorld::RegisterInit(const CStrW& mapFile)
{
Initialize(pAttribs);
Initialize(mapFile);
}

View File

@ -34,13 +34,13 @@ ERROR_SUBGROUP(Game, World);
ERROR_TYPE(Game_World, MapLoadFailed);
class CGame;
class CGameAttributes;
class CUnitManager;
class CEntityManager;
class CProjectileManager;
class CLOSManager;
class CTerritoryManager;
class CTerrain;
class CStrW;
/**
* CWorld is a general data class containing whatever is needed to accurately represent the world.
@ -76,11 +76,11 @@ public:
CWorld(CGame *pGame);
~CWorld();
void RegisterInit(CGameAttributes *pGameAttributes);
void RegisterInit(const CStrW& mapFile);
/*
Initialize the World - load the map and all objects
*/
void Initialize(CGameAttributes *pGameAttributes);
void Initialize(const CStrW& mapFile);
// provided for JS _rewritemaps function
void RewriteMap();

View File

@ -1,19 +0,0 @@
/* 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"
#include "JSCollection.h"

View File

@ -1,448 +0,0 @@
/* 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/>.
*/
// JSCollection.h
//
// A Collection object for JavaScript to hold one specific type of
// object.
#include "scripting/ScriptingHost.h"
#include "scripting/ScriptObject.h"
#include "scripting/JSConversions.h"
#ifndef INCLUDED_JSCOLLECTION
#define INCLUDED_JSCOLLECTION
template<typename T, JSClass* ScriptType> class CJSCollection
{
public:
class CJSCollectionData
{
public:
std::vector<T>* m_Data;
bool m_EngineOwned;
CJSCollectionData() { m_Data = new std::vector<T>; m_EngineOwned = false; }
CJSCollectionData( const std::vector<T>& Copy ) { m_Data = new std::vector<T>( Copy ); m_EngineOwned = false; }
CJSCollectionData( std::vector<T>* Reference ) { m_Data = Reference; m_EngineOwned = true; }
~CJSCollectionData() { if( !m_EngineOwned ) delete( m_Data ); }
};
static JSClass JSI_class;
private:
static JSPropertySpec JSI_props[];
static JSFunctionSpec JSI_methods[];
static JSBool ToString( JSContext* cx, JSObject* obj, uintN argc, jsval* argv, jsval* rval );
static JSBool Subset( JSContext* cx, JSObject* obj, uintN argc, jsval* agv, jsval* rval );
static JSBool Push( JSContext* cx, JSObject* obj, uintN argc, jsval* agv, jsval* rval );
static JSBool Pop( JSContext* cx, JSObject* obj, uintN argc, jsval* agv, jsval* rval );
static JSBool Remove( JSContext* cx, JSObject* obj, uintN argc, jsval* agv, jsval* rval );
static JSBool GetLength( JSContext* cx, JSObject* obj, jsval id, jsval* vp );
static JSBool IsEmpty( JSContext* cx, JSObject* obj, jsval id, jsval* vp );
static JSBool Clear( JSContext* cx, JSObject* obj, uintN argc, jsval* argv, jsval* rval );
static JSBool Equals( JSContext* cx, JSObject* obj, uintN argc, jsval* argv, jsval* rval );
static JSBool AddProperty( JSContext* cx, JSObject* obj, jsval id, jsval* vp );
static JSBool RemoveProperty( JSContext* cx, JSObject* obj, jsval id, jsval* vp );
static JSBool GetProperty( JSContext* cx, JSObject* obj, jsval id, jsval* vp );
static JSBool SetProperty( JSContext* cx, JSObject* obj, jsval id, jsval* vp );
static void Finalize( JSContext* cx, JSObject* obj );
static bool GetNative( jsval m, T& Storage );
public:
static void Init( const char* ClassName );
static JSObject* Create( const std::vector<T>& Copy );
static JSObject* CreateReference( std::vector<T>* Reference );
static std::vector<T>* RetrieveSet( JSContext* cx, JSObject* obj );
};
template<typename T, JSClass* ScriptType> JSClass CJSCollection<T, ScriptType>::JSI_class =
{
0, JSCLASS_HAS_PRIVATE,
AddProperty, RemoveProperty,
GetProperty, SetProperty,
JS_EnumerateStub, JS_ResolveStub,
JS_ConvertStub, Finalize,
NULL, NULL, NULL, NULL,
NULL, NULL, NULL, NULL
};
template<typename T, JSClass* ScriptType> JSPropertySpec CJSCollection<T, ScriptType>::JSI_props[] =
{
{ "length", 0, JSPROP_PERMANENT | JSPROP_READONLY, GetLength, NULL },
{ "empty", 0, JSPROP_PERMANENT | JSPROP_READONLY, IsEmpty, NULL },
{ NULL, 0, 0, NULL, NULL }
};
template<typename T, JSClass* ScriptType> JSFunctionSpec CJSCollection<T, ScriptType>::JSI_methods[] =
{
{ "toString", ToString, 0, 0, 0 },
{ "subset", Subset, 1, 0, 0 },
{ "push", Push, 1, 0, 0 },
{ "pop", Pop, 0, 0, 0 },
{ "remove", Remove, 1, 0, 0 },
{ "clear", Clear, 0, 0, 0 },
{ "equals", Equals, 1, 0, 0 },
{ NULL, NULL, 0, 0, 0 },
};
template<typename T, JSClass* ScriptType> std::vector<T>* CJSCollection<T, ScriptType>::RetrieveSet( JSContext* cx, JSObject* obj )
{
CJSCollectionData* Info = (CJSCollectionData*)JS_GetInstancePrivate( cx, obj, &JSI_class, NULL );
if( !Info ) return( NULL );
return( Info->m_Data );
}
template<typename T, JSClass* ScriptType> JSBool CJSCollection<T, ScriptType>::AddProperty ( JSContext* cx, JSObject* obj, jsval id, jsval* vp )
{
if( !JSVAL_IS_INT( id ) )
return( JS_TRUE ); // Accessing a named property; nothing interesting.
int index = JSVAL_TO_INT( id );
std::vector<T>* set = RetrieveSet( cx, obj );
if( !set )
return( JS_TRUE ); // That's odd; we've lost the pointer.
if( ( index >= 0 ) && ( index < (int)set->size() ) )
return( JS_TRUE );
if( index != (int)set->size() )
{
// If you add something to the collection, it must be at the
// next empty array element; i.e. set->size()
JS_ReportError( cx, "Cannot create a property at that subscript" );
return( JS_FALSE );
}
T m;
if( !GetNative( *vp, m ) )
return( JS_TRUE );
set->resize( index + 1 );
set->at( index ) = m;
return( JS_TRUE );
}
template<typename T, JSClass* ScriptType> JSBool CJSCollection<T, ScriptType>::RemoveProperty (
JSContext* cx, JSObject* obj, jsval id, jsval* UNUSED(vp) )
{
if( !JSVAL_IS_INT( id ) )
return( JS_TRUE ); // Accessing a named property; nothing interesting.
int index = JSVAL_TO_INT( id );
std::vector<T>* set = RetrieveSet( cx, obj );
if( !set )
return( JS_TRUE ); // That's odd; we've lost the pointer.
if( ( index < 0 ) || ( index >= (int)set->size() ) )
{
JS_ReportError( cx, "Index out of bounds." );
return( JS_TRUE );
}
set->erase( set->begin() + index );
return( JS_TRUE );
}
template<typename T, JSClass* ScriptType> JSBool CJSCollection<T, ScriptType>::GetProperty ( JSContext* cx, JSObject* obj, jsval id, jsval* vp )
{
if( !JSVAL_IS_INT( id ) )
return( JS_TRUE ); // Accessing a named property; nothing interesting.
int index = JSVAL_TO_INT( id );
std::vector<T>* set = RetrieveSet( cx, obj );
if( !set )
return( JS_FALSE ); // That's odd; we've lost the pointer.
if( ( index < 0 ) || ( index >= (int)set->size() ) )
{
JS_ReportError( cx, "Index out of bounds." );
return( JS_TRUE );
}
*vp = ToJSVal( set->at( index ) );
return( JS_TRUE );
}
template<typename T, JSClass* ScriptType> JSBool CJSCollection<T, ScriptType>::SetProperty ( JSContext* cx, JSObject* obj, jsval id, jsval* vp )
{
if( !JSVAL_IS_INT( id ) )
return( JS_TRUE ); // Accessing a named property; nothing interesting.
int index = JSVAL_TO_INT( id );
std::vector<T>* set = RetrieveSet( cx, obj );
if( !set )
return( JS_FALSE ); // That's odd; we've lost the pointer.
if( ( index < 0 ) || ( index >= (int)set->size() ) )
{
JS_ReportError( cx, "Index out of bounds." );
return( JS_TRUE );
}
T m;
if( !GetNative( *vp, m ) )
return( JS_TRUE );
set->at( index ) = m;
return( JS_TRUE );
}
template<typename T, JSClass* ScriptType> void CJSCollection<T, ScriptType>::Finalize ( JSContext* cx, JSObject* obj )
{
CJSCollectionData* info = (CJSCollectionData*)JS_GetPrivate( cx, obj );
if( info )
delete( info );
}
template<typename T, JSClass* ScriptType> JSObject* CJSCollection<T, ScriptType>::Create( const std::vector<T>& Copy )
{
JSObject* Collection = JS_NewObject( g_ScriptingHost.GetContext(), &JSI_class, NULL, NULL );
JS_SetPrivate( g_ScriptingHost.GetContext(), Collection, new CJSCollectionData( Copy ) );
return( Collection );
}
template<typename T, JSClass* ScriptType> JSObject* CJSCollection<T, ScriptType>::CreateReference( std::vector<T>* Reference )
{
JSObject* Collection = JS_NewObject( g_ScriptingHost.GetContext(), &JSI_class, NULL, NULL );
JS_SetPrivate( g_ScriptingHost.GetContext(), Collection, new CJSCollectionData( Reference ) );
return( Collection );
}
template<typename T, JSClass* ScriptType> void CJSCollection<T, ScriptType>::Init( const char* ClassName )
{
JSI_class.name = ClassName;
g_ScriptingHost.DefineCustomObjectType( &JSI_class, NULL, 0, JSI_props, JSI_methods, NULL, NULL );
}
template<typename T, JSClass* ScriptType> bool CJSCollection<T, ScriptType>::GetNative( jsval m, T& Storage )
{
if( ToPrimitive( g_ScriptingHost.GetContext(), m, Storage ) )
return( true );
JS_ReportError( g_ScriptingHost.GetContext(), "Only objects of type %s can be stored in this collection.", ScriptType->name );
return( false );
}
template<typename T, JSClass* ScriptType> JSBool CJSCollection<T, ScriptType>::ToString(
JSContext* cx, JSObject* obj, uintN UNUSED(argc), jsval* UNUSED(argv), jsval* rval )
{
std::vector<T>* set = RetrieveSet( cx, obj );
if( !set )
return( JS_FALSE ); // That's odd; we've lost the pointer.
wchar_t buffer[256];
int len = swprintf_s( buffer, ARRAY_SIZE(buffer), L"[object Collection: %hs: %ld members]", ScriptType->name, set->size() );
buffer[255] = 0;
if (len < 0 || len > 255) len=255;
utf16string u16str(buffer, buffer+len);
*rval = STRING_TO_JSVAL( JS_NewUCStringCopyZ( cx, u16str.c_str() ) );
return( JS_TRUE );
}
template<typename T, JSClass* ScriptType> JSBool CJSCollection<T, ScriptType>::GetLength(
JSContext* cx, JSObject* obj, jsval UNUSED(id), jsval* vp )
{
std::vector<T>* set = RetrieveSet( cx, obj );
if( !set )
return( JS_FALSE ); // That's odd; we've lost the pointer.
*vp = INT_TO_JSVAL( set->size() );
return( JS_TRUE );
}
template<typename T, JSClass* ScriptType> JSBool CJSCollection<T, ScriptType>::IsEmpty(
JSContext* cx, JSObject* obj, jsval UNUSED(id), jsval* vp )
{
std::vector<T>* set = RetrieveSet( cx, obj );
if( !set )
return( JS_FALSE ); // That's odd; we've lost the pointer.
*vp = BOOLEAN_TO_JSVAL( set->empty() );
return( JS_TRUE );
}
template<typename T, JSClass* ScriptType> JSBool CJSCollection<T, ScriptType>::Equals( JSContext* cx, JSObject* obj, uintN argc, jsval* argv, jsval* rval )
{
std::vector<T>* a = RetrieveSet( cx, obj );
if( !a )
return( JS_FALSE );
if( ( argc == 0 ) || ( !JSVAL_IS_OBJECT( argv[0] ) ) ) return( JS_FALSE );
std::vector<T>* b = RetrieveSet( cx, JSVAL_TO_OBJECT( argv[0] ) );
if( !b )
return( JS_FALSE );
typename std::vector<T>::iterator ita, itb;
size_t seek = a->size();
for( ita = a->begin(); ita != a->end(); ita++ )
for( itb = b->begin(); itb != b->end(); itb++ )
if( *ita == *itb ) { seek--; break; }
if( seek )
{
*rval = JSVAL_FALSE;
return( JS_TRUE );
}
seek = b->size();
for( itb = b->begin(); itb != b->end(); itb++ )
for( ita = a->begin(); ita != a->end(); ita++ )
if( *ita == *itb ) { seek--; break; }
if( seek )
{
*rval = JSVAL_FALSE;
return( JS_TRUE );
}
*rval = JSVAL_TRUE;
return( JS_TRUE );
}
template<typename T, JSClass* ScriptType> JSBool CJSCollection<T, ScriptType>::Subset(
JSContext* cx, JSObject* obj, uintN argc, jsval* argv, jsval* rval )
{
debug_assert( argc > 0 );
std::vector<T>* Set = RetrieveSet( cx, obj );
if( !Set )
return( JS_FALSE );
CJSCollectionData* CollectionData = new CJSCollectionData();
CollectionData->m_EngineOwned = false;
typename std::vector<T>::iterator it;
CScriptObject Predicate( argv[0] );
JSObject* Collection = JS_NewObject( g_ScriptingHost.GetContext(), &JSI_class, NULL, NULL );
JS_SetPrivate( g_ScriptingHost.GetContext(), Collection, CollectionData );
for( it = Set->begin(); it != Set->end(); it++ )
if( Predicate.Run( ToScript( (T*)&( *it ) ) ) )
CollectionData->m_Data->push_back( *it );
*rval = OBJECT_TO_JSVAL( Collection );
return( JS_TRUE );
}
template<typename T, JSClass* ScriptType> JSBool CJSCollection<T, ScriptType>::Clear(
JSContext* cx, JSObject* obj, uintN UNUSED(argc), jsval* UNUSED(argv), jsval* rval )
{
std::vector<T>* Set = RetrieveSet( cx, obj );
if( !Set )
return( JS_FALSE );
Set->clear();
*rval = JSVAL_TRUE;
return( JS_TRUE );
}
template<typename T, JSClass* ScriptType> JSBool CJSCollection<T, ScriptType>::Push( JSContext* cx, JSObject* obj, uintN argc, jsval* argv, jsval* rval )
{
debug_assert( argc > 0 );
std::vector<T>* Set = RetrieveSet( cx, obj );
if( !Set )
return( JS_FALSE );
for( int i = 0; i < (int)argc; i++ )
{
T m;
if( GetNative( argv[i], m ) )
Set->push_back( m );
}
*rval = INT_TO_JSVAL( Set->size() );
return( JS_TRUE );
}
template<typename T, JSClass* ScriptType> JSBool CJSCollection<T, ScriptType>::Pop(
JSContext* cx, JSObject* obj, uintN UNUSED(argc), jsval* UNUSED(argv), jsval* rval )
{
std::vector<T>* Set = RetrieveSet( cx, obj );
if( !Set )
return( JS_FALSE );
if( !Set->size() )
{
JS_ReportError( cx, "Can't pop members from an empty Collection." );
return( JS_TRUE );
}
*rval = ToJSVal( Set->back() );
Set->pop_back();
return( JS_TRUE );
}
template<typename T, JSClass* ScriptType> JSBool CJSCollection<T, ScriptType>::Remove(
JSContext* cx, JSObject* obj, uintN argc, jsval* argv, jsval* UNUSED(rval) )
{
debug_assert( argc > 0 );
std::vector<T>* Set = RetrieveSet( cx, obj );
if( !Set )
return( JS_TRUE ); // That's odd; we've lost the pointer.
int index;
try
{
index = ToPrimitive<int>( argv[0] );
}
catch( PSERROR_Scripting_ConversionFailed )
{
JS_ReportError( cx, "Index must be an integer." );
return( JS_TRUE );
}
if( ( index < 0 ) || ( index >= (int)Set->size() ) )
{
JS_ReportError( cx, "Index out of bounds." );
return( JS_TRUE );
}
Set->erase( Set->begin() + index );
return( JS_TRUE );
}
#endif

View File

@ -1,115 +0,0 @@
/* 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/>.
*/
#ifndef INCLUDED_JSMAP
#define INCLUDED_JSMAP
/*
A simple read-only JS wrapper for map types (STL associative containers).
Has been tested with integer keys. Writeability shouldn't be all that hard
to add, it's just not needed by the current users of the class.
*/
template <typename T, typename KeyType = typename T::key_type>
class CJSMap
{
T *m_pInstance;
JSObject *m_JSObject;
typedef typename T::iterator iterator;
static JSBool GetProperty( JSContext* cx, JSObject* obj, jsval id, jsval* vp )
{
CJSMap<T, KeyType> *object=ToNative<CJSMap<T, KeyType> >(cx, obj);
T *pInstance = object->m_pInstance;
KeyType key=ToPrimitive<KeyType>(id);
iterator it;
it=pInstance->find(key);
if (it == pInstance->end())
return JS_FALSE;
*vp=OBJECT_TO_JSVAL(ToScript(it->second));
return JS_TRUE;
}
static JSBool SetProperty( JSContext* UNUSED(cx), JSObject* UNUSED(obj), jsval UNUSED(id), jsval* UNUSED(vp) )
{
return JS_FALSE;
}
void CreateScriptObject()
{
if( !m_JSObject )
{
m_JSObject = JS_NewObject( g_ScriptingHost.GetContext(), &JSI_class, NULL, NULL );
JS_AddRoot( g_ScriptingHost.GetContext(), (void*)&m_JSObject );
JS_SetPrivate( g_ScriptingHost.GetContext(), m_JSObject, this );
}
}
void ReleaseScriptObject()
{
if( m_JSObject )
{
JS_SetPrivate( g_ScriptingHost.GetContext(), m_JSObject, NULL );
JS_RemoveRoot( g_ScriptingHost.GetContext(), &m_JSObject );
m_JSObject = NULL;
}
}
public:
CJSMap(T* pInstance):
m_pInstance(pInstance),
m_JSObject(NULL)
{
}
~CJSMap()
{
ReleaseScriptObject();
}
JSObject *GetScript()
{
if (!m_JSObject)
CreateScriptObject();
return m_JSObject;
}
static void ScriptingInit(const char *className)
{
JSI_class.name=className;
g_ScriptingHost.DefineCustomObjectType(&JSI_class,
NULL, 0, NULL, NULL, NULL, NULL);
}
// Needs to be public for ToScript/ToNative
static JSClass JSI_class;
};
template<typename T, typename KeyType>
JSClass CJSMap<T, KeyType>::JSI_class =
{
NULL, JSCLASS_HAS_PRIVATE,
JS_PropertyStub, JS_PropertyStub,
GetProperty, SetProperty,
JS_EnumerateStub, JS_ResolveStub,
JS_ConvertStub, JS_FinalizeStub,
NULL, NULL, NULL, NULL
};
#endif // INCLUDED_JSMAP

View File

@ -35,9 +35,7 @@
#include "ps/Game.h"
#include "ps/Profile.h"
#include "ps/Filesystem.h"
#include "ps/Game.h"
#include "ps/World.h"
#include "ps/Player.h"
#include "ps/Loader.h"
#include "ps/ProfileViewer.h"
#include "graphics/Camera.h"

View File

@ -21,27 +21,11 @@
#include "graphics/ObjectManager.h"
#include "maths/scripting/JSInterface_Vector3D.h"
#include "ps/Parser.h"
#include "ps/Player.h"
#include "lib/sysdep/sysdep.h" // isfinite
#include <math.h>
#include <cfloat>
#include "scripting/ScriptableComplex.inl"
// CPlayer*
template<> bool ToPrimitive<CPlayer*>( JSContext* cx, jsval v, CPlayer*& Storage )
{
if( !JSVAL_IS_OBJECT( v ) || ( v == JSVAL_NULL ) ) return( false );
CPlayer* Data = (CPlayer*)JS_GetInstancePrivate( cx, JSVAL_TO_OBJECT( v ), &CPlayer::JSI_class, NULL );
if( !Data ) return( false );
Storage = Data;
return( true );
}
template<> JSObject* ToScript<CPlayer*>( CPlayer** Native )
{
return( ToScript<CPlayer>( *Native ) );
}
// CVector3D
template<> CVector3D* ToNative<CVector3D>( JSContext* cx, JSObject* obj )

View File

@ -26,7 +26,6 @@ class CStrW;
class CScriptObject;
class CObjectEntry;
class CVector3D;
class CPlayer;
// -----
//
@ -112,10 +111,6 @@ template<> jsval ToJSVal<CVector3D>( const CVector3D& Native );
template<> bool ToPrimitive<CObjectEntry>( JSContext* cx, jsval v, CObjectEntry*& Storage );
template<> jsval ToJSVal<CObjectEntry>( CObjectEntry*& Native );
// CPlayer*
template<> bool ToPrimitive<CPlayer*>( JSContext* cx, jsval v, CPlayer*& Storage );
template<> JSObject* ToScript<CPlayer*>( CPlayer** Native );
// CScriptObject
template<> bool ToPrimitive<CScriptObject>( JSContext* cx, jsval v, CScriptObject& Storage );
template<> jsval ToJSVal<CScriptObject>( CScriptObject& Native );

View File

@ -39,25 +39,23 @@
#include "lib/svn_revision.h"
#include "lib/frequency_filter.h"
#include "maths/scripting/JSInterface_Vector3D.h"
#include "network/NetClient.h"
#include "network/NetServer.h"
#include "ps/CConsole.h"
#include "ps/CLogger.h"
#include "ps/CStr.h"
#include "ps/Game.h"
#include "ps/GameAttributes.h"
#include "ps/Globals.h" // g_frequencyFilter
#include "ps/GameSetup/GameSetup.h"
#include "ps/Hotkey.h"
#include "ps/ProfileViewer.h"
#include "ps/World.h"
#include "ps/i18n.h"
#include "ps/scripting/JSCollection.h"
#include "ps/scripting/JSInterface_Console.h"
#include "ps/scripting/JSInterface_VFS.h"
#include "renderer/Renderer.h"
#include "renderer/SkyManager.h"
#include "scriptinterface/ScriptInterface.h"
#include "simulation/LOSManager.h"
#include "simulation2/Simulation2.h"
#define LOG_CATEGORY L"script"
@ -194,90 +192,6 @@ JSBool StopJsTimer(JSContext* cx, JSObject*, uintN argc, jsval* argv, jsval* rva
// Game Setup
//-----------------------------------------------------------------------------
// Create a new network server object.
// params:
// returns: net server object
JSBool CreateServer(JSContext* cx, JSObject*, uintN argc, jsval* argv, jsval* rval)
{
JSU_REQUIRE_NO_PARAMS();
if( !g_Game )
g_Game = new CGame();
if( !g_NetServer )
g_NetServer = new CNetServer(g_Game->GetSimulation2()->GetScriptInterface(), g_Game, g_GameAttributes);
*rval = OBJECT_TO_JSVAL(g_NetServer->GetScript());
return( JS_TRUE );
}
// Create a new network client object.
// params:
// returns: net client object
JSBool CreateClient(JSContext* cx, JSObject*, uintN argc, jsval* argv, jsval* rval)
{
JSU_REQUIRE_NO_PARAMS();
if( !g_Game )
g_Game = new CGame();
if( !g_NetClient )
g_NetClient = new CNetClient(g_Game->GetSimulation2()->GetScriptInterface(), g_Game, g_GameAttributes);
*rval = OBJECT_TO_JSVAL(g_NetClient->GetScript());
return( JS_TRUE );
}
// Begin the process of starting a game.
// params:
// returns: success [bool]
// notes:
// - Performs necessary initialization while calling back into the
// main loop, so the game remains responsive to display+user input.
// - When complete, the engine calls the reallyStartGame JS function.
// TODO: Replace StartGame with Create(Game|Server|Client)/game.start() -
// after merging CGame and CGameAttributes
JSBool StartGame(JSContext* cx, JSObject*, uintN argc, jsval* argv, jsval* rval)
{
JSU_REQUIRE_NO_PARAMS();
*rval = BOOLEAN_TO_JSVAL(JS_TRUE);
// Hosted MP Game
if (g_NetServer)
{
*rval = BOOLEAN_TO_JSVAL(g_NetServer->StartGame() == 0);
}
// Joined MP Game
else if (g_NetClient)
{
*rval = BOOLEAN_TO_JSVAL(g_NetClient->StartGame() == 0);
}
// Start an SP Game Session
else if (!g_Game)
{
g_Game = new CGame();
PSRETURN ret = g_Game->StartGame(g_GameAttributes);
if (ret != PSRETURN_OK)
{
// Failed to start the game - destroy it, and return false
delete g_Game;
g_Game = NULL;
*rval = BOOLEAN_TO_JSVAL(JS_FALSE);
return( JS_TRUE );
}
}
else
{
*rval = BOOLEAN_TO_JSVAL(JS_FALSE);
}
return( JS_TRUE );
}
// Immediately ends the current game (if any).
// params:
// returns:
@ -289,14 +203,6 @@ JSBool EndGame(JSContext* cx, JSObject*, uintN argc, jsval* argv, jsval* rval)
return JS_TRUE;
}
JSBool GetGameMode(JSContext* cx, JSObject*, uintN argc, jsval* argv, jsval* rval)
{
JSU_REQUIRE_NO_PARAMS();
*rval = ToJSVal( g_GameAttributes->GetGameMode() );
return JS_TRUE;
}
//-----------------------------------------------------------------------------
// Internationalization
//-----------------------------------------------------------------------------
@ -391,7 +297,10 @@ JSBool ForceGarbageCollection(JSContext* cx, JSObject* UNUSED(obj), uintN argc,
JSBool GetFps( JSContext* cx, JSObject*, uintN argc, jsval* argv, jsval* rval )
{
JSU_REQUIRE_NO_PARAMS();
*rval = INT_TO_JSVAL(g_frequencyFilter->StableFrequency());
int freq = 0;
if (g_frequencyFilter)
freq = g_frequencyFilter->StableFrequency();
*rval = INT_TO_JSVAL(freq);
return JS_TRUE;
}
@ -703,11 +612,7 @@ JSFunctionSpec ScriptFunctionTable[] =
JS_FUNC("stopXTimer", StopJsTimer, 1)
// Game Setup
JS_FUNC("startGame", StartGame, 0)
JS_FUNC("endGame", EndGame, 0)
JS_FUNC("getGameMode", GetGameMode, 0)
JS_FUNC("createClient", CreateClient, 0)
JS_FUNC("createServer", CreateServer, 0)
// VFS (external)
JS_FUNC("buildDirEntList", JSI_VFS::BuildDirEntList, 1)
@ -757,45 +662,6 @@ JSFunctionSpec ScriptFunctionTable[] =
// property accessors
//-----------------------------------------------------------------------------
JSBool GetPlayerSet( JSContext* UNUSED(cx), JSObject* UNUSED(obj), jsval UNUSED(id), jsval* vp )
{
std::vector<CPlayer*>* players = g_Game->GetPlayers();
*vp = OBJECT_TO_JSVAL( PlayerCollection::Create( *players ) );
return( JS_TRUE );
}
JSBool GetLocalPlayer( JSContext* UNUSED(cx), JSObject* UNUSED(obj), jsval UNUSED(id), jsval* vp )
{
*vp = OBJECT_TO_JSVAL( g_Game->GetLocalPlayer()->GetScript() );
return( JS_TRUE );
}
JSBool GetGaiaPlayer( JSContext* UNUSED(cx), JSObject* UNUSED(obj), jsval UNUSED(id), jsval* vp )
{
*vp = OBJECT_TO_JSVAL( g_Game->GetPlayer( 0 )->GetScript() );
return( JS_TRUE );
}
JSBool SetLocalPlayer( JSContext* cx, JSObject* UNUSED(obj), jsval UNUSED(id), jsval* vp )
{
CPlayer* newLocalPlayer = ToNative<CPlayer>( *vp );
if( !newLocalPlayer )
{
JS_ReportError( cx, "Not a valid Player." );
return( JS_TRUE );
}
g_Game->SetLocalPlayer( newLocalPlayer );
return( JS_TRUE );
}
JSBool GetGameView( JSContext* UNUSED(cx), JSObject* UNUSED(obj), jsval UNUSED(id), jsval* vp )
{
if (g_Game)
@ -830,9 +696,6 @@ JSPropertySpec ScriptGlobalTable[] =
{ "camera" , GLOBAL_CAMERA, JSPROP_PERMANENT, JSI_Camera::getCamera, JSI_Camera::setCamera },
{ "console" , GLOBAL_CONSOLE, JSPROP_PERMANENT|JSPROP_READONLY, JSI_Console::getConsole, 0 },
{ "lightenv" , GLOBAL_LIGHTENV, JSPROP_PERMANENT, JSI_LightEnv::getLightEnv, JSI_LightEnv::setLightEnv },
{ "players" , 0, JSPROP_PERMANENT|JSPROP_READONLY, GetPlayerSet, 0 },
{ "localPlayer", 0, JSPROP_PERMANENT, GetLocalPlayer, SetLocalPlayer },
{ "gaiaPlayer" , 0, JSPROP_PERMANENT|JSPROP_READONLY, GetGaiaPlayer, 0 },
{ "gameView" , 0, JSPROP_PERMANENT|JSPROP_READONLY, GetGameView, 0 },
{ "renderer" , 0, JSPROP_PERMANENT|JSPROP_READONLY, GetRenderer, 0 },

View File

@ -48,3 +48,9 @@ jsval CScriptValRooted::get() const
return JSVAL_VOID;
return *m_Val;
}
bool CScriptValRooted::undefined() const
{
return (!m_Val || *m_Val == JSVAL_VOID);
}

View File

@ -47,6 +47,8 @@ public:
jsval get() const;
bool undefined() const;
private:
boost::shared_ptr<jsval> m_Val;
};

View File

@ -37,6 +37,16 @@ public:
m_Script.CallVoid("SetName", name);
}
virtual void SetCiv(const std::wstring& civcode)
{
m_Script.CallVoid("SetCiv", civcode);
}
virtual void SetColour(u8 r, u8 g, u8 b)
{
m_Script.CallVoid("SetColour", (u32)r, (u32)g, (u32)b);
}
virtual CColor GetColour()
{
return m_Script.Call<CColor>("GetColour");

View File

@ -31,7 +31,8 @@ class ICmpPlayer : public IComponent
{
public:
virtual void SetName(const std::wstring& name) = 0;
// TODO: some more data
virtual void SetCiv(const std::wstring& civcode) = 0;
virtual void SetColour(u8 r, u8 g, u8 b) = 0;
virtual CColor GetColour() = 0;

View File

@ -38,7 +38,6 @@
namespace AtlasMessage {
static bool g_IsInitialised = false;
static bool g_DidInitSim;
MESSAGEHANDLER(Init)
{
@ -69,12 +68,10 @@ MESSAGEHANDLER(Init)
g_Quickstart = true;
int flags = INIT_HAVE_VMODE|INIT_NO_GUI;
if (! msg->initsimulation)
flags |= INIT_NO_SIM;
Init(g_GameLoop->args, flags);
g_DidInitSim = msg->initsimulation; // so we can shut down the right things later
// TODO: we don't use msg->initsimulation any more, it should be deleted
#if OS_WIN
// HACK (to stop things looking very ugly when scrolling) - should
@ -103,8 +100,6 @@ MESSAGEHANDLER(Shutdown)
g_GameLoop->view = View::GetView_None();
int flags = 0;
if (! g_DidInitSim)
flags |= INIT_NO_SIM;
Shutdown(flags);
g_IsInitialised = false;

View File

@ -1,4 +1,4 @@
/* Copyright (C) 2009 Wildfire Games.
/* Copyright (C) 2010 Wildfire Games.
* This file is part of 0 A.D.
*
* 0 A.D. is free software: you can redistribute it and/or modify
@ -27,10 +27,10 @@
#include "graphics/TextureEntry.h"
#include "graphics/TextureManager.h"
#include "ps/Game.h"
#include "ps/GameAttributes.h"
#include "ps/Loader.h"
#include "ps/World.h"
#include "renderer/Renderer.h"
#include "scriptinterface/ScriptInterface.h"
#include "simulation/LOSManager.h"
#include "simulation/Simulation.h"
#include "simulation2/Simulation2.h"
@ -40,7 +40,7 @@
namespace
{
void InitGame(const CStrW& map)
void InitGame()
{
if (g_Game)
{
@ -48,48 +48,23 @@ namespace
g_Game = NULL;
}
// Set attributes for the game:
g_GameAttributes->m_MapFile = map;
// Make all players locally controlled
for (int i = 1; i < 8; ++i)
g_GameAttributes->GetSlot(i)->AssignLocal();
// Make the whole world visible
g_GameAttributes->m_LOSSetting = LOS_SETTING_ALL_VISIBLE;
g_GameAttributes->m_FogOfWar = false;
// Don't use screenshot mode, because we want working AI for the
// simulation-testing. Outside that simulation-testing, we avoid having
// the units move into attack mode by never calling CEntity::update.
g_GameAttributes->m_ScreenshotMode = false;
// Initialise the game:
g_Game = new CGame();
// Default to player 1 for playtesting
g_Game->SetPlayerID(1);
}
void AddDefaultPlayers()
void StartGame(const CStrW& map)
{
CmpPtr<ICmpPlayerManager> cmpPlayerMan(*g_Game->GetSimulation2(), SYSTEM_ENTITY);
debug_assert(!cmpPlayerMan.null());
CStrW mapBase = map.BeforeLast(L".pmp"); // strip the file extension, if any
// TODO: pick a sensible number, give them names and colours etc
size_t numPlayers = 4;
for (size_t i = 0; i < numPlayers; ++i)
{
entity_id_t ent = g_Game->GetSimulation2()->AddEntity(L"special/player");
cmpPlayerMan->AddPlayer(ent);
}
// Also TODO: Maybe it'd be sensible to load this from a map XML file via CMapReader,
// rather than duplicating the creation code here?
}
CScriptValRooted attrs;
g_Game->GetSimulation2()->GetScriptInterface().Eval("({})", attrs);
g_Game->GetSimulation2()->GetScriptInterface().SetProperty(attrs.get(), "map", std::wstring(mapBase), false);
void StartGame()
{
PSRETURN ret = g_Game->StartGame(g_GameAttributes);
debug_assert(ret == PSRETURN_OK);
g_Game->StartGame(attrs);
LDR_NonprogressiveLoad();
ret = g_Game->ReallyStartGame();
PSRETURN ret = g_Game->ReallyStartGame();
debug_assert(ret == PSRETURN_OK);
}
}
@ -98,58 +73,20 @@ namespace AtlasMessage {
MESSAGEHANDLER(GenerateMap)
{
InitGame(L"");
InitGame();
// Convert size in patches to number of vertices
int vertices = msg->size * PATCH_SIZE + 1;
// Generate flat heightmap
u16* heightmap = new u16[vertices*vertices];
for (int z = 0; z < vertices; ++z)
for (int x = 0; x < vertices; ++x)
heightmap[x + z*vertices] = 16384;
// Initialise terrain using the heightmap
CTerrain* terrain = g_Game->GetWorld()->GetTerrain();
terrain->Initialize(msg->size, heightmap);
delete[] heightmap;
AddDefaultPlayers();
// Start the game, load data files - this must be done before initialising
// the terrain texture below, since the terrains must be loaded before being
// used.
StartGame();
// Cover terrain with default texture
// TODO: split into fCoverWithTexture
CTextureEntry* texentry = g_TexMan.FindTexture("grass1_spring"); // TODO: make default customisable
int patchesPerSide = terrain->GetPatchesPerSide();
for (int pz = 0; pz < patchesPerSide; ++pz)
{
for (int px = 0; px < patchesPerSide; ++px)
{
CPatch* patch = terrain->GetPatch(px, pz); // can't fail
for (ssize_t z = 0; z < PATCH_SIZE; ++z)
{
for (ssize_t x = 0; x < PATCH_SIZE; ++x)
{
patch->m_MiniPatches[z][x].Tex = texentry;
patch->m_MiniPatches[z][x].Priority = 0;
}
}
}
}
// Load the empty default map
StartGame(L"_default");
// TODO: use msg->size somehow
// (e.g. load the map then resize the terrain to match it)
UNUSED2(msg);
}
MESSAGEHANDLER(LoadMap)
{
InitGame(*msg->filename);
StartGame();
InitGame();
StartGame(*msg->filename);
}
MESSAGEHANDLER(SaveMap)